From 7be962bfc2879a72c32ff67518731347dcdff6de Mon Sep 17 00:00:00 2001 From: Dirk Hohndel Date: Mon, 4 Apr 2016 22:02:03 -0700 Subject: Move subsurface-core to core and qt-mobile to mobile-widgets Having subsurface-core as a directory name really messes with autocomplete and is obviously redundant. Simmilarly, qt-mobile caused an autocomplete conflict and also was inconsistent with the desktop-widget name for the directory containing the "other" UI. And while cleaning up the resulting change in the path name for include files, I decided to clean up those even more to make them consistent overall. This could have been handled in more commits, but since this requires a make clean before the build, it seemed more sensible to do it all in one. Signed-off-by: Dirk Hohndel --- .gitignore | 2 +- CMakeLists.txt | 12 +- core/CMakeLists.txt | 98 + core/android.cpp | 199 ++ core/checkcloudconnection.cpp | 106 + core/checkcloudconnection.h | 22 + core/cloudstorage.cpp | 109 + core/cloudstorage.h | 27 + core/cochran.c | 809 +++++ core/cochran.h | 44 + core/color.cpp | 88 + core/color.h | 152 + core/compressibility.r | 115 + core/configuredivecomputer.cpp | 681 ++++ core/configuredivecomputer.h | 68 + core/configuredivecomputerthreads.cpp | 1778 ++++++++++ core/configuredivecomputerthreads.h | 60 + core/datatrak.c | 698 ++++ core/datatrak.h | 41 + core/deco.c | 601 ++++ core/deco.h | 20 + core/device.c | 184 + core/device.h | 18 + core/devicedetails.cpp | 70 + core/devicedetails.h | 104 + core/display.h | 63 + core/dive.c | 3561 +++++++++++++++++++ core/dive.h | 913 +++++ core/divecomputer.cpp | 228 ++ core/divecomputer.h | 38 + core/divelist.c | 1207 +++++++ core/divelist.h | 62 + core/divelogexportlogic.cpp | 161 + core/divelogexportlogic.h | 20 + core/divesite.c | 337 ++ core/divesite.cpp | 31 + core/divesite.h | 80 + core/divesitehelpers.cpp | 208 ++ core/divesitehelpers.h | 18 + core/equipment.c | 238 ++ core/exif.cpp | 587 +++ core/exif.h | 147 + core/file.c | 1115 ++++++ core/file.h | 24 + core/gas-model.c | 64 + core/gaspressures.c | 430 +++ core/gaspressures.h | 35 + core/gettext.h | 10 + core/gettextfromc.cpp | 27 + core/gettextfromc.h | 18 + core/git-access.c | 929 +++++ core/git-access.h | 36 + core/gpslocation.cpp | 606 ++++ core/gpslocation.h | 66 + core/helpers.h | 56 + core/imagedownloader.cpp | 113 + core/imagedownloader.h | 34 + core/isocialnetworkintegration.cpp | 6 + core/isocialnetworkintegration.h | 73 + core/libdivecomputer.c | 1081 ++++++ core/libdivecomputer.h | 72 + core/linux.c | 232 ++ core/liquivision.c | 420 +++ core/load-git.c | 1709 +++++++++ core/macos.c | 218 ++ core/membuffer.c | 288 ++ core/membuffer.h | 74 + core/metrics.cpp | 65 + core/metrics.h | 36 + core/ostctools.c | 193 + core/parse-xml.c | 3751 ++++++++++++++++++++ core/planner.c | 1471 ++++++++ core/planner.h | 32 + core/pluginmanager.cpp | 53 + core/pluginmanager.h | 19 + core/pref.h | 172 + core/prefs-macros.h | 68 + core/profile.c | 1544 ++++++++ core/profile.h | 111 + core/qt-gui.h | 15 + core/qt-init.cpp | 48 + core/qthelper.cpp | 1615 +++++++++ core/qthelper.h | 48 + core/qthelperfromc.h | 22 + core/qtserialbluetooth.cpp | 416 +++ core/save-git.c | 1249 +++++++ core/save-html.c | 559 +++ core/save-html.h | 31 + core/save-xml.c | 749 ++++ core/serial_ftdi.c | 665 ++++ core/sha1.c | 300 ++ core/sha1.h | 38 + core/statistics.c | 404 +++ core/statistics.h | 59 + core/strndup.h | 21 + core/strtod.c | 128 + core/subsurface-qt/DiveObjectHelper.cpp | 338 ++ core/subsurface-qt/DiveObjectHelper.h | 89 + core/subsurface-qt/SettingsObjectWrapper.cpp | 1617 +++++++++ core/subsurface-qt/SettingsObjectWrapper.h | 642 ++++ core/subsurfacestartup.c | 310 ++ core/subsurfacestartup.h | 26 + core/subsurfacesysinfo.cpp | 620 ++++ core/subsurfacesysinfo.h | 65 + core/taxonomy.c | 48 + core/taxonomy.h | 41 + core/time.c | 98 + core/uemis-downloader.c | 1403 ++++++++ core/uemis.c | 392 ++ core/uemis.h | 54 + core/units.h | 280 ++ core/version.c | 18 + core/version.h | 19 + core/webservice.h | 24 + core/windows.c | 454 +++ core/windowtitleupdate.cpp | 32 + core/windowtitleupdate.h | 20 + core/worldmap-options.h | 7 + core/worldmap-save.c | 117 + core/worldmap-save.h | 15 + desktop-widgets/about.cpp | 4 +- desktop-widgets/configuredivecomputerdialog.cpp | 8 +- desktop-widgets/configuredivecomputerdialog.h | 6 +- desktop-widgets/divecomputermanagementdialog.cpp | 8 +- desktop-widgets/divelistview.cpp | 22 +- desktop-widgets/divelistview.h | 2 +- desktop-widgets/divelogexportdialog.cpp | 14 +- desktop-widgets/divelogexportdialog.h | 4 +- desktop-widgets/divelogimportdialog.cpp | 6 +- desktop-widgets/divelogimportdialog.h | 4 +- desktop-widgets/divelogimportdialog.ui | 4 +- desktop-widgets/divepicturewidget.cpp | 14 +- desktop-widgets/diveplanner.cpp | 16 +- desktop-widgets/diveplanner.h | 2 +- desktop-widgets/diveplanner.ui | 2 +- desktop-widgets/diveshareexportdialog.cpp | 12 +- desktop-widgets/downloadfromdivecomputer.cpp | 14 +- desktop-widgets/downloadfromdivecomputer.h | 4 +- desktop-widgets/globe.cpp | 12 +- desktop-widgets/locationInformation.ui | 2 +- desktop-widgets/locationinformation.cpp | 20 +- desktop-widgets/maintab.cpp | 36 +- desktop-widgets/maintab.h | 6 +- desktop-widgets/maintab.ui | 16 +- desktop-widgets/mainwindow.cpp | 62 +- desktop-widgets/mainwindow.h | 6 +- desktop-widgets/mainwindow.ui | 4 +- desktop-widgets/modeldelegates.cpp | 26 +- desktop-widgets/notificationwidget.h | 2 +- .../plugins/facebook/facebook_integration.h | 4 +- .../preferences/preferences_defaults.cpp | 4 +- desktop-widgets/preferences/preferences_defaults.h | 4 +- .../preferences/preferences_georeference.cpp | 6 +- desktop-widgets/preferences/preferences_graph.cpp | 4 +- .../preferences/preferences_language.cpp | 2 +- .../preferences/preferences_network.cpp | 6 +- desktop-widgets/preferences/preferences_units.cpp | 4 +- desktop-widgets/preferences/preferencesdialog.cpp | 2 +- desktop-widgets/preferences/preferencesdialog.h | 2 +- desktop-widgets/simplewidgets.cpp | 16 +- desktop-widgets/simplewidgets.h | 4 +- desktop-widgets/starwidget.cpp | 6 +- desktop-widgets/statistics/statisticswidget.cpp | 4 +- desktop-widgets/subsurfacewebservices.cpp | 22 +- desktop-widgets/tableview.cpp | 4 +- desktop-widgets/tableview.h | 2 +- desktop-widgets/templatelayout.h | 2 +- desktop-widgets/undocommands.cpp | 6 +- desktop-widgets/undocommands.h | 2 +- desktop-widgets/updatemanager.cpp | 14 +- desktop-widgets/usermanual.cpp | 6 +- desktop-widgets/usersurvey.cpp | 12 +- mobile-widgets/qml/About.qml | 59 + mobile-widgets/qml/CloudCredentials.qml | 84 + mobile-widgets/qml/DiveDetails.qml | 216 ++ mobile-widgets/qml/DiveDetailsEdit.qml | 236 ++ mobile-widgets/qml/DiveDetailsView.qml | 303 ++ mobile-widgets/qml/DiveList.qml | 302 ++ mobile-widgets/qml/DownloadFromDiveComputer.qml | 125 + mobile-widgets/qml/GpsList.qml | 128 + mobile-widgets/qml/Log.qml | 40 + mobile-widgets/qml/Preferences.qml | 74 + mobile-widgets/qml/StartPage.qml | 42 + mobile-widgets/qml/SubsurfaceButton.qml | 26 + mobile-widgets/qml/TextButton.qml | 37 + mobile-widgets/qml/ThemeTest.qml | 115 + mobile-widgets/qml/TopBar.qml | 59 + mobile-widgets/qml/dive.jpg | Bin 0 -> 235727 bytes mobile-widgets/qml/icons/context-menu.png | Bin 0 -> 641 bytes mobile-widgets/qml/icons/context-menu.svg | 1 + mobile-widgets/qml/icons/main-menu.png | Bin 0 -> 112 bytes mobile-widgets/qml/icons/main-menu.svg | 1 + mobile-widgets/qml/icons/menu-back.png | Bin 0 -> 3715 bytes mobile-widgets/qml/icons/menu-edit.png | Bin 0 -> 7369 bytes mobile-widgets/qml/main.qml | 360 ++ mobile-widgets/qml/mobile-resources.qrc | 66 + mobile-widgets/qml/theme/Theme.qml | 57 + mobile-widgets/qml/theme/Units.qml | 99 + mobile-widgets/qml/theme/qmldir | 2 + mobile-widgets/qmlmanager.cpp | 1078 ++++++ mobile-widgets/qmlmanager.h | 162 + mobile-widgets/qmlprofile.cpp | 111 + mobile-widgets/qmlprofile.h | 40 + .../ios/Subsurface-mobile/Subsurface-mobile.pro | 232 +- profile-widget/animationfunctions.cpp | 4 +- profile-widget/divecartesianaxis.cpp | 18 +- profile-widget/divecartesianaxis.h | 2 +- profile-widget/diveeventitem.cpp | 16 +- profile-widget/divepixmapitem.cpp | 8 +- profile-widget/diveprofileitem.cpp | 22 +- profile-widget/divetextitem.cpp | 2 +- profile-widget/divetooltipitem.cpp | 17 +- profile-widget/divetooltipitem.h | 2 +- profile-widget/profilewidget2.cpp | 34 +- profile-widget/profilewidget2.h | 6 +- profile-widget/ruleritem.cpp | 12 +- profile-widget/ruleritem.h | 4 +- profile-widget/tankitem.cpp | 8 +- profile-widget/tankitem.h | 6 +- qt-mobile/qml/About.qml | 59 - qt-mobile/qml/CloudCredentials.qml | 84 - qt-mobile/qml/DiveDetails.qml | 216 -- qt-mobile/qml/DiveDetailsEdit.qml | 236 -- qt-mobile/qml/DiveDetailsView.qml | 303 -- qt-mobile/qml/DiveList.qml | 302 -- qt-mobile/qml/DownloadFromDiveComputer.qml | 125 - qt-mobile/qml/GpsList.qml | 128 - qt-mobile/qml/Log.qml | 40 - qt-mobile/qml/Preferences.qml | 74 - qt-mobile/qml/StartPage.qml | 42 - qt-mobile/qml/SubsurfaceButton.qml | 26 - qt-mobile/qml/TextButton.qml | 37 - qt-mobile/qml/ThemeTest.qml | 115 - qt-mobile/qml/TopBar.qml | 59 - qt-mobile/qml/dive.jpg | Bin 235727 -> 0 bytes qt-mobile/qml/icons/context-menu.png | Bin 641 -> 0 bytes qt-mobile/qml/icons/context-menu.svg | 1 - qt-mobile/qml/icons/main-menu.png | Bin 112 -> 0 bytes qt-mobile/qml/icons/main-menu.svg | 1 - qt-mobile/qml/icons/menu-back.png | Bin 3715 -> 0 bytes qt-mobile/qml/icons/menu-edit.png | Bin 7369 -> 0 bytes qt-mobile/qml/main.qml | 360 -- qt-mobile/qml/mobile-resources.qrc | 66 - qt-mobile/qml/theme/Theme.qml | 57 - qt-mobile/qml/theme/Units.qml | 99 - qt-mobile/qml/theme/qmldir | 2 - qt-mobile/qmlmanager.cpp | 1078 ------ qt-mobile/qmlmanager.h | 162 - qt-mobile/qmlprofile.cpp | 111 - qt-mobile/qmlprofile.h | 40 - qt-models/cleanertablemodel.cpp | 2 +- qt-models/completionmodels.cpp | 4 +- qt-models/cylindermodel.cpp | 10 +- qt-models/cylindermodel.h | 2 +- qt-models/divecomputerextradatamodel.cpp | 6 +- qt-models/divecomputermodel.cpp | 6 +- qt-models/divecomputermodel.h | 4 +- qt-models/divelistmodel.cpp | 4 +- qt-models/divelistmodel.h | 6 +- qt-models/divelocationmodel.cpp | 6 +- qt-models/divelocationmodel.h | 2 +- qt-models/divepicturemodel.cpp | 10 +- qt-models/diveplannermodel.cpp | 10 +- qt-models/diveplannermodel.h | 2 +- qt-models/diveplotdatamodel.cpp | 10 +- qt-models/diveplotdatamodel.h | 2 +- qt-models/divesitepicturesmodel.cpp | 6 +- qt-models/divetripmodel.cpp | 10 +- qt-models/divetripmodel.h | 2 +- qt-models/filtermodels.cpp | 10 +- qt-models/gpslistmodel.cpp | 4 +- qt-models/gpslistmodel.h | 2 +- qt-models/models.cpp | 4 +- qt-models/models.h | 8 +- qt-models/tankinfomodel.cpp | 8 +- qt-models/treemodel.cpp | 4 +- qt-models/weightmodel.cpp | 12 +- qt-models/weightmodel.h | 2 +- qt-models/weigthsysteminfomodel.cpp | 8 +- qt-models/yearlystatisticsmodel.cpp | 10 +- scripts/mobilecomponents.sh | 4 +- subsurface-core/CMakeLists.txt | 98 - subsurface-core/android.cpp | 199 -- subsurface-core/checkcloudconnection.cpp | 106 - subsurface-core/checkcloudconnection.h | 22 - subsurface-core/cloudstorage.cpp | 109 - subsurface-core/cloudstorage.h | 27 - subsurface-core/cochran.c | 809 ----- subsurface-core/cochran.h | 44 - subsurface-core/color.cpp | 88 - subsurface-core/color.h | 152 - subsurface-core/compressibility.r | 115 - subsurface-core/configuredivecomputer.cpp | 681 ---- subsurface-core/configuredivecomputer.h | 68 - subsurface-core/configuredivecomputerthreads.cpp | 1778 ---------- subsurface-core/configuredivecomputerthreads.h | 60 - subsurface-core/datatrak.c | 698 ---- subsurface-core/datatrak.h | 41 - subsurface-core/deco.c | 601 ---- subsurface-core/deco.h | 20 - subsurface-core/device.c | 184 - subsurface-core/device.h | 18 - subsurface-core/devicedetails.cpp | 70 - subsurface-core/devicedetails.h | 104 - subsurface-core/display.h | 63 - subsurface-core/dive.c | 3561 ------------------- subsurface-core/dive.h | 913 ----- subsurface-core/divecomputer.cpp | 228 -- subsurface-core/divecomputer.h | 38 - subsurface-core/divelist.c | 1207 ------- 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 | 238 -- subsurface-core/exif.cpp | 587 --- subsurface-core/exif.h | 147 - subsurface-core/file.c | 1115 ------ subsurface-core/file.h | 24 - subsurface-core/gas-model.c | 64 - subsurface-core/gaspressures.c | 430 --- 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 | 929 ----- subsurface-core/git-access.h | 36 - subsurface-core/gpslocation.cpp | 606 ---- subsurface-core/gpslocation.h | 66 - subsurface-core/helpers.h | 56 - subsurface-core/imagedownloader.cpp | 113 - subsurface-core/imagedownloader.h | 34 - subsurface-core/isocialnetworkintegration.cpp | 6 - subsurface-core/isocialnetworkintegration.h | 73 - subsurface-core/libdivecomputer.c | 1081 ------ subsurface-core/libdivecomputer.h | 72 - subsurface-core/linux.c | 232 -- subsurface-core/liquivision.c | 420 --- subsurface-core/load-git.c | 1709 --------- subsurface-core/macos.c | 218 -- subsurface-core/membuffer.c | 288 -- subsurface-core/membuffer.h | 74 - subsurface-core/metrics.cpp | 65 - subsurface-core/metrics.h | 36 - subsurface-core/ostctools.c | 193 - subsurface-core/parse-xml.c | 3751 -------------------- subsurface-core/planner.c | 1471 -------- subsurface-core/planner.h | 32 - subsurface-core/pluginmanager.cpp | 53 - subsurface-core/pluginmanager.h | 19 - subsurface-core/pref.h | 172 - subsurface-core/prefs-macros.h | 68 - subsurface-core/profile.c | 1544 -------- subsurface-core/profile.h | 111 - subsurface-core/qt-gui.h | 15 - subsurface-core/qt-init.cpp | 48 - subsurface-core/qthelper.cpp | 1615 --------- subsurface-core/qthelper.h | 48 - subsurface-core/qthelperfromc.h | 22 - subsurface-core/qtserialbluetooth.cpp | 416 --- subsurface-core/save-git.c | 1249 ------- subsurface-core/save-html.c | 559 --- subsurface-core/save-html.h | 31 - subsurface-core/save-xml.c | 749 ---- subsurface-core/serial_ftdi.c | 665 ---- subsurface-core/sha1.c | 300 -- subsurface-core/sha1.h | 38 - subsurface-core/statistics.c | 404 --- subsurface-core/statistics.h | 59 - subsurface-core/strndup.h | 21 - subsurface-core/strtod.c | 128 - subsurface-core/subsurface-qt/DiveObjectHelper.cpp | 338 -- subsurface-core/subsurface-qt/DiveObjectHelper.h | 89 - .../subsurface-qt/SettingsObjectWrapper.cpp | 1617 --------- .../subsurface-qt/SettingsObjectWrapper.h | 642 ---- subsurface-core/subsurfacestartup.c | 310 -- 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 | 1403 -------- subsurface-core/uemis.c | 392 -- subsurface-core/uemis.h | 54 - subsurface-core/units.h | 280 -- subsurface-core/version.c | 18 - subsurface-core/version.h | 19 - subsurface-core/webservice.h | 24 - subsurface-core/windows.c | 454 --- subsurface-core/windowtitleupdate.cpp | 32 - subsurface-core/windowtitleupdate.h | 20 - subsurface-core/worldmap-options.h | 7 - subsurface-core/worldmap-save.c | 117 - subsurface-core/worldmap-save.h | 15 - subsurface-desktop-helper.cpp | 15 +- subsurface-desktop-main.cpp | 12 +- subsurface-mobile-helper.cpp | 12 +- subsurface-mobile-main.cpp | 12 +- 403 files changed, 46713 insertions(+), 46723 deletions(-) create mode 100644 core/CMakeLists.txt create mode 100644 core/android.cpp create mode 100644 core/checkcloudconnection.cpp create mode 100644 core/checkcloudconnection.h create mode 100644 core/cloudstorage.cpp create mode 100644 core/cloudstorage.h create mode 100644 core/cochran.c create mode 100644 core/cochran.h create mode 100644 core/color.cpp create mode 100644 core/color.h create mode 100644 core/compressibility.r create mode 100644 core/configuredivecomputer.cpp create mode 100644 core/configuredivecomputer.h create mode 100644 core/configuredivecomputerthreads.cpp create mode 100644 core/configuredivecomputerthreads.h create mode 100644 core/datatrak.c create mode 100644 core/datatrak.h create mode 100644 core/deco.c create mode 100644 core/deco.h create mode 100644 core/device.c create mode 100644 core/device.h create mode 100644 core/devicedetails.cpp create mode 100644 core/devicedetails.h create mode 100644 core/display.h create mode 100644 core/dive.c create mode 100644 core/dive.h create mode 100644 core/divecomputer.cpp create mode 100644 core/divecomputer.h create mode 100644 core/divelist.c create mode 100644 core/divelist.h create mode 100644 core/divelogexportlogic.cpp create mode 100644 core/divelogexportlogic.h create mode 100644 core/divesite.c create mode 100644 core/divesite.cpp create mode 100644 core/divesite.h create mode 100644 core/divesitehelpers.cpp create mode 100644 core/divesitehelpers.h create mode 100644 core/equipment.c create mode 100644 core/exif.cpp create mode 100644 core/exif.h create mode 100644 core/file.c create mode 100644 core/file.h create mode 100644 core/gas-model.c create mode 100644 core/gaspressures.c create mode 100644 core/gaspressures.h create mode 100644 core/gettext.h create mode 100644 core/gettextfromc.cpp create mode 100644 core/gettextfromc.h create mode 100644 core/git-access.c create mode 100644 core/git-access.h create mode 100644 core/gpslocation.cpp create mode 100644 core/gpslocation.h create mode 100644 core/helpers.h create mode 100644 core/imagedownloader.cpp create mode 100644 core/imagedownloader.h create mode 100644 core/isocialnetworkintegration.cpp create mode 100644 core/isocialnetworkintegration.h create mode 100644 core/libdivecomputer.c create mode 100644 core/libdivecomputer.h create mode 100644 core/linux.c create mode 100644 core/liquivision.c create mode 100644 core/load-git.c create mode 100644 core/macos.c create mode 100644 core/membuffer.c create mode 100644 core/membuffer.h create mode 100644 core/metrics.cpp create mode 100644 core/metrics.h create mode 100644 core/ostctools.c create mode 100644 core/parse-xml.c create mode 100644 core/planner.c create mode 100644 core/planner.h create mode 100644 core/pluginmanager.cpp create mode 100644 core/pluginmanager.h create mode 100644 core/pref.h create mode 100644 core/prefs-macros.h create mode 100644 core/profile.c create mode 100644 core/profile.h create mode 100644 core/qt-gui.h create mode 100644 core/qt-init.cpp create mode 100644 core/qthelper.cpp create mode 100644 core/qthelper.h create mode 100644 core/qthelperfromc.h create mode 100644 core/qtserialbluetooth.cpp create mode 100644 core/save-git.c create mode 100644 core/save-html.c create mode 100644 core/save-html.h create mode 100644 core/save-xml.c create mode 100644 core/serial_ftdi.c create mode 100644 core/sha1.c create mode 100644 core/sha1.h create mode 100644 core/statistics.c create mode 100644 core/statistics.h create mode 100644 core/strndup.h create mode 100644 core/strtod.c create mode 100644 core/subsurface-qt/DiveObjectHelper.cpp create mode 100644 core/subsurface-qt/DiveObjectHelper.h create mode 100644 core/subsurface-qt/SettingsObjectWrapper.cpp create mode 100644 core/subsurface-qt/SettingsObjectWrapper.h create mode 100644 core/subsurfacestartup.c create mode 100644 core/subsurfacestartup.h create mode 100644 core/subsurfacesysinfo.cpp create mode 100644 core/subsurfacesysinfo.h create mode 100644 core/taxonomy.c create mode 100644 core/taxonomy.h create mode 100644 core/time.c create mode 100644 core/uemis-downloader.c create mode 100644 core/uemis.c create mode 100644 core/uemis.h create mode 100644 core/units.h create mode 100644 core/version.c create mode 100644 core/version.h create mode 100644 core/webservice.h create mode 100644 core/windows.c create mode 100644 core/windowtitleupdate.cpp create mode 100644 core/windowtitleupdate.h create mode 100644 core/worldmap-options.h create mode 100644 core/worldmap-save.c create mode 100644 core/worldmap-save.h create mode 100644 mobile-widgets/qml/About.qml create mode 100644 mobile-widgets/qml/CloudCredentials.qml create mode 100644 mobile-widgets/qml/DiveDetails.qml create mode 100644 mobile-widgets/qml/DiveDetailsEdit.qml create mode 100644 mobile-widgets/qml/DiveDetailsView.qml create mode 100644 mobile-widgets/qml/DiveList.qml create mode 100644 mobile-widgets/qml/DownloadFromDiveComputer.qml create mode 100644 mobile-widgets/qml/GpsList.qml create mode 100644 mobile-widgets/qml/Log.qml create mode 100644 mobile-widgets/qml/Preferences.qml create mode 100644 mobile-widgets/qml/StartPage.qml create mode 100644 mobile-widgets/qml/SubsurfaceButton.qml create mode 100644 mobile-widgets/qml/TextButton.qml create mode 100644 mobile-widgets/qml/ThemeTest.qml create mode 100644 mobile-widgets/qml/TopBar.qml create mode 100644 mobile-widgets/qml/dive.jpg create mode 100644 mobile-widgets/qml/icons/context-menu.png create mode 100644 mobile-widgets/qml/icons/context-menu.svg create mode 100644 mobile-widgets/qml/icons/main-menu.png create mode 100644 mobile-widgets/qml/icons/main-menu.svg create mode 100644 mobile-widgets/qml/icons/menu-back.png create mode 100644 mobile-widgets/qml/icons/menu-edit.png create mode 100644 mobile-widgets/qml/main.qml create mode 100644 mobile-widgets/qml/mobile-resources.qrc create mode 100644 mobile-widgets/qml/theme/Theme.qml create mode 100644 mobile-widgets/qml/theme/Units.qml create mode 100644 mobile-widgets/qml/theme/qmldir create mode 100644 mobile-widgets/qmlmanager.cpp create mode 100644 mobile-widgets/qmlmanager.h create mode 100644 mobile-widgets/qmlprofile.cpp create mode 100644 mobile-widgets/qmlprofile.h delete mode 100644 qt-mobile/qml/About.qml delete mode 100644 qt-mobile/qml/CloudCredentials.qml delete mode 100644 qt-mobile/qml/DiveDetails.qml delete mode 100644 qt-mobile/qml/DiveDetailsEdit.qml delete mode 100644 qt-mobile/qml/DiveDetailsView.qml delete mode 100644 qt-mobile/qml/DiveList.qml delete mode 100644 qt-mobile/qml/DownloadFromDiveComputer.qml delete mode 100644 qt-mobile/qml/GpsList.qml delete mode 100644 qt-mobile/qml/Log.qml delete mode 100644 qt-mobile/qml/Preferences.qml delete mode 100644 qt-mobile/qml/StartPage.qml delete mode 100644 qt-mobile/qml/SubsurfaceButton.qml delete mode 100644 qt-mobile/qml/TextButton.qml delete mode 100644 qt-mobile/qml/ThemeTest.qml delete mode 100644 qt-mobile/qml/TopBar.qml delete mode 100644 qt-mobile/qml/dive.jpg delete mode 100644 qt-mobile/qml/icons/context-menu.png delete mode 100644 qt-mobile/qml/icons/context-menu.svg delete mode 100644 qt-mobile/qml/icons/main-menu.png delete mode 100644 qt-mobile/qml/icons/main-menu.svg delete mode 100644 qt-mobile/qml/icons/menu-back.png delete mode 100644 qt-mobile/qml/icons/menu-edit.png delete mode 100644 qt-mobile/qml/main.qml delete mode 100644 qt-mobile/qml/mobile-resources.qrc delete mode 100644 qt-mobile/qml/theme/Theme.qml delete mode 100644 qt-mobile/qml/theme/Units.qml delete mode 100644 qt-mobile/qml/theme/qmldir delete mode 100644 qt-mobile/qmlmanager.cpp delete mode 100644 qt-mobile/qmlmanager.h delete mode 100644 qt-mobile/qmlprofile.cpp delete mode 100644 qt-mobile/qmlprofile.h delete mode 100644 subsurface-core/CMakeLists.txt delete mode 100644 subsurface-core/android.cpp delete mode 100644 subsurface-core/checkcloudconnection.cpp delete mode 100644 subsurface-core/checkcloudconnection.h delete mode 100644 subsurface-core/cloudstorage.cpp delete mode 100644 subsurface-core/cloudstorage.h delete mode 100644 subsurface-core/cochran.c delete mode 100644 subsurface-core/cochran.h delete mode 100644 subsurface-core/color.cpp delete mode 100644 subsurface-core/color.h delete mode 100644 subsurface-core/compressibility.r delete mode 100644 subsurface-core/configuredivecomputer.cpp delete mode 100644 subsurface-core/configuredivecomputer.h delete mode 100644 subsurface-core/configuredivecomputerthreads.cpp delete mode 100644 subsurface-core/configuredivecomputerthreads.h delete mode 100644 subsurface-core/datatrak.c delete mode 100644 subsurface-core/datatrak.h delete mode 100644 subsurface-core/deco.c delete mode 100644 subsurface-core/deco.h delete mode 100644 subsurface-core/device.c delete mode 100644 subsurface-core/device.h delete mode 100644 subsurface-core/devicedetails.cpp delete mode 100644 subsurface-core/devicedetails.h delete mode 100644 subsurface-core/display.h delete mode 100644 subsurface-core/dive.c delete mode 100644 subsurface-core/dive.h delete mode 100644 subsurface-core/divecomputer.cpp delete mode 100644 subsurface-core/divecomputer.h delete mode 100644 subsurface-core/divelist.c delete mode 100644 subsurface-core/divelist.h delete mode 100644 subsurface-core/divelogexportlogic.cpp delete mode 100644 subsurface-core/divelogexportlogic.h delete mode 100644 subsurface-core/divesite.c delete mode 100644 subsurface-core/divesite.cpp delete mode 100644 subsurface-core/divesite.h delete mode 100644 subsurface-core/divesitehelpers.cpp delete mode 100644 subsurface-core/divesitehelpers.h delete mode 100644 subsurface-core/equipment.c delete mode 100644 subsurface-core/exif.cpp delete mode 100644 subsurface-core/exif.h delete mode 100644 subsurface-core/file.c delete mode 100644 subsurface-core/file.h delete mode 100644 subsurface-core/gas-model.c delete mode 100644 subsurface-core/gaspressures.c delete mode 100644 subsurface-core/gaspressures.h delete mode 100644 subsurface-core/gettext.h delete mode 100644 subsurface-core/gettextfromc.cpp delete mode 100644 subsurface-core/gettextfromc.h delete mode 100644 subsurface-core/git-access.c delete mode 100644 subsurface-core/git-access.h delete mode 100644 subsurface-core/gpslocation.cpp delete mode 100644 subsurface-core/gpslocation.h delete mode 100644 subsurface-core/helpers.h delete mode 100644 subsurface-core/imagedownloader.cpp delete mode 100644 subsurface-core/imagedownloader.h delete mode 100644 subsurface-core/isocialnetworkintegration.cpp delete mode 100644 subsurface-core/isocialnetworkintegration.h delete mode 100644 subsurface-core/libdivecomputer.c delete mode 100644 subsurface-core/libdivecomputer.h delete mode 100644 subsurface-core/linux.c delete mode 100644 subsurface-core/liquivision.c delete mode 100644 subsurface-core/load-git.c delete mode 100644 subsurface-core/macos.c delete mode 100644 subsurface-core/membuffer.c delete mode 100644 subsurface-core/membuffer.h delete mode 100644 subsurface-core/metrics.cpp delete mode 100644 subsurface-core/metrics.h delete mode 100644 subsurface-core/ostctools.c delete mode 100644 subsurface-core/parse-xml.c delete mode 100644 subsurface-core/planner.c delete mode 100644 subsurface-core/planner.h delete mode 100644 subsurface-core/pluginmanager.cpp delete mode 100644 subsurface-core/pluginmanager.h delete mode 100644 subsurface-core/pref.h delete mode 100644 subsurface-core/prefs-macros.h delete mode 100644 subsurface-core/profile.c delete mode 100644 subsurface-core/profile.h delete mode 100644 subsurface-core/qt-gui.h delete mode 100644 subsurface-core/qt-init.cpp delete mode 100644 subsurface-core/qthelper.cpp delete mode 100644 subsurface-core/qthelper.h delete mode 100644 subsurface-core/qthelperfromc.h delete mode 100644 subsurface-core/qtserialbluetooth.cpp delete mode 100644 subsurface-core/save-git.c delete mode 100644 subsurface-core/save-html.c delete mode 100644 subsurface-core/save-html.h delete mode 100644 subsurface-core/save-xml.c delete mode 100644 subsurface-core/serial_ftdi.c delete mode 100644 subsurface-core/sha1.c delete mode 100644 subsurface-core/sha1.h delete mode 100644 subsurface-core/statistics.c delete mode 100644 subsurface-core/statistics.h delete mode 100644 subsurface-core/strndup.h delete mode 100644 subsurface-core/strtod.c delete mode 100644 subsurface-core/subsurface-qt/DiveObjectHelper.cpp delete mode 100644 subsurface-core/subsurface-qt/DiveObjectHelper.h delete mode 100644 subsurface-core/subsurface-qt/SettingsObjectWrapper.cpp delete mode 100644 subsurface-core/subsurface-qt/SettingsObjectWrapper.h delete mode 100644 subsurface-core/subsurfacestartup.c delete mode 100644 subsurface-core/subsurfacestartup.h delete mode 100644 subsurface-core/subsurfacesysinfo.cpp delete mode 100644 subsurface-core/subsurfacesysinfo.h delete mode 100644 subsurface-core/taxonomy.c delete mode 100644 subsurface-core/taxonomy.h delete mode 100644 subsurface-core/time.c delete mode 100644 subsurface-core/uemis-downloader.c delete mode 100644 subsurface-core/uemis.c delete mode 100644 subsurface-core/uemis.h delete mode 100644 subsurface-core/units.h delete mode 100644 subsurface-core/version.c delete mode 100644 subsurface-core/version.h delete mode 100644 subsurface-core/webservice.h delete mode 100644 subsurface-core/windows.c delete mode 100644 subsurface-core/windowtitleupdate.cpp delete mode 100644 subsurface-core/windowtitleupdate.h delete mode 100644 subsurface-core/worldmap-options.h delete mode 100644 subsurface-core/worldmap-save.c delete mode 100644 subsurface-core/worldmap-save.h diff --git a/.gitignore b/.gitignore index 35af50390..2c3c7d774 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,4 @@ Subsurface.app .DS_Store !android/**/*.xml build* -qt-mobile/qml/mobilecomponents +mobile-widgets/qml/kirigami diff --git a/CMakeLists.txt b/CMakeLists.txt index ea6f17e66..cc7db1329 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,10 +115,6 @@ include_directories(. ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_BINARY_DIR}/desktop-widgets - desktop-widgets/ - qt-models - desktop-widgets/profile - subsurface-core/ ) # Project Target specific configuration should go here, @@ -212,7 +208,7 @@ qt5_add_resources(SUBSURFACE_RESOURCES subsurface.qrc) # include translations add_subdirectory(translations) -add_subdirectory(subsurface-core) +add_subdirectory(core) add_subdirectory(qt-models) add_subdirectory(profile-widget) @@ -227,12 +223,12 @@ endif() # create the executables if(${SUBSURFACE_TARGET_EXECUTABLE} MATCHES "MobileExecutable") set(MOBILE_SRC - qt-mobile/qmlmanager.cpp - qt-mobile/qmlprofile.cpp + mobile-widgets/qmlmanager.cpp + mobile-widgets/qmlprofile.cpp subsurface-mobile-main.cpp subsurface-mobile-helper.cpp ) - qt5_add_resources(MOBILE_RESOURCES qt-mobile/qml/mobile-resources.qrc) + qt5_add_resources(MOBILE_RESOURCES mobile-widgets/qml/mobile-resources.qrc) # When building the mobile application in Android, link it and Qt will do the rest, when doing the mobile application on Desktop, create an executable. if(ANDROID) add_library(${SUBSURFACE_TARGET} SHARED ${SUBSURFACE_PKG} ${MOBILE_SRC} ${SUBSURFACE_RESOURCES} ${MOBILE_RESOURCES}) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt new file mode 100644 index 000000000..d9b1d3421 --- /dev/null +++ b/core/CMakeLists.txt @@ -0,0 +1,98 @@ +set(PLATFORM_SRC unknown_platform.c) +message(STATUS "system name ${CMAKE_SYSTEM_NAME}") +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + if(ANDROID) + set(PLATFORM_SRC android.cpp) + else() + set(PLATFORM_SRC linux.c) + endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") + set(PLATFORM_SRC android.cpp) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(PLATFORM_SRC macos.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(PLATFORM_SRC windows.c) +endif() + +if(FTDISUPPORT) + set(SERIAL_FTDI serial_ftdi.c) +endif() + +if(BTSUPPORT) + add_definitions(-DBT_SUPPORT) + set(BT_SRC_FILES desktop-widgets/btdeviceselectiondialog.cpp) + set(BT_CORE_SRC_FILES qtserialbluetooth.cpp) +endif() + +# compile the core library, in C. +set(SUBSURFACE_CORE_LIB_SRCS + cochran.c + datatrak.c + deco.c + device.c + dive.c + divesite.c + divesite.cpp + divelist.c + equipment.c + file.c + gas-model.c + git-access.c + libdivecomputer.c + liquivision.c + load-git.c + membuffer.c + ostctools.c + parse-xml.c + planner.c + profile.c + gaspressures.c + worldmap-save.c + save-git.c + save-xml.c + save-html.c + sha1.c + statistics.c + strtod.c + subsurfacestartup.c + time.c + uemis.c + uemis-downloader.c + version.c + # gettextfrommoc should be added because we are using it on the c-code. + gettextfromc.cpp + # dirk ported some core functionality to c++. + qthelper.cpp + divecomputer.cpp + exif.cpp + subsurfacesysinfo.cpp + devicedetails.cpp + configuredivecomputer.cpp + configuredivecomputerthreads.cpp + divesitehelpers.cpp + taxonomy.c + checkcloudconnection.cpp + windowtitleupdate.cpp + divelogexportlogic.cpp + qt-init.cpp + qtserialbluetooth.cpp + metrics.cpp + color.cpp + pluginmanager.cpp + imagedownloader.cpp + isocialnetworkintegration.cpp + gpslocation.cpp + cloudstorage.cpp + + #Subsurface Qt have the Subsurface structs QObjectified for easy access via QML. + subsurface-qt/DiveObjectHelper.cpp + subsurface-qt/SettingsObjectWrapper.cpp + ${SERIAL_FTDI} + ${PLATFORM_SRC} + ${BT_CORE_SRC_FILES} +) +source_group("Subsurface Core" FILES ${SUBSURFACE_CORE_LIB_SRCS}) + +add_library(subsurface_corelib STATIC ${SUBSURFACE_CORE_LIB_SRCS} ) +target_link_libraries(subsurface_corelib ${QT_LIBRARIES}) + diff --git a/core/android.cpp b/core/android.cpp new file mode 100644 index 000000000..3631b07a1 --- /dev/null +++ b/core/android.cpp @@ -0,0 +1,199 @@ +/* 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 = -1; + +int get_usb_fd(uint16_t idVendor, uint16_t idProduct); +void subsurface_OS_pref_setup(void) +{ + // Abusing this function to get a decent place where we can wire in + // our open callback into libusb +#ifdef libusb_android_open_callback_func + libusb_set_android_open_callback(get_usb_fd); +#elif __ANDROID__ +#error we need libusb_android_open_callback +#endif +} + +bool subsurface_ignore_font(const char *font) +{ + // there are no old default fonts that we would want to ignore + return false; +} + +void subsurface_user_info(struct user_info *user) +{ /* Encourage use of at least libgit2-0.20 */ } + +static const char *system_default_path_append(const char *append) +{ + // Qt appears to find a working path for us - let's just go with that + QString path = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first(); + + if (append) + path += QString("/%1").arg(append); + + return strdup(path.toUtf8().data()); +} + +const char *system_default_directory(void) +{ + static const char *path = NULL; + if (!path) + path = system_default_path_append(NULL); + return path; +} + +const char *system_default_filename(void) +{ + static const char *filename = "subsurface.xml"; + static const char *path = NULL; + if (!path) + path = system_default_path_append(filename); + return path; +} + +int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) +{ + /* FIXME: we need to enumerate in some other way on android */ + /* qtserialport maybee? */ + return -1; +} + +/** + * Get the file descriptor of first available matching device attached to usb in android. + * + * returns a fd to the device, or -1 and errno is set. + */ +int get_usb_fd(uint16_t idVendor, uint16_t idProduct) +{ + int i; + jint fd, vendorid, productid; + QAndroidJniObject usbName, usbDevice; + + // Get the current main activity of the application. + QAndroidJniObject activity = QtAndroid::androidActivity(); + + QAndroidJniObject usb_service = QAndroidJniObject::fromString(USB_SERVICE); + + // Get UsbManager from activity + QAndroidJniObject usbManager = activity.callObjectMethod("getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", usb_service.object()); + + // Get a HashMap 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/core/checkcloudconnection.cpp b/core/checkcloudconnection.cpp new file mode 100644 index 000000000..f29d971ba --- /dev/null +++ b/core/checkcloudconnection.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include + +#include "pref.h" +#include "helpers.h" +#include "git-access.h" + +#include "checkcloudconnection.h" + + +CheckCloudConnection::CheckCloudConnection(QObject *parent) : + QObject(parent), + reply(0) +{ + +} + +#define TEAPOT "/make-latte?number-of-shots=3" +#define HTTP_I_AM_A_TEAPOT 418 +#define MILK "Linus does not like non-fat milk" +bool CheckCloudConnection::checkServer() +{ + if (verbose) + fprintf(stderr, "Checking cloud connection...\n"); + + QTimer timer; + timer.setSingleShot(true); + QEventLoop loop; + QNetworkRequest request; + request.setRawHeader("Accept", "text/plain"); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + request.setRawHeader("Client-Id", getUUID().toUtf8()); + request.setUrl(QString(prefs.cloud_base_url) + TEAPOT); + QNetworkAccessManager *mgr = new QNetworkAccessManager(); + reply = mgr->get(request); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + connect(reply, &QNetworkReply::sslErrors, this, &CheckCloudConnection::sslErrors); + for (int seconds = 1; seconds <= 5; seconds++) { + timer.start(1000); // wait five seconds + loop.exec(); + if (timer.isActive()) { + // didn't time out, did we get the right response? + timer.stop(); + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == HTTP_I_AM_A_TEAPOT && + reply->readAll() == QByteArray(MILK)) { + reply->deleteLater(); + mgr->deleteLater(); + if (verbose > 1) + qWarning() << "Cloud storage: successfully checked connection to cloud server"; + git_storage_update_progress(last_git_storage_update_val + 1, "successfully checked cloud connection"); + return true; + } + } else if (seconds < 5) { + git_storage_update_progress(last_git_storage_update_val + 1, "waited 1 sec for cloud connection"); + } else { + disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + reply->abort(); + } + } + git_storage_update_progress(last_git_storage_update_val + 1, "cloud connection failed"); + if (verbose) + qDebug() << "connection test to cloud server failed" << + reply->error() << reply->errorString() << + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << + reply->readAll(); + reply->deleteLater(); + mgr->deleteLater(); + if (verbose) + qWarning() << "Cloud storage: unable to connect to cloud server"; + return false; +} + +void CheckCloudConnection::sslErrors(QList 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/core/checkcloudconnection.h b/core/checkcloudconnection.h new file mode 100644 index 000000000..58a412797 --- /dev/null +++ b/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/core/cloudstorage.cpp b/core/cloudstorage.cpp new file mode 100644 index 000000000..575191891 --- /dev/null +++ b/core/cloudstorage.cpp @@ -0,0 +1,109 @@ +#include "cloudstorage.h" +#include "pref.h" +#include "dive.h" +#include "helpers.h" + +#include + +CloudStorageAuthenticate::CloudStorageAuthenticate(QObject *parent) : + QObject(parent), + reply(NULL) +{ + userAgent = getUserAgent(); +} + +#define CLOUDURL QString(prefs.cloud_base_url) +#define CLOUDBACKENDSTORAGE CLOUDURL + "/storage" +#define CLOUDBACKENDVERIFY CLOUDURL + "/verify" +#define CLOUDBACKENDUPDATE CLOUDURL + "/update" + +QNetworkReply* CloudStorageAuthenticate::backend(const QString& email,const QString& password,const QString& pin,const QString& newpasswd) +{ + QString payload(email + QChar(' ') + password); + QUrl requestUrl; + if (pin.isEmpty() && newpasswd.isEmpty()) { + requestUrl = QUrl(CLOUDBACKENDSTORAGE); + } else if (!newpasswd.isEmpty()) { + requestUrl = QUrl(CLOUDBACKENDUPDATE); + payload += QChar(' ') + newpasswd; + } else { + requestUrl = QUrl(CLOUDBACKENDVERIFY); + payload += QChar(' ') + pin; + } + QNetworkRequest *request = new QNetworkRequest(requestUrl); + request->setRawHeader("Accept", "text/xml, text/plain"); + request->setRawHeader("User-Agent", userAgent.toUtf8()); + request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); + reply = manager()->post(*request, qPrintable(payload)); + connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished())); + connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, + SLOT(uploadError(QNetworkReply::NetworkError))); + return reply; +} + +void CloudStorageAuthenticate::uploadFinished() +{ + static QString myLastError; + + QString cloudAuthReply(reply->readAll()); + qDebug() << "Completed connection with cloud storage backend, response" << cloudAuthReply; + if (cloudAuthReply == QLatin1String("[VERIFIED]") || cloudAuthReply == QLatin1String("[OK]")) { + prefs.cloud_verification_status = CS_VERIFIED; + /* TODO: Move this to a correct place + NotificationWidget *nw = MainWindow::instance()->getNotificationWidget(); + if (nw->getNotificationText() == myLastError) + nw->hideNotification(); + */ + myLastError.clear(); + } else if (cloudAuthReply == QLatin1String("[VERIFY]")) { + prefs.cloud_verification_status = CS_NEED_TO_VERIFY; + } else if (cloudAuthReply == QLatin1String("[PASSWDCHANGED]")) { + free(prefs.cloud_storage_password); + prefs.cloud_storage_password = prefs.cloud_storage_newpassword; + prefs.cloud_storage_newpassword = NULL; + emit passwordChangeSuccessful(); + return; + } else { + prefs.cloud_verification_status = CS_INCORRECT_USER_PASSWD; + myLastError = cloudAuthReply; + report_error("%s", qPrintable(cloudAuthReply)); + /* TODO: Emit a signal with the error + MainWindow::instance()->getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); + */ + } + emit finishedAuthenticate(); +} + +void CloudStorageAuthenticate::uploadError(QNetworkReply::NetworkError) +{ + qDebug() << "Received error response from cloud storage backend:" << reply->errorString(); +} + +void CloudStorageAuthenticate::sslErrors(QList errorList) +{ + if (verbose) { + qDebug() << "Received error response trying to set up https connection with cloud storage backend:"; + Q_FOREACH (QSslError err, errorList) { + qDebug() << err.errorString(); + } + } + QSslConfiguration conf = reply->sslConfiguration(); + QSslCertificate cert = conf.peerCertificate(); + QByteArray hexDigest = cert.digest().toHex(); + if (reply->url().toString().contains(prefs.cloud_base_url) && + hexDigest == "13ff44c62996cfa5cd69d6810675490e") { + if (verbose) + qDebug() << "Overriding SSL check as I recognize the certificate digest" << hexDigest; + reply->ignoreSslErrors(); + } else { + if (verbose) + qDebug() << "got invalid SSL certificate with hex digest" << hexDigest; + } +} + +QNetworkAccessManager *manager() +{ + static QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); + return manager; +} diff --git a/core/cloudstorage.h b/core/cloudstorage.h new file mode 100644 index 000000000..6addb739d --- /dev/null +++ b/core/cloudstorage.h @@ -0,0 +1,27 @@ +#ifndef CLOUD_STORAGE_H +#define CLOUD_STORAGE_H + +#include +#include + +class CloudStorageAuthenticate : public QObject { + Q_OBJECT +public: + QNetworkReply* backend(const QString& email,const QString& password,const QString& pin = QString(),const QString& newpasswd = QString()); + explicit CloudStorageAuthenticate(QObject *parent); +signals: + void finishedAuthenticate(); + void passwordChangeSuccessful(); +private +slots: + void uploadError(QNetworkReply::NetworkError error); + void sslErrors(QList errorList); + void uploadFinished(); +private: + QNetworkReply *reply; + QString userAgent; + bool verbose; +}; + +QNetworkAccessManager *manager(); +#endif \ No newline at end of file diff --git a/core/cochran.c b/core/cochran.c new file mode 100644 index 000000000..b42ed8233 --- /dev/null +++ b/core/cochran.c @@ -0,0 +1,809 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#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, unsigned int size, + unsigned int *duration, double *max_depth, + double *avg_depth, double *min_temp) +{ + const unsigned char *s; + unsigned int offset = 0, seconds = 0; + double depth = 0, temp = 0, depth_sample = 0, psi = 0, sgc_rate = 0; + int ascent_rate = 0; + unsigned int ndl = 0; + unsigned int in_deco = 0, deco_ceiling = 0, deco_time = 0; + + struct divecomputer *dc = &dive->dc; + struct sample *sample; + + // Initialize stat variables + *max_depth = 0, *avg_depth = 0, *min_temp = 0xFF; + + // Get starting depth and temp (tank PSI???) + switch (config.type) { + case TYPE_GEMINI: + depth = (float) (log[CMD_START_DEPTH] + + log[CMD_START_DEPTH + 1] * 256) / 4; + temp = log[CMD_START_TEMP]; + psi = log[CMD_START_PSI] + log[CMD_START_PSI + 1] * 256; + sgc_rate = (float)(log[CMD_START_SGC] + + log[CMD_START_SGC + 1] * 256) / 2; + break; + case TYPE_COMMANDER: + depth = (float) (log[CMD_START_DEPTH] + + log[CMD_START_DEPTH + 1] * 256) / 4; + temp = log[CMD_START_TEMP]; + break; + + case TYPE_EMC: + depth = (float) log [EMC_START_DEPTH] / 256 + + log[EMC_START_DEPTH + 1]; + temp = log[EMC_START_TEMP]; + break; + } + + // Skip past pre-dive events + unsigned int x = 0; + if (samples[x] != 0x40) { + unsigned int c; + while ((samples[x] & 0x80) == 0 && samples[x] != 0x40 && x < size) { + c = cochran_predive_event_bytes(samples[x]) + 1; +#ifdef COCHRAN_DEBUG + printf("Predive event: ", samples[x]); + for (int y = 0; y < c; y++) printf("%02x ", samples[x + y]); + putchar('\n'); +#endif + x += c; + } + } + + // Now process samples + offset = x; + while (offset < size) { + s = samples + offset; + + // Start with an empty sample + sample = prepare_sample(dc); + sample->time.seconds = seconds; + + // Check for event + if (s[0] & 0x80) { + cochran_dive_event(dc, s, seconds, &in_deco, &deco_ceiling, &deco_time); + offset += cochran_dive_event_bytes(s[0]) + 1; + continue; + } + + // Depth is in every sample + depth_sample = (float)(s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); + depth += depth_sample; + +#ifdef COCHRAN_DEBUG + cochran_debug_sample(s, seconds); +#endif + + switch (config.type) { + case TYPE_COMMANDER: + switch (seconds % 2) { + case 0: // Ascent rate + ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1); + break; + case 1: // Temperature + temp = s[1] / 2 + 20; + break; + } + break; + case TYPE_GEMINI: + // Gemini with tank pressure and SAC rate. + switch (seconds % 4) { + case 0: // Ascent rate + ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1); + break; + case 2: // PSI change + psi -= (float)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 4; + break; + case 1: // SGC rate + sgc_rate -= (float)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 2; + break; + case 3: // Temperature + temp = (float)s[1] / 2 + 20; + break; + } + break; + case TYPE_EMC: + switch (seconds % 2) { + case 0: // Ascent rate + ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1); + break; + case 1: // Temperature + temp = (float)s[1] / 2 + 20; + break; + } + // Get NDL and deco information + switch (seconds % 24) { + case 20: + if (in_deco) { + // Fist stop time + //first_deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds + ndl = 0; + } else { + // NDL + ndl = (s[2] + s[5] * 256 + 1) * 60; // seconds + deco_time = 0; + } + break; + case 22: + if (in_deco) { + // Total stop time + deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds + ndl = 0; + } + break; + } + } + + // Track dive stats + if (depth > *max_depth) *max_depth = depth; + if (temp < *min_temp) *min_temp = temp; + *avg_depth = (*avg_depth * seconds + depth) / (seconds + 1); + + sample->depth.mm = depth * FEET * 1000; + sample->ndl.seconds = ndl; + sample->in_deco = in_deco; + sample->stoptime.seconds = deco_time; + sample->stopdepth.mm = deco_ceiling * FEET * 1000; + sample->temperature.mkelvin = C_to_mkelvin((temp - 32) / 1.8); + sample->sensor = 0; + sample->cylinderpressure.mbar = psi * PSI / 100; + + finish_sample(dc); + + offset += config.sample_size; + seconds++; + } + (void)ascent_rate; // mark the variable as unused + + if (seconds > 0) + *duration = seconds - 1; +} + +static void cochran_parse_dive(const unsigned char *decode, unsigned mod, + const unsigned char *in, unsigned size) +{ + unsigned char *buf = malloc(size); + struct dive *dive; + struct divecomputer *dc; + struct tm tm = {0}; + uint32_t csum[5]; + + double max_depth, avg_depth, min_temp; + unsigned int duration = 0, corrupt_dive = 0; + + /* + * The scrambling has odd boundaries. I think the boundaries + * match some data structure size, but I don't know. They were + * discovered the same way we dynamically discover the decode + * size: automatically looking for least random output. + * + * The boundaries are also this confused "off-by-one" thing, + * the same way the file size is off by one. It's as if the + * cochran software forgot to write one byte at the beginning. + */ + partial_decode(0, 0x0fff, decode, 1, mod, in, size, buf); + partial_decode(0x0fff, 0x1fff, decode, 0, mod, in, size, buf); + partial_decode(0x1fff, 0x2fff, decode, 0, mod, in, size, buf); + partial_decode(0x2fff, 0x48ff, decode, 0, mod, in, size, buf); + + /* + * This is not all the descrambling you need - the above are just + * what appears to be the fixed-size blocks. The rest is also + * scrambled, but there seems to be size differences in the data, + * so this just descrambles part of it: + */ + // Decode log entry (512 bytes + random prefix) + partial_decode(0x48ff, 0x4914 + config.logbook_size, decode, + 0, mod, in, size, buf); + + unsigned int sample_size = size - 0x4914 - config.logbook_size; + int g; + + // Decode sample data + partial_decode(0x4914 + config.logbook_size, size, decode, + 0, mod, in, size, buf); + +#ifdef COCHRAN_DEBUG + // Display pre-logbook data + puts("\nPre Logbook Data\n"); + cochran_debug_write(buf, 0x4914); + + // Display log book + puts("\nLogbook Data\n"); + cochran_debug_write(buf + 0x4914, config.logbook_size + 0x400); + + // Display sample data + puts("\nSample Data\n"); +#endif + + dive = alloc_dive(); + dc = &dive->dc; + + unsigned char *log = (buf + 0x4914); + + switch (config.type) { + case TYPE_GEMINI: + case TYPE_COMMANDER: + if (config.type == TYPE_GEMINI) { + dc->model = "Gemini"; + dc->deviceid = buf[0x18c] * 256 + buf[0x18d]; // serial no + fill_default_cylinder(&dive->cylinder[0]); + dive->cylinder[0].gasmix.o2.permille = (log[CMD_O2_PERCENT] / 256 + + log[CMD_O2_PERCENT + 1]) * 10; + dive->cylinder[0].gasmix.he.permille = 0; + } else { + dc->model = "Commander"; + dc->deviceid = array_uint32_le(buf + 0x31e); // serial no + for (g = 0; g < 2; g++) { + fill_default_cylinder(&dive->cylinder[g]); + dive->cylinder[g].gasmix.o2.permille = (log[CMD_O2_PERCENT + g * 2] / 256 + + log[CMD_O2_PERCENT + g * 2 + 1]) * 10; + dive->cylinder[g].gasmix.he.permille = 0; + } + } + + tm.tm_year = log[CMD_YEAR]; + tm.tm_mon = log[CMD_MON] - 1; + tm.tm_mday = log[CMD_DAY]; + tm.tm_hour = log[CMD_HOUR]; + tm.tm_min = log[CMD_MIN]; + tm.tm_sec = log[CMD_SEC]; + tm.tm_isdst = -1; + + dive->when = dc->when = utc_mktime(&tm); + dive->number = log[CMD_NUMBER] + log[CMD_NUMBER + 1] * 256 + 1; + dc->duration.seconds = (log[CMD_BT] + log[CMD_BT + 1] * 256) * 60; + dc->surfacetime.seconds = (log[CMD_SIT] + log[CMD_SIT + 1] * 256) * 60; + dc->maxdepth.mm = (log[CMD_MAX_DEPTH] + + log[CMD_MAX_DEPTH + 1] * 256) / 4 * FEET * 1000; + dc->meandepth.mm = (log[CMD_AVG_DEPTH] + + log[CMD_AVG_DEPTH + 1] * 256) / 4 * FEET * 1000; + dc->watertemp.mkelvin = C_to_mkelvin((log[CMD_MIN_TEMP] / 32) - 1.8); + dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577 + * (double) log[CMD_ALTITUDE] * 250 * FEET, 5.25588) * 1000; + dc->salinity = 10000 + 150 * log[CMD_WATER_CONDUCTIVITY]; + + SHA1(log + CMD_NUMBER, 2, (unsigned char *)csum); + dc->diveid = csum[0]; + + if (log[CMD_MAX_DEPTH] == 0xff && log[CMD_MAX_DEPTH + 1] == 0xff) + corrupt_dive = 1; + + break; + case TYPE_EMC: + dc->model = "EMC"; + dc->deviceid = array_uint32_le(buf + 0x31e); // serial no + for (g = 0; g < 4; g++) { + fill_default_cylinder(&dive->cylinder[g]); + dive->cylinder[g].gasmix.o2.permille = + (log[EMC_O2_PERCENT + g * 2] / 256 + + log[EMC_O2_PERCENT + g * 2 + 1]) * 10; + dive->cylinder[g].gasmix.he.permille = + (log[EMC_HE_PERCENT + g * 2] / 256 + + log[EMC_HE_PERCENT + g * 2 + 1]) * 10; + } + + tm.tm_year = log[EMC_YEAR]; + tm.tm_mon = log[EMC_MON] - 1; + tm.tm_mday = log[EMC_DAY]; + tm.tm_hour = log[EMC_HOUR]; + tm.tm_min = log[EMC_MIN]; + tm.tm_sec = log[EMC_SEC]; + tm.tm_isdst = -1; + + dive->when = dc->when = utc_mktime(&tm); + dive->number = log[EMC_NUMBER] + log[EMC_NUMBER + 1] * 256 + 1; + dc->duration.seconds = (log[EMC_BT] + log[EMC_BT + 1] * 256) * 60; + dc->surfacetime.seconds = (log[EMC_SIT] + log[EMC_SIT + 1] * 256) * 60; + dc->maxdepth.mm = (log[EMC_MAX_DEPTH] + + log[EMC_MAX_DEPTH + 1] * 256) / 4 * FEET * 1000; + dc->meandepth.mm = (log[EMC_AVG_DEPTH] + + log[EMC_AVG_DEPTH + 1] * 256) / 4 * FEET * 1000; + dc->watertemp.mkelvin = C_to_mkelvin((log[EMC_MIN_TEMP] - 32) / 1.8); + dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577 + * (double) log[EMC_ALTITUDE] * 250 * FEET, 5.25588) * 1000; + dc->salinity = 10000 + 150 * (log[EMC_WATER_CONDUCTIVITY] & 0x3); + + SHA1(log + EMC_NUMBER, 2, (unsigned char *)csum); + dc->diveid = csum[0]; + + if (log[EMC_MAX_DEPTH] == 0xff && log[EMC_MAX_DEPTH + 1] == 0xff) + corrupt_dive = 1; + + break; + } + + cochran_parse_samples(dive, buf + 0x4914, buf + 0x4914 + + config.logbook_size, sample_size, + &duration, &max_depth, &avg_depth, &min_temp); + + // Check for corrupt dive + if (corrupt_dive) { + dc->maxdepth.mm = max_depth * FEET * 1000; + dc->meandepth.mm = avg_depth * FEET * 1000; + dc->watertemp.mkelvin = C_to_mkelvin((min_temp - 32) / 1.8); + dc->duration.seconds = duration; + } + + dive->downloaded = true; + record_dive(dive); + mark_divelist_changed(true); + + free(buf); +} + +int try_to_open_cochran(const char *filename, struct memblock *mem) +{ + (void) filename; + unsigned int i; + unsigned int mod; + unsigned int *offsets, dive1, dive2; + unsigned char *decode = mem->buffer + 0x40001; + + if (mem->size < 0x40000) + return 0; + + offsets = (unsigned int *) mem->buffer; + dive1 = offsets[0]; + dive2 = offsets[1]; + + if (dive1 < 0x40000 || dive2 < dive1 || dive2 > mem->size) + return 0; + + mod = decode[0x100] + 1; + cochran_parse_header(decode, mod, mem->buffer + 0x40000, dive1 - 0x40000); + + // Decode each dive + for (i = 0; i < 65534; i++) { + dive1 = offsets[i]; + dive2 = offsets[i + 1]; + if (dive2 < dive1) + break; + if (dive2 > mem->size) + break; + + cochran_parse_dive(decode, mod, mem->buffer + dive1, + dive2 - dive1); + } + + return 1; // no further processing needed +} diff --git a/core/cochran.h b/core/cochran.h new file mode 100644 index 000000000..97d4361c8 --- /dev/null +++ b/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/core/color.cpp b/core/color.cpp new file mode 100644 index 000000000..cf6f43916 --- /dev/null +++ b/core/color.cpp @@ -0,0 +1,88 @@ +#include "color.h" + +QMap > profile_color; + +void fill_profile_color() +{ +#define COLOR(x, y, z) QVector() << x << y << z; + profile_color[SAC_1] = COLOR(FUNGREEN1, BLACK1_LOW_TRANS, FUNGREEN1); + profile_color[SAC_2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); + profile_color[SAC_3] = COLOR(ATLANTIS1, BLACK1_LOW_TRANS, ATLANTIS1); + profile_color[SAC_4] = COLOR(ATLANTIS2, BLACK1_LOW_TRANS, ATLANTIS2); + profile_color[SAC_5] = COLOR(EARLSGREEN1, BLACK1_LOW_TRANS, EARLSGREEN1); + profile_color[SAC_6] = COLOR(HOKEYPOKEY1, BLACK1_LOW_TRANS, HOKEYPOKEY1); + profile_color[SAC_7] = COLOR(TUSCANY1, BLACK1_LOW_TRANS, TUSCANY1); + profile_color[SAC_8] = COLOR(CINNABAR1, BLACK1_LOW_TRANS, CINNABAR1); + profile_color[SAC_9] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); + + profile_color[VELO_STABLE] = COLOR(CAMARONE1, BLACK1_LOW_TRANS, CAMARONE1); + profile_color[VELO_SLOW] = COLOR(LIMENADE1, BLACK1_LOW_TRANS, LIMENADE1); + profile_color[VELO_MODERATE] = COLOR(RIOGRANDE1, BLACK1_LOW_TRANS, RIOGRANDE1); + profile_color[VELO_FAST] = COLOR(PIRATEGOLD1, BLACK1_LOW_TRANS, PIRATEGOLD1); + profile_color[VELO_CRAZY] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + + profile_color[PO2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); + profile_color[PO2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[PN2] = COLOR(BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS); + profile_color[PN2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[PHE] = COLOR(PEANUT, BLACK1_LOW_TRANS, PEANUT); + profile_color[PHE_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[O2SETPOINT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); + profile_color[CCRSENSOR1] = COLOR(TUNDORA1_MED_TRANS, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS); + profile_color[CCRSENSOR2] = COLOR(ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS); + profile_color[CCRSENSOR3] = COLOR(PEANUT, BLACK1_LOW_TRANS, PEANUT); + profile_color[PP_LINES] = COLOR(BLACK1_HIGH_TRANS, BLACK1_LOW_TRANS, BLACK1_HIGH_TRANS); + + profile_color[TEXT_BACKGROUND] = COLOR(CONCRETE1_LOWER_TRANS, WHITE1, CONCRETE1_LOWER_TRANS); + profile_color[ALERT_BG] = COLOR(BROOM1_LOWER_TRANS, BLACK1_LOW_TRANS, BROOM1_LOWER_TRANS); + profile_color[ALERT_FG] = COLOR(BLACK1_LOW_TRANS, WHITE1, BLACK1_LOW_TRANS); + profile_color[EVENTS] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); + profile_color[SAMPLE_DEEP] = COLOR(QColor(Qt::red).darker(), BLACK1, PERSIANRED1); + profile_color[SAMPLE_SHALLOW] = COLOR(QColor(Qt::red).lighter(), BLACK1_LOW_TRANS, PERSIANRED1); + profile_color[SMOOTHED] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_LOW_TRANS, REDORANGE1_HIGH_TRANS); + profile_color[MINUTE] = COLOR(MEDIUMREDVIOLET1_HIGHER_TRANS, BLACK1_LOW_TRANS, MEDIUMREDVIOLET1_HIGHER_TRANS); + profile_color[TIME_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); + profile_color[TIME_TEXT] = COLOR(FORESTGREEN1, BLACK1, FORESTGREEN1); + profile_color[DEPTH_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); + profile_color[MEAN_DEPTH] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); + profile_color[HR_PLOT] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); + profile_color[HR_TEXT] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); + profile_color[HR_AXIS] = COLOR(MED_GRAY_HIGH_TRANS, MED_GRAY_HIGH_TRANS, MED_GRAY_HIGH_TRANS); + profile_color[DEPTH_BOTTOM] = COLOR(GOVERNORBAY1_MED_TRANS, BLACK1_HIGH_TRANS, GOVERNORBAY1_MED_TRANS); + profile_color[DEPTH_TOP] = COLOR(MERCURY1_MED_TRANS, WHITE1_MED_TRANS, MERCURY1_MED_TRANS); + profile_color[TEMP_TEXT] = COLOR(GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2); + profile_color[TEMP_PLOT] = COLOR(ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS); + profile_color[SAC_DEFAULT] = COLOR(WHITE1, BLACK1_LOW_TRANS, FORESTGREEN1); + profile_color[BOUNDING_BOX] = COLOR(WHITE1, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS); + profile_color[PRESSURE_TEXT] = COLOR(KILLARNEY1, BLACK1_LOW_TRANS, KILLARNEY1); + profile_color[BACKGROUND] = COLOR(SPRINGWOOD1, WHITE1, SPRINGWOOD1); + profile_color[BACKGROUND_TRANS] = COLOR(SPRINGWOOD1_MED_TRANS, WHITE1_MED_TRANS, SPRINGWOOD1_MED_TRANS); + profile_color[CEILING_SHALLOW] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_HIGH_TRANS, REDORANGE1_HIGH_TRANS); + profile_color[CEILING_DEEP] = COLOR(RED1_MED_TRANS, BLACK1_HIGH_TRANS, RED1_MED_TRANS); + profile_color[CALC_CEILING_SHALLOW] = COLOR(FUNGREEN1_HIGH_TRANS, BLACK1_HIGH_TRANS, FUNGREEN1_HIGH_TRANS); + profile_color[CALC_CEILING_DEEP] = COLOR(APPLE1_HIGH_TRANS, BLACK1_HIGH_TRANS, APPLE1_HIGH_TRANS); + profile_color[TISSUE_PERCENTAGE] = COLOR(GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2); + profile_color[GF_LINE] = COLOR(BLACK1, BLACK1_LOW_TRANS, BLACK1); + profile_color[AMB_PRESSURE_LINE] = COLOR(TUNDORA1_MED_TRANS, BLACK1_LOW_TRANS, ATLANTIS1); +#undef COLOR +} + +QColor getColor(const color_indice_t i, bool isGrayscale) +{ + if (profile_color.count() > i && i >= 0) + return profile_color[i].at((isGrayscale) ? 1 : 0); + return QColor(Qt::black); +} + +QColor getSacColor(int sac, int avg_sac) +{ + int sac_index = 0; + int delta = sac - avg_sac + 7000; + + sac_index = delta / 2000; + if (sac_index < 0) + sac_index = 0; + if (sac_index > SAC_COLORS - 1) + sac_index = SAC_COLORS - 1; + return getColor((color_indice_t)(SAC_COLORS_START_IDX + sac_index), false); +} diff --git a/core/color.h b/core/color.h new file mode 100644 index 000000000..57ad77242 --- /dev/null +++ b/core/color.h @@ -0,0 +1,152 @@ +#ifndef COLOR_H +#define COLOR_H + +/* The colors are named by picking the closest match + from http://chir.ag/projects/name-that-color */ + +#include +#include +#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) + +#define SAC_COLORS_START_IDX SAC_1 +#define SAC_COLORS 9 +#define VELOCITY_COLORS_START_IDX VELO_STABLE +#define VELOCITY_COLORS 5 + +typedef enum { + /* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */ + SAC_1, + SAC_2, + SAC_3, + SAC_4, + SAC_5, + SAC_6, + SAC_7, + SAC_8, + SAC_9, + + /* Velocity colors. Order is still important, ref VELOCITY_COLORS_START_IDX. */ + VELO_STABLE, + VELO_SLOW, + VELO_MODERATE, + VELO_FAST, + VELO_CRAZY, + + /* gas colors */ + PO2, + PO2_ALERT, + PN2, + PN2_ALERT, + PHE, + PHE_ALERT, + O2SETPOINT, + CCRSENSOR1, + CCRSENSOR2, + CCRSENSOR3, + PP_LINES, + + /* Other colors */ + TEXT_BACKGROUND, + ALERT_BG, + ALERT_FG, + EVENTS, + SAMPLE_DEEP, + SAMPLE_SHALLOW, + SMOOTHED, + MINUTE, + TIME_GRID, + TIME_TEXT, + DEPTH_GRID, + MEAN_DEPTH, + HR_TEXT, + HR_PLOT, + HR_AXIS, + DEPTH_TOP, + DEPTH_BOTTOM, + TEMP_TEXT, + TEMP_PLOT, + SAC_DEFAULT, + BOUNDING_BOX, + PRESSURE_TEXT, + BACKGROUND, + BACKGROUND_TRANS, + CEILING_SHALLOW, + CEILING_DEEP, + CALC_CEILING_SHALLOW, + CALC_CEILING_DEEP, + TISSUE_PERCENTAGE, + GF_LINE, + AMB_PRESSURE_LINE +} color_indice_t; + +extern QMap > profile_color; +void fill_profile_color(); +QColor getColor(const color_indice_t i, bool isGrayscale = false); +QColor getSacColor(int sac, int diveSac); +struct text_render_options { + double size; + color_indice_t color; + double hpos, vpos; +}; + +typedef text_render_options text_render_options_t; + +#endif // COLOR_H diff --git a/core/compressibility.r b/core/compressibility.r new file mode 100644 index 000000000..66310f3aa --- /dev/null +++ b/core/compressibility.r @@ -0,0 +1,115 @@ +# Compressibility data gathered by Lubomir I Ivanov: +# +# "Data obtained by finding two books online: +# +# [1] +# PERRY’S CHEMICAL ENGINEERS’ HANDBOOK SEVENTH EDITION +# pretty serious book, from which the wiki AIR values come from! +# +# http://www.unhas.ac.id/rhiza/arsip/kuliah/Sistem-dan-Tekn-Kendali-Proses/PDF_Collections/REFERENSI/Perrys_Chemical_Engineering_Handbook.pdf +# page 2-165 +# +# [*](Computed from pressure-volume-temperature tables in Vasserman monographs) +# ^ i have no idea idea what this means, but the values might not be exactly +# experimental?! +# +# the only thing this book is missing is helium, thus [2]! +# +# [2] +# VOLUMETRIC BEHAVIOR OF HELIUM-ARGON MIXTURES AT HIGH PRESSURE AND MODERATE TEMPERATURE. +# +# https://shareok.org/bitstream/handle/11244/2062/6614196.PDF?sequence=1 +# page 108 +# +# +# the book has some tables with pressure values in atmosphere units. i'm +# converting them bars. one of the relevant tables is for 323K and one for 273K +# (both almost equal distance from 300K). +# +# this again is a linear mix operation between isotherms, which is probably not +# the most accurate solution but it works. +# +# all data sets contain Z values at 300k, while the pressures are in bars in +# the 1 to 500 range +# +# + +x = c(1, 5, 10, 20, 40, 60, 80, 100, 200, 300, 400, 500) +o2 = c(0.9994, 0.9968, 0.9941, 0.9884, 0.9771, 0.9676, 0.9597, 0.9542, 0.9560, 0.9972, 1.0689, 1.1572) +n2 = c(0.9998, 0.9990, 0.9983, 0.9971, 0.9964, 0.9973, 1.0000, 1.0052, 1.0559, 1.1422, 1.2480, 1.3629) +he = c(1.0005, 1.0024, 1.0048, 1.0096, 1.0191, 1.0286, 1.0381, 1.0476, 1.0943, 1.1402, 1.1854, 1.2297) + +options(digits=15) + +# +# Get the O2 virial coefficients +# +plot(x,o2) +o2fit = nls(o2 ~ 1.0 + p1*x + p2 *x^2 + p3*x^3, start=list(p1=0,p2=0,p3=0)) +summary(o2fit) + +new = data.frame(x = seq(min(x),max(x),len=200)) +lines(new$x,predict(o2fit,newdata=new)) + +# +# Get the N2 virial coefficients +# +plot(x,n2) +n2fit = nls(n2 ~ 1.0 + p1*x + p2 *x^2 + p3*x^3, start=list(p1=0,p2=0,p3=0)) +summary(n2fit) + +new = data.frame(x = seq(min(x),max(x),len=200)) +lines(new$x,predict(n2fit,newdata=new)) + +# +# Get the He virial coefficients +# +# NOTE! This will not confirm convergence, thus the warnOnly. +# That may be a sign that the data is possibly artificial. +# +plot(x,he) +hefit = nls(he ~ 1.0 + p1*x + p2 *x^2 + p3*x^3, + start=list(p1=0,p2=0,p3=0), + control=nls.control(warnOnly=TRUE)) +summary(hefit) + +new = data.frame(x = seq(min(x),max(x),len=200)) +lines(new$x,predict(hefit,newdata=new)) + +# +# Raw data from VOLUMETRIC BEHAVIOR OF HELIUM-ARGON MIXTURES [..] +# T=323.15K (50 C) +p323atm = c(674.837, 393.223, 237.310, 146.294, 91.4027, 57.5799, 36.4620, 23.1654, 14.7478, 9.4017, 5.9987, 3.8300, + 540.204, 319.943, 195.008, 120.951, 75.8599, 47.9005, 30.3791, 19.3193, 12.3080, 7.8495, 5.0100, 3.1992) + +Hez323 = c(1.28067, 1.16782, 1.10289, 1.06407, 1.04028, 1.02548, 1.01617, 1.01029, 1.00656, 1.00418, 1.00267, 1.00171, + 1.22738, 1.13754, 1.08493, 1.05312, 1.03349, 1.02122, 1.01349, 1.00859, 1.00548, 1.00349, 1.00223, 1.00143) + + +# T=273.15 (0 C) +p273atm = c(683.599, 391.213, 233.607, 143.091, 89.0521, 55.9640, 35.3851, 22.4593, 14.2908, 9.1072, 5.8095, 3.7083, + 534.047, 312.144, 188.741, 116.508, 72.8529, 45.9194, 29.0883, 18.4851, 11.7702, 7.5040, 4.7881, 3.0570) + +Hez273 = c(1.33969, 1.19985, 1.12121, 1.07494, 1.04689, 1.02957, 1.01874, 1.01191, 1.00758, 1.00484, 1.00309, 1.00197, + 1.26914, 1.16070, 1.09837, 1.06118, 1.03843, 1.02429, 1.01541, 1.00980, 1.00625, 1.00398, 1.00254, 1.00162) + +p323 = p323atm * 1.01325 +p273 = p273atm * 1.01325 + +x2=append(p323,p273) +he2=append(Hez323,Hez273) + +plot(x2,he2) + +hefit2 = nls(he2 ~ 1.0 + p1*x2 + p2*x2^2 + p3*x2^3, + start=list(p1=0,p2=0,p3=0)) +summary(hefit2) + +he3 = function(bar) +{ + 1.0 +0.00047961098687979363 * bar -0.00000004077670019935 * bar^2 +0.00000000000077707035 * bar^3 +} + +new = data.frame(x2 = seq(min(x2),max(x2),len=200)) +lines(new$x2,predict(hefit2,newdata=new)) +curve(he3, min(x2),max(x2),add=TRUE) diff --git a/core/configuredivecomputer.cpp b/core/configuredivecomputer.cpp new file mode 100644 index 000000000..2457ffe82 --- /dev/null +++ b/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/core/configuredivecomputer.h b/core/configuredivecomputer.h new file mode 100644 index 000000000..f14eeeca3 --- /dev/null +++ b/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/core/configuredivecomputerthreads.cpp b/core/configuredivecomputerthreads.cpp new file mode 100644 index 000000000..b229fc808 --- /dev/null +++ b/core/configuredivecomputerthreads.cpp @@ -0,0 +1,1778 @@ +#include "configuredivecomputerthreads.h" +#include "libdivecomputer/hw.h" +#include "libdivecomputer.h" +#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_TEMP_SENSOR_OFFSET 0x42 +#define OSTC3_SAFETY_STOP_LENGTH 0x43 +#define OSTC3_SAFETY_STOP_START_DEPTH 0x44 +#define OSTC3_SAFETY_STOP_END_DEPTH 0x45 +#define OSTC3_SAFETY_STOP_RESET_DEPTH 0x46 + +#define OSTC3_HW_OSTC_3 0x0A +#define OSTC3_HW_OSTC_3P 0x1A +#define OSTC3_HW_OSTC_CR 0x05 +#define OSTC3_HW_OSTC_SPORT 0x12 +#define OSTC3_HW_OSTC_2 0x11 + + +#define SUUNTO_VYPER_MAXDEPTH 0x1e +#define SUUNTO_VYPER_TOTAL_TIME 0x20 +#define SUUNTO_VYPER_NUMBEROFDIVES 0x22 +#define SUUNTO_VYPER_COMPUTER_TYPE 0x24 +#define SUUNTO_VYPER_FIRMWARE 0x25 +#define SUUNTO_VYPER_SERIALNUMBER 0x26 +#define SUUNTO_VYPER_CUSTOM_TEXT 0x2c +#define SUUNTO_VYPER_SAMPLING_RATE 0x53 +#define SUUNTO_VYPER_ALTITUDE_SAFETY 0x54 +#define SUUNTO_VYPER_TIMEFORMAT 0x60 +#define SUUNTO_VYPER_UNITS 0x62 +#define SUUNTO_VYPER_MODEL 0x63 +#define SUUNTO_VYPER_LIGHT 0x64 +#define SUUNTO_VYPER_ALARM_DEPTH_TIME 0x65 +#define SUUNTO_VYPER_ALARM_TIME 0x66 +#define SUUNTO_VYPER_ALARM_DEPTH 0x68 +#define SUUNTO_VYPER_CUSTOM_TEXT_LENGHT 30 + +#ifdef DEBUG_OSTC +// Fake io to ostc memory banks +#define hw_ostc_device_eeprom_read local_hw_ostc_device_eeprom_read +#define hw_ostc_device_eeprom_write local_hw_ostc_device_eeprom_write +#define hw_ostc_device_clock local_hw_ostc_device_clock +#define OSTC_FILE "../OSTC-data-dump.bin" + +// Fake the open function. +static dc_status_t local_dc_device_open(dc_device_t **out, dc_context_t *context, dc_descriptor_t *descriptor, const char *name) +{ + if (strcmp(dc_descriptor_get_vendor(descriptor), "Heinrichs Weikamp") == 0 &&strcmp(dc_descriptor_get_product(descriptor), "OSTC 2N") == 0) + return DC_STATUS_SUCCESS; + else + return dc_device_open(out, context, descriptor, name); +} +#define dc_device_open local_dc_device_open + +// Fake the custom open function +static dc_status_t local_dc_device_custom_open(dc_device_t **out, dc_context_t *context, dc_descriptor_t *descriptor, dc_serial_t *serial) +{ + if (strcmp(dc_descriptor_get_vendor(descriptor), "Heinrichs Weikamp") == 0 &&strcmp(dc_descriptor_get_product(descriptor), "OSTC 2N") == 0) + return DC_STATUS_SUCCESS; + else + return dc_device_custom_open(out, context, descriptor, serial); +} +#define dc_device_custom_open local_dc_device_custom_open + +static dc_status_t local_hw_ostc_device_eeprom_read(void *ignored, unsigned char bank, unsigned char data[], unsigned int data_size) +{ + FILE *f; + if ((f = fopen(OSTC_FILE, "r")) == NULL) + return DC_STATUS_NODEVICE; + fseek(f, bank * 256, SEEK_SET); + if (fread(data, sizeof(unsigned char), data_size, f) != data_size) { + fclose(f); + return DC_STATUS_IO; + } + fclose(f); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t local_hw_ostc_device_eeprom_write(void *ignored, unsigned char bank, unsigned char data[], unsigned int data_size) +{ + FILE *f; + if ((f = fopen(OSTC_FILE, "r+")) == NULL) + f = fopen(OSTC_FILE, "w"); + fseek(f, bank * 256, SEEK_SET); + fwrite(data, sizeof(unsigned char), data_size, f); + fclose(f); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t local_hw_ostc_device_clock(void *ignored, dc_datetime_t *time) +{ + return DC_STATUS_SUCCESS; +} +#endif + +static int read_ostc_cf(unsigned char data[], unsigned char cf) +{ + return data[128 + (cf % 32) * 4 + 3] << 8 ^ data[128 + (cf % 32) * 4 + 2]; +} + +static void write_ostc_cf(unsigned char data[], unsigned char cf, unsigned char max_CF, unsigned int value) +{ + // Only write settings supported by this firmware. + if (cf > max_CF) + return; + + data[128 + (cf % 32) * 4 + 3] = (value & 0xff00) >> 8; + data[128 + (cf % 32) * 4 + 2] = (value & 0x00ff); +} + +#define EMIT_PROGRESS() do { \ + progress.current++; \ + progress_cb(device, DC_EVENT_PROGRESS, &progress, userdata); \ + } while (0) + +static dc_status_t read_suunto_vyper_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) +{ + unsigned char data[SUUNTO_VYPER_CUSTOM_TEXT_LENGHT + 1]; + dc_status_t rc; + dc_event_progress_t progress; + progress.current = 0; + progress.maximum = 16; + + rc = dc_device_read(device, SUUNTO_VYPER_COMPUTER_TYPE, data, 1); + if (rc == DC_STATUS_SUCCESS) { + const char *model; + // FIXME: grab this info from libdivecomputer descriptor + // instead of hard coded here + switch (data[0]) { + case 0x03: + model = "Stinger"; + break; + case 0x04: + model = "Mosquito"; + break; + case 0x05: + model = "D3"; + break; + case 0x0A: + model = "Vyper"; + break; + case 0x0B: + model = "Vytec"; + break; + case 0x0C: + model = "Cobra"; + break; + case 0x0D: + model = "Gekko"; + break; + case 0x16: + model = "Zoop"; + break; + case 20: + case 30: + case 60: + // Suunto Spyder have there sample interval at this position + // Fallthrough + default: + return DC_STATUS_UNSUPPORTED; + } + // We found a supported device + // we can safely proceed with reading/writing to this device. + m_deviceDetails->model = model; + } + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_MAXDEPTH, data, 2); + if (rc != DC_STATUS_SUCCESS) + return rc; + // in ft * 128.0 + int depth = feet_to_mm(data[0] << 8 ^ data[1]) / 128; + m_deviceDetails->maxDepth = depth; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_TOTAL_TIME, data, 2); + if (rc != DC_STATUS_SUCCESS) + return rc; + int total_time = data[0] << 8 ^ data[1]; + m_deviceDetails->totalTime = total_time; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_NUMBEROFDIVES, data, 2); + if (rc != DC_STATUS_SUCCESS) + return rc; + int number_of_dives = data[0] << 8 ^ data[1]; + m_deviceDetails->numberOfDives = number_of_dives; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_FIRMWARE, data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + m_deviceDetails->firmwareVersion = QString::number(data[0]) + ".0.0"; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_SERIALNUMBER, data, 4); + if (rc != DC_STATUS_SUCCESS) + return rc; + int serial_number = data[0] * 1000000 + data[1] * 10000 + data[2] * 100 + data[3]; + m_deviceDetails->serialNo = QString::number(serial_number); + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_CUSTOM_TEXT, data, SUUNTO_VYPER_CUSTOM_TEXT_LENGHT); + if (rc != DC_STATUS_SUCCESS) + return rc; + data[SUUNTO_VYPER_CUSTOM_TEXT_LENGHT] = 0; + m_deviceDetails->customText = (const char *)data; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_SAMPLING_RATE, data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + m_deviceDetails->samplingRate = (int)data[0]; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_ALTITUDE_SAFETY, data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + m_deviceDetails->altitude = data[0] & 0x03; + m_deviceDetails->personalSafety = data[0] >> 2 & 0x03; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_TIMEFORMAT, data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + m_deviceDetails->timeFormat = data[0] & 0x01; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_UNITS, data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + m_deviceDetails->units = data[0] & 0x01; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_MODEL, data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + m_deviceDetails->diveMode = data[0] & 0x03; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_LIGHT, data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + m_deviceDetails->lightEnabled = data[0] >> 7; + m_deviceDetails->light = data[0] & 0x7F; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_ALARM_DEPTH_TIME, data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + m_deviceDetails->alarmTimeEnabled = data[0] & 0x01; + m_deviceDetails->alarmDepthEnabled = data[0] >> 1 & 0x01; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_ALARM_TIME, data, 2); + if (rc != DC_STATUS_SUCCESS) + return rc; + int time = data[0] << 8 ^ data[1]; + // The stinger stores alarm time in seconds instead of minutes. + if (m_deviceDetails->model == "Stinger") + time /= 60; + m_deviceDetails->alarmTime = time; + EMIT_PROGRESS(); + + rc = dc_device_read(device, SUUNTO_VYPER_ALARM_DEPTH, data, 2); + if (rc != DC_STATUS_SUCCESS) + return rc; + depth = feet_to_mm(data[0] << 8 ^ data[1]) / 128; + m_deviceDetails->alarmDepth = depth; + EMIT_PROGRESS(); + + return DC_STATUS_SUCCESS; +} + +static dc_status_t write_suunto_vyper_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) +{ + dc_status_t rc; + dc_event_progress_t progress; + progress.current = 0; + progress.maximum = 10; + unsigned char data; + unsigned char data2[2]; + int time; + + // Maybee we should read the model from the device to sanity check it here too.. + // For now we just check that we actually read a device before writing to one. + if (m_deviceDetails->model == "") + return DC_STATUS_UNSUPPORTED; + + rc = dc_device_write(device, SUUNTO_VYPER_CUSTOM_TEXT, + // Convert the customText to a 30 char wide padded with " " + (const unsigned char *)QString("%1").arg(m_deviceDetails->customText, -30, QChar(' ')).toUtf8().data(), + SUUNTO_VYPER_CUSTOM_TEXT_LENGHT); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + data = m_deviceDetails->samplingRate; + rc = dc_device_write(device, SUUNTO_VYPER_SAMPLING_RATE, &data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + data = m_deviceDetails->personalSafety << 2 ^ m_deviceDetails->altitude; + rc = dc_device_write(device, SUUNTO_VYPER_ALTITUDE_SAFETY, &data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + data = m_deviceDetails->timeFormat; + rc = dc_device_write(device, SUUNTO_VYPER_TIMEFORMAT, &data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + data = m_deviceDetails->units; + rc = dc_device_write(device, SUUNTO_VYPER_UNITS, &data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + data = m_deviceDetails->diveMode; + rc = dc_device_write(device, SUUNTO_VYPER_MODEL, &data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + data = m_deviceDetails->lightEnabled << 7 ^ (m_deviceDetails->light & 0x7F); + rc = dc_device_write(device, SUUNTO_VYPER_LIGHT, &data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + data = m_deviceDetails->alarmDepthEnabled << 1 ^ m_deviceDetails->alarmTimeEnabled; + rc = dc_device_write(device, SUUNTO_VYPER_ALARM_DEPTH_TIME, &data, 1); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + // The stinger stores alarm time in seconds instead of minutes. + time = m_deviceDetails->alarmTime; + if (m_deviceDetails->model == "Stinger") + time *= 60; + data2[0] = time >> 8; + data2[1] = time & 0xFF; + rc = dc_device_write(device, SUUNTO_VYPER_ALARM_TIME, data2, 2); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + data2[0] = (int)(mm_to_feet(m_deviceDetails->alarmDepth) * 128) >> 8; + data2[1] = (int)(mm_to_feet(m_deviceDetails->alarmDepth) * 128) & 0x0FF; + rc = dc_device_write(device, SUUNTO_VYPER_ALARM_DEPTH, data2, 2); + EMIT_PROGRESS(); + return rc; +} + +#if DC_VERSION_CHECK(0, 5, 0) +static dc_status_t read_ostc3_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) +{ + dc_status_t rc; + dc_event_progress_t progress; + progress.current = 0; + progress.maximum = 57; + unsigned char hardware[1]; + + //Read hardware type + rc = hw_ostc3_device_hardware (device, hardware, sizeof (hardware)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + // FIXME: can we grab this info from libdivecomputer descriptor + // instead of hard coded here? + switch(hardware[0]) { + case OSTC3_HW_OSTC_3: + m_deviceDetails->model = "3"; + break; + case OSTC3_HW_OSTC_3P: + m_deviceDetails->model = "3+"; + break; + case OSTC3_HW_OSTC_CR: + m_deviceDetails->model = "CR"; + break; + case OSTC3_HW_OSTC_SPORT: + m_deviceDetails->model = "Sport"; + break; + case OSTC3_HW_OSTC_2: + m_deviceDetails->model = "2"; + break; + } + + //Read gas mixes + gas gas1; + gas gas2; + gas gas3; + gas gas4; + gas gas5; + unsigned char gasData[4] = { 0, 0, 0, 0 }; + + rc = hw_ostc3_device_config_read(device, OSTC3_GAS1, gasData, sizeof(gasData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + gas1.oxygen = gasData[0]; + gas1.helium = gasData[1]; + gas1.type = gasData[2]; + gas1.depth = gasData[3]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_GAS2, gasData, sizeof(gasData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + gas2.oxygen = gasData[0]; + gas2.helium = gasData[1]; + gas2.type = gasData[2]; + gas2.depth = gasData[3]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_GAS3, gasData, sizeof(gasData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + gas3.oxygen = gasData[0]; + gas3.helium = gasData[1]; + gas3.type = gasData[2]; + gas3.depth = gasData[3]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_GAS4, gasData, sizeof(gasData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + gas4.oxygen = gasData[0]; + gas4.helium = gasData[1]; + gas4.type = gasData[2]; + gas4.depth = gasData[3]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_GAS5, gasData, sizeof(gasData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + gas5.oxygen = gasData[0]; + gas5.helium = gasData[1]; + gas5.type = gasData[2]; + gas5.depth = gasData[3]; + EMIT_PROGRESS(); + + m_deviceDetails->gas1 = gas1; + m_deviceDetails->gas2 = gas2; + m_deviceDetails->gas3 = gas3; + m_deviceDetails->gas4 = gas4; + m_deviceDetails->gas5 = gas5; + EMIT_PROGRESS(); + + //Read Dil Values + gas dil1; + gas dil2; + gas dil3; + gas dil4; + gas dil5; + unsigned char dilData[4] = { 0, 0, 0, 0 }; + + rc = hw_ostc3_device_config_read(device, OSTC3_DIL1, dilData, sizeof(dilData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + dil1.oxygen = dilData[0]; + dil1.helium = dilData[1]; + dil1.type = dilData[2]; + dil1.depth = dilData[3]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_DIL2, dilData, sizeof(dilData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + dil2.oxygen = dilData[0]; + dil2.helium = dilData[1]; + dil2.type = dilData[2]; + dil2.depth = dilData[3]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_DIL3, dilData, sizeof(dilData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + dil3.oxygen = dilData[0]; + dil3.helium = dilData[1]; + dil3.type = dilData[2]; + dil3.depth = dilData[3]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_DIL4, dilData, sizeof(dilData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + dil4.oxygen = dilData[0]; + dil4.helium = dilData[1]; + dil4.type = dilData[2]; + dil4.depth = dilData[3]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_DIL5, dilData, sizeof(dilData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + dil5.oxygen = dilData[0]; + dil5.helium = dilData[1]; + dil5.type = dilData[2]; + dil5.depth = dilData[3]; + EMIT_PROGRESS(); + + m_deviceDetails->dil1 = dil1; + m_deviceDetails->dil2 = dil2; + m_deviceDetails->dil3 = dil3; + m_deviceDetails->dil4 = dil4; + m_deviceDetails->dil5 = dil5; + + //Read set point Values + setpoint sp1; + setpoint sp2; + setpoint sp3; + setpoint sp4; + setpoint sp5; + unsigned char spData[2] = { 0, 0 }; + + rc = hw_ostc3_device_config_read(device, OSTC3_SP1, spData, sizeof(spData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + sp1.sp = spData[0]; + sp1.depth = spData[1]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_SP2, spData, sizeof(spData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + sp2.sp = spData[0]; + sp2.depth = spData[1]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_SP3, spData, sizeof(spData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + sp3.sp = spData[0]; + sp3.depth = spData[1]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_SP4, spData, sizeof(spData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + sp4.sp = spData[0]; + sp4.depth = spData[1]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_SP5, spData, sizeof(spData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + sp5.sp = spData[0]; + sp5.depth = spData[1]; + EMIT_PROGRESS(); + + m_deviceDetails->sp1 = sp1; + m_deviceDetails->sp2 = sp2; + m_deviceDetails->sp3 = sp3; + m_deviceDetails->sp4 = sp4; + m_deviceDetails->sp5 = sp5; + + //Read other settings + unsigned char uData[1] = { 0 }; + +#define READ_SETTING(_OSTC3_SETTING, _DEVICE_DETAIL) \ + do { \ + rc = hw_ostc3_device_config_read(device, _OSTC3_SETTING, uData, sizeof(uData)); \ + if (rc != DC_STATUS_SUCCESS) \ + return rc; \ + m_deviceDetails->_DEVICE_DETAIL = uData[0]; \ + EMIT_PROGRESS(); \ + } while (0) + + READ_SETTING(OSTC3_DIVE_MODE, diveMode); + READ_SETTING(OSTC3_SATURATION, saturation); + READ_SETTING(OSTC3_DESATURATION, desaturation); + READ_SETTING(OSTC3_LAST_DECO, lastDeco); + READ_SETTING(OSTC3_BRIGHTNESS, brightness); + READ_SETTING(OSTC3_UNITS, units); + READ_SETTING(OSTC3_SAMPLING_RATE, samplingRate); + READ_SETTING(OSTC3_SALINITY, salinity); + READ_SETTING(OSTC3_DIVEMODE_COLOR, diveModeColor); + READ_SETTING(OSTC3_LANGUAGE, language); + READ_SETTING(OSTC3_DATE_FORMAT, dateFormat); + READ_SETTING(OSTC3_COMPASS_GAIN, compassGain); + READ_SETTING(OSTC3_SAFETY_STOP, safetyStop); + READ_SETTING(OSTC3_GF_HIGH, gfHigh); + READ_SETTING(OSTC3_GF_LOW, gfLow); + READ_SETTING(OSTC3_PPO2_MIN, ppO2Min); + READ_SETTING(OSTC3_PPO2_MAX, ppO2Max); + READ_SETTING(OSTC3_FUTURE_TTS, futureTTS); + READ_SETTING(OSTC3_CCR_MODE, ccrMode); + READ_SETTING(OSTC3_DECO_TYPE, decoType); + READ_SETTING(OSTC3_AGF_SELECTABLE, aGFSelectable); + READ_SETTING(OSTC3_AGF_HIGH, aGFHigh); + READ_SETTING(OSTC3_AGF_LOW, aGFLow); + READ_SETTING(OSTC3_CALIBRATION_GAS_O2, calibrationGas); + READ_SETTING(OSTC3_FLIP_SCREEN, flipScreen); + READ_SETTING(OSTC3_SETPOINT_FALLBACK, setPointFallback); + READ_SETTING(OSTC3_LEFT_BUTTON_SENSIVITY, leftButtonSensitivity); + READ_SETTING(OSTC3_RIGHT_BUTTON_SENSIVITY, rightButtonSensitivity); + READ_SETTING(OSTC3_BOTTOM_GAS_CONSUMPTION, bottomGasConsumption); + READ_SETTING(OSTC3_DECO_GAS_CONSUMPTION, decoGasConsumption); + READ_SETTING(OSTC3_MOD_WARNING, modWarning); + READ_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); + READ_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); + READ_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); + READ_SETTING(OSTC3_SAFETY_STOP_LENGTH, safetyStopLength); + READ_SETTING(OSTC3_SAFETY_STOP_START_DEPTH, safetyStopStartDepth); + READ_SETTING(OSTC3_SAFETY_STOP_END_DEPTH, safetyStopEndDepth); + READ_SETTING(OSTC3_SAFETY_STOP_RESET_DEPTH, safetyStopResetDepth); + +#undef READ_SETTING + + rc = hw_ostc3_device_config_read(device, OSTC3_PRESSURE_SENSOR_OFFSET, uData, sizeof(uData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + // OSTC3 stores the pressureSensorOffset in two-complement + m_deviceDetails->pressureSensorOffset = (signed char)uData[0]; + EMIT_PROGRESS(); + + rc = hw_ostc3_device_config_read(device, OSTC3_TEMP_SENSOR_OFFSET, uData, sizeof(uData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + // OSTC3 stores the tempSensorOffset in two-complement + m_deviceDetails->tempSensorOffset = (signed char)uData[0]; + EMIT_PROGRESS(); + + //read firmware settings + unsigned char fData[64] = { 0 }; + rc = hw_ostc3_device_version(device, fData, sizeof(fData)); + if (rc != DC_STATUS_SUCCESS) + return rc; + int serial = fData[0] + (fData[1] << 8); + m_deviceDetails->serialNo = QString::number(serial); + m_deviceDetails->firmwareVersion = QString::number(fData[2]) + "." + QString::number(fData[3]); + QByteArray ar((char *)fData + 4, 60); + m_deviceDetails->customText = ar.trimmed(); + EMIT_PROGRESS(); + + return rc; +} + +static dc_status_t write_ostc3_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) +{ + dc_status_t rc; + dc_event_progress_t progress; + progress.current = 0; + progress.maximum = 56; + + //write gas values + unsigned char gas1Data[4] = { + m_deviceDetails->gas1.oxygen, + m_deviceDetails->gas1.helium, + m_deviceDetails->gas1.type, + m_deviceDetails->gas1.depth + }; + + unsigned char gas2Data[4] = { + m_deviceDetails->gas2.oxygen, + m_deviceDetails->gas2.helium, + m_deviceDetails->gas2.type, + m_deviceDetails->gas2.depth + }; + + unsigned char gas3Data[4] = { + m_deviceDetails->gas3.oxygen, + m_deviceDetails->gas3.helium, + m_deviceDetails->gas3.type, + m_deviceDetails->gas3.depth + }; + + unsigned char gas4Data[4] = { + m_deviceDetails->gas4.oxygen, + m_deviceDetails->gas4.helium, + m_deviceDetails->gas4.type, + m_deviceDetails->gas4.depth + }; + + unsigned char gas5Data[4] = { + m_deviceDetails->gas5.oxygen, + m_deviceDetails->gas5.helium, + m_deviceDetails->gas5.type, + m_deviceDetails->gas5.depth + }; + //gas 1 + rc = hw_ostc3_device_config_write(device, OSTC3_GAS1, gas1Data, sizeof(gas1Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //gas 2 + rc = hw_ostc3_device_config_write(device, OSTC3_GAS2, gas2Data, sizeof(gas2Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //gas 3 + rc = hw_ostc3_device_config_write(device, OSTC3_GAS3, gas3Data, sizeof(gas3Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //gas 4 + rc = hw_ostc3_device_config_write(device, OSTC3_GAS4, gas4Data, sizeof(gas4Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //gas 5 + rc = hw_ostc3_device_config_write(device, OSTC3_GAS5, gas5Data, sizeof(gas5Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + //write set point values + unsigned char sp1Data[2] = { + m_deviceDetails->sp1.sp, + m_deviceDetails->sp1.depth + }; + + unsigned char sp2Data[2] = { + m_deviceDetails->sp2.sp, + m_deviceDetails->sp2.depth + }; + + unsigned char sp3Data[2] = { + m_deviceDetails->sp3.sp, + m_deviceDetails->sp3.depth + }; + + unsigned char sp4Data[2] = { + m_deviceDetails->sp4.sp, + m_deviceDetails->sp4.depth + }; + + unsigned char sp5Data[2] = { + m_deviceDetails->sp5.sp, + m_deviceDetails->sp5.depth + }; + + //sp 1 + rc = hw_ostc3_device_config_write(device, OSTC3_SP1, sp1Data, sizeof(sp1Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //sp 2 + rc = hw_ostc3_device_config_write(device, OSTC3_SP2, sp2Data, sizeof(sp2Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //sp 3 + rc = hw_ostc3_device_config_write(device, OSTC3_SP3, sp3Data, sizeof(sp3Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //sp 4 + rc = hw_ostc3_device_config_write(device, OSTC3_SP4, sp4Data, sizeof(sp4Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //sp 5 + rc = hw_ostc3_device_config_write(device, OSTC3_SP5, sp5Data, sizeof(sp5Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + //write dil values + unsigned char dil1Data[4] = { + m_deviceDetails->dil1.oxygen, + m_deviceDetails->dil1.helium, + m_deviceDetails->dil1.type, + m_deviceDetails->dil1.depth + }; + + unsigned char dil2Data[4] = { + m_deviceDetails->dil2.oxygen, + m_deviceDetails->dil2.helium, + m_deviceDetails->dil2.type, + m_deviceDetails->dil2.depth + }; + + unsigned char dil3Data[4] = { + m_deviceDetails->dil3.oxygen, + m_deviceDetails->dil3.helium, + m_deviceDetails->dil3.type, + m_deviceDetails->dil3.depth + }; + + unsigned char dil4Data[4] = { + m_deviceDetails->dil4.oxygen, + m_deviceDetails->dil4.helium, + m_deviceDetails->dil4.type, + m_deviceDetails->dil4.depth + }; + + unsigned char dil5Data[4] = { + m_deviceDetails->dil5.oxygen, + m_deviceDetails->dil5.helium, + m_deviceDetails->dil5.type, + m_deviceDetails->dil5.depth + }; + //dil 1 + rc = hw_ostc3_device_config_write(device, OSTC3_DIL1, dil1Data, sizeof(gas1Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //dil 2 + rc = hw_ostc3_device_config_write(device, OSTC3_DIL2, dil2Data, sizeof(dil2Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //dil 3 + rc = hw_ostc3_device_config_write(device, OSTC3_DIL3, dil3Data, sizeof(dil3Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //dil 4 + rc = hw_ostc3_device_config_write(device, OSTC3_DIL4, dil4Data, sizeof(dil4Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //dil 5 + rc = hw_ostc3_device_config_write(device, OSTC3_DIL5, dil5Data, sizeof(dil5Data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + //write general settings + //custom text + rc = hw_ostc3_device_customtext(device, m_deviceDetails->customText.toUtf8().data()); + if (rc != DC_STATUS_SUCCESS) + return rc; + + unsigned char data[1] = { 0 }; +#define WRITE_SETTING(_OSTC3_SETTING, _DEVICE_DETAIL) \ + do { \ + data[0] = m_deviceDetails->_DEVICE_DETAIL; \ + rc = hw_ostc3_device_config_write(device, _OSTC3_SETTING, data, sizeof(data)); \ + if (rc != DC_STATUS_SUCCESS) \ + return rc; \ + EMIT_PROGRESS(); \ + } while (0) + + WRITE_SETTING(OSTC3_DIVE_MODE, diveMode); + WRITE_SETTING(OSTC3_SATURATION, saturation); + WRITE_SETTING(OSTC3_DESATURATION, desaturation); + WRITE_SETTING(OSTC3_LAST_DECO, lastDeco); + WRITE_SETTING(OSTC3_BRIGHTNESS, brightness); + WRITE_SETTING(OSTC3_UNITS, units); + WRITE_SETTING(OSTC3_SAMPLING_RATE, samplingRate); + WRITE_SETTING(OSTC3_SALINITY, salinity); + WRITE_SETTING(OSTC3_DIVEMODE_COLOR, diveModeColor); + WRITE_SETTING(OSTC3_LANGUAGE, language); + WRITE_SETTING(OSTC3_DATE_FORMAT, dateFormat); + WRITE_SETTING(OSTC3_COMPASS_GAIN, compassGain); + WRITE_SETTING(OSTC3_SAFETY_STOP, safetyStop); + WRITE_SETTING(OSTC3_GF_HIGH, gfHigh); + WRITE_SETTING(OSTC3_GF_LOW, gfLow); + WRITE_SETTING(OSTC3_PPO2_MIN, ppO2Min); + WRITE_SETTING(OSTC3_PPO2_MAX, ppO2Max); + WRITE_SETTING(OSTC3_FUTURE_TTS, futureTTS); + WRITE_SETTING(OSTC3_CCR_MODE, ccrMode); + WRITE_SETTING(OSTC3_DECO_TYPE, decoType); + WRITE_SETTING(OSTC3_AGF_SELECTABLE, aGFSelectable); + WRITE_SETTING(OSTC3_AGF_HIGH, aGFHigh); + WRITE_SETTING(OSTC3_AGF_LOW, aGFLow); + WRITE_SETTING(OSTC3_CALIBRATION_GAS_O2, calibrationGas); + WRITE_SETTING(OSTC3_FLIP_SCREEN, flipScreen); + WRITE_SETTING(OSTC3_SETPOINT_FALLBACK, setPointFallback); + WRITE_SETTING(OSTC3_LEFT_BUTTON_SENSIVITY, leftButtonSensitivity); + WRITE_SETTING(OSTC3_RIGHT_BUTTON_SENSIVITY, rightButtonSensitivity); + WRITE_SETTING(OSTC3_BOTTOM_GAS_CONSUMPTION, bottomGasConsumption); + WRITE_SETTING(OSTC3_DECO_GAS_CONSUMPTION, decoGasConsumption); + WRITE_SETTING(OSTC3_MOD_WARNING, modWarning); + WRITE_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); + WRITE_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); + WRITE_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); + WRITE_SETTING(OSTC3_TEMP_SENSOR_OFFSET, tempSensorOffset); + WRITE_SETTING(OSTC3_SAFETY_STOP_LENGTH, safetyStopLength); + WRITE_SETTING(OSTC3_SAFETY_STOP_START_DEPTH, safetyStopStartDepth); + WRITE_SETTING(OSTC3_SAFETY_STOP_END_DEPTH, safetyStopEndDepth); + WRITE_SETTING(OSTC3_SAFETY_STOP_RESET_DEPTH, safetyStopResetDepth); + +#undef WRITE_SETTING + + // OSTC3 stores the pressureSensorOffset in two-complement + data[0] = (unsigned char)m_deviceDetails->pressureSensorOffset; + rc = hw_ostc3_device_config_write(device, OSTC3_PRESSURE_SENSOR_OFFSET, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + // OSTC3 stores the tempSensorOffset in two-complement + data[0] = (unsigned char)m_deviceDetails->pressureSensorOffset; + rc = hw_ostc3_device_config_write(device, OSTC3_TEMP_SENSOR_OFFSET, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + //sync date and time + if (m_deviceDetails->syncTime) { + dc_datetime_t now; + dc_datetime_localtime(&now, dc_datetime_now()); + + rc = hw_ostc3_device_clock(device, &now); + } + EMIT_PROGRESS(); + + return rc; +} +#endif /* DC_VERSION_CHECK(0, 5, 0) */ + +static dc_status_t read_ostc_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) +{ + dc_status_t rc; + dc_event_progress_t progress; + progress.current = 0; + progress.maximum = 3; + + unsigned char data[256] = {}; +#ifdef DEBUG_OSTC_CF + // FIXME: how should we report settings not supported back? + unsigned char max_CF = 0; +#endif + rc = hw_ostc_device_eeprom_read(device, 0, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + m_deviceDetails->serialNo = QString::number(data[1] << 8 ^ data[0]); + m_deviceDetails->numberOfDives = data[3] << 8 ^ data[2]; + //Byte5-6: + //Gas 1 default (%O2=21, %He=0) + gas gas1; + gas1.oxygen = data[6]; + gas1.helium = data[7]; + //Byte9-10: + //Gas 2 default (%O2=21, %He=0) + gas gas2; + gas2.oxygen = data[10]; + gas2.helium = data[11]; + //Byte13-14: + //Gas 3 default (%O2=21, %He=0) + gas gas3; + gas3.oxygen = data[14]; + gas3.helium = data[15]; + //Byte17-18: + //Gas 4 default (%O2=21, %He=0) + gas gas4; + gas4.oxygen = data[18]; + gas4.helium = data[19]; + //Byte21-22: + //Gas 5 default (%O2=21, %He=0) + gas gas5; + gas5.oxygen = data[22]; + gas5.helium = data[23]; + //Byte25-26: + //Gas 6 current (%O2, %He) + m_deviceDetails->salinity = data[26]; + // Active Gas Flag Register + gas1.type = data[27] & 0x01; + gas2.type = (data[27] & 0x02) >> 1; + gas3.type = (data[27] & 0x04) >> 2; + gas4.type = (data[27] & 0x08) >> 3; + gas5.type = (data[27] & 0x10) >> 4; + + // Gas switch depths + gas1.depth = data[28]; + gas2.depth = data[29]; + gas3.depth = data[30]; + gas4.depth = data[31]; + gas5.depth = data[32]; + // 33 which gas is Fist gas + switch (data[33]) { + case 1: + gas1.type = 2; + break; + case 2: + gas2.type = 2; + break; + case 3: + gas3.type = 2; + break; + case 4: + gas4.type = 2; + break; + case 5: + gas5.type = 2; + break; + default: + //Error? + break; + } + // Data filled up, set the gases. + m_deviceDetails->gas1 = gas1; + m_deviceDetails->gas2 = gas2; + m_deviceDetails->gas3 = gas3; + m_deviceDetails->gas4 = gas4; + m_deviceDetails->gas5 = gas5; + m_deviceDetails->decoType = data[34]; + //Byte36: + //Use O2 Sensor Module in CC Modes (0= OFF, 1= ON) (Only available in old OSTC1 - unused for OSTC Mk.2/2N) + //m_deviceDetails->ccrMode = data[35]; + setpoint sp1; + sp1.sp = data[36]; + sp1.depth = 0; + setpoint sp2; + sp2.sp = data[37]; + sp2.depth = 0; + setpoint sp3; + sp3.sp = data[38]; + sp3.depth = 0; + m_deviceDetails->sp1 = sp1; + m_deviceDetails->sp2 = sp2; + m_deviceDetails->sp3 = sp3; + // Byte41-42: + // Lowest Battery voltage seen (in mV) + // Byte43: + // Lowest Battery voltage seen at (Month) + // Byte44: + // Lowest Battery voltage seen at (Day) + // Byte45: + // Lowest Battery voltage seen at (Year) + // Byte46-47: + // Lowest Battery voltage seen at (Temperature in 0.1 °C) + // Byte48: + // Last complete charge at (Month) + // Byte49: + // Last complete charge at (Day) + // Byte50: + // Last complete charge at (Year) + // Byte51-52: + // Total charge cycles + // Byte53-54: + // Total complete charge cycles + // Byte55-56: + // Temperature Extrema minimum (Temperature in 0.1 °C) + // Byte57: + // Temperature Extrema minimum at (Month) + // Byte58: + // Temperature Extrema minimum at (Day) + // Byte59: + // Temperature Extrema minimum at (Year) + // Byte60-61: + // Temperature Extrema maximum (Temperature in 0.1 °C) + // Byte62: + // Temperature Extrema maximum at (Month) + // Byte63: + // Temperature Extrema maximum at (Day) + // Byte64: + // Temperature Extrema maximum at (Year) + // Byte65: + // Custom Text active (=1), Custom Text Disabled (<>1) + // Byte66-90: + // TO FIX EDITOR SYNTAX/INDENT { + // (25Bytes): Custom Text for Surfacemode (Real text must end with "}") + // Example: OSTC Dive Computer} (19 Characters incl. "}") Bytes 85-90 will be ignored. + if (data[64] == 1) { + // Make shure the data is null-terminated + data[89] = 0; + // Find the internal termination and replace it with 0 + char *term = strchr((char *)data + 65, (int)'}'); + if (term) + *term = 0; + m_deviceDetails->customText = (const char *)data + 65; + } + // Byte91: + // Dim OLED in Divemode (>0), Normal mode (=0) + // Byte92: + // Date format for all outputs: + // =0: MM/DD/YY + // =1: DD/MM/YY + // =2: YY/MM/DD + m_deviceDetails->dateFormat = data[91]; +// Byte93: +// Total number of CF used in installed firmware +#ifdef DEBUG_OSTC_CF + max_CF = data[92]; +#endif + // Byte94: + // Last selected view for customview area in surface mode + // Byte95: + // Last selected view for customview area in dive mode + // Byte96-97: + // Diluent 1 Default (%O2,%He) + // Byte98-99: + // Diluent 1 Current (%O2,%He) + gas dil1(data[97], data[98]); + // Byte100-101: + // Gasuent 2 Default (%O2,%He) + // Byte102-103: + // Gasuent 2 Current (%O2,%He) + gas dil2(data[101], data[102]); + // Byte104-105: + // Gasuent 3 Default (%O2,%He) + // Byte106-107: + // Gasuent 3 Current (%O2,%He) + gas dil3(data[105], data[106]); + // Byte108-109: + // Gasuent 4 Default (%O2,%He) + // Byte110-111: + // Gasuent 4 Current (%O2,%He) + gas dil4(data[109], data[110]); + // Byte112-113: + // Gasuent 5 Default (%O2,%He) + // Byte114-115: + // Gasuent 5 Current (%O2,%He) + gas dil5(data[113], data[114]); + // Byte116: + // First Diluent (1-5) + switch (data[115]) { + case 1: + dil1.type = 2; + break; + case 2: + dil2.type = 2; + break; + case 3: + dil3.type = 2; + break; + case 4: + dil4.type = 2; + break; + case 5: + dil5.type = 2; + break; + default: + //Error? + break; + } + m_deviceDetails->dil1 = dil1; + m_deviceDetails->dil2 = dil2; + m_deviceDetails->dil3 = dil3; + m_deviceDetails->dil4 = dil4; + m_deviceDetails->dil5 = dil5; + // Byte117-128: + // not used/reserved + // Byte129-256: + // 32 custom Functions (CF0-CF31) + + // Decode the relevant ones + // CF11: Factor for saturation processes + m_deviceDetails->saturation = read_ostc_cf(data, 11); + // CF12: Factor for desaturation processes + m_deviceDetails->desaturation = read_ostc_cf(data, 12); + // CF17: Lower threshold for ppO2 warning + m_deviceDetails->ppO2Min = read_ostc_cf(data, 17); + // CF18: Upper threshold for ppO2 warning + m_deviceDetails->ppO2Max = read_ostc_cf(data, 18); + // CF20: Depth sampling rate for Profile storage + m_deviceDetails->samplingRate = read_ostc_cf(data, 20); + // CF29: Depth of last decompression stop + m_deviceDetails->lastDeco = read_ostc_cf(data, 29); + +#ifdef DEBUG_OSTC_CF + for (int cf = 0; cf <= 31 && cf <= max_CF; cf++) + printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); +#endif + + rc = hw_ostc_device_eeprom_read(device, 1, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + // Byte1: + // Logbook version indicator (Not writable!) + // Byte2-3: + // Last Firmware installed, 1st Byte.2nd Byte (e.g. „1.90“) (Not writable!) + m_deviceDetails->firmwareVersion = QString::number(data[1]) + "." + QString::number(data[2]); + // Byte4: + // OLED brightness (=0: Eco, =1 High) (Not writable!) + // Byte5-11: + // Time/Date vault during firmware updates + // Byte12-128 + // not used/reserved + // Byte129-256: + // 32 custom Functions (CF 32-63) + + // Decode the relevant ones + // CF32: Gradient Factor low + m_deviceDetails->gfLow = read_ostc_cf(data, 32); + // CF33: Gradient Factor high + m_deviceDetails->gfHigh = read_ostc_cf(data, 33); + // CF56: Bottom gas consumption + m_deviceDetails->bottomGasConsumption = read_ostc_cf(data, 56); + // CF57: Ascent gas consumption + m_deviceDetails->decoGasConsumption = read_ostc_cf(data, 57); + // CF58: Future time to surface setFutureTTS + m_deviceDetails->futureTTS = read_ostc_cf(data, 58); + +#ifdef DEBUG_OSTC_CF + for (int cf = 32; cf <= 63 && cf <= max_CF; cf++) + printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); +#endif + + rc = hw_ostc_device_eeprom_read(device, 2, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + // Byte1-4: + // not used/reserved (Not writable!) + // Byte5-128: + // not used/reserved + // Byte129-256: + // 32 custom Functions (CF 64-95) + + // Decode the relevant ones + // CF60: Graphic velocity + m_deviceDetails->graphicalSpeedIndicator = read_ostc_cf(data, 60); + // CF65: Show safety stop + m_deviceDetails->safetyStop = read_ostc_cf(data, 65); + // CF67: Alternaitve Gradient Factor low + m_deviceDetails->aGFLow = read_ostc_cf(data, 67); + // CF68: Alternative Gradient Factor high + m_deviceDetails->aGFHigh = read_ostc_cf(data, 68); + // CF69: Allow Gradient Factor change + m_deviceDetails->aGFSelectable = read_ostc_cf(data, 69); + // CF70: Safety Stop Duration [s] + m_deviceDetails->safetyStopLength = read_ostc_cf(data, 70); + // CF71: Safety Stop Start Depth [m] + m_deviceDetails->safetyStopStartDepth = read_ostc_cf(data, 71); + // CF72: Safety Stop End Depth [m] + m_deviceDetails->safetyStopEndDepth = read_ostc_cf(data, 72); + // CF73: Safety Stop Reset Depth [m] + m_deviceDetails->safetyStopResetDepth = read_ostc_cf(data, 73); + // CF74: Battery Timeout [min] + +#ifdef DEBUG_OSTC_CF + for (int cf = 64; cf <= 95 && cf <= max_CF; cf++) + printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); +#endif + + return rc; +} + +static dc_status_t write_ostc_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) +{ + dc_status_t rc; + dc_event_progress_t progress; + progress.current = 0; + progress.maximum = 7; + unsigned char data[256] = {}; + unsigned char max_CF = 0; + + // Because we write whole memory blocks, we read all the current + // values out and then change then ones we should change. + rc = hw_ostc_device_eeprom_read(device, 0, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + //Byte5-6: + //Gas 1 default (%O2=21, %He=0) + gas gas1 = m_deviceDetails->gas1; + data[6] = gas1.oxygen; + data[7] = gas1.helium; + //Byte9-10: + //Gas 2 default (%O2=21, %He=0) + gas gas2 = m_deviceDetails->gas2; + data[10] = gas2.oxygen; + data[11] = gas2.helium; + //Byte13-14: + //Gas 3 default (%O2=21, %He=0) + gas gas3 = m_deviceDetails->gas3; + data[14] = gas3.oxygen; + data[15] = gas3.helium; + //Byte17-18: + //Gas 4 default (%O2=21, %He=0) + gas gas4 = m_deviceDetails->gas4; + data[18] = gas4.oxygen; + data[19] = gas4.helium; + //Byte21-22: + //Gas 5 default (%O2=21, %He=0) + gas gas5 = m_deviceDetails->gas5; + data[22] = gas5.oxygen; + data[23] = gas5.helium; + //Byte25-26: + //Gas 6 current (%O2, %He) + data[26] = m_deviceDetails->salinity; + // Gas types, 0=Disabled, 1=Active, 2=Fist + // Active Gas Flag Register + data[27] = 0; + if (gas1.type) + data[27] ^= 0x01; + if (gas2.type) + data[27] ^= 0x02; + if (gas3.type) + data[27] ^= 0x04; + if (gas4.type) + data[27] ^= 0x08; + if (gas5.type) + data[27] ^= 0x10; + + // Gas switch depths + data[28] = gas1.depth; + data[29] = gas2.depth; + data[30] = gas3.depth; + data[31] = gas4.depth; + data[32] = gas5.depth; + // 33 which gas is Fist gas + if (gas1.type == 2) + data[33] = 1; + else if (gas2.type == 2) + data[33] = 2; + else if (gas3.type == 2) + data[33] = 3; + else if (gas4.type == 2) + data[33] = 4; + else if (gas5.type == 2) + data[33] = 5; + else + // FIXME: No gas was First? + // Set gas 1 to first + data[33] = 1; + + data[34] = m_deviceDetails->decoType; + //Byte36: + //Use O2 Sensor Module in CC Modes (0= OFF, 1= ON) (Only available in old OSTC1 - unused for OSTC Mk.2/2N) + //m_deviceDetails->ccrMode = data[35]; + data[36] = m_deviceDetails->sp1.sp; + data[37] = m_deviceDetails->sp2.sp; + data[38] = m_deviceDetails->sp3.sp; + // Byte41-42: + // Lowest Battery voltage seen (in mV) + // Byte43: + // Lowest Battery voltage seen at (Month) + // Byte44: + // Lowest Battery voltage seen at (Day) + // Byte45: + // Lowest Battery voltage seen at (Year) + // Byte46-47: + // Lowest Battery voltage seen at (Temperature in 0.1 °C) + // Byte48: + // Last complete charge at (Month) + // Byte49: + // Last complete charge at (Day) + // Byte50: + // Last complete charge at (Year) + // Byte51-52: + // Total charge cycles + // Byte53-54: + // Total complete charge cycles + // Byte55-56: + // Temperature Extrema minimum (Temperature in 0.1 °C) + // Byte57: + // Temperature Extrema minimum at (Month) + // Byte58: + // Temperature Extrema minimum at (Day) + // Byte59: + // Temperature Extrema minimum at (Year) + // Byte60-61: + // Temperature Extrema maximum (Temperature in 0.1 °C) + // Byte62: + // Temperature Extrema maximum at (Month) + // Byte63: + // Temperature Extrema maximum at (Day) + // Byte64: + // Temperature Extrema maximum at (Year) + // Byte65: + // Custom Text active (=1), Custom Text Disabled (<>1) + // Byte66-90: + // (25Bytes): Custom Text for Surfacemode (Real text must end with "}") + // Example: "OSTC Dive Computer}" (19 Characters incl. "}") Bytes 85-90 will be ignored. + if (m_deviceDetails->customText == "") { + data[64] = 0; + } else { + data[64] = 1; + // Copy the string to the right place in the memory, padded with 0x20 (" ") + strncpy((char *)data + 65, QString("%1").arg(m_deviceDetails->customText, -23, QChar(' ')).toUtf8().data(), 23); + // And terminate the string. + if (m_deviceDetails->customText.length() <= 23) + data[65 + m_deviceDetails->customText.length()] = '}'; + else + data[90] = '}'; + } + // Byte91: + // Dim OLED in Divemode (>0), Normal mode (=0) + // Byte92: + // Date format for all outputs: + // =0: MM/DD/YY + // =1: DD/MM/YY + // =2: YY/MM/DD + data[91] = m_deviceDetails->dateFormat; + // Byte93: + // Total number of CF used in installed firmware + max_CF = data[92]; + // Byte94: + // Last selected view for customview area in surface mode + // Byte95: + // Last selected view for customview area in dive mode + // Byte96-97: + // Diluent 1 Default (%O2,%He) + // Byte98-99: + // Diluent 1 Current (%O2,%He) + gas dil1 = m_deviceDetails->dil1; + data[97] = dil1.oxygen; + data[98] = dil1.helium; + // Byte100-101: + // Gasuent 2 Default (%O2,%He) + // Byte102-103: + // Gasuent 2 Current (%O2,%He) + gas dil2 = m_deviceDetails->dil2; + data[101] = dil2.oxygen; + data[102] = dil2.helium; + // Byte104-105: + // Gasuent 3 Default (%O2,%He) + // Byte106-107: + // Gasuent 3 Current (%O2,%He) + gas dil3 = m_deviceDetails->dil3; + data[105] = dil3.oxygen; + data[106] = dil3.helium; + // Byte108-109: + // Gasuent 4 Default (%O2,%He) + // Byte110-111: + // Gasuent 4 Current (%O2,%He) + gas dil4 = m_deviceDetails->dil4; + data[109] = dil4.oxygen; + data[110] = dil4.helium; + // Byte112-113: + // Gasuent 5 Default (%O2,%He) + // Byte114-115: + // Gasuent 5 Current (%O2,%He) + gas dil5 = m_deviceDetails->dil5; + data[113] = dil5.oxygen; + data[114] = dil5.helium; + // Byte116: + // First Diluent (1-5) + if (dil1.type == 2) + data[115] = 1; + else if (dil2.type == 2) + data[115] = 2; + else if (dil3.type == 2) + data[115] = 3; + else if (dil4.type == 2) + data[115] = 4; + else if (dil5.type == 2) + data[115] = 5; + else + // FIXME: No first diluent? + // Set gas 1 to fist + data[115] = 1; + + // Byte117-128: + // not used/reserved + // Byte129-256: + // 32 custom Functions (CF0-CF31) + + // Write the relevant ones + // CF11: Factor for saturation processes + write_ostc_cf(data, 11, max_CF, m_deviceDetails->saturation); + // CF12: Factor for desaturation processes + write_ostc_cf(data, 12, max_CF, m_deviceDetails->desaturation); + // CF17: Lower threshold for ppO2 warning + write_ostc_cf(data, 17, max_CF, m_deviceDetails->ppO2Min); + // CF18: Upper threshold for ppO2 warning + write_ostc_cf(data, 18, max_CF, m_deviceDetails->ppO2Max); + // CF20: Depth sampling rate for Profile storage + write_ostc_cf(data, 20, max_CF, m_deviceDetails->samplingRate); + // CF29: Depth of last decompression stop + write_ostc_cf(data, 29, max_CF, m_deviceDetails->lastDeco); + +#ifdef DEBUG_OSTC_CF + for (int cf = 0; cf <= 31 && cf <= max_CF; cf++) + printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); +#endif + rc = hw_ostc_device_eeprom_write(device, 0, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + rc = hw_ostc_device_eeprom_read(device, 1, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + // Byte1: + // Logbook version indicator (Not writable!) + // Byte2-3: + // Last Firmware installed, 1st Byte.2nd Byte (e.g. „1.90“) (Not writable!) + // Byte4: + // OLED brightness (=0: Eco, =1 High) (Not writable!) + // Byte5-11: + // Time/Date vault during firmware updates + // Byte12-128 + // not used/reserved + // Byte129-256: + // 32 custom Functions (CF 32-63) + + // Decode the relevant ones + // CF32: Gradient Factor low + write_ostc_cf(data, 32, max_CF, m_deviceDetails->gfLow); + // CF33: Gradient Factor high + write_ostc_cf(data, 33, max_CF, m_deviceDetails->gfHigh); + // CF56: Bottom gas consumption + write_ostc_cf(data, 56, max_CF, m_deviceDetails->bottomGasConsumption); + // CF57: Ascent gas consumption + write_ostc_cf(data, 57, max_CF, m_deviceDetails->decoGasConsumption); + // CF58: Future time to surface setFutureTTS + write_ostc_cf(data, 58, max_CF, m_deviceDetails->futureTTS); +#ifdef DEBUG_OSTC_CF + for (int cf = 32; cf <= 63 && cf <= max_CF; cf++) + printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); +#endif + rc = hw_ostc_device_eeprom_write(device, 1, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + rc = hw_ostc_device_eeprom_read(device, 2, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + // Byte1-4: + // not used/reserved (Not writable!) + // Byte5-128: + // not used/reserved + // Byte129-256: + // 32 custom Functions (CF 64-95) + + // Decode the relevant ones + // CF60: Graphic velocity + write_ostc_cf(data, 60, max_CF, m_deviceDetails->graphicalSpeedIndicator); + // CF65: Show safety stop + write_ostc_cf(data, 65, max_CF, m_deviceDetails->safetyStop); + // CF67: Alternaitve Gradient Factor low + write_ostc_cf(data, 67, max_CF, m_deviceDetails->aGFLow); + // CF68: Alternative Gradient Factor high + write_ostc_cf(data, 68, max_CF, m_deviceDetails->aGFHigh); + // CF69: Allow Gradient Factor change + write_ostc_cf(data, 69, max_CF, m_deviceDetails->aGFSelectable); + // CF70: Safety Stop Duration [s] + write_ostc_cf(data, 70, max_CF, m_deviceDetails->safetyStopLength); + // CF71: Safety Stop Start Depth [m] + write_ostc_cf(data, 71, max_CF, m_deviceDetails->safetyStopStartDepth); + // CF72: Safety Stop End Depth [m] + write_ostc_cf(data, 72, max_CF, m_deviceDetails->safetyStopEndDepth); + // CF73: Safety Stop Reset Depth [m] + write_ostc_cf(data, 73, max_CF, m_deviceDetails->safetyStopResetDepth); + // CF74: Battery Timeout [min] + +#ifdef DEBUG_OSTC_CF + for (int cf = 64; cf <= 95 && cf <= max_CF; cf++) + printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); +#endif + rc = hw_ostc_device_eeprom_write(device, 2, data, sizeof(data)); + if (rc != DC_STATUS_SUCCESS) + return rc; + EMIT_PROGRESS(); + + //sync date and time + if (m_deviceDetails->syncTime) { + QDateTime timeToSet = QDateTime::currentDateTime(); + dc_datetime_t time; + time.year = timeToSet.date().year(); + time.month = timeToSet.date().month(); + time.day = timeToSet.date().day(); + time.hour = timeToSet.time().hour(); + time.minute = timeToSet.time().minute(); + time.second = timeToSet.time().second(); + rc = hw_ostc_device_clock(device, &time); + } + EMIT_PROGRESS(); + return rc; +} + +#undef EMIT_PROGRESS + +DeviceThread::DeviceThread(QObject *parent, device_data_t *data) : QThread(parent), m_data(data) +{ +} + +void DeviceThread::progressCB(int percent) +{ + emit progress(percent); +} + +void DeviceThread::event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) +{ + Q_UNUSED(device); + + const dc_event_progress_t *progress = (dc_event_progress_t *) data; + DeviceThread *dt = static_cast(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/core/configuredivecomputerthreads.h b/core/configuredivecomputerthreads.h new file mode 100644 index 000000000..8817d848a --- /dev/null +++ b/core/configuredivecomputerthreads.h @@ -0,0 +1,60 @@ +#ifndef CONFIGUREDIVECOMPUTERTHREADS_H +#define CONFIGUREDIVECOMPUTERTHREADS_H + +#include +#include +#include "libdivecomputer.h" +#include "devicedetails.h" + +class DeviceThread : public QThread { + Q_OBJECT +public: + DeviceThread(QObject *parent, device_data_t *data); + virtual void run() = 0; +signals: + void error(QString err); + void progress(int value); +protected: + device_data_t *m_data; + void progressCB(int value); + static void event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata); +}; + +class ReadSettingsThread : public DeviceThread { + Q_OBJECT +public: + ReadSettingsThread(QObject *parent, device_data_t *data); + void run(); +signals: + void devicedetails(DeviceDetails *newDeviceDetails); +}; + +class WriteSettingsThread : public DeviceThread { + Q_OBJECT +public: + WriteSettingsThread(QObject *parent, device_data_t *data); + void setDeviceDetails(DeviceDetails *details); + void run(); + +private: + DeviceDetails *m_deviceDetails; +}; + +class FirmwareUpdateThread : public DeviceThread { + Q_OBJECT +public: + FirmwareUpdateThread(QObject *parent, device_data_t *data, QString fileName); + void run(); + +private: + QString m_fileName; +}; + +class ResetSettingsThread : public DeviceThread { + Q_OBJECT +public: + ResetSettingsThread(QObject *parent, device_data_t *data); + void run(); +}; + +#endif // CONFIGUREDIVECOMPUTERTHREADS_H diff --git a/core/datatrak.c b/core/datatrak.c new file mode 100644 index 000000000..204ebd9b3 --- /dev/null +++ b/core/datatrak.c @@ -0,0 +1,698 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#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 subtracts + * (1970 - 1600) * 365,2425 = 135139,725 to our date variable, getting the + * days since Epoch. + */ +static time_t date_time_to_ssrfc(unsigned long date, int time) +{ + time_t tmp; + tmp = (date - 135140) * 86400 + time * 60; + return tmp; +} + +static unsigned char to_8859(unsigned char char_cp850) +{ + static const unsigned char char_8859[46] = { 0xc7, 0xfc, 0xe9, 0xe2, 0xe4, 0xe0, 0xe5, 0xe7, + 0xea, 0xeb, 0xe8, 0xef, 0xee, 0xec, 0xc4, 0xc5, + 0xc9, 0xe6, 0xc6, 0xf4, 0xf6, 0xf2, 0xfb, 0xf9, + 0xff, 0xd6, 0xdc, 0xf8, 0xa3, 0xd8, 0xd7, 0x66, + 0xe1, 0xed, 0xf3, 0xfa, 0xf1, 0xd1, 0xaa, 0xba, + 0xbf, 0xae, 0xac, 0xbd, 0xbc, 0xa1 }; + return char_8859[char_cp850 - 0x80]; +} + +static char *to_utf8(unsigned char *in_string) +{ + int outlen, inlen, i = 0, j = 0; + inlen = strlen((char *)in_string); + outlen = inlen * 2 + 1; + + char *out_string = calloc(outlen, 1); + for (i = 0; i < inlen; i++) { + if (in_string[i] < 127) + out_string[j] = in_string[i]; + else { + if (in_string[i] > 127 && in_string[i] <= 173) + in_string[i] = to_8859(in_string[i]); + out_string[j] = (in_string[i] >> 6) | 0xC0; + j++; + out_string[j] = (in_string[i] & 0x3F) | 0x80; + } + j++; + } + out_string[j + 1] = '\0'; + return out_string; +} + +/* + * Subsurface sample structure doesn't support the flags and alarms in the dt .log + * so will treat them as dc events. + */ +static struct sample *dtrak_profile(struct dive *dt_dive, FILE *archivo) +{ + int i, j = 1, interval, o2percent = dt_dive->cylinder[0].gasmix.o2.permille / 10; + struct sample *sample = dt_dive->dc.sample; + struct divecomputer *dc = &dt_dive->dc; + + for (i = 1; i <= dt_dive->dc.alloc_samples; i++) { + if (fread(&lector_bytes, 1, 2, archivo) != 2) + return sample; + interval= 20 * (i + 1); + sample = add_sample(sample, interval, dc); + sample->depth.mm = (two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0xFFC0) * 1000 / 410; + byte = byte_to_bits(two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0x003F); + if (byte[0] != 0) + sample->in_deco = true; + else + sample->in_deco = false; + if (byte[1] != 0) + add_event(dc, sample->time.seconds, 0, 0, 0, "rbt"); + if (byte[2] != 0) + add_event(dc, sample->time.seconds, 0, 0, 0, "ascent"); + if (byte[3] != 0) + add_event(dc, sample->time.seconds, 0, 0, 0, "ceiling"); + if (byte[4] != 0) + add_event(dc, sample->time.seconds, 0, 0, 0, "workload"); + if (byte[5] != 0) + add_event(dc, sample->time.seconds, 0, 0, 0, "transmitter"); + if (j == 3) { + read_bytes(1); + if (is_O2) { + read_bytes(1); + o2percent = tmp_1byte; + } + j = 0; + } + free(byte); + + // In commit 5f44fdd setpoint replaced po2, so although this is not necessarily CCR dive ... + if (is_O2) + sample->setpoint.mbar = calculate_depth_to_mbar(sample->depth.mm, dt_dive->surface_pressure, 0) * o2percent / 100; + j++; + } +bail: + return sample; +} + +/* + * Reads the header of a file and returns the header struct + * If it's not a DATATRAK file returns header zero initalized + */ +static dtrakheader read_file_header(FILE *archivo) +{ + dtrakheader fileheader = { 0 }; + const short headerbytes = 12; + unsigned char *lector = (unsigned char *)malloc(headerbytes); + + if (fread(lector, 1, headerbytes, archivo) != headerbytes) { + free(lector); + return fileheader; + } + if (two_bytes_to_int(lector[0], lector[1]) != 0xA100) { + report_error(translate("gettextFromC", "Error: the file does not appear to be a DATATRAK divelog")); + free(lector); + return fileheader; + } + fileheader.header = (lector[0] << 8) + lector[1]; + fileheader.dc_serial_1 = two_bytes_to_int(lector[2], lector[3]); + fileheader.dc_serial_2 = two_bytes_to_int(lector[4], lector[5]); + fileheader.divesNum = two_bytes_to_int(lector[7], lector[6]); + free(lector); + return fileheader; +} + +#define CHECK(_func, _val) if ((_func) != (_val)) goto bail + +/* + * Parses the dive extracting its data and filling a subsurface's dive structure + */ +bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) +{ + unsigned char n; + int profile_length; + char *tmp_notes_str = NULL; + unsigned char *tmp_string1 = NULL, + *locality = NULL, + *dive_point = NULL; + char buffer[1024]; + struct divecomputer *dc = &dt_dive->dc; + + is_nitrox = is_O2 = is_SCR = 0; + + /* + * Parse byte to byte till next dive entry + */ + n = 0; + CHECK(fread(&lector_bytes[n], 1, 1, archivo), 1); + while (lector_bytes[n] != 0xA0) + CHECK(fread(&lector_bytes[n], 1, 1, archivo), 1); + + /* + * Found dive header 0xA000, verify second byte + */ + CHECK(fread(&lector_bytes[n+1], 1, 1, archivo), 1); + if (two_bytes_to_int(lector_bytes[0], lector_bytes[1]) != 0xA000) { + printf("Error: byte = %4x\n", two_bytes_to_int(lector_bytes[0], lector_bytes[1])); + return false; + } + + /* + * Begin parsing + * First, Date of dive, 4 bytes + */ + read_bytes(4); + + + /* + * Next, Time in minutes since 00:00 + */ + read_bytes(2); + + dt_dive->dc.when = dt_dive->when = (timestamp_t)date_time_to_ssrfc(tmp_4bytes, tmp_2bytes); + + /* + * Now, Locality, 1st byte is long of string, rest is string + */ + read_bytes(1); + read_string(locality); + + /* + * Next, Dive point, defined as Locality + */ + read_bytes(1); + read_string(dive_point); + + /* + * Subsurface only have a location variable, so we have to merge DTrak's + * Locality and Dive points. + */ + snprintf(buffer, sizeof(buffer), "%s, %s", locality, dive_point); + dt_dive->dive_site_uuid = get_dive_site_uuid_by_name(buffer, NULL); + if (dt_dive->dive_site_uuid == 0) + dt_dive->dive_site_uuid = create_dive_site(buffer, dt_dive->when); + free(locality); + free(dive_point); + + /* + * Altitude. Don't exist in Subsurface, the equivalent would be + * surface air pressure which can, be calculated from altitude. + * As dtrak registers altitude intervals, we, arbitrarily, choose + * the lower altitude/pressure equivalence for each segment. So + * + * Datatrak table * Conversion formula: + * * + * byte = 1 0 - 700 m * P = P0 * exp(-(g * M * h ) / (R * T0)) + * byte = 2 700 - 1700m * P0 = sealevel pressure = 101325 Pa + * byte = 3 1700 - 2700 m * g = grav. acceleration = 9,80665 m/s² + * byte = 4 2700 - * m * M = molar mass (dry air) = 0,0289644 Kg/mol + * * h = altitude over sea level (m) + * * R = Universal gas constant = 8,31447 J/(mol*K) + * * T0 = sea level standard temperature = 288,15 K + */ + read_bytes(1); + switch (tmp_1byte) { + case 1: + dt_dive->dc.surface_pressure.mbar = 1013; + break; + case 2: + dt_dive->dc.surface_pressure.mbar = 932; + break; + case 3: + dt_dive->dc.surface_pressure.mbar = 828; + break; + case 4: + dt_dive->dc.surface_pressure.mbar = 735; + break; + default: + dt_dive->dc.surface_pressure.mbar = 1013; + } + + /* + * Interval (minutes) + */ + read_bytes(2); + if (tmp_2bytes != 0x7FFF) + dt_dive->dc.surfacetime.seconds = (uint32_t) tmp_2bytes * 60; + + /* + * Weather, values table, 0 to 6 + * Subsurface don't have this record but we can use tags + */ + dt_dive->tag_list = NULL; + read_bytes(1); + switch (tmp_1byte) { + case 1: + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "clear"))); + break; + case 2: + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "misty"))); + break; + case 3: + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fog"))); + break; + case 4: + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "rain"))); + break; + case 5: + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "storm"))); + break; + case 6: + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "snow"))); + break; + default: + // unknown, do nothing + break; + } + + /* + * Air Temperature + */ + read_bytes(2); + if (tmp_2bytes != 0x7FFF) + dt_dive->dc.airtemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100)); + + /* + * Dive suit, values table, 0 to 6 + */ + read_bytes(1); + switch (tmp_1byte) { + case 1: + dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "No suit")); + break; + case 2: + dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Shorty")); + break; + case 3: + dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Combi")); + break; + case 4: + dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Wet suit")); + break; + case 5: + dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Semidry suit")); + break; + case 6: + dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Dry suit")); + break; + default: + // unknown, do nothing + break; + } + + /* + * Tank, volume size in liter*100. And initialize gasmix to air (default). + * Dtrak don't record init and end pressures, but consumed bar, so let's + * init a default pressure of 200 bar. + */ + read_bytes(2); + if (tmp_2bytes != 0x7FFF) { + dt_dive->cylinder[0].type.size.mliter = tmp_2bytes * 10; + dt_dive->cylinder[0].type.description = strdup(""); + dt_dive->cylinder[0].start.mbar = 200000; + dt_dive->cylinder[0].gasmix.he.permille = 0; + dt_dive->cylinder[0].gasmix.o2.permille = 210; + dt_dive->cylinder[0].manually_added = true; + } + + /* + * Maximum depth, in cm. + */ + read_bytes(2); + if (tmp_2bytes != 0x7FFF) + dt_dive->maxdepth.mm = dt_dive->dc.maxdepth.mm = (int32_t)tmp_2bytes * 10; + + /* + * Dive time in minutes. + */ + read_bytes(2); + if (tmp_2bytes != 0x7FFF) + dt_dive->duration.seconds = dt_dive->dc.duration.seconds = (uint32_t)tmp_2bytes * 60; + + /* + * Minimum water temperature in C*100. If unknown, set it to 0K which + * is subsurface's value for "unknown" + */ + read_bytes(2); + if (tmp_2bytes != 0x7fff) + dt_dive->watertemp.mkelvin = dt_dive->dc.watertemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100)); + else + dt_dive->watertemp.mkelvin = 0; + + /* + * Air used in bar*100. + */ + read_bytes(2); + if (tmp_2bytes != 0x7FFF && dt_dive->cylinder[0].type.size.mliter) + dt_dive->cylinder[0].gas_used.mliter = dt_dive->cylinder[0].type.size.mliter * (tmp_2bytes / 100.0); + + /* + * Dive Type 1 - Bit table. Subsurface don't have this record, but + * will use tags. Bits 0 and 1 are not used. Reuse coincident tags. + */ + read_bytes(1); + byte = byte_to_bits(tmp_1byte); + if (byte[2] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "no stop"))); + if (byte[3] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "deco"))); + if (byte[4] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "single ascent"))); + if (byte[5] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "multiple ascent"))); + if (byte[6] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fresh"))); + if (byte[7] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "salt water"))); + free(byte); + + /* + * Dive Type 2 - Bit table, use tags again + */ + read_bytes(1); + byte = byte_to_bits(tmp_1byte); + if (byte[0] != 0) { + taglist_add_tag(&dt_dive->tag_list, strdup("nitrox")); + is_nitrox = 1; + } + if (byte[1] != 0) { + taglist_add_tag(&dt_dive->tag_list, strdup("rebreather")); + is_SCR = 1; + dt_dive->dc.divemode = PSCR; + } + free(byte); + + /* + * Dive Activity 1 - Bit table, use tags again + */ + read_bytes(1); + byte = byte_to_bits(tmp_1byte); + if (byte[0] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "sight seeing"))); + if (byte[1] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "club dive"))); + if (byte[2] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instructor"))); + if (byte[3] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instruction"))); + if (byte[4] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "night"))); + if (byte[5] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "cave"))); + if (byte[6] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "ice"))); + if (byte[7] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "search"))); + free(byte); + + + /* + * Dive Activity 2 - Bit table, use tags again + */ + read_bytes(1); + byte = byte_to_bits(tmp_1byte); + if (byte[0] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "wreck"))); + if (byte[1] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "river"))); + if (byte[2] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "drift"))); + if (byte[3] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "photo"))); + if (byte[4] != 0) + taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "other"))); + free(byte); + + /* + * Other activities - String 1st byte = long + * Will put this in dive notes before the true notes + */ + read_bytes(1); + if (tmp_1byte != 0) { + read_string(tmp_string1); + snprintf(buffer, sizeof(buffer), "%s: %s\n", + QT_TRANSLATE_NOOP("gettextFromC", "Other activities"), + tmp_string1); + tmp_notes_str = strdup(buffer); + free(tmp_string1); + } + + /* + * Dive buddies + */ + read_bytes(1); + if (tmp_1byte != 0) { + read_string(tmp_string1); + dt_dive->buddy = strdup((char *)tmp_string1); + free(tmp_string1); + } + + /* + * Dive notes + */ + read_bytes(1); + if (tmp_1byte != 0) { + read_string(tmp_string1); + int len = snprintf(buffer, sizeof(buffer), "%s%s:\n%s", + tmp_notes_str ? tmp_notes_str : "", + QT_TRANSLATE_NOOP("gettextFromC", "Datatrak/Wlog notes"), + tmp_string1); + dt_dive->notes = calloc((len +1), 1); + dt_dive->notes = memcpy(dt_dive->notes, buffer, len); + free(tmp_string1); + if (tmp_notes_str != NULL) + free(tmp_notes_str); + } + + /* + * Alarms 1 - Bit table - Not in Subsurface, we use the profile + */ + read_bytes(1); + + /* + * Alarms 2 - Bit table - Not in Subsurface, we use the profile + */ + read_bytes(1); + + /* + * Dive number (in datatrak, after import user has to renumber) + */ + read_bytes(2); + dt_dive->number = tmp_2bytes; + + /* + * Computer timestamp - Useless for Subsurface + */ + read_bytes(4); + + /* + * Model - table - Not included 0x14, 0x24, 0x41, and 0x73 + * known to exist, but not its model name - To add in the future. + * Strangely 0x00 serves for manually added dives and a dc too, at + * least in EXAMPLE.LOG file, shipped with the software. + */ + read_bytes(1); + switch (tmp_1byte) { + case 0x00: + dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Manually entered dive")); + break; + case 0x1C: + dt_dive->dc.model = strdup("Aladin Air"); + break; + case 0x1D: + dt_dive->dc.model = strdup("Spiro Monitor 2 plus"); + break; + case 0x1E: + dt_dive->dc.model = strdup("Aladin Sport"); + break; + case 0x1F: + dt_dive->dc.model = strdup("Aladin Pro"); + break; + case 0x34: + dt_dive->dc.model = strdup("Aladin Air X"); + break; + case 0x3D: + dt_dive->dc.model = strdup("Spiro Monitor 2 plus"); + break; + case 0x3F: + dt_dive->dc.model = strdup("Mares Genius"); + break; + case 0x44: + dt_dive->dc.model = strdup("Aladin Air X"); + break; + case 0x48: + dt_dive->dc.model = strdup("Spiro Monitor 3 Air"); + break; + case 0xA4: + dt_dive->dc.model = strdup("Aladin Air X O2"); + break; + case 0xB1: + dt_dive->dc.model = strdup("Citizen Hyper Aqualand"); + break; + case 0xB2: + dt_dive->dc.model = strdup("Citizen ProMaster"); + break; + case 0xB3: + dt_dive->dc.model = strdup("Mares Guardian"); + break; + case 0xBC: + dt_dive->dc.model = strdup("Aladin Air X Nitrox"); + break; + case 0xF4: + dt_dive->dc.model = strdup("Aladin Air X Nitrox"); + break; + case 0xFF: + dt_dive->dc.model = strdup("Aladin Pro Nitrox"); + break; + default: + dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Unknown")); + break; + } + if ((tmp_1byte & 0xF0) == 0xF0) + is_nitrox = 1; + if ((tmp_1byte & 0xF0) == 0xA0) + is_O2 = 1; + + /* + * Air usage, unknown use. Probably allows or deny manually entering gas + * comsumption based on dc model - Useless for Subsurface + */ + read_bytes(1); + if (fseek(archivo, 6, 1) != 0) // jump over 6 bytes whitout known use + goto bail; + /* + * Profile data length + */ + read_bytes(2); + profile_length = tmp_2bytes; + if (profile_length != 0) { + /* + * 8 x 2 bytes for the tissues saturation useless for subsurface + * and other 6 bytes without known use + */ + if (fseek(archivo, 22, 1) != 0) + goto bail; + if (is_nitrox || is_O2) { + + /* + * CNS % (unsure) values table (only nitrox computers) + */ + read_bytes(1); + + /* + * % O2 in nitrox mix - (only nitrox and O2 computers but differents) + */ + read_bytes(1); + if (is_nitrox) { + dt_dive->cylinder[0].gasmix.o2.permille = + (tmp_1byte & 0x0F ? 20.0 + 2 * (tmp_1byte & 0x0F) : 21.0) * 10; + } else { + dt_dive->cylinder[0].gasmix.o2.permille = tmp_1byte * 10; + read_bytes(1) // Jump over one byte, unknown use + } + } + /* + * profileLength = Nº bytes, need to know how many samples are there. + * 2bytes per sample plus another one each three samples. Also includes the + * bytes jumped over (22) and the nitrox (2) or O2 (3). + */ + int samplenum = is_O2 ? (profile_length - 25) * 3 / 8 : (profile_length - 24) * 3 / 7; + + dc->events = calloc(samplenum, sizeof(struct event)); + dc->alloc_samples = samplenum; + dc->samples = 0; + dc->sample = calloc(samplenum, sizeof(struct sample)); + + dtrak_profile(dt_dive, archivo); + } + /* + * Initialize some dive data not supported by Datatrak/WLog + */ + if (!strcmp(dt_dive->dc.model, "Manually entered dive")) + dt_dive->dc.deviceid = 0; + else + dt_dive->dc.deviceid = 0xffffffff; + create_device_node(dt_dive->dc.model, dt_dive->dc.deviceid, "", "", dt_dive->dc.model); + dt_dive->dc.next = NULL; + if (!is_SCR && dt_dive->cylinder[0].type.size.mliter) { + dt_dive->cylinder[0].end.mbar = dt_dive->cylinder[0].start.mbar - + ((dt_dive->cylinder[0].gas_used.mliter / dt_dive->cylinder[0].type.size.mliter) * 1000); + } + return true; + +bail: + return false; +} + +void datatrak_import(const char *file, struct dive_table *table) +{ + FILE *archivo; + dtrakheader *fileheader = (dtrakheader *)malloc(sizeof(dtrakheader)); + int i = 0; + + if ((archivo = subsurface_fopen(file, "rb")) == NULL) { + report_error(translate("gettextFromC", "Error: couldn't open the file %s"), file); + free(fileheader); + return; + } + + /* + * Verify fileheader, get number of dives in datatrak divelog + */ + *fileheader = read_file_header(archivo); + while (i < fileheader->divesNum) { + struct dive *ptdive = alloc_dive(); + + if (!dt_dive_parser(archivo, ptdive)) { + report_error(translate("gettextFromC", "Error: no dive")); + free(ptdive); + } else { + record_dive(ptdive); + } + i++; + } + taglist_cleanup(&g_tag_list); + fclose(archivo); + sort_table(table); + free(fileheader); +} diff --git a/core/datatrak.h b/core/datatrak.h new file mode 100644 index 000000000..3a37e0465 --- /dev/null +++ b/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/core/deco.c b/core/deco.c new file mode 100644 index 000000000..af3e06126 --- /dev/null +++ b/core/deco.c @@ -0,0 +1,601 @@ +/* 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 "core/planner.h" + +#define cube(x) (x * x * x) + +// Subsurface appears to produce marginally less conservative plans than our benchmarks +// Introduce 1.2% additional conservatism +#define subsurface_conservatism_factor 1.012 + + +extern bool in_planner(); + +extern pressure_t first_ceiling_pressure; + +//! Option structure for Buehlmann decompression. +struct buehlmann_config { + double satmult; //! safety at inert gas accumulation as percentage of effect (more than 100). + double desatmult; //! safety at inert gas depletion as percentage of effect (less than 100). + int last_deco_stop_in_mtr; //! depth of last_deco_stop. + double gf_high; //! gradient factor high (at surface). + double gf_low; //! gradient factor low (at bottom/start of deco calculation). + double gf_low_position_min; //! gf_low_position below surface_min_shallow. + bool gf_low_at_maxdepth; //! if true, gf_low applies at max depth instead of at deepest ceiling. +}; + +struct buehlmann_config buehlmann_config = { + .satmult = 1.0, + .desatmult = 1.01, + .last_deco_stop_in_mtr = 0, + .gf_high = 0.75, + .gf_low = 0.35, + .gf_low_position_min = 1.0, + .gf_low_at_maxdepth = false +}; + +//! Option structure for VPM-B decompression. +struct vpmb_config { + double crit_radius_N2; //! Critical radius of N2 nucleon (microns). + double crit_radius_He; //! Critical radius of He nucleon (microns). + double crit_volume_lambda; //! Constant corresponding to critical gas volume (bar * min). + double gradient_of_imperm; //! Gradient after which bubbles become impermeable (bar). + double surface_tension_gamma; //! Nucleons surface tension constant (N / bar = m2). + double skin_compression_gammaC; //! Skin compression gammaC (N / bar = m2). + double regeneration_time; //! Time needed for the bubble to regenerate to the start radius (min). + double other_gases_pressure; //! Always present pressure of other gasses in tissues (bar). +}; + +struct vpmb_config vpmb_config = { + .crit_radius_N2 = 0.55, + .crit_radius_He = 0.45, + .crit_volume_lambda = 199.58, + .gradient_of_imperm = 8.30865, // = 8.2 atm + .surface_tension_gamma = 0.18137175, // = 0.0179 N/msw + .skin_compression_gammaC = 2.6040525, // = 0.257 N/msw + .regeneration_time = 20160.0, + .other_gases_pressure = 0.1359888 +}; + +const double buehlmann_N2_a[] = { 1.1696, 1.0, 0.8618, 0.7562, + 0.62, 0.5043, 0.441, 0.4, + 0.375, 0.35, 0.3295, 0.3065, + 0.2835, 0.261, 0.248, 0.2327 }; + +const double buehlmann_N2_b[] = { 0.5578, 0.6514, 0.7222, 0.7825, + 0.8126, 0.8434, 0.8693, 0.8910, + 0.9092, 0.9222, 0.9319, 0.9403, + 0.9477, 0.9544, 0.9602, 0.9653 }; + +const double buehlmann_N2_t_halflife[] = { 5.0, 8.0, 12.5, 18.5, + 27.0, 38.3, 54.3, 77.0, + 109.0, 146.0, 187.0, 239.0, + 305.0, 390.0, 498.0, 635.0 }; + +const double buehlmann_N2_factor_expositon_one_second[] = { + 2.30782347297664E-003, 1.44301447809736E-003, 9.23769302935806E-004, 6.24261986779007E-004, + 4.27777107246730E-004, 3.01585140931371E-004, 2.12729727268379E-004, 1.50020603047807E-004, + 1.05980191127841E-004, 7.91232600646508E-005, 6.17759153688224E-005, 4.83354552742732E-005, + 3.78761777920511E-005, 2.96212356654113E-005, 2.31974277413727E-005, 1.81926738960225E-005 +}; + +const double buehlmann_He_a[] = { 1.6189, 1.383, 1.1919, 1.0458, + 0.922, 0.8205, 0.7305, 0.6502, + 0.595, 0.5545, 0.5333, 0.5189, + 0.5181, 0.5176, 0.5172, 0.5119 }; + +const double buehlmann_He_b[] = { 0.4770, 0.5747, 0.6527, 0.7223, + 0.7582, 0.7957, 0.8279, 0.8553, + 0.8757, 0.8903, 0.8997, 0.9073, + 0.9122, 0.9171, 0.9217, 0.9267 }; + +const double buehlmann_He_t_halflife[] = { 1.88, 3.02, 4.72, 6.99, + 10.21, 14.48, 20.53, 29.11, + 41.20, 55.19, 70.69, 90.34, + 115.29, 147.42, 188.24, 240.03 }; + +const double buehlmann_He_factor_expositon_one_second[] = { + 6.12608039419837E-003, 3.81800836683133E-003, 2.44456078654209E-003, 1.65134647076792E-003, + 1.13084424730725E-003, 7.97503165599123E-004, 5.62552521860549E-004, 3.96776399429366E-004, + 2.80360036664540E-004, 2.09299583354805E-004, 1.63410794820518E-004, 1.27869320250551E-004, + 1.00198406028040E-004, 7.83611475491108E-005, 6.13689891868496E-005, 4.81280465299827E-005 +}; + +const double conservatism_lvls[] = { 1.0, 1.05, 1.12, 1.22, 1.35 }; + +/* Inspired gas loading equations depend on the partial pressure of inert gas in the alveolar. + * P_alv = (P_amb - P_H2O + (1 - Rq) / Rq * P_CO2) * f + * where: + * P_alv alveolar partial pressure of inert gas + * P_amb ambient pressure + * P_H2O water vapour partial pressure = ~0.0627 bar + * P_CO2 carbon dioxide partial pressure = ~0.0534 bar + * Rq respiratory quotient (O2 consumption / CO2 production) + * f fraction of inert gas + * + * In our calculations, we simplify this to use an effective water vapour pressure + * WV = P_H20 - (1 - Rq) / Rq * P_CO2 + * + * Buhlmann ignored the contribution of CO2 (i.e. Rq = 1.0), whereas Schreiner adopted Rq = 0.8. + * WV_Buhlmann = PP_H2O = 0.0627 bar + * WV_Schreiner = 0.0627 - (1 - 0.8) / Rq * 0.0534 = 0.0493 bar + + * Buhlmann calculations use the Buhlmann value, VPM-B calculations use the Schreiner value. +*/ +#define WV_PRESSURE 0.0627 // water vapor pressure in bar, based on respiratory quotient Rq = 1.0 (Buhlmann value) +#define WV_PRESSURE_SCHREINER 0.0493 // water vapor pressure in bar, based on respiratory quotient Rq = 0.8 (Schreiner value) + +#define DECO_STOPS_MULTIPLIER_MM 3000.0 +#define NITROGEN_FRACTION 0.79 + +double tissue_n2_sat[16]; +double tissue_he_sat[16]; +int ci_pointing_to_guiding_tissue; +double gf_low_pressure_this_dive; +#define TISSUE_ARRAY_SZ sizeof(tissue_n2_sat) + +double tolerated_by_tissue[16]; +double tissue_inertgas_saturation[16]; +double buehlmann_inertgas_a[16], buehlmann_inertgas_b[16]; + +double max_n2_crushing_pressure[16]; +double max_he_crushing_pressure[16]; + +double crushing_onset_tension[16]; // total inert gas tension in the t* moment +double n2_regen_radius[16]; // rs +double he_regen_radius[16]; +double max_ambient_pressure; // last moment we were descending + +double bottom_n2_gradient[16]; +double bottom_he_gradient[16]; + +double initial_n2_gradient[16]; +double initial_he_gradient[16]; + +double get_crit_radius_He() +{ + if (prefs.conservatism_level <= 4) + return vpmb_config.crit_radius_He * conservatism_lvls[prefs.conservatism_level] * subsurface_conservatism_factor; + return vpmb_config.crit_radius_He; +} + +double get_crit_radius_N2() +{ + if (prefs.conservatism_level <= 4) + return vpmb_config.crit_radius_N2 * conservatism_lvls[prefs.conservatism_level] * subsurface_conservatism_factor; + return vpmb_config.crit_radius_N2; +} + +// Solve another cubic equation, this time +// x^3 - B x - C == 0 +// Use trigonometric formula for negative discriminants (see Wikipedia for details) + +double solve_cubic2(double B, double C) +{ + double discriminant = 27 * C * C - 4 * cube(B); + if (discriminant < 0.0) { + return 2.0 * sqrt(B / 3.0) * cos(acos(3.0 * C * sqrt(3.0 / B) / (2.0 * B)) / 3.0); + } + + double denominator = pow(9 * C + sqrt(3 * discriminant), 1 / 3.0); + + return pow(2.0 / 3.0, 1.0 / 3.0) * B / denominator + denominator / pow(18.0, 1.0 / 3.0); +} + +// This is a simplified formula avoiding radii. It uses the fact that Boyle's law says +// pV = (G + P_amb) / G^3 is constant to solve for the new gradient G. + +double update_gradient(double next_stop_pressure, double first_gradient) +{ + double B = cube(first_gradient) / (first_ceiling_pressure.mbar / 1000.0 + first_gradient); + double C = next_stop_pressure * B; + + double new_gradient = solve_cubic2(B, C); + + if (new_gradient < 0.0) + report_error("Negative gradient encountered!"); + return new_gradient; +} + +double vpmb_tolerated_ambient_pressure(double reference_pressure, int ci) +{ + double n2_gradient, he_gradient, total_gradient; + + if (reference_pressure >= first_ceiling_pressure.mbar / 1000.0 || !first_ceiling_pressure.mbar) { + n2_gradient = bottom_n2_gradient[ci]; + he_gradient = bottom_he_gradient[ci]; + } else { + n2_gradient = update_gradient(reference_pressure, bottom_n2_gradient[ci]); + he_gradient = update_gradient(reference_pressure, bottom_he_gradient[ci]); + } + + total_gradient = ((n2_gradient * tissue_n2_sat[ci]) + (he_gradient * tissue_he_sat[ci])) / (tissue_n2_sat[ci] + tissue_he_sat[ci]); + + return tissue_n2_sat[ci] + tissue_he_sat[ci] + vpmb_config.other_gases_pressure - total_gradient; +} + + +double tissue_tolerance_calc(const struct dive *dive, double pressure) +{ + int ci = -1; + double ret_tolerance_limit_ambient_pressure = 0.0; + double gf_high = buehlmann_config.gf_high; + double gf_low = buehlmann_config.gf_low; + double surface = get_surface_pressure_in_mbar(dive, true) / 1000.0; + double lowest_ceiling = 0.0; + double tissue_lowest_ceiling[16]; + + if (prefs.deco_mode != VPMB) { + for (ci = 0; ci < 16; ci++) { + tissue_inertgas_saturation[ci] = tissue_n2_sat[ci] + tissue_he_sat[ci]; + buehlmann_inertgas_a[ci] = ((buehlmann_N2_a[ci] * tissue_n2_sat[ci]) + (buehlmann_He_a[ci] * tissue_he_sat[ci])) / tissue_inertgas_saturation[ci]; + buehlmann_inertgas_b[ci] = ((buehlmann_N2_b[ci] * tissue_n2_sat[ci]) + (buehlmann_He_b[ci] * tissue_he_sat[ci])) / tissue_inertgas_saturation[ci]; + + + /* tolerated = (tissue_inertgas_saturation - buehlmann_inertgas_a) * buehlmann_inertgas_b; */ + + tissue_lowest_ceiling[ci] = (buehlmann_inertgas_b[ci] * tissue_inertgas_saturation[ci] - gf_low * buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci]) / + ((1.0 - buehlmann_inertgas_b[ci]) * gf_low + buehlmann_inertgas_b[ci]); + if (tissue_lowest_ceiling[ci] > lowest_ceiling) + lowest_ceiling = tissue_lowest_ceiling[ci]; + if (!buehlmann_config.gf_low_at_maxdepth) { + if (lowest_ceiling > gf_low_pressure_this_dive) + gf_low_pressure_this_dive = lowest_ceiling; + } + } + for (ci = 0; ci < 16; ci++) { + double tolerated; + + if ((surface / buehlmann_inertgas_b[ci] + buehlmann_inertgas_a[ci] - surface) * gf_high + surface < + (gf_low_pressure_this_dive / buehlmann_inertgas_b[ci] + buehlmann_inertgas_a[ci] - gf_low_pressure_this_dive) * gf_low + gf_low_pressure_this_dive) + tolerated = (-buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci] * (gf_high * gf_low_pressure_this_dive - gf_low * surface) - + (1.0 - buehlmann_inertgas_b[ci]) * (gf_high - gf_low) * gf_low_pressure_this_dive * surface + + buehlmann_inertgas_b[ci] * (gf_low_pressure_this_dive - surface) * tissue_inertgas_saturation[ci]) / + (-buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci] * (gf_high - gf_low) + + (1.0 - buehlmann_inertgas_b[ci]) * (gf_low * gf_low_pressure_this_dive - gf_high * surface) + + buehlmann_inertgas_b[ci] * (gf_low_pressure_this_dive - surface)); + else + tolerated = ret_tolerance_limit_ambient_pressure; + + + tolerated_by_tissue[ci] = tolerated; + + if (tolerated >= ret_tolerance_limit_ambient_pressure) { + ci_pointing_to_guiding_tissue = ci; + ret_tolerance_limit_ambient_pressure = tolerated; + } + } + } else { + // VPM-B ceiling + double reference_pressure; + + ret_tolerance_limit_ambient_pressure = pressure; + // The Boyle compensated gradient depends on ambient pressure. For the ceiling, this should set the ambient pressure. + do { + reference_pressure = ret_tolerance_limit_ambient_pressure; + ret_tolerance_limit_ambient_pressure = 0.0; + for (ci = 0; ci < 16; ci++) { + double tolerated = vpmb_tolerated_ambient_pressure(reference_pressure, ci); + if (tolerated >= ret_tolerance_limit_ambient_pressure) { + ci_pointing_to_guiding_tissue = ci; + ret_tolerance_limit_ambient_pressure = tolerated; + } + tolerated_by_tissue[ci] = tolerated; + } + // We are doing ok if the gradient was computed within ten centimeters of the ceiling. + } while (fabs(ret_tolerance_limit_ambient_pressure - reference_pressure) > 0.01); + } + return ret_tolerance_limit_ambient_pressure; +} + +/* + * Return buelman factor for a particular period and tissue index. + * + * We cache the last factor, since we commonly call this with the + * same values... We have a special "fixed cache" for the one second + * case, although I wonder if that's even worth it considering the + * more general-purpose cache. + */ +struct factor_cache { + int last_period; + double last_factor; +}; + +double n2_factor(int period_in_seconds, int ci) +{ + static struct factor_cache cache[16]; + + if (period_in_seconds == 1) + return buehlmann_N2_factor_expositon_one_second[ci]; + + if (period_in_seconds != cache[ci].last_period) { + cache[ci].last_period = period_in_seconds; + cache[ci].last_factor = 1 - pow(2.0, -period_in_seconds / (buehlmann_N2_t_halflife[ci] * 60)); + } + + return cache[ci].last_factor; +} + +double he_factor(int period_in_seconds, int ci) +{ + static struct factor_cache cache[16]; + + if (period_in_seconds == 1) + return buehlmann_He_factor_expositon_one_second[ci]; + + if (period_in_seconds != cache[ci].last_period) { + cache[ci].last_period = period_in_seconds; + cache[ci].last_factor = 1 - pow(2.0, -period_in_seconds / (buehlmann_He_t_halflife[ci] * 60)); + } + + return cache[ci].last_factor; +} + +double calc_surface_phase(double surface_pressure, double he_pressure, double n2_pressure, double he_time_constant, double n2_time_constant) +{ + double inspired_n2 = (surface_pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE)) * NITROGEN_FRACTION; + + if (n2_pressure > inspired_n2) + return (he_pressure / he_time_constant + (n2_pressure - inspired_n2) / n2_time_constant) / (he_pressure + n2_pressure - inspired_n2); + + if (he_pressure + n2_pressure >= inspired_n2){ + double gradient_decay_time = 1.0 / (n2_time_constant - he_time_constant) * log ((inspired_n2 - n2_pressure) / he_pressure); + double gradients_integral = he_pressure / he_time_constant * (1.0 - exp(-he_time_constant * gradient_decay_time)) + (n2_pressure - inspired_n2) / n2_time_constant * (1.0 - exp(-n2_time_constant * gradient_decay_time)); + return gradients_integral / (he_pressure + n2_pressure - inspired_n2); + } + + return 0; +} + +void vpmb_start_gradient() +{ + int ci; + + for (ci = 0; ci < 16; ++ci) { + initial_n2_gradient[ci] = bottom_n2_gradient[ci] = 2.0 * (vpmb_config.surface_tension_gamma / vpmb_config.skin_compression_gammaC) * ((vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma) / n2_regen_radius[ci]); + initial_he_gradient[ci] = bottom_he_gradient[ci] = 2.0 * (vpmb_config.surface_tension_gamma / vpmb_config.skin_compression_gammaC) * ((vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma) / he_regen_radius[ci]); + } +} + +void vpmb_next_gradient(double deco_time, double surface_pressure) +{ + int ci; + double n2_b, n2_c; + double he_b, he_c; + double desat_time; + deco_time /= 60.0; + + for (ci = 0; ci < 16; ++ci) { + desat_time = deco_time + calc_surface_phase(surface_pressure, tissue_he_sat[ci], tissue_n2_sat[ci], log(2.0) / buehlmann_He_t_halflife[ci], log(2.0) / buehlmann_N2_t_halflife[ci]); + + n2_b = initial_n2_gradient[ci] + (vpmb_config.crit_volume_lambda * vpmb_config.surface_tension_gamma) / (vpmb_config.skin_compression_gammaC * desat_time); + he_b = initial_he_gradient[ci] + (vpmb_config.crit_volume_lambda * vpmb_config.surface_tension_gamma) / (vpmb_config.skin_compression_gammaC * desat_time); + + n2_c = vpmb_config.surface_tension_gamma * vpmb_config.surface_tension_gamma * vpmb_config.crit_volume_lambda * max_n2_crushing_pressure[ci]; + n2_c = n2_c / (vpmb_config.skin_compression_gammaC * vpmb_config.skin_compression_gammaC * desat_time); + he_c = vpmb_config.surface_tension_gamma * vpmb_config.surface_tension_gamma * vpmb_config.crit_volume_lambda * max_he_crushing_pressure[ci]; + he_c = he_c / (vpmb_config.skin_compression_gammaC * vpmb_config.skin_compression_gammaC * desat_time); + + bottom_n2_gradient[ci] = 0.5 * ( n2_b + sqrt(n2_b * n2_b - 4.0 * n2_c)); + bottom_he_gradient[ci] = 0.5 * ( he_b + sqrt(he_b * he_b - 4.0 * he_c)); + } +} + +// A*r^3 - B*r^2 - C == 0 +// Solved with the help of mathematica + +double solve_cubic(double A, double B, double C) +{ + double BA = B/A; + double CA = C/A; + + double discriminant = CA * (4 * cube(BA) + 27 * CA); + + // Let's make sure we have a real solution: + if (discriminant < 0.0) { + // This should better not happen + report_error("Complex solution for inner pressure encountered!\n A=%f\tB=%f\tC=%f\n", A, B, C); + return 0.0; + } + double denominator = pow(cube(BA) + 1.5 * (9 * CA + sqrt(3.0) * sqrt(discriminant)), 1/3.0); + return (BA + BA * BA / denominator + denominator) / 3.0; + +} + + +void nuclear_regeneration(double time) +{ + time /= 60.0; + int ci; + double crushing_radius_N2, crushing_radius_He; + for (ci = 0; ci < 16; ++ci) { + //rm + crushing_radius_N2 = 1.0 / (max_n2_crushing_pressure[ci] / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / get_crit_radius_N2()); + crushing_radius_He = 1.0 / (max_he_crushing_pressure[ci] / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / get_crit_radius_He()); + //rs + n2_regen_radius[ci] = crushing_radius_N2 + (get_crit_radius_N2() - crushing_radius_N2) * (1.0 - exp (-time / vpmb_config.regeneration_time)); + he_regen_radius[ci] = crushing_radius_He + (get_crit_radius_He() - crushing_radius_He) * (1.0 - exp (-time / vpmb_config.regeneration_time)); + } +} + + +// Calculates the nucleons inner pressure during the impermeable period +double calc_inner_pressure(double crit_radius, double onset_tension, double current_ambient_pressure) +{ + double onset_radius = 1.0 / (vpmb_config.gradient_of_imperm / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / crit_radius); + + + double A = current_ambient_pressure - vpmb_config.gradient_of_imperm + (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) / onset_radius; + double B = 2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma); + double C = onset_tension * pow(onset_radius, 3); + + double current_radius = solve_cubic(A, B, C); + + return onset_tension * onset_radius * onset_radius * onset_radius / (current_radius * current_radius * current_radius); +} + +// Calculates the crushing pressure in the given moment. Updates crushing_onset_tension and critical radius if needed +void calc_crushing_pressure(double pressure) +{ + int ci; + double gradient; + double gas_tension; + double n2_crushing_pressure, he_crushing_pressure; + double n2_inner_pressure, he_inner_pressure; + + for (ci = 0; ci < 16; ++ci) { + gas_tension = tissue_n2_sat[ci] + tissue_he_sat[ci] + vpmb_config.other_gases_pressure; + gradient = pressure - gas_tension; + + if (gradient <= vpmb_config.gradient_of_imperm) { // permeable situation + n2_crushing_pressure = he_crushing_pressure = gradient; + crushing_onset_tension[ci] = gas_tension; + } + else { // impermeable + if (max_ambient_pressure >= pressure) + return; + + n2_inner_pressure = calc_inner_pressure(get_crit_radius_N2(), crushing_onset_tension[ci], pressure); + he_inner_pressure = calc_inner_pressure(get_crit_radius_He(), crushing_onset_tension[ci], pressure); + + n2_crushing_pressure = pressure - n2_inner_pressure; + he_crushing_pressure = pressure - he_inner_pressure; + } + max_n2_crushing_pressure[ci] = MAX(max_n2_crushing_pressure[ci], n2_crushing_pressure); + max_he_crushing_pressure[ci] = MAX(max_he_crushing_pressure[ci], he_crushing_pressure); + } + max_ambient_pressure = MAX(pressure, max_ambient_pressure); +} + +/* add period_in_seconds at the given pressure and gas to the deco calculation */ +void add_segment(double pressure, const struct gasmix *gasmix, int period_in_seconds, int ccpo2, const struct dive *dive, int sac) +{ + (void) sac; + int ci; + struct gas_pressures pressures; + + fill_pressures(&pressures, pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE), + gasmix, (double) ccpo2 / 1000.0, dive->dc.divemode); + + if (buehlmann_config.gf_low_at_maxdepth && pressure > gf_low_pressure_this_dive) + gf_low_pressure_this_dive = pressure; + + for (ci = 0; ci < 16; ci++) { + double pn2_oversat = pressures.n2 - tissue_n2_sat[ci]; + double phe_oversat = pressures.he - tissue_he_sat[ci]; + double n2_f = n2_factor(period_in_seconds, ci); + double he_f = he_factor(period_in_seconds, ci); + double n2_satmult = pn2_oversat > 0 ? buehlmann_config.satmult : buehlmann_config.desatmult; + double he_satmult = phe_oversat > 0 ? buehlmann_config.satmult : buehlmann_config.desatmult; + + tissue_n2_sat[ci] += n2_satmult * pn2_oversat * n2_f; + tissue_he_sat[ci] += he_satmult * phe_oversat * he_f; + } + if(prefs.deco_mode == VPMB) + calc_crushing_pressure(pressure); + return; +} + +void dump_tissues() +{ + int ci; + printf("N2 tissues:"); + for (ci = 0; ci < 16; ci++) + printf(" %6.3e", tissue_n2_sat[ci]); + printf("\nHe tissues:"); + for (ci = 0; ci < 16; ci++) + printf(" %6.3e", tissue_he_sat[ci]); + printf("\n"); +} + +void clear_deco(double surface_pressure) +{ + int ci; + for (ci = 0; ci < 16; ci++) { + tissue_n2_sat[ci] = (surface_pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE)) * N2_IN_AIR / 1000; + tissue_he_sat[ci] = 0.0; + max_n2_crushing_pressure[ci] = 0.0; + max_he_crushing_pressure[ci] = 0.0; + n2_regen_radius[ci] = get_crit_radius_N2(); + he_regen_radius[ci] = get_crit_radius_He(); + } + gf_low_pressure_this_dive = surface_pressure; + if (!buehlmann_config.gf_low_at_maxdepth) + gf_low_pressure_this_dive += buehlmann_config.gf_low_position_min; + max_ambient_pressure = 0.0; +} + +void cache_deco_state(char **cached_datap) +{ + char *data = *cached_datap; + + if (!data) { + data = malloc(2 * TISSUE_ARRAY_SZ + sizeof(double) + sizeof(int)); + *cached_datap = data; + } + memcpy(data, tissue_n2_sat, TISSUE_ARRAY_SZ); + data += TISSUE_ARRAY_SZ; + memcpy(data, tissue_he_sat, TISSUE_ARRAY_SZ); + data += TISSUE_ARRAY_SZ; + memcpy(data, &gf_low_pressure_this_dive, sizeof(double)); + data += sizeof(double); + memcpy(data, &ci_pointing_to_guiding_tissue, sizeof(int)); +} + +void restore_deco_state(char *data) +{ + memcpy(tissue_n2_sat, data, TISSUE_ARRAY_SZ); + data += TISSUE_ARRAY_SZ; + memcpy(tissue_he_sat, data, TISSUE_ARRAY_SZ); + data += TISSUE_ARRAY_SZ; + memcpy(&gf_low_pressure_this_dive, data, sizeof(double)); + data += sizeof(double); + memcpy(&ci_pointing_to_guiding_tissue, data, sizeof(int)); +} + +int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth) +{ + int depth; + double pressure_delta; + + /* Avoid negative depths */ + pressure_delta = tissues_tolerance > surface_pressure ? tissues_tolerance - surface_pressure : 0.0; + + depth = rel_mbar_to_depth(pressure_delta * 1000, dive); + + if (!smooth) + depth = ceil(depth / DECO_STOPS_MULTIPLIER_MM) * DECO_STOPS_MULTIPLIER_MM; + + if (depth > 0 && depth < buehlmann_config.last_deco_stop_in_mtr * 1000) + depth = buehlmann_config.last_deco_stop_in_mtr * 1000; + + return depth; +} + +void set_gf(short gflow, short gfhigh, bool gf_low_at_maxdepth) +{ + if (gflow != -1) + buehlmann_config.gf_low = (double)gflow / 100.0; + if (gfhigh != -1) + buehlmann_config.gf_high = (double)gfhigh / 100.0; + buehlmann_config.gf_low_at_maxdepth = gf_low_at_maxdepth; +} diff --git a/core/deco.h b/core/deco.h new file mode 100644 index 000000000..fd3b94a9f --- /dev/null +++ b/core/deco.h @@ -0,0 +1,20 @@ +#ifndef DECO_H +#define DECO_H + +#ifdef __cplusplus +extern "C" { +#endif + +extern double tolerated_by_tissue[]; +extern double buehlmann_N2_t_halflife[]; +extern double tissue_inertgas_saturation[16]; +extern double buehlmann_inertgas_a[16], buehlmann_inertgas_b[16]; +extern double gf_low_pressure_this_dive; + +extern int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth); + +#ifdef __cplusplus +} +#endif + +#endif // DECO_H diff --git a/core/device.c b/core/device.c new file mode 100644 index 000000000..6c4452f78 --- /dev/null +++ b/core/device.c @@ -0,0 +1,184 @@ +#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 rectangular profile (for short and/or + * shallow dives) or more reasonably a six point profile with a 3 minute + * safety stop at 5m */ +static void fill_samples_no_avg(struct sample *s, int max_d, int max_t, double slope) +{ + // shallow or short dives are just trapecoids based on the given slope + if (max_d < 10000 || max_t < 600) { + s[1].time.seconds = max_d / slope; + s[1].depth.mm = max_d; + s[2].time.seconds = max_t - max_d / slope; + s[2].depth.mm = max_d; + } else { + s[1].time.seconds = max_d / slope; + s[1].depth.mm = max_d; + s[2].time.seconds = max_t - max_d / slope - 180; + s[2].depth.mm = max_d; + s[3].time.seconds = max_t - 5000 / slope - 180; + s[3].depth.mm = 5000; + s[4].time.seconds = max_t - 5000 / slope; + s[4].depth.mm = 5000; + } +} + +struct divecomputer *fake_dc(struct divecomputer *dc, bool alloc) +{ + static struct sample fake_samples[6]; + static struct divecomputer fakedc; + struct sample *fake = fake_samples; + + fakedc = (*dc); + if (alloc) + fake = malloc(sizeof(fake_samples)); + + fakedc.sample = fake; + fakedc.samples = 6; + + /* The dive has no samples, so create a few fake ones */ + int max_t = dc->duration.seconds; + int max_d = dc->maxdepth.mm; + int avg_d = dc->meandepth.mm; + + memset(fake, 0, sizeof(fake_samples)); + fake[5].time.seconds = max_t; + if (!max_t || !max_d) + return &fakedc; + + /* + * We want to fake the profile so that the average + * depth ends up correct. However, in the absence of + * a reasonable average, let's just make something + * up. Note that 'avg_d == max_d' is _not_ a reasonable + * average. + * We explicitly treat avg_d == 0 differently */ + if (avg_d == 0) { + /* we try for a sane slope, but bow to the insanity of + * the user supplied data */ + fill_samples_no_avg(fake, max_d, max_t, MAX(2.0 * max_d / max_t, 5000.0 / 60)); + if (fake[3].time.seconds == 0) { // just a 4 point profile + fakedc.samples = 4; + fake[3].time.seconds = max_t; + } + return &fakedc; + } + if (avg_d < max_d / 10 || avg_d >= max_d) { + avg_d = (max_d + 10000) / 3; + if (avg_d > max_d) + avg_d = max_d * 2 / 3; + } + if (!avg_d) + avg_d = 1; + + /* + * Ok, first we try a basic profile with a specific ascent + * rate (5 meters per minute) and d_frac (1/3). + */ + if (fill_samples(fake, max_d, avg_d, max_t, 5000.0 / 60, 0.33)) + return &fakedc; + + /* + * Ok, assume that didn't work because we cannot make the + * average come out right because it was a quick deep dive + * followed by a much shallower region + */ + if (fill_samples(fake, max_d, avg_d, max_t, 10000.0 / 60, 0.10)) + return &fakedc; + + /* + * Uhhuh. That didn't work. We'd need to find a good combination that + * satisfies our constraints. Currently, we don't, we just give insane + * slopes. + */ + if (fill_samples(fake, max_d, avg_d, max_t, 10000.0, 0.01)) + return &fakedc; + + /* Even that didn't work? Give up, there's something wrong */ + return &fakedc; +} diff --git a/core/device.h b/core/device.h new file mode 100644 index 000000000..8a00b96d3 --- /dev/null +++ b/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, bool alloc); +extern void create_device_node(const char *model, uint32_t deviceid, const char *serial, const char *firmware, const char *nickname); +extern void call_for_each_dc(void *f, void (*callback)(void *, const char *, uint32_t, + const char *, const char *, const char *), bool select_only); + +#ifdef __cplusplus +} +#endif + +#endif // DEVICE_H diff --git a/core/devicedetails.cpp b/core/devicedetails.cpp new file mode 100644 index 000000000..a917a4d0e --- /dev/null +++ b/core/devicedetails.cpp @@ -0,0 +1,70 @@ +#include "devicedetails.h" + +gas::gas(unsigned char oxygen, unsigned char helium, unsigned char type, unsigned char depth) : + oxygen(oxygen), helium(helium), type(type), depth(depth) +{ +} + +setpoint::setpoint(unsigned char sp, unsigned char depth) : + sp(sp), depth(depth) +{ +} + +DeviceDetails::DeviceDetails(QObject *parent) : + QObject(parent), + data(0), + syncTime(false), + setPointFallback(0), + ccrMode(0), + calibrationGas(0), + diveMode(0), + decoType(0), + ppO2Max(0), + ppO2Min(0), + futureTTS(0), + gfLow(0), + gfHigh(0), + aGFLow(0), + aGFHigh(0), + aGFSelectable(0), + saturation(0), + desaturation(0), + lastDeco(0), + brightness(0), + units(0), + samplingRate(0), + salinity(0), + diveModeColor(0), + language(0), + dateFormat(0), + compassGain(0), + pressureSensorOffset(0), + flipScreen(0), + safetyStop(0), + maxDepth(0), + totalTime(0), + numberOfDives(0), + altitude(0), + personalSafety(0), + timeFormat(0), + lightEnabled(false), + light(0), + alarmTimeEnabled(false), + alarmTime(0), + alarmDepthEnabled(false), + alarmDepth(0), + leftButtonSensitivity(0), + rightButtonSensitivity(0), + bottomGasConsumption(0), + decoGasConsumption(0), + modWarning(false), + dynamicAscendRate(false), + graphicalSpeedIndicator(false), + alwaysShowppO2(false), + tempSensorOffset(0), + safetyStopLength(0), + safetyStopStartDepth(0), + safetyStopEndDepth(0), + safetyStopResetDepth(0) +{ +} diff --git a/core/devicedetails.h b/core/devicedetails.h new file mode 100644 index 000000000..ff3009bc5 --- /dev/null +++ b/core/devicedetails.h @@ -0,0 +1,104 @@ +#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; + gas(unsigned char oxygen = 0, unsigned char helium = 0, unsigned char type = 0, unsigned char depth = 0); +}; + +struct setpoint { + unsigned char sp; + unsigned char depth; + setpoint(unsigned char sp = 0, unsigned char depth = 0); +}; + +class DeviceDetails : public QObject +{ + Q_OBJECT +public: + explicit DeviceDetails(QObject *parent = 0); + + device_data_t *data; + QString serialNo; + QString firmwareVersion; + QString customText; + QString model; + bool syncTime; + gas gas1; + gas gas2; + gas gas3; + gas gas4; + gas gas5; + gas dil1; + gas dil2; + gas dil3; + gas dil4; + gas dil5; + setpoint sp1; + setpoint sp2; + setpoint sp3; + setpoint sp4; + setpoint sp5; + bool setPointFallback; + int ccrMode; + int calibrationGas; + int diveMode; + int decoType; + int ppO2Max; + int ppO2Min; + int futureTTS; + int gfLow; + int gfHigh; + int aGFLow; + int aGFHigh; + int aGFSelectable; + int saturation; + int desaturation; + int lastDeco; + int brightness; + int units; + int samplingRate; + int salinity; + int diveModeColor; + int language; + int dateFormat; + int compassGain; + int pressureSensorOffset; + bool flipScreen; + bool safetyStop; + int maxDepth; + int totalTime; + int numberOfDives; + int altitude; + int personalSafety; + int timeFormat; + bool lightEnabled; + int light; + bool alarmTimeEnabled; + int alarmTime; + bool alarmDepthEnabled; + int alarmDepth; + int leftButtonSensitivity; + int rightButtonSensitivity; + int bottomGasConsumption; + int decoGasConsumption; + bool modWarning; + bool dynamicAscendRate; + bool graphicalSpeedIndicator; + bool alwaysShowppO2; + int tempSensorOffset; + unsigned safetyStopLength; + unsigned safetyStopStartDepth; + unsigned safetyStopEndDepth; + unsigned safetyStopResetDepth; +}; + + +#endif // DEVICEDETAILS_H diff --git a/core/display.h b/core/display.h new file mode 100644 index 000000000..9e3e1d159 --- /dev/null +++ b/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/core/dive.c b/core/dive.c new file mode 100644 index 000000000..ce730da28 --- /dev/null +++ b/core/dive.c @@ -0,0 +1,3561 @@ +/* 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, unsigned int time, int type, int flags, int value, const char *name) +{ + int gas_index = -1; + struct event *ev, **p; + unsigned int size, len = strlen(name); + + size = sizeof(*ev) + len + 1; + ev = malloc(size); + if (!ev) + return NULL; + memset(ev, 0, size); + memcpy(ev->name, name, len); + ev->time.seconds = time; + ev->type = type; + ev->flags = flags; + ev->value = value; + + /* + * Expand the events into a sane format. Currently + * just gas switches + */ + switch (type) { + case SAMPLE_EVENT_GASCHANGE2: + /* High 16 bits are He percentage */ + ev->gas.mix.he.permille = (value >> 16) * 10; + + /* Extension to the GASCHANGE2 format: cylinder index in 'flags' */ + if (flags > 0 && flags <= MAX_CYLINDERS) + gas_index = flags-1; + /* Fallthrough */ + case SAMPLE_EVENT_GASCHANGE: + /* Low 16 bits are O2 percentage */ + ev->gas.mix.o2.permille = (value & 0xffff) * 10; + ev->gas.index = gas_index; + break; + } + + p = &dc->events; + + /* insert in the sorted list of events */ + while (*p && (*p)->time.seconds <= time) + p = &(*p)->next; + ev->next = *p; + *p = ev; + remember_event(name); + return ev; +} + +static int same_event(struct event *a, struct event *b) +{ + if (a->time.seconds != b->time.seconds) + return 0; + if (a->type != b->type) + return 0; + if (a->flags != b->flags) + return 0; + if (a->value != b->value) + return 0; + return !strcmp(a->name, b->name); +} + +void remove_event(struct event *event) +{ + struct event **ep = ¤t_dc->events; + while (ep && !same_event(*ep, event)) + ep = &(*ep)->next; + if (ep) { + /* we can't link directly with event->next + * because 'event' can be a copy from another + * dive (for instance the displayed_dive + * that we use on the interface to show things). */ + struct event *temp = (*ep)->next; + free(*ep); + *ep = temp; + } +} + +/* since the name is an array as part of the structure (how silly is that?) we + * have to actually remove the existing event and replace it with a new one. + * WARNING, WARNING... this may end up freeing event in case that event is indeed + * WARNING, WARNING... part of this divecomputer on this dive! */ +void update_event_name(struct dive *d, struct event *event, char *name) +{ + if (!d || !event) + return; + struct divecomputer *dc = get_dive_dc(d, dc_number); + if (!dc) + return; + struct event **removep = &dc->events; + struct event *remove; + while ((*removep)->next && !same_event(*removep, event)) + removep = &(*removep)->next; + if (!same_event(*removep, event)) + return; + remove = *removep; + *removep = (*removep)->next; + add_event(dc, event->time.seconds, event->type, event->flags, event->value, name); + free(remove); + invalidate_dive_cache(d); +} + +void add_extra_data(struct divecomputer *dc, const char *key, const char *value) +{ + struct extra_data **ed = &dc->extra_data; + + while (*ed) + ed = &(*ed)->next; + *ed = malloc(sizeof(struct extra_data)); + if (*ed) { + (*ed)->key = strdup(key); + (*ed)->value = strdup(value); + (*ed)->next = NULL; + } +} + +/* this returns a pointer to static variable - so use it right away after calling */ +struct gasmix *get_gasmix_from_event(struct event *ev) +{ + static struct gasmix dummy; + if (ev && event_is_gaschange(ev)) + return &ev->gas.mix; + + return &dummy; +} + +int get_pressure_units(int mb, const char **units) +{ + int pressure; + const char *unit; + struct units *units_p = get_units(); + + switch (units_p->pressure) { + case PASCAL: + pressure = mb * 100; + unit = translate("gettextFromC", "pascal"); + break; + case BAR: + default: + pressure = (mb + 500) / 1000; + unit = translate("gettextFromC", "bar"); + break; + case PSI: + pressure = mbar_to_PSI(mb); + unit = translate("gettextFromC", "psi"); + break; + } + if (units) + *units = unit; + return pressure; +} + +double get_temp_units(unsigned int mk, const char **units) +{ + double deg; + const char *unit; + struct units *units_p = get_units(); + + if (units_p->temperature == FAHRENHEIT) { + deg = mkelvin_to_F(mk); + unit = UTF8_DEGREE "F"; + } else { + deg = mkelvin_to_C(mk); + unit = UTF8_DEGREE "C"; + } + if (units) + *units = unit; + return deg; +} + +double get_volume_units(unsigned int ml, int *frac, const char **units) +{ + int decimals; + double vol; + const char *unit; + struct units *units_p = get_units(); + + switch (units_p->volume) { + case LITER: + default: + vol = ml / 1000.0; + unit = translate("gettextFromC", "ℓ"); + decimals = 1; + break; + case CUFT: + vol = ml_to_cuft(ml); + unit = translate("gettextFromC", "cuft"); + decimals = 2; + break; + } + if (frac) + *frac = decimals; + if (units) + *units = unit; + return vol; +} + +int units_to_sac(double volume) +{ + if (get_units()->volume == CUFT) + return rint(cuft_to_l(volume) * 1000.0); + else + return rint(volume * 1000); +} + +unsigned int units_to_depth(double depth) +{ + if (get_units()->length == METERS) + return rint(depth * 1000); + return feet_to_mm(depth); +} + +double get_depth_units(int mm, int *frac, const char **units) +{ + int decimals; + double d; + const char *unit; + struct units *units_p = get_units(); + + switch (units_p->length) { + case METERS: + default: + d = mm / 1000.0; + unit = translate("gettextFromC", "m"); + decimals = d < 20; + break; + case FEET: + d = mm_to_feet(mm); + unit = translate("gettextFromC", "ft"); + decimals = 0; + break; + } + if (frac) + *frac = decimals; + if (units) + *units = unit; + return d; +} + +double get_vertical_speed_units(unsigned int mms, int *frac, const char **units) +{ + double d; + const char *unit; + const struct units *units_p = get_units(); + const double time_factor = units_p->vertical_speed_time == MINUTES ? 60.0 : 1.0; + + switch (units_p->length) { + case METERS: + default: + d = mms / 1000.0 * time_factor; + if (units_p->vertical_speed_time == MINUTES) + unit = translate("gettextFromC", "m/min"); + else + unit = translate("gettextFromC", "m/s"); + break; + case FEET: + d = mm_to_feet(mms) * time_factor; + if (units_p->vertical_speed_time == MINUTES) + unit = translate("gettextFromC", "ft/min"); + else + unit = translate("gettextFromC", "ft/s"); + break; + } + if (frac) + *frac = d < 10; + if (units) + *units = unit; + return d; +} + +double get_weight_units(unsigned int grams, int *frac, const char **units) +{ + int decimals; + double value; + const char *unit; + struct units *units_p = get_units(); + + if (units_p->weight == LBS) { + value = grams_to_lbs(grams); + unit = translate("gettextFromC", "lbs"); + decimals = 0; + } else { + value = grams / 1000.0; + unit = translate("gettextFromC", "kg"); + decimals = 1; + } + if (frac) + *frac = decimals; + if (units) + *units = unit; + return value; +} + +bool has_hr_data(struct divecomputer *dc) +{ + int i; + struct sample *sample; + + if (!dc) + return false; + + sample = dc->sample; + for (i = 0; i < dc->samples; i++) + if (sample[i].heartbeat) + return true; + return false; +} + +struct dive *alloc_dive(void) +{ + struct dive *dive; + + dive = malloc(sizeof(*dive)); + if (!dive) + exit(1); + memset(dive, 0, sizeof(*dive)); + dive->id = dive_getUniqID(dive); + return dive; +} + +static void free_dc(struct divecomputer *dc); +static void free_pic(struct picture *picture); + +/* this is very different from the copy_divecomputer later in this file; + * this function actually makes full copies of the content */ +static void copy_dc(struct divecomputer *sdc, struct divecomputer *ddc) +{ + *ddc = *sdc; + ddc->model = copy_string(sdc->model); + copy_samples(sdc, ddc); + copy_events(sdc, ddc); +} + +/* copy an element in a list of pictures */ +static void copy_pl(struct picture *sp, struct picture *dp) +{ + *dp = *sp; + dp->filename = copy_string(sp->filename); + dp->hash = copy_string(sp->hash); +} + +/* copy an element in a list of tags */ +static void copy_tl(struct tag_entry *st, struct tag_entry *dt) +{ + dt->tag = malloc(sizeof(struct divetag)); + dt->tag->name = copy_string(st->tag->name); + dt->tag->source = copy_string(st->tag->source); +} + +/* Clear everything but the first element; + * this works for taglist, picturelist, even dive computers */ +#define STRUCTURED_LIST_FREE(_type, _start, _free) \ + { \ + _type *_ptr = _start; \ + while (_ptr) { \ + _type *_next = _ptr->next; \ + _free(_ptr); \ + _ptr = _next; \ + } \ + } + +#define STRUCTURED_LIST_COPY(_type, _first, _dest, _cpy) \ + { \ + _type *_sptr = _first; \ + _type **_dptr = &_dest; \ + while (_sptr) { \ + *_dptr = malloc(sizeof(_type)); \ + _cpy(_sptr, *_dptr); \ + _sptr = _sptr->next; \ + _dptr = &(*_dptr)->next; \ + } \ + *_dptr = 0; \ + } + +/* copy_dive makes duplicates of many components of a dive; + * in order not to leak memory, we need to free those . + * copy_dive doesn't play with the divetrip and forward/backward pointers + * so we can ignore those */ +void clear_dive(struct dive *d) +{ + if (!d) + return; + /* free the strings */ + free(d->buddy); + free(d->divemaster); + free(d->notes); + free(d->suit); + /* free tags, additional dive computers, and pictures */ + taglist_free(d->tag_list); + STRUCTURED_LIST_FREE(struct divecomputer, d->dc.next, free_dc); + STRUCTURED_LIST_FREE(struct picture, d->picture_list, free_pic); + for (int i = 0; i < MAX_CYLINDERS; i++) + free((void *)d->cylinder[i].type.description); + for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) + free((void *)d->weightsystem[i].description); + memset(d, 0, sizeof(struct dive)); +} + +/* make a true copy that is independent of the source dive; + * all data structures are duplicated, so the copy can be modified without + * any impact on the source */ +void copy_dive(struct dive *s, struct dive *d) +{ + clear_dive(d); + /* simply copy things over, but then make actual copies of the + * relevant components that are referenced through pointers, + * so all the strings and the structured lists */ + *d = *s; + invalidate_dive_cache(d); + d->buddy = copy_string(s->buddy); + d->divemaster = copy_string(s->divemaster); + d->notes = copy_string(s->notes); + d->suit = copy_string(s->suit); + for (int i = 0; i < MAX_CYLINDERS; i++) + d->cylinder[i].type.description = copy_string(s->cylinder[i].type.description); + for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) + d->weightsystem[i].description = copy_string(s->weightsystem[i].description); + STRUCTURED_LIST_COPY(struct picture, s->picture_list, d->picture_list, copy_pl); + STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl); + + // Copy the first dc explicitly, then the list of subsequent dc's + copy_dc(&s->dc, &d->dc); + STRUCTURED_LIST_COPY(struct divecomputer, s->dc.next, d->dc.next, copy_dc); +} + +/* make a clone of the source dive and clean out the source dive; + * this is specifically so we can create a dive in the displayed_dive and then + * add it to the divelist. + * Note the difference to copy_dive() / clean_dive() */ +struct dive *clone_dive(struct dive *s) +{ + struct dive *dive = alloc_dive(); + *dive = *s; // so all the pointers in dive point to the things s pointed to + memset(s, 0, sizeof(struct dive)); // and now the pointers in s are gone + return dive; +} + +#define CONDITIONAL_COPY_STRING(_component) \ + if (what._component) \ + d->_component = copy_string(s->_component) + +// copy elements, depending on bits in what that are set +void selective_copy_dive(struct dive *s, struct dive *d, struct dive_components what, bool clear) +{ + if (clear) + clear_dive(d); + CONDITIONAL_COPY_STRING(notes); + CONDITIONAL_COPY_STRING(divemaster); + CONDITIONAL_COPY_STRING(buddy); + CONDITIONAL_COPY_STRING(suit); + if (what.rating) + d->rating = s->rating; + if (what.visibility) + d->visibility = s->visibility; + if (what.divesite) + d->dive_site_uuid = s->dive_site_uuid; + if (what.tags) + STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl); + if (what.cylinders) + copy_cylinders(s, d, false); + if (what.weights) + for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) { + free((void *)d->weightsystem[i].description); + d->weightsystem[i] = s->weightsystem[i]; + d->weightsystem[i].description = copy_string(s->weightsystem[i].description); + } +} +#undef CONDITIONAL_COPY_STRING + +struct event *clone_event(const struct event *src_ev) +{ + struct event *ev; + if (!src_ev) + return NULL; + + size_t size = sizeof(*src_ev) + strlen(src_ev->name) + 1; + ev = (struct event*) malloc(size); + if (!ev) + exit(1); + memcpy(ev, src_ev, size); + ev->next = NULL; + + return ev; +} + +/* copies all events in this dive computer */ +void copy_events(struct divecomputer *s, struct divecomputer *d) +{ + struct event *ev, **pev; + if (!s || !d) + return; + ev = s->events; + pev = &d->events; + while (ev != NULL) { + struct event *new_ev = clone_event(ev); + *pev = new_ev; + pev = &new_ev->next; + ev = ev->next; + } + *pev = NULL; +} + +int nr_cylinders(struct dive *dive) +{ + int nr; + + for (nr = MAX_CYLINDERS; nr; --nr) { + cylinder_t *cylinder = dive->cylinder + nr - 1; + if (!cylinder_nodata(cylinder)) + break; + } + return nr; +} + +int nr_weightsystems(struct dive *dive) +{ + int nr; + + for (nr = MAX_WEIGHTSYSTEMS; nr; --nr) { + weightsystem_t *ws = dive->weightsystem + nr - 1; + if (!weightsystem_none(ws)) + break; + } + return nr; +} + +/* copy the equipment data part of the cylinders */ +void copy_cylinders(struct dive *s, struct dive *d, bool used_only) +{ + int i,j; + if (!s || !d) + return; + for (i = 0; i < MAX_CYLINDERS; i++) { + free((void *)d->cylinder[i].type.description); + memset(&d->cylinder[i], 0, sizeof(cylinder_t)); + } + for (i = j = 0; i < MAX_CYLINDERS; i++) { + if (!used_only || is_cylinder_used(s, i)) { + d->cylinder[j].type = s->cylinder[i].type; + d->cylinder[j].type.description = copy_string(s->cylinder[i].type.description); + d->cylinder[j].gasmix = s->cylinder[i].gasmix; + d->cylinder[j].depth = s->cylinder[i].depth; + d->cylinder[j].cylinder_use = s->cylinder[i].cylinder_use; + d->cylinder[j].manually_added = true; + j++; + } + } +} + +int cylinderuse_from_text(const char *text) +{ + for (enum cylinderuse i = 0; i < NUM_GAS_USE; i++) { + if (same_string(text, cylinderuse_text[i]) || same_string(text, translate("gettextFromC", cylinderuse_text[i]))) + return i; + } + return -1; +} + +void copy_samples(struct divecomputer *s, struct divecomputer *d) +{ + /* instead of carefully copying them one by one and calling add_sample + * over and over again, let's just copy the whole blob */ + if (!s || !d) + return; + int nr = s->samples; + d->samples = nr; + d->alloc_samples = nr; + // We expect to be able to read the memory in the other end of the pointer + // if its a valid pointer, so don't expect malloc() to return NULL for + // zero-sized malloc, do it ourselves. + d->sample = NULL; + + if(!nr) + return; + + d->sample = malloc(nr * sizeof(struct sample)); + if (d->sample) + memcpy(d->sample, s->sample, nr * sizeof(struct sample)); +} + +struct sample *prepare_sample(struct divecomputer *dc) +{ + if (dc) { + int nr = dc->samples; + int alloc_samples = dc->alloc_samples; + struct sample *sample; + if (nr >= alloc_samples) { + struct sample *newsamples; + + alloc_samples = (alloc_samples * 3) / 2 + 10; + newsamples = realloc(dc->sample, alloc_samples * sizeof(struct sample)); + if (!newsamples) + return NULL; + dc->alloc_samples = alloc_samples; + dc->sample = newsamples; + } + sample = dc->sample + nr; + memset(sample, 0, sizeof(*sample)); + return sample; + } + return NULL; +} + +void finish_sample(struct divecomputer *dc) +{ + dc->samples++; +} + +/* + * So when we re-calculate maxdepth and meandepth, we will + * not override the old numbers if they are close to the + * new ones. + * + * Why? Because a dive computer may well actually track the + * max depth and mean depth at finer granularity than the + * samples it stores. So it's possible that the max and mean + * have been reported more correctly originally. + * + * Only if the values calculated from the samples are clearly + * different do we override the normal depth values. + * + * This considers 1m to be "clearly different". That's + * a totally random number. + */ +static void update_depth(depth_t *depth, int new) +{ + if (new) { + int old = depth->mm; + + if (abs(old - new) > 1000) + depth->mm = new; + } +} + +static void update_temperature(temperature_t *temperature, int new) +{ + if (new) { + int old = temperature->mkelvin; + + if (abs(old - new) > 1000) + temperature->mkelvin = new; + } +} + +/* + * Calculate how long we were actually under water, and the average + * depth while under water. + * + * This ignores any surface time in the middle of the dive. + */ +void fixup_dc_duration(struct divecomputer *dc) +{ + int duration, i; + int lasttime, lastdepth, depthtime; + + duration = 0; + lasttime = 0; + lastdepth = 0; + depthtime = 0; + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + int time = sample->time.seconds; + int depth = sample->depth.mm; + + /* We ignore segments at the surface */ + if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { + duration += time - lasttime; + depthtime += (time - lasttime) * (depth + lastdepth) / 2; + } + lastdepth = depth; + lasttime = time; + } + if (duration) { + dc->duration.seconds = duration; + dc->meandepth.mm = (depthtime + duration / 2) / duration; + } +} + +void per_cylinder_mean_depth(struct dive *dive, struct divecomputer *dc, int *mean, int *duration) +{ + int i; + int depthtime[MAX_CYLINDERS] = { 0, }; + uint32_t lasttime = 0; + int lastdepth = 0; + int idx = 0; + + for (i = 0; i < MAX_CYLINDERS; i++) + mean[i] = duration[i] = 0; + if (!dc) + return; + struct event *ev = get_next_event(dc->events, "gaschange"); + if (!ev || (dc && dc->sample && ev->time.seconds == dc->sample[0].time.seconds && get_next_event(ev->next, "gaschange") == NULL)) { + // we have either no gas change or only one gas change and that's setting an explicit first cylinder + mean[explicit_first_cylinder(dive, dc)] = dc->meandepth.mm; + duration[explicit_first_cylinder(dive, dc)] = dc->duration.seconds; + + if (dc->divemode == CCR) { + // Do the same for the O2 cylinder + int o2_cyl = get_cylinder_idx_by_use(dive, OXYGEN); + if (o2_cyl < 0) + return; + mean[o2_cyl] = dc->meandepth.mm; + duration[o2_cyl] = dc->duration.seconds; + } + return; + } + if (!dc->samples) + dc = fake_dc(dc, false); + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + uint32_t time = sample->time.seconds; + int depth = sample->depth.mm; + + /* Make sure to move the event past 'lasttime' */ + while (ev && lasttime >= ev->time.seconds) { + idx = get_cylinder_index(dive, ev); + ev = get_next_event(ev->next, "gaschange"); + } + + /* Do we need to fake a midway sample at an event? */ + if (ev && time > ev->time.seconds) { + int newtime = ev->time.seconds; + int newdepth = interpolate(lastdepth, depth, newtime - lasttime, time - lasttime); + + time = newtime; + depth = newdepth; + i--; + } + /* We ignore segments at the surface */ + if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { + duration[idx] += time - lasttime; + depthtime[idx] += (time - lasttime) * (depth + lastdepth) / 2; + } + lastdepth = depth; + lasttime = time; + } + for (i = 0; i < MAX_CYLINDERS; i++) { + if (duration[i]) + mean[i] = (depthtime[i] + duration[i] / 2) / duration[i]; + } +} + +static void update_min_max_temperatures(struct dive *dive, temperature_t temperature) +{ + if (temperature.mkelvin) { + if (!dive->maxtemp.mkelvin || temperature.mkelvin > dive->maxtemp.mkelvin) + dive->maxtemp = temperature; + if (!dive->mintemp.mkelvin || temperature.mkelvin < dive->mintemp.mkelvin) + dive->mintemp = temperature; + } +} + +int gas_volume(cylinder_t *cyl, pressure_t p) +{ + double bar = p.mbar / 1000.0; + double z_factor = gas_compressibility_factor(&cyl->gasmix, bar); + return cyl->type.size.mliter * bar_to_atm(bar) / z_factor; +} + +/* + * If the cylinder tank pressures are within half a bar + * (about 8 PSI) of the sample pressures, we consider it + * to be a rounding error, and throw them away as redundant. + */ +static int same_rounded_pressure(pressure_t a, pressure_t b) +{ + return abs(a.mbar - b.mbar) <= 500; +} + +/* Some dive computers (Cobalt) don't start the dive with cylinder 0 but explicitly + * tell us what the first gas is with a gas change event in the first sample. + * Sneakily we'll use a return value of 0 (or FALSE) when there is no explicit + * first cylinder - in which case cylinder 0 is indeed the first cylinder */ +int explicit_first_cylinder(struct dive *dive, struct divecomputer *dc) +{ + if (dc) { + struct event *ev = get_next_event(dc->events, "gaschange"); + if (ev && dc->sample && ev->time.seconds == dc->sample[0].time.seconds) + return get_cylinder_index(dive, ev); + else if (dc->divemode == CCR) + return MAX(get_cylinder_idx_by_use(dive, DILUENT), 0); + } + return 0; +} + +/* this gets called when the dive mode has changed (so OC vs. CC) + * there are two places we might have setpoints... events or in the samples + */ +void update_setpoint_events(struct divecomputer *dc) +{ + struct event *ev; + int new_setpoint = 0; + + if (dc->divemode == CCR) + new_setpoint = prefs.defaultsetpoint; + + if (dc->divemode == OC && + (same_string(dc->model, "Shearwater Predator") || + same_string(dc->model, "Shearwater Petrel") || + same_string(dc->model, "Shearwater Nerd"))) { + // make sure there's no setpoint in the samples + // this is an irreversible change - so switching a dive to OC + // by mistake when it's actually CCR is _bad_ + // So we make sure, this comes from a Predator or Petrel and we only remove + // pO2 values we would have computed anyway. + struct event *ev = get_next_event(dc->events, "gaschange"); + struct gasmix *gasmix = get_gasmix_from_event(ev); + struct event *next = get_next_event(ev, "gaschange"); + + for (int i = 0; i < dc->samples; i++) { + struct gas_pressures pressures; + if (next && dc->sample[i].time.seconds >= next->time.seconds) { + ev = next; + gasmix = get_gasmix_from_event(ev); + next = get_next_event(ev, "gaschange"); + } + fill_pressures(&pressures, calculate_depth_to_mbar(dc->sample[i].depth.mm, dc->surface_pressure, 0), gasmix ,0, OC); + if (abs(dc->sample[i].setpoint.mbar - (int)(1000 * pressures.o2)) <= 50) + dc->sample[i].setpoint.mbar = 0; + } + } + + // an "SP change" event at t=0 is currently our marker for OC vs CCR + // this will need to change to a saner setup, but for now we can just + // check if such an event is there and adjust it, or add that event + ev = get_next_event(dc->events, "SP change"); + if (ev && ev->time.seconds == 0) { + ev->value = new_setpoint; + } else { + if (!add_event(dc, 0, SAMPLE_EVENT_PO2, 0, new_setpoint, "SP change")) + fprintf(stderr, "Could not add setpoint change event\n"); + } +} + +void sanitize_gasmix(struct gasmix *mix) +{ + unsigned int o2, he; + + o2 = mix->o2.permille; + he = mix->he.permille; + + /* Regular air: leave empty */ + if (!he) { + if (!o2) + return; + /* 20.8% to 21% O2 is just air */ + if (gasmix_is_air(mix)) { + mix->o2.permille = 0; + return; + } + } + + /* Sane mix? */ + if (o2 <= 1000 && he <= 1000 && o2 + he <= 1000) + return; + fprintf(stderr, "Odd gasmix: %u O2 %u He\n", o2, he); + memset(mix, 0, sizeof(*mix)); +} + +/* + * See if the size/workingpressure looks like some standard cylinder + * size, eg "AL80". + * + * NOTE! We don't take compressibility into account when naming + * cylinders. That makes a certain amount of sense, since the + * cylinder name is independent from the gasmix, and different + * gasmixes have different compressibility. + */ +static void match_standard_cylinder(cylinder_type_t *type) +{ + double cuft, bar; + int psi, len; + const char *fmt; + char buffer[40], *p; + + /* Do we already have a cylinder description? */ + if (type->description) + return; + + bar = type->workingpressure.mbar / 1000.0; + cuft = ml_to_cuft(type->size.mliter); + cuft *= bar_to_atm(bar); + psi = to_PSI(type->workingpressure); + + switch (psi) { + case 2300 ... 2500: /* 2400 psi: LP tank */ + fmt = "LP%d"; + break; + case 2600 ... 2700: /* 2640 psi: LP+10% */ + fmt = "LP%d"; + break; + case 2900 ... 3100: /* 3000 psi: ALx tank */ + fmt = "AL%d"; + break; + case 3400 ... 3500: /* 3442 psi: HP tank */ + fmt = "HP%d"; + break; + case 3700 ... 3850: /* HP+10% */ + fmt = "HP%d+"; + break; + default: + return; + } + len = snprintf(buffer, sizeof(buffer), fmt, (int)rint(cuft)); + p = malloc(len + 1); + if (!p) + return; + memcpy(p, buffer, len + 1); + type->description = p; +} + + +/* + * There are two ways to give cylinder size information: + * - total amount of gas in cuft (depends on working pressure and physical size) + * - physical size + * + * where "physical size" is the one that actually matters and is sane. + * + * We internally use physical size only. But we save the workingpressure + * so that we can do the conversion if required. + */ +static void sanitize_cylinder_type(cylinder_type_t *type) +{ + double volume_of_air, volume; + + /* If we have no working pressure, it had *better* be just a physical size! */ + if (!type->workingpressure.mbar) + return; + + /* No size either? Nothing to go on */ + if (!type->size.mliter) + return; + + if (xml_parsing_units.volume == CUFT) { + double bar = type->workingpressure.mbar / 1000.0; + /* confusing - we don't really start from ml but millicuft !*/ + volume_of_air = cuft_to_l(type->size.mliter); + /* milliliters at 1 atm: not corrected for compressibility! */ + volume = volume_of_air / bar_to_atm(bar); + type->size.mliter = rint(volume); + } + + /* Ok, we have both size and pressure: try to match a description */ + match_standard_cylinder(type); +} + +static void sanitize_cylinder_info(struct dive *dive) +{ + int i; + + for (i = 0; i < MAX_CYLINDERS; i++) { + sanitize_gasmix(&dive->cylinder[i].gasmix); + sanitize_cylinder_type(&dive->cylinder[i].type); + } +} + +/* some events should never be thrown away */ +static bool is_potentially_redundant(struct event *event) +{ + if (!strcmp(event->name, "gaschange")) + return false; + if (!strcmp(event->name, "bookmark")) + return false; + if (!strcmp(event->name, "heading")) + return false; + return true; +} + +/* match just by name - we compare the details in the code that uses this helper */ +static struct event *find_previous_event(struct divecomputer *dc, struct event *event) +{ + struct event *ev = dc->events; + struct event *previous = NULL; + + if (same_string(event->name, "")) + return NULL; + while (ev && ev != event) { + if (same_string(ev->name, event->name)) + previous = ev; + ev = ev->next; + } + return previous; +} + +static void fixup_surface_pressure(struct dive *dive) +{ + struct divecomputer *dc; + int sum = 0, nr = 0; + + for_each_dc (dive, dc) { + if (dc->surface_pressure.mbar) { + sum += dc->surface_pressure.mbar; + nr++; + } + } + if (nr) + dive->surface_pressure.mbar = (sum + nr / 2) / nr; +} + +static void fixup_water_salinity(struct dive *dive) +{ + struct divecomputer *dc; + int sum = 0, nr = 0; + + for_each_dc (dive, dc) { + if (dc->salinity) { + if (dc->salinity < 500) + dc->salinity += FRESHWATER_SALINITY; + sum += dc->salinity; + nr++; + } + } + if (nr) + dive->salinity = (sum + nr / 2) / nr; +} + +static void fixup_meandepth(struct dive *dive) +{ + struct divecomputer *dc; + int sum = 0, nr = 0; + + for_each_dc (dive, dc) { + if (dc->meandepth.mm) { + sum += dc->meandepth.mm; + nr++; + } + } + if (nr) + dive->meandepth.mm = (sum + nr / 2) / nr; +} + +static void fixup_duration(struct dive *dive) +{ + struct divecomputer *dc; + unsigned int duration = 0; + + for_each_dc (dive, dc) + duration = MAX(duration, dc->duration.seconds); + + dive->duration.seconds = duration; +} + +/* + * What do the dive computers say the water temperature is? + * (not in the samples, but as dc property for dcs that support that) + */ +unsigned int dc_watertemp(struct divecomputer *dc) +{ + int sum = 0, nr = 0; + + do { + if (dc->watertemp.mkelvin) { + sum += dc->watertemp.mkelvin; + nr++; + } + } while ((dc = dc->next) != NULL); + if (!nr) + return 0; + return (sum + nr / 2) / nr; +} + +static void fixup_watertemp(struct dive *dive) +{ + if (!dive->watertemp.mkelvin) + dive->watertemp.mkelvin = dc_watertemp(&dive->dc); +} + +/* + * What do the dive computers say the air temperature is? + */ +unsigned int dc_airtemp(struct divecomputer *dc) +{ + int sum = 0, nr = 0; + + do { + if (dc->airtemp.mkelvin) { + sum += dc->airtemp.mkelvin; + nr++; + } + } while ((dc = dc->next) != NULL); + if (!nr) + return 0; + return (sum + nr / 2) / nr; +} + +static void fixup_cylinder_use(struct dive *dive) // for CCR dives, store the indices +{ // of the oxygen and diluent cylinders + dive->oxygen_cylinder_index = get_cylinder_idx_by_use(dive, OXYGEN); + dive->diluent_cylinder_index = get_cylinder_idx_by_use(dive, DILUENT); +} + +static void fixup_airtemp(struct dive *dive) +{ + if (!dive->airtemp.mkelvin) + dive->airtemp.mkelvin = dc_airtemp(&dive->dc); +} + +/* zero out the airtemp in the dive structure if it was just created by + * running fixup on the dive. keep it if it had been edited by hand */ +static void un_fixup_airtemp(struct dive *a) +{ + if (a->airtemp.mkelvin && a->airtemp.mkelvin == dc_airtemp(&a->dc)) + a->airtemp.mkelvin = 0; +} + +/* + * events are stored as a linked list, so the concept of + * "consecutive, identical events" is somewhat hard to + * implement correctly (especially given that on some dive + * computers events are asynchronous, so they can come in + * between what would be the non-constant sample rate). + * + * So what we do is that we throw away clearly redundant + * events that are fewer than 61 seconds apart (assuming there + * is no dive computer with a sample rate of more than 60 + * seconds... that would be pretty pointless to plot the + * profile with) + * + * We first only mark the events for deletion so that we + * still know when the previous event happened. + */ +static void fixup_dc_events(struct divecomputer *dc) +{ + struct event *event; + + event = dc->events; + while (event) { + struct event *prev; + if (is_potentially_redundant(event)) { + prev = find_previous_event(dc, event); + if (prev && prev->value == event->value && + prev->flags == event->flags && + event->time.seconds - prev->time.seconds < 61) + event->deleted = true; + } + event = event->next; + } + event = dc->events; + while (event) { + if (event->next && event->next->deleted) { + struct event *nextnext = event->next->next; + free(event->next); + event->next = nextnext; + } else { + event = event->next; + } + } +} + +static int interpolate_depth(struct divecomputer *dc, int idx, int lastdepth, int lasttime, int now) +{ + int i; + int nextdepth = lastdepth; + int nexttime = now; + + for (i = idx+1; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + if (sample->depth.mm < 0) + continue; + nextdepth = sample->depth.mm; + nexttime = sample->time.seconds; + break; + } + return interpolate(lastdepth, nextdepth, now-lasttime, nexttime-lasttime); +} + +static void fixup_dc_depths(struct dive *dive, struct divecomputer *dc) +{ + int i; + int maxdepth = dc->maxdepth.mm; + int lasttime = 0, lastdepth = 0; + + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + int time = sample->time.seconds; + int depth = sample->depth.mm; + + if (depth < 0) { + depth = interpolate_depth(dc, i, lastdepth, lasttime, time); + sample->depth.mm = depth; + } + + if (depth > SURFACE_THRESHOLD) { + if (depth > maxdepth) + maxdepth = depth; + } + + lastdepth = depth; + lasttime = time; + if (sample->cns > dive->maxcns) + dive->maxcns = sample->cns; + } + + update_depth(&dc->maxdepth, maxdepth); + if (maxdepth > dive->maxdepth.mm) + dive->maxdepth.mm = maxdepth; +} + +static void fixup_dc_temp(struct dive *dive, struct divecomputer *dc) +{ + int i; + int mintemp = 0, lasttemp = 0; + + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + int temp = sample->temperature.mkelvin; + + if (temp) { + /* + * If we have consecutive identical + * temperature readings, throw away + * the redundant ones. + */ + if (lasttemp == temp) + sample->temperature.mkelvin = 0; + else + lasttemp = temp; + + if (!mintemp || temp < mintemp) + mintemp = temp; + } + + update_min_max_temperatures(dive, sample->temperature); + } + update_temperature(&dc->watertemp, mintemp); + update_min_max_temperatures(dive, dc->watertemp); +} + +/* + * Fix up cylinder sensor information in the samples if we have + * an explicit first cylinder + */ +static void fixup_dc_cylinder_index(struct dive *dive, struct divecomputer *dc) +{ + int i; + int first_cylinder = explicit_first_cylinder(dive, dc); + + if (!first_cylinder) + return; + + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + + if (sample->sensor == 0) + sample->sensor = first_cylinder; + } +} + +/* + * Simplify dc pressure information: + * (a) Remove redundant pressure information + * (b) Remove linearly interpolated pressure data + */ +static void simplify_dc_pressures(struct dive *dive, struct divecomputer *dc) +{ + int i, j; + int lastindex = -1; + int lastpressure = 0, lasto2pressure = 0; + int pressure_delta[MAX_CYLINDERS] = { INT_MAX, }; + + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + int pressure = sample->cylinderpressure.mbar; + int o2_pressure = sample->o2cylinderpressure.mbar; + int index; + + index = sample->sensor; + + if (index == lastindex) { + /* Remove duplicate redundant pressure information */ + if (pressure == lastpressure) + sample->cylinderpressure.mbar = 0; + if (o2_pressure == lasto2pressure) + sample->o2cylinderpressure.mbar = 0; + /* check for simply linear data in the samples + +INT_MAX means uninitialized, -INT_MAX means not linear */ + if (pressure_delta[index] != -INT_MAX && lastpressure) { + if (pressure_delta[index] == INT_MAX) { + pressure_delta[index] = abs(pressure - lastpressure); + } else { + int cur_delta = abs(pressure - lastpressure); + if (cur_delta && abs(cur_delta - pressure_delta[index]) > 150) { + /* ok the samples aren't just a linearisation + * between start and end */ + pressure_delta[index] = -INT_MAX; + } + } + } + } + lastindex = index; + lastpressure = pressure; + lasto2pressure = o2_pressure; + } + + /* if all the samples for a cylinder have pressure data that + * is basically equidistant throw out the sample cylinder pressure + * information but make sure we still have a valid start and end + * pressure + * this happens when DivingLog decides to linearalize the + * pressure between beginning and end and for strange reasons + * decides to put that in the sample data as if it came from + * the dive computer; we don't want that (we'll visualize with + * constant SAC rate instead) + * WARNING WARNING - I have only seen this in single tank dives + * --- maybe I should try to create a multi tank dive and see what + * --- divinglog does there - but the code right now is only tested + * --- for the single tank case */ + for (j = 0; j < MAX_CYLINDERS; j++) { + if (abs(pressure_delta[j]) != INT_MAX) { + cylinder_t *cyl = dive->cylinder + j; + for (i = 0; i < dc->samples; i++) + if (dc->sample[i].sensor == j) + dc->sample[i].cylinderpressure.mbar = 0; + if (!cyl->start.mbar) + cyl->start.mbar = cyl->sample_start.mbar; + if (!cyl->end.mbar) + cyl->end.mbar = cyl->sample_end.mbar; + cyl->sample_start.mbar = 0; + cyl->sample_end.mbar = 0; + } + } +} + +/* FIXME! sensor -> cylinder mapping? */ +static void fixup_start_pressure(struct dive *dive, int idx, pressure_t p) +{ + if (idx >= 0 && idx < MAX_CYLINDERS) { + cylinder_t *cyl = dive->cylinder + idx; + if (p.mbar && !cyl->sample_start.mbar) + cyl->sample_start = p; + } +} + +static void fixup_end_pressure(struct dive *dive, int idx, pressure_t p) +{ + if (idx >= 0 && idx < MAX_CYLINDERS) { + cylinder_t *cyl = dive->cylinder + idx; + if (p.mbar && !cyl->sample_end.mbar) + cyl->sample_end = p; + } +} + +/* + * Check the cylinder pressure sample information and fill in the + * overall cylinder pressures from those. + * + * We ignore surface samples for tank pressure information. + * + * At the beginning of the dive, let the cylinder cool down + * if the diver starts off at the surface. And at the end + * of the dive, there may be surface pressures where the + * diver has already turned off the air supply (especially + * for computers like the Uemis Zurich that end up saving + * quite a bit of samples after the dive has ended). + */ +static void fixup_dive_pressures(struct dive *dive, struct divecomputer *dc) +{ + int i, o2index = -1; + + if (dive->dc.divemode == CCR) + o2index = get_cylinder_idx_by_use(dive, OXYGEN); + + /* Walk the samples from the beginning to find starting pressures.. */ + for (i = 0; i < dc->samples; i++) { + struct sample *sample = dc->sample + i; + + if (sample->depth.mm < SURFACE_THRESHOLD) + continue; + + fixup_start_pressure(dive, sample->sensor, sample->cylinderpressure); + fixup_start_pressure(dive, o2index, sample->o2cylinderpressure); + } + + /* ..and from the end for ending pressures */ + for (i = dc->samples; --i >= 0; ) { + struct sample *sample = dc->sample + i; + + if (sample->depth.mm < SURFACE_THRESHOLD) + continue; + + fixup_end_pressure(dive, sample->sensor, sample->cylinderpressure); + fixup_end_pressure(dive, o2index, sample->o2cylinderpressure); + } + + simplify_dc_pressures(dive, dc); +} + +static void fixup_dive_dc(struct dive *dive, struct divecomputer *dc) +{ + int i; + + /* Add device information to table */ + if (dc->deviceid && (dc->serial || dc->fw_version)) + create_device_node(dc->model, dc->deviceid, dc->serial, dc->fw_version, ""); + + /* Fixup duration and mean depth */ + fixup_dc_duration(dc); + + /* Fix up sample depth data */ + fixup_dc_depths(dive, dc); + + /* Fix up dive temperatures based on dive computer samples */ + fixup_dc_temp(dive, dc); + + /* Fix up cylinder sensor data */ + fixup_dc_cylinder_index(dive, dc); + + /* Fix up cylinder pressures based on DC info */ + fixup_dive_pressures(dive, dc); + + fixup_dc_events(dc); +} + +struct dive *fixup_dive(struct dive *dive) +{ + int i; + struct divecomputer *dc; + + sanitize_cylinder_info(dive); + dive->maxcns = dive->cns; + + /* + * Use the dive's temperatures for minimum and maximum in case + * we do not have temperatures recorded by DC. + */ + + update_min_max_temperatures(dive, dive->watertemp); + + for_each_dc (dive, dc) + fixup_dive_dc(dive, dc); + + fixup_water_salinity(dive); + fixup_surface_pressure(dive); + fixup_meandepth(dive); + fixup_duration(dive); + fixup_watertemp(dive); + fixup_airtemp(dive); + fixup_cylinder_use(dive); // store indices for CCR oxygen and diluent cylinders + for (i = 0; i < MAX_CYLINDERS; i++) { + cylinder_t *cyl = dive->cylinder + i; + add_cylinder_description(&cyl->type); + if (same_rounded_pressure(cyl->sample_start, cyl->start)) + cyl->start.mbar = 0; + if (same_rounded_pressure(cyl->sample_end, cyl->end)) + cyl->end.mbar = 0; + } + update_cylinder_related_info(dive); + for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) { + weightsystem_t *ws = dive->weightsystem + i; + add_weightsystem_description(ws); + } + /* we should always have a uniq ID as that gets assigned during alloc_dive(), + * but we want to make sure... */ + if (!dive->id) + dive->id = dive_getUniqID(dive); + + return dive; +} + +/* Don't pick a zero for MERGE_MIN() */ +#define MERGE_MAX(res, a, b, n) res->n = MAX(a->n, b->n) +#define MERGE_MIN(res, a, b, n) res->n = (a->n) ? (b->n) ? MIN(a->n, b->n) : (a->n) : (b->n) +#define MERGE_TXT(res, a, b, n) res->n = merge_text(a->n, b->n) +#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n + +struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc) +{ + struct sample *p = prepare_sample(dc); + + if (p) { + *p = *sample; + p->time.seconds = time; + finish_sample(dc); + } + return p; +} + +/* + * This is like add_sample(), but if the distance from the last sample + * is excessive, we add two surface samples in between. + * + * This is so that if you merge two non-overlapping dives, we make sure + * that the time in between the dives is at the surface, not some "last + * sample that happened to be at a depth of 1.2m". + */ +static void merge_one_sample(struct sample *sample, int time, struct divecomputer *dc) +{ + int last = dc->samples - 1; + if (last >= 0) { + static struct sample surface; + struct sample *prev = dc->sample + last; + int last_time = prev->time.seconds; + int last_depth = prev->depth.mm; + + /* + * Only do surface events if the samples are more than + * a minute apart, and shallower than 5m + */ + if (time > last_time + 60 && last_depth < 5000) { + add_sample(&surface, last_time + 20, dc); + add_sample(&surface, time - 20, dc); + } + } + add_sample(sample, time, dc); +} + + +/* + * Merge samples. Dive 'a' is "offset" seconds before Dive 'b' + */ +static void merge_samples(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int offset) +{ + int asamples = a->samples; + int bsamples = b->samples; + struct sample *as = a->sample; + struct sample *bs = b->sample; + + /* + * We want a positive sample offset, so that sample + * times are always positive. So if the samples for + * 'b' are before the samples for 'a' (so the offset + * is negative), we switch a and b around, and use + * the reverse offset. + */ + if (offset < 0) { + offset = -offset; + asamples = bsamples; + bsamples = a->samples; + as = bs; + bs = a->sample; + } + + for (;;) { + int at, bt; + struct sample sample; + + if (!res) + return; + + at = asamples ? as->time.seconds : -1; + bt = bsamples ? bs->time.seconds + offset : -1; + + /* No samples? All done! */ + if (at < 0 && bt < 0) + return; + + /* Only samples from a? */ + if (bt < 0) { + add_sample_a: + merge_one_sample(as, at, res); + as++; + asamples--; + continue; + } + + /* Only samples from b? */ + if (at < 0) { + add_sample_b: + merge_one_sample(bs, bt, res); + bs++; + bsamples--; + continue; + } + + if (at < bt) + goto add_sample_a; + if (at > bt) + goto add_sample_b; + + /* same-time sample: add a merged sample. Take the non-zero ones */ + sample = *bs; + if (as->depth.mm) + sample.depth = as->depth; + if (as->temperature.mkelvin) + sample.temperature = as->temperature; + if (as->cylinderpressure.mbar) + sample.cylinderpressure = as->cylinderpressure; + if (as->sensor) + sample.sensor = as->sensor; + if (as->cns) + sample.cns = as->cns; + if (as->setpoint.mbar) + sample.setpoint = as->setpoint; + if (as->ndl.seconds) + sample.ndl = as->ndl; + if (as->stoptime.seconds) + sample.stoptime = as->stoptime; + if (as->stopdepth.mm) + sample.stopdepth = as->stopdepth; + if (as->in_deco) + sample.in_deco = true; + + merge_one_sample(&sample, at, res); + + as++; + bs++; + asamples--; + bsamples--; + } +} + +static char *merge_text(const char *a, const char *b) +{ + char *res; + if (!a && !b) + return NULL; + if (!a || !*a) + return copy_string(b); + if (!b || !*b) + return strdup(a); + if (!strcmp(a, b)) + return copy_string(a); + res = malloc(strlen(a) + strlen(b) + 32); + if (!res) + return (char *)a; + sprintf(res, translate("gettextFromC", "(%s) or (%s)"), a, b); + return res; +} + +#define SORT(a, b, field) \ + if (a->field != b->field) \ + return a->field < b->field ? -1 : 1 + +static int sort_event(struct event *a, struct event *b) +{ + SORT(a, b, time.seconds); + SORT(a, b, type); + SORT(a, b, flags); + SORT(a, b, value); + return strcmp(a->name, b->name); +} + +static void merge_events(struct divecomputer *res, struct divecomputer *src1, struct divecomputer *src2, int offset) +{ + struct event *a, *b; + struct event **p = &res->events; + + /* Always use positive offsets */ + if (offset < 0) { + struct divecomputer *tmp; + + offset = -offset; + tmp = src1; + src1 = src2; + src2 = tmp; + } + + a = src1->events; + b = src2->events; + while (b) { + b->time.seconds += offset; + b = b->next; + } + b = src2->events; + + while (a || b) { + int s; + if (!b) { + *p = a; + break; + } + if (!a) { + *p = b; + break; + } + s = sort_event(a, b); + /* Pick b */ + if (s > 0) { + *p = b; + p = &b->next; + b = b->next; + continue; + } + /* Pick 'a' or neither */ + if (s < 0) { + *p = a; + p = &a->next; + } + a = a->next; + continue; + } +} + +/* Pick whichever has any info (if either). Prefer 'a' */ +static void merge_cylinder_type(cylinder_type_t *src, cylinder_type_t *dst) +{ + if (!dst->size.mliter) + dst->size.mliter = src->size.mliter; + if (!dst->workingpressure.mbar) + dst->workingpressure.mbar = src->workingpressure.mbar; + if (!dst->description) { + dst->description = src->description; + src->description = NULL; + } +} + +static void merge_cylinder_mix(struct gasmix *src, struct gasmix *dst) +{ + if (!dst->o2.permille) + *dst = *src; +} + +static void merge_cylinder_info(cylinder_t *src, cylinder_t *dst) +{ + merge_cylinder_type(&src->type, &dst->type); + merge_cylinder_mix(&src->gasmix, &dst->gasmix); + MERGE_MAX(dst, dst, src, start.mbar); + MERGE_MIN(dst, dst, src, end.mbar); +} + +static void merge_weightsystem_info(weightsystem_t *res, weightsystem_t *a, weightsystem_t *b) +{ + if (!a->weight.grams) + a = b; + *res = *a; +} + +/* get_cylinder_idx_by_use(): Find the index of the first cylinder with a particular CCR use type. + * The index returned corresponds to that of the first cylinder with a cylinder_use that + * equals the appropriate enum value [oxygen, diluent, bailout] given by cylinder_use_type. + * A negative number returned indicates that a match could not be found. + * Call parameters: dive = the dive being processed + * cylinder_use_type = an enum, one of {oxygen, diluent, bailout} */ +extern int get_cylinder_idx_by_use(struct dive *dive, enum cylinderuse cylinder_use_type) +{ + int cylinder_index; + for (cylinder_index = 0; cylinder_index < MAX_CYLINDERS; cylinder_index++) { + if (dive->cylinder[cylinder_index].cylinder_use == cylinder_use_type) + return cylinder_index; // return the index of the cylinder with that cylinder use type + } + return -1; // negative number means cylinder_use_type not found in list of cylinders +} + +int gasmix_distance(const struct gasmix *a, const struct gasmix *b) +{ + int a_o2 = get_o2(a), b_o2 = get_o2(b); + int a_he = get_he(a), b_he = get_he(b); + int delta_o2 = a_o2 - b_o2, delta_he = a_he - b_he; + + delta_he = delta_he * delta_he; + delta_o2 = delta_o2 * delta_o2; + return delta_he + delta_o2; +} + +/* fill_pressures(): Compute partial gas pressures in bar from gasmix and ambient pressures, possibly for OC or CCR, to be + * extended to PSCT. This function does the calculations of gas pressures applicable to a single point on the dive profile. + * The structure "pressures" is used to return calculated gas pressures to the calling software. + * Call parameters: po2 = po2 value applicable to the record in calling function + * amb_pressure = ambient pressure applicable to the record in calling function + * *pressures = structure for communicating o2 sensor values from and gas pressures to the calling function. + * *mix = structure containing cylinder gas mixture information. + * This function called by: calculate_gas_information_new() in profile.c; add_segment() in deco.c. + */ +extern void fill_pressures(struct gas_pressures *pressures, const double amb_pressure, const struct gasmix *mix, double po2, enum dive_comp_type divemode) +{ + if (po2) { // This is probably a CCR dive where pressures->o2 is defined + if (po2 >= amb_pressure) { + pressures->o2 = amb_pressure; + pressures->n2 = pressures->he = 0.0; + } else { + pressures->o2 = po2; + if (get_o2(mix) == 1000) { + pressures->he = pressures->n2 = 0; + } else { + pressures->he = (amb_pressure - pressures->o2) * (double)get_he(mix) / (1000 - get_o2(mix)); + pressures->n2 = amb_pressure - pressures->o2 - pressures->he; + } + } + } else { + if (divemode == PSCR) { /* The steady state approximation should be good enough */ + pressures->o2 = get_o2(mix) / 1000.0 * amb_pressure - (1.0 - get_o2(mix) / 1000.0) * prefs.o2consumption / (prefs.bottomsac * prefs.pscr_ratio / 1000.0); + if (pressures->o2 < 0) // He's dead, Jim. + pressures->o2 = 0; + if (get_o2(mix) != 1000) { + pressures->he = (amb_pressure - pressures->o2) * get_he(mix) / (1000.0 - get_o2(mix)); + pressures->n2 = (amb_pressure - pressures->o2) * (1000 - get_o2(mix) - get_he(mix)) / (1000.0 - get_o2(mix)); + } else { + pressures->he = pressures->n2 = 0; + } + } else { + // Open circuit dives: no gas pressure values available, they need to be calculated + pressures->o2 = get_o2(mix) / 1000.0 * amb_pressure; // These calculations are also used if the CCR calculation above.. + pressures->he = get_he(mix) / 1000.0 * amb_pressure; // ..returned a po2 of zero (i.e. o2 sensor data not resolvable) + pressures->n2 = (1000 - get_o2(mix) - get_he(mix)) / 1000.0 * amb_pressure; + } + } +} + +static int find_cylinder_match(cylinder_t *cyl, cylinder_t array[], unsigned int used) +{ + int i; + int best = -1, score = INT_MAX; + + if (cylinder_nodata(cyl)) + return -1; + for (i = 0; i < MAX_CYLINDERS; i++) { + const cylinder_t *match; + int distance; + + if (used & (1 << i)) + continue; + match = array + i; + distance = gasmix_distance(&cyl->gasmix, &match->gasmix); + if (distance >= score) + continue; + best = i; + score = distance; + } + return best; +} + +/* Force an initial gaschange event to the (old) gas #0 */ +static void add_initial_gaschange(struct dive *dive, struct divecomputer *dc) +{ + struct event *ev = get_next_event(dc->events, "gaschange"); + + if (ev && ev->time.seconds < 30) + return; + + /* Old starting gas mix */ + add_gas_switch_event(dive, dc, 0, 0); +} + +void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, int mapping[]) +{ + int i; + struct event *ev; + + /* Did the first gas get remapped? Add gas switch event */ + if (mapping[0] > 0) + add_initial_gaschange(dive, dc); + + /* Remap the sensor indexes */ + for (i = 0; i < dc->samples; i++) { + struct sample *s = dc->sample + i; + int sensor; + + if (!s->cylinderpressure.mbar) + continue; + sensor = mapping[s->sensor]; + if (sensor >= 0) + s->sensor = sensor; + } + + /* Remap the gas change indexes */ + for (ev = dc->events; ev; ev = ev->next) { + if (!event_is_gaschange(ev)) + continue; + if (ev->gas.index < 0) + continue; + ev->gas.index = mapping[ev->gas.index]; + } +} + +/* + * If the cylinder indexes change (due to merging dives or deleting + * cylinders in the middle), we need to change the indexes in the + * dive computer data for this dive. + * + * Also note that we assume that the initial cylinder is cylinder 0, + * so if that got renamed, we need to create a fake gas change event + */ +static void cylinder_renumber(struct dive *dive, int mapping[]) +{ + struct divecomputer *dc; + for_each_dc (dive, dc) + dc_cylinder_renumber(dive, dc, mapping); +} + +/* + * Merging cylinder information is non-trivial, because the two dive computers + * may have different ideas of what the different cylinder indexing is. + * + * Logic: take all the cylinder information from the preferred dive ('a'), and + * then try to match each of the cylinders in the other dive by the gasmix that + * is the best match and hasn't been used yet. + */ +static void merge_cylinders(struct dive *res, struct dive *a, struct dive *b) +{ + int i, renumber = 0; + int mapping[MAX_CYLINDERS]; + unsigned int used = 0; + + /* Copy the cylinder info raw from 'a' */ + memcpy(res->cylinder, a->cylinder, sizeof(res->cylinder)); + memset(a->cylinder, 0, sizeof(a->cylinder)); + + for (i = 0; i < MAX_CYLINDERS; i++) { + int j; + cylinder_t *cyl = b->cylinder + i; + + j = find_cylinder_match(cyl, res->cylinder, used); + mapping[i] = j; + if (j < 0) + continue; + used |= 1 << j; + merge_cylinder_info(cyl, res->cylinder + j); + + /* If that renumbered the cylinders, fix it up! */ + if (i != j) + renumber = 1; + } + if (renumber) + cylinder_renumber(b, mapping); +} + +static void merge_equipment(struct dive *res, struct dive *a, struct dive *b) +{ + int i; + + merge_cylinders(res, a, b); + for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) + merge_weightsystem_info(res->weightsystem + i, a->weightsystem + i, b->weightsystem + i); +} + +static void merge_airtemps(struct dive *res, struct dive *a, struct dive *b) +{ + un_fixup_airtemp(a); + un_fixup_airtemp(b); + MERGE_NONZERO(res, a, b, airtemp.mkelvin); +} + +/* + * When merging two dives, this picks the trip from one, and removes it + * from the other. + * + * The 'next' dive is not involved in the dive merging, but is the dive + * that will be the next dive after the merged dive. + */ +static void pick_trip(struct dive *res, struct dive *pick) +{ + tripflag_t tripflag = pick->tripflag; + dive_trip_t *trip = pick->divetrip; + + res->tripflag = tripflag; + add_dive_to_trip(res, trip); +} + +/* + * Pick a trip for a dive + */ +static void merge_trip(struct dive *res, struct dive *a, struct dive *b) +{ + dive_trip_t *atrip, *btrip; + + /* + * The larger tripflag is more relevant: we prefer + * take manually assigned trips over auto-generated + * ones. + */ + if (a->tripflag > b->tripflag) + goto pick_a; + + if (a->tripflag < b->tripflag) + goto pick_b; + + /* Otherwise, look at the trip data and pick the "better" one */ + atrip = a->divetrip; + btrip = b->divetrip; + if (!atrip) + goto pick_b; + if (!btrip) + goto pick_a; + if (!atrip->location) + goto pick_b; + if (!btrip->location) + goto pick_a; + if (!atrip->notes) + goto pick_b; + if (!btrip->notes) + goto pick_a; + + /* + * Ok, so both have location and notes. + * Pick the earlier one. + */ + if (a->when < b->when) + goto pick_a; + goto pick_b; + +pick_a: + b = a; +pick_b: + pick_trip(res, b); +} + +#if CURRENTLY_NOT_USED +/* + * Sample 's' is between samples 'a' and 'b'. It is 'offset' seconds before 'b'. + * + * If 's' and 'a' are at the same time, offset is 0, and b is NULL. + */ +static int compare_sample(struct sample *s, struct sample *a, struct sample *b, int offset) +{ + unsigned int depth = a->depth.mm; + int diff; + + if (offset) { + unsigned int interval = b->time.seconds - a->time.seconds; + unsigned int depth_a = a->depth.mm; + unsigned int depth_b = b->depth.mm; + + if (offset > interval) + return -1; + + /* pick the average depth, scaled by the offset from 'b' */ + depth = (depth_a * offset) + (depth_b * (interval - offset)); + depth /= interval; + } + diff = s->depth.mm - depth; + if (diff < 0) + diff = -diff; + /* cut off at one meter difference */ + if (diff > 1000) + diff = 1000; + return diff * diff; +} + +/* + * Calculate a "difference" in samples between the two dives, given + * the offset in seconds between them. Use this to find the best + * match of samples between two different dive computers. + */ +static unsigned long sample_difference(struct divecomputer *a, struct divecomputer *b, int offset) +{ + int asamples = a->samples; + int bsamples = b->samples; + struct sample *as = a->sample; + struct sample *bs = b->sample; + unsigned long error = 0; + int start = -1; + + if (!asamples || !bsamples) + return 0; + + /* + * skip the first sample - this way we know can always look at + * as/bs[-1] to look at the samples around it in the loop. + */ + as++; + bs++; + asamples--; + bsamples--; + + for (;;) { + int at, bt, diff; + + + /* If we run out of samples, punt */ + if (!asamples) + return INT_MAX; + if (!bsamples) + return INT_MAX; + + at = as->time.seconds; + bt = bs->time.seconds + offset; + + /* b hasn't started yet? Ignore it */ + if (bt < 0) { + bs++; + bsamples--; + continue; + } + + if (at < bt) { + diff = compare_sample(as, bs - 1, bs, bt - at); + as++; + asamples--; + } else if (at > bt) { + diff = compare_sample(bs, as - 1, as, at - bt); + bs++; + bsamples--; + } else { + diff = compare_sample(as, bs, NULL, 0); + as++; + bs++; + asamples--; + bsamples--; + } + + /* Invalid comparison point? */ + if (diff < 0) + continue; + + if (start < 0) + start = at; + + error += diff; + + if (at - start > 120) + break; + } + return error; +} + +/* + * Dive 'a' is 'offset' seconds before dive 'b' + * + * This is *not* because the dive computers clocks aren't in sync, + * it is because the dive computers may "start" the dive at different + * points in the dive, so the sample at time X in dive 'a' is the + * same as the sample at time X+offset in dive 'b'. + * + * For example, some dive computers take longer to "wake up" when + * they sense that you are under water (ie Uemis Zurich if it was off + * when the dive started). And other dive computers have different + * depths that they activate at, etc etc. + * + * If we cannot find a shared offset, don't try to merge. + */ +static int find_sample_offset(struct divecomputer *a, struct divecomputer *b) +{ + int offset, best; + unsigned long max; + + /* No samples? Merge at any time (0 offset) */ + if (!a->samples) + return 0; + if (!b->samples) + return 0; + + /* + * Common special-case: merging a dive that came from + * the same dive computer, so the samples are identical. + * Check this first, without wasting time trying to find + * some minimal offset case. + */ + best = 0; + max = sample_difference(a, b, 0); + if (!max) + return 0; + + /* + * Otherwise, look if we can find anything better within + * a thirty second window.. + */ + for (offset = -30; offset <= 30; offset++) { + unsigned long diff; + + diff = sample_difference(a, b, offset); + if (diff > max) + continue; + best = offset; + max = diff; + } + + return best; +} +#endif + +/* + * Are a and b "similar" values, when given a reasonable lower end expected + * difference? + * + * So for example, we'd expect different dive computers to give different + * max depth readings. You might have them on different arms, and they + * have different pressure sensors and possibly different ideas about + * water salinity etc. + * + * So have an expected minimum difference, but also allow a larger relative + * error value. + */ +static int similar(unsigned long a, unsigned long b, unsigned long expected) +{ + if (!a && !b) + return 1; + + if (a && b) { + unsigned long min, max, diff; + + min = a; + max = b; + if (a > b) { + min = b; + max = a; + } + diff = max - min; + + /* Smaller than expected difference? */ + if (diff < expected) + return 1; + /* Error less than 10% or the maximum */ + if (diff * 10 < max) + return 1; + } + return 0; +} + +/* + * Match two dive computer entries against each other, and + * tell if it's the same dive. Return 0 if "don't know", + * positive for "same dive" and negative for "definitely + * not the same dive" + */ +int match_one_dc(struct divecomputer *a, struct divecomputer *b) +{ + /* Not same model? Don't know if matching.. */ + if (!a->model || !b->model) + return 0; + if (strcasecmp(a->model, b->model)) + return 0; + + /* Different device ID's? Don't know */ + if (a->deviceid != b->deviceid) + return 0; + + /* Do we have dive IDs? */ + if (!a->diveid || !b->diveid) + return 0; + + /* + * If they have different dive ID's on the same + * dive computer, that's a definite "same or not" + */ + return a->diveid == b->diveid ? 1 : -1; +} + +/* + * Match every dive computer against each other to see if + * we have a matching dive. + * + * Return values: + * -1 for "is definitely *NOT* the same dive" + * 0 for "don't know" + * 1 for "is definitely the same dive" + */ +static int match_dc_dive(struct divecomputer *a, struct divecomputer *b) +{ + do { + struct divecomputer *tmp = b; + do { + int match = match_one_dc(a, tmp); + if (match) + return match; + tmp = tmp->next; + } while (tmp); + a = a->next; + } while (a); + return 0; +} + +static bool new_without_trip(struct dive *a) +{ + return a->downloaded && !a->divetrip; +} + +/* + * Do we want to automatically try to merge two dives that + * look like they are the same dive? + * + * This happens quite commonly because you download a dive + * that you already had, or perhaps because you maintained + * multiple dive logs and want to load them all together + * (possibly one of them was imported from another dive log + * application entirely). + * + * NOTE! We mainly look at the dive time, but it can differ + * between two dives due to a few issues: + * + * - rounding the dive date to the nearest minute in other dive + * applications + * + * - dive computers with "relative datestamps" (ie the dive + * computer doesn't actually record an absolute date at all, + * but instead at download-time synchronizes its internal + * time with real-time on the downloading computer) + * + * - using multiple dive computers with different real time on + * the same dive + * + * We do not merge dives that look radically different, and if + * the dates are *too* far off the user will have to join two + * dives together manually. But this tries to handle the sane + * cases. + */ +static int likely_same_dive(struct dive *a, struct dive *b) +{ + int match, fuzz = 20 * 60; + + /* don't merge manually added dives with anything */ + if (same_string(a->dc.model, "manually added dive") || + same_string(b->dc.model, "manually added dive")) + return 0; + + /* Don't try to merge dives with different trip information */ + if (a->divetrip != b->divetrip) { + /* + * Exception: if the dive is downloaded without any + * explicit trip information, we do want to merge it + * with existing old dives even if they have trips. + */ + if (!new_without_trip(a) && !new_without_trip(b)) + return 0; + } + + /* + * Do some basic sanity testing of the values we + * have filled in during 'fixup_dive()' + */ + if (!similar(a->maxdepth.mm, b->maxdepth.mm, 1000) || + (a->meandepth.mm && b->meandepth.mm && !similar(a->meandepth.mm, b->meandepth.mm, 1000)) || + !similar(a->duration.seconds, b->duration.seconds, 5 * 60)) + return 0; + + /* See if we can get an exact match on the dive computer */ + match = match_dc_dive(&a->dc, &b->dc); + if (match) + return match > 0; + + /* + * Allow a time difference due to dive computer time + * setting etc. Check if they overlap. + */ + fuzz = MAX(a->duration.seconds, b->duration.seconds) / 2; + if (fuzz < 60) + fuzz = 60; + + return ((a->when <= b->when + fuzz) && (a->when >= b->when - fuzz)); +} + +/* + * This could do a lot more merging. Right now it really only + * merges almost exact duplicates - something that happens easily + * with overlapping dive downloads. + */ +struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded) +{ + if (likely_same_dive(a, b)) + return merge_dives(a, b, 0, prefer_downloaded); + return NULL; +} + +void free_events(struct event *ev) +{ + while (ev) { + struct event *next = ev->next; + free(ev); + ev = next; + } +} + +static void free_dc(struct divecomputer *dc) +{ + free(dc->sample); + free((void *)dc->model); + free_events(dc->events); + free(dc); +} + +static void free_pic(struct picture *picture) +{ + if (picture) { + free(picture->filename); + free(picture); + } +} + +static int same_sample(struct sample *a, struct sample *b) +{ + if (a->time.seconds != b->time.seconds) + return 0; + if (a->depth.mm != b->depth.mm) + return 0; + if (a->temperature.mkelvin != b->temperature.mkelvin) + return 0; + if (a->cylinderpressure.mbar != b->cylinderpressure.mbar) + return 0; + return a->sensor == b->sensor; +} + +static int same_dc(struct divecomputer *a, struct divecomputer *b) +{ + int i; + struct event *eva, *evb; + + i = match_one_dc(a, b); + if (i) + return i > 0; + + if (a->when && b->when && a->when != b->when) + return 0; + if (a->samples != b->samples) + return 0; + for (i = 0; i < a->samples; i++) + if (!same_sample(a->sample + i, b->sample + i)) + return 0; + eva = a->events; + evb = b->events; + while (eva && evb) { + if (!same_event(eva, evb)) + return 0; + eva = eva->next; + evb = evb->next; + } + return eva == evb; +} + +static int might_be_same_device(struct divecomputer *a, struct divecomputer *b) +{ + /* No dive computer model? That matches anything */ + if (!a->model || !b->model) + return 1; + + /* Otherwise at least the model names have to match */ + if (strcasecmp(a->model, b->model)) + return 0; + + /* No device ID? Match */ + if (!a->deviceid || !b->deviceid) + return 1; + + return a->deviceid == b->deviceid; +} + +static void remove_redundant_dc(struct divecomputer *dc, int prefer_downloaded) +{ + do { + struct divecomputer **p = &dc->next; + + /* Check this dc against all the following ones.. */ + while (*p) { + struct divecomputer *check = *p; + if (same_dc(dc, check) || (prefer_downloaded && might_be_same_device(dc, check))) { + *p = check->next; + check->next = NULL; + free_dc(check); + continue; + } + p = &check->next; + } + + /* .. and then continue down the chain, but we */ + prefer_downloaded = 0; + dc = dc->next; + } while (dc); +} + +static void clear_dc(struct divecomputer *dc) +{ + memset(dc, 0, sizeof(*dc)); +} + +static struct divecomputer *find_matching_computer(struct divecomputer *match, struct divecomputer *list) +{ + struct divecomputer *p; + + while ((p = list) != NULL) { + list = list->next; + + if (might_be_same_device(match, p)) + break; + } + return p; +} + + +static void copy_dive_computer(struct divecomputer *res, struct divecomputer *a) +{ + *res = *a; + res->model = copy_string(a->model); + res->samples = res->alloc_samples = 0; + res->sample = NULL; + res->events = NULL; + res->next = NULL; +} + +/* + * Join dive computers with a specific time offset between + * them. + * + * Use the dive computer ID's (or names, if ID's are missing) + * to match them up. If we find a matching dive computer, we + * merge them. If not, we just take the data from 'a'. + */ +static void interleave_dive_computers(struct divecomputer *res, + struct divecomputer *a, struct divecomputer *b, int offset) +{ + do { + struct divecomputer *match; + + copy_dive_computer(res, a); + + match = find_matching_computer(a, b); + if (match) { + merge_events(res, a, match, offset); + merge_samples(res, a, match, offset); + /* Use the diveid of the later dive! */ + if (offset > 0) + res->diveid = match->diveid; + } else { + res->sample = a->sample; + res->samples = a->samples; + res->events = a->events; + a->sample = NULL; + a->samples = 0; + a->events = NULL; + } + a = a->next; + if (!a) + break; + res->next = calloc(1, sizeof(struct divecomputer)); + res = res->next; + } while (res); +} + + +/* + * Join dive computer information. + * + * If we have old-style dive computer information (no model + * name etc), we will prefer a new-style one and just throw + * away the old. We're assuming it's a re-download. + * + * Otherwise, we'll just try to keep all the information, + * unless the user has specified that they prefer the + * downloaded computer, in which case we'll aggressively + * try to throw out old information that *might* be from + * that one. + */ +static void join_dive_computers(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int prefer_downloaded) +{ + struct divecomputer *tmp; + + if (a->model && !b->model) { + *res = *a; + clear_dc(a); + return; + } + if (b->model && !a->model) { + *res = *b; + clear_dc(b); + return; + } + + *res = *a; + clear_dc(a); + tmp = res; + while (tmp->next) + tmp = tmp->next; + + tmp->next = calloc(1, sizeof(*tmp)); + *tmp->next = *b; + clear_dc(b); + + remove_redundant_dc(res, prefer_downloaded); +} + +static bool tag_seen_before(struct tag_entry *start, struct tag_entry *before) +{ + while (start && start != before) { + if (same_string(start->tag->name, before->tag->name)) + return true; + start = start->next; + } + return false; +} + +/* remove duplicates and empty nodes */ +void taglist_cleanup(struct tag_entry **tag_list) +{ + struct tag_entry **tl = tag_list; + while (*tl) { + /* skip tags that are empty or that we have seen before */ + if (same_string((*tl)->tag->name, "") || tag_seen_before(*tag_list, *tl)) { + *tl = (*tl)->next; + continue; + } + tl = &(*tl)->next; + } +} + +int taglist_get_tagstring(struct tag_entry *tag_list, char *buffer, int len) +{ + int i = 0; + struct tag_entry *tmp; + tmp = tag_list; + memset(buffer, 0, len); + while (tmp != NULL) { + int newlength = strlen(tmp->tag->name); + if (i > 0) + newlength += 2; + if ((i + newlength) < len) { + if (i > 0) { + strcpy(buffer + i, ", "); + strcpy(buffer + i + 2, tmp->tag->name); + } else { + strcpy(buffer, tmp->tag->name); + } + } else { + return i; + } + i += newlength; + tmp = tmp->next; + } + return i; +} + +static inline void taglist_free_divetag(struct divetag *tag) +{ + if (tag->name != NULL) + free(tag->name); + if (tag->source != NULL) + free(tag->source); + free(tag); +} + +/* Add a tag to the tag_list, keep the list sorted */ +static struct divetag *taglist_add_divetag(struct tag_entry **tag_list, struct divetag *tag) +{ + struct tag_entry *next, *entry; + + while ((next = *tag_list) != NULL) { + int cmp = strcmp(next->tag->name, tag->name); + + /* Already have it? */ + if (!cmp) + return next->tag; + /* Is the entry larger? If so, insert here */ + if (cmp > 0) + break; + /* Continue traversing the list */ + tag_list = &next->next; + } + + /* Insert in front of it */ + entry = malloc(sizeof(struct tag_entry)); + entry->next = next; + entry->tag = tag; + *tag_list = entry; + return tag; +} + +struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag) +{ + size_t i = 0; + int is_default_tag = 0; + struct divetag *ret_tag, *new_tag; + const char *translation; + new_tag = malloc(sizeof(struct divetag)); + + for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++) { + if (strcmp(default_tags[i], tag) == 0) { + is_default_tag = 1; + break; + } + } + /* Only translate default tags */ + if (is_default_tag) { + translation = translate("gettextFromC", tag); + new_tag->name = malloc(strlen(translation) + 1); + memcpy(new_tag->name, translation, strlen(translation) + 1); + new_tag->source = malloc(strlen(tag) + 1); + memcpy(new_tag->source, tag, strlen(tag) + 1); + } else { + new_tag->source = NULL; + new_tag->name = malloc(strlen(tag) + 1); + memcpy(new_tag->name, tag, strlen(tag) + 1); + } + /* Try to insert new_tag into g_tag_list if we are not operating on it */ + if (tag_list != &g_tag_list) { + ret_tag = taglist_add_divetag(&g_tag_list, new_tag); + /* g_tag_list already contains new_tag, free the duplicate */ + if (ret_tag != new_tag) + taglist_free_divetag(new_tag); + ret_tag = taglist_add_divetag(tag_list, ret_tag); + } else { + ret_tag = taglist_add_divetag(tag_list, new_tag); + if (ret_tag != new_tag) + taglist_free_divetag(new_tag); + } + return ret_tag; +} + +void taglist_free(struct tag_entry *entry) +{ + STRUCTURED_LIST_FREE(struct tag_entry, entry, free) +} + +/* Merge src1 and src2, write to *dst */ +static void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2) +{ + struct tag_entry *entry; + + for (entry = src1; entry; entry = entry->next) + taglist_add_divetag(dst, entry->tag); + for (entry = src2; entry; entry = entry->next) + taglist_add_divetag(dst, entry->tag); +} + +void taglist_init_global() +{ + size_t i; + + for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++) + taglist_add_tag(&g_tag_list, default_tags[i]); +} + +bool taglist_contains(struct tag_entry *tag_list, const char *tag) +{ + while (tag_list) { + if (same_string(tag_list->tag->name, tag)) + return true; + tag_list = tag_list->next; + } + return false; +} + +// check if all tags in subtl are included in supertl (so subtl is a subset of supertl) +static bool taglist_contains_all(struct tag_entry *subtl, struct tag_entry *supertl) +{ + while (subtl) { + if (!taglist_contains(supertl, subtl->tag->name)) + return false; + subtl = subtl->next; + } + return true; +} + +struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list) +{ + struct tag_entry *added_list = NULL; + while (new_list) { + if (!taglist_contains(original_list, new_list->tag->name)) + taglist_add_tag(&added_list, new_list->tag->name); + new_list = new_list->next; + } + return added_list; +} + +void dump_taglist(const char *intro, struct tag_entry *tl) +{ + char *comma = ""; + fprintf(stderr, "%s", intro); + while(tl) { + fprintf(stderr, "%s %s", comma, tl->tag->name); + comma = ","; + tl = tl->next; + } + fprintf(stderr, "\n"); +} + +// if tl1 is both a subset and superset of tl2 they must be the same +bool taglist_equal(struct tag_entry *tl1, struct tag_entry *tl2) +{ + return taglist_contains_all(tl1, tl2) && taglist_contains_all(tl2, tl1); +} + +// count the dives where the tag list contains the given tag +int count_dives_with_tag(const char *tag) +{ + int i, counter = 0; + struct dive *d; + + for_each_dive (i, d) { + if (same_string(tag, "")) { + // count dives with no tags + if (d->tag_list == NULL) + counter++; + } else if (taglist_contains(d->tag_list, tag)) { + counter++; + } + } + return counter; +} + +extern bool string_sequence_contains(const char *string_sequence, const char *text); + +// count the dives where the person is included in the comma separated string sequences of buddies or divemasters +int count_dives_with_person(const char *person) +{ + int i, counter = 0; + struct dive *d; + + for_each_dive (i, d) { + if (same_string(person, "")) { + // solo dive + if (same_string(d->buddy, "") && same_string(d->divemaster, "")) + counter++; + } else if (string_sequence_contains(d->buddy, person) || string_sequence_contains(d->divemaster, person)) { + counter++; + } + } + return counter; +} + +// count the dives with exactly the location +int count_dives_with_location(const char *location) +{ + int i, counter = 0; + struct dive *d; + + for_each_dive (i, d) { + if (same_string(get_dive_location(d), location)) + counter++; + } + return counter; +} + +// count the dives with exactly the suit +int count_dives_with_suit(const char *suit) +{ + int i, counter = 0; + struct dive *d; + + for_each_dive (i, d) { + if (same_string(d->suit, suit)) + counter++; + } + return counter; +} + +/* + * Merging two dives can be subtle, because there's two different ways + * of merging: + * + * (a) two distinctly _different_ dives that have the same dive computer + * are merged into one longer dive, because the user asked for it + * in the divelist. + * + * Because this case is with the same dive computer, we *know* the + * two must have a different start time, and "offset" is the relative + * time difference between the two. + * + * (a) two different dive computers that we might want to merge into + * one single dive with multiple dive computers. + * + * This is the "try_to_merge()" case, which will have offset == 0, + * even if the dive times might be different. + */ +struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer_downloaded) +{ + struct dive *res = alloc_dive(); + struct dive *dl = NULL; + + if (offset) { + /* + * If "likely_same_dive()" returns true, that means that + * it is *not* the same dive computer, and we do not want + * to try to turn it into a single longer dive. So we'd + * join them as two separate dive computers at zero offset. + */ + if (likely_same_dive(a, b)) + offset = 0; + } else { + /* Aim for newly downloaded dives to be 'b' (keep old dive data first) */ + if (a->downloaded && !b->downloaded) { + struct dive *tmp = a; + a = b; + b = tmp; + } + if (prefer_downloaded && b->downloaded) + dl = b; + } + + res->when = dl ? dl->when : a->when; + res->selected = a->selected || b->selected; + merge_trip(res, a, b); + MERGE_TXT(res, a, b, notes); + MERGE_TXT(res, a, b, buddy); + MERGE_TXT(res, a, b, divemaster); + MERGE_MAX(res, a, b, rating); + MERGE_TXT(res, a, b, suit); + MERGE_MAX(res, a, b, number); + MERGE_NONZERO(res, a, b, cns); + MERGE_NONZERO(res, a, b, visibility); + MERGE_NONZERO(res, a, b, picture_list); + taglist_merge(&res->tag_list, a->tag_list, b->tag_list); + merge_equipment(res, a, b); + merge_airtemps(res, a, b); + if (dl) { + /* If we prefer downloaded, do those first, and get rid of "might be same" computers */ + join_dive_computers(&res->dc, &dl->dc, &a->dc, 1); + } else if (offset && might_be_same_device(&a->dc, &b->dc)) + interleave_dive_computers(&res->dc, &a->dc, &b->dc, offset); + else + join_dive_computers(&res->dc, &a->dc, &b->dc, 0); + res->dive_site_uuid = a->dive_site_uuid ?: b->dive_site_uuid; + fixup_dive(res); + return res; +} + +// copy_dive(), but retaining the new ID for the copied dive +static struct dive *create_new_copy(struct dive *from) +{ + struct dive *to = alloc_dive(); + int id; + + // alloc_dive() gave us a new ID, we just need to + // make sure it's not overwritten. + id = to->id; + copy_dive(from, to); + to->id = id; + return to; +} + +static void force_fixup_dive(struct dive *d) +{ + struct divecomputer *dc = &d->dc; + int old_temp = dc->watertemp.mkelvin; + int old_mintemp = d->mintemp.mkelvin; + int old_maxtemp = d->maxtemp.mkelvin; + duration_t old_duration = d->duration; + + d->maxdepth.mm = 0; + dc->maxdepth.mm = 0; + d->watertemp.mkelvin = 0; + dc->watertemp.mkelvin = 0; + d->duration.seconds = 0; + d->maxtemp.mkelvin = 0; + d->mintemp.mkelvin = 0; + + fixup_dive(d); + + if (!d->watertemp.mkelvin) + d->watertemp.mkelvin = old_temp; + + if (!dc->watertemp.mkelvin) + dc->watertemp.mkelvin = old_temp; + + if (!d->maxtemp.mkelvin) + d->maxtemp.mkelvin = old_maxtemp; + + if (!d->mintemp.mkelvin) + d->mintemp.mkelvin = old_mintemp; + + if (!d->duration.seconds) + d->duration = old_duration; + +} + +/* + * Split a dive that has a surface interval from samples 'a' to 'b' + * into two dives. + */ +static int split_dive_at(struct dive *dive, int a, int b) +{ + int i, nr; + uint32_t t; + struct dive *d1, *d2; + struct divecomputer *dc1, *dc2; + struct event *event, **evp; + + /* if we can't find the dive in the dive list, don't bother */ + if ((nr = get_divenr(dive)) < 0) + return 0; + + /* We're not trying to be efficient here.. */ + d1 = create_new_copy(dive); + d2 = create_new_copy(dive); + + /* now unselect the first first segment so we don't keep all + * dives selected by mistake. But do keep the second one selected + * so the algorithm keeps splitting the dive further */ + d1->selected = false; + + dc1 = &d1->dc; + dc2 = &d2->dc; + /* + * Cut off the samples of d1 at the beginning + * of the interval. + */ + dc1->samples = a; + + /* And get rid of the 'b' first samples of d2 */ + dc2->samples -= b; + memmove(dc2->sample, dc2->sample+b, dc2->samples * sizeof(struct sample)); + + /* + * This is where we cut off events from d1, + * and shift everything in d2 + */ + t = dc2->sample[0].time.seconds; + d2->when += t; + for (i = 0; i < dc2->samples; i++) + dc2->sample[i].time.seconds -= t; + + /* Remove the events past 't' from d1 */ + evp = &dc1->events; + while ((event = *evp) != NULL && event->time.seconds < t) + evp = &event->next; + *evp = NULL; + while (event) { + struct event *next = event->next; + free(event); + event = next; + } + + /* Remove the events before 't' from d2, and shift the rest */ + evp = &dc2->events; + while ((event = *evp) != NULL) { + if (event->time.seconds < t) { + *evp = event->next; + free(event); + } else { + event->time.seconds -= t; + } + } + + force_fixup_dive(d1); + force_fixup_dive(d2); + + if (dive->divetrip) { + d1->divetrip = d2->divetrip = 0; + add_dive_to_trip(d1, dive->divetrip); + add_dive_to_trip(d2, dive->divetrip); + } + + delete_single_dive(nr); + add_single_dive(nr, d1); + + /* + * Was the dive numbered? If it was the last dive, then we'll + * increment the dive number for the tail part that we split off. + * Otherwise the tail is unnumbered. + */ + if (d2->number) { + if (dive_table.nr == nr + 1) + d2->number++; + else + d2->number = 0; + } + add_single_dive(nr + 1, d2); + + mark_divelist_changed(true); + + return 1; +} + +/* in freedive mode we split for as little as 10 seconds on the surface, + * otherwise we use a minute */ +static bool should_split(struct divecomputer *dc, int t1, int t2) +{ + int threshold = dc->divemode == FREEDIVE ? 10 : 60; + + return t2 - t1 >= threshold; +} + +/* + * Try to split a dive into multiple dives at a surface interval point. + * + * NOTE! We will not split dives with multiple dive computers, and + * only split when there is at least one surface event that has + * non-surface events on both sides. + * + * In other words, this is a (simplified) reversal of the dive merging. + */ +int split_dive(struct dive *dive) +{ + int i; + int at_surface, surface_start; + struct divecomputer *dc; + + if (!dive || (dc = &dive->dc)->next) + return 0; + + surface_start = 0; + at_surface = 1; + for (i = 1; i < dc->samples; i++) { + struct sample *sample = dc->sample+i; + int surface_sample = sample->depth.mm < SURFACE_THRESHOLD; + + /* + * We care about the transition from and to depth 0, + * not about the depth staying similar. + */ + if (at_surface == surface_sample) + continue; + at_surface = surface_sample; + + // Did it become surface after having been non-surface? We found the start + if (at_surface) { + surface_start = i; + continue; + } + + // Going down again? We want at least a minute from + // the surface start. + if (!surface_start) + continue; + if (!should_split(dc, dc->sample[surface_start].time.seconds, sample[i - 1].time.seconds)) + continue; + + return split_dive_at(dive, surface_start, i-1); + } + return 0; +} + +/* + * "dc_maxtime()" is how much total time this dive computer + * has for this dive. Note that it can differ from "duration" + * if there are surface events in the middle. + * + * Still, we do ignore all but the last surface samples from the + * end, because some divecomputers just generate lots of them. + */ +static inline int dc_totaltime(const struct divecomputer *dc) +{ + int time = dc->duration.seconds; + int nr = dc->samples; + + while (nr--) { + struct sample *s = dc->sample + nr; + time = s->time.seconds; + if (s->depth.mm >= SURFACE_THRESHOLD) + break; + } + return time; +} + +/* + * The end of a dive is actually not trivial, because "duration" + * is not the duration until the end, but the time we spend under + * water, which can be very different if there are surface events + * during the dive. + * + * So walk the dive computers, looking for the longest actual + * time in the samples (and just default to the dive duration if + * there are no samples). + */ +static inline int dive_totaltime(const struct dive *dive) +{ + int time = dive->duration.seconds; + const struct divecomputer *dc; + + for_each_dc(dive, dc) { + int dc_time = dc_totaltime(dc); + if (dc_time > time) + time = dc_time; + } + return time; +} + +timestamp_t dive_endtime(const struct dive *dive) +{ + return dive->when + dive_totaltime(dive); +} + +struct dive *find_dive_including(timestamp_t when) +{ + int i; + struct dive *dive; + + /* binary search, anyone? Too lazy for now; + * also we always use the duration from the first divecomputer + * could this ever be a problem? */ + for_each_dive (i, dive) { + if (dive->when <= when && when <= dive_endtime(dive)) + return dive; + } + return NULL; +} + +bool time_during_dive_with_offset(struct dive *dive, timestamp_t when, timestamp_t offset) +{ + timestamp_t start = dive->when; + timestamp_t end = dive_endtime(dive); + return start - offset <= when && when <= end + offset; +} + +bool dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset) +{ + timestamp_t start = dive->when; + timestamp_t end = dive_endtime(dive); + return when - offset <= start && end <= when + offset; +} + +/* find the n-th dive that is part of a group of dives within the offset around 'when'. + * How is that for a vague definition of what this function should do... */ +struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset) +{ + int i, j = 0; + struct dive *dive; + + for_each_dive (i, dive) { + if (dive_within_time_range(dive, when, offset)) + if (++j == n) + return dive; + } + return NULL; +} + +void shift_times(const timestamp_t amount) +{ + int i; + struct dive *dive; + + for_each_dive (i, dive) { + if (!dive->selected) + continue; + dive->when += amount; + invalidate_dive_cache(dive); + } +} + +timestamp_t get_times() +{ + int i; + struct dive *dive; + + for_each_dive (i, dive) { + if (dive->selected) + break; + } + return dive->when; +} + +void set_save_userid_local(short value) +{ + prefs.save_userid_local = value; +} + +void set_userid(char *rUserId) +{ + if (prefs.userid) + free(prefs.userid); + prefs.userid = strdup(rUserId); + if (strlen(prefs.userid) > 30) + prefs.userid[30]='\0'; +} + +/* this sets a usually unused copy of the preferences with the units + * that were active the last time the dive list was saved to git storage + * (this isn't used in XML files); storing the unit preferences in the + * data file is usually pointless (that's a setting of the software, + * not a property of the data), but it's a great hint of what the user + * might expect to see when creating a backend service that visualizes + * the dive list without Subsurface running - so this is basically a + * functionality for the core library that Subsurface itself doesn't + * use but that another consumer of the library (like an HTML exporter) + * will need */ +void set_informational_units(char *units) +{ + if (strstr(units, "METRIC")) { + informational_prefs.unit_system = METRIC; + } else if (strstr(units, "IMPERIAL")) { + informational_prefs.unit_system = IMPERIAL; + } else if (strstr(units, "PERSONALIZE")) { + informational_prefs.unit_system = PERSONALIZE; + if (strstr(units, "METERS")) + informational_prefs.units.length = METERS; + if (strstr(units, "FEET")) + informational_prefs.units.length = FEET; + if (strstr(units, "LITER")) + informational_prefs.units.volume = LITER; + if (strstr(units, "CUFT")) + informational_prefs.units.volume = CUFT; + if (strstr(units, "BAR")) + informational_prefs.units.pressure = BAR; + if (strstr(units, "PSI")) + informational_prefs.units.pressure = PSI; + if (strstr(units, "PASCAL")) + informational_prefs.units.pressure = PASCAL; + if (strstr(units, "CELSIUS")) + informational_prefs.units.temperature = CELSIUS; + if (strstr(units, "FAHRENHEIT")) + informational_prefs.units.temperature = FAHRENHEIT; + if (strstr(units, "KG")) + informational_prefs.units.weight = KG; + if (strstr(units, "LBS")) + informational_prefs.units.weight = LBS; + if (strstr(units, "SECONDS")) + informational_prefs.units.vertical_speed_time = SECONDS; + if (strstr(units, "MINUTES")) + informational_prefs.units.vertical_speed_time = MINUTES; + } +} + +void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_depth) +{ + int integral = 0; + int last_time = 0; + int last_depth = 0; + struct divedatapoint *dp = dive->dp; + + *max_depth = 0; + + while (dp) { + if (dp->time) { + /* Ignore gas indication samples */ + integral += (dp->depth + last_depth) * (dp->time - last_time) / 2; + last_time = dp->time; + last_depth = dp->depth; + if (dp->depth > *max_depth) + *max_depth = dp->depth; + } + dp = dp->next; + } + if (last_time) + *avg_depth = integral / last_time; + else + *avg_depth = *max_depth = 0; +} + +struct picture *alloc_picture() +{ + struct picture *pic = malloc(sizeof(struct picture)); + if (!pic) + exit(1); + memset(pic, 0, sizeof(struct picture)); + return pic; +} + +static bool new_picture_for_dive(struct dive *d, char *filename) +{ + FOR_EACH_PICTURE (d) { + if (same_string(picture->filename, filename)) + return false; + } + return true; +} + +// only add pictures that have timestamps between 30 minutes before the dive and +// 30 minutes after the dive ends +#define D30MIN (30 * 60) +bool dive_check_picture_time(struct dive *d, int shift_time, timestamp_t timestamp) +{ + offset_t offset; + if (timestamp) { + offset.seconds = timestamp - d->when + shift_time; + if (offset.seconds > -D30MIN && offset.seconds < dive_totaltime(d) + D30MIN) { + // this picture belongs to this dive + return true; + } + } + return false; +} + +bool picture_check_valid(char *filename, int shift_time) +{ + int i; + struct dive *dive; + + timestamp_t timestamp = picture_get_timestamp(filename); + for_each_dive (i, dive) + if (dive->selected && dive_check_picture_time(dive, shift_time, timestamp)) + return true; + return false; +} + +void dive_create_picture(struct dive *dive, char *filename, int shift_time, bool match_all) +{ + timestamp_t timestamp = picture_get_timestamp(filename); + if (!new_picture_for_dive(dive, filename)) + return; + if (!match_all && !dive_check_picture_time(dive, shift_time, timestamp)) + return; + + struct picture *picture = alloc_picture(); + picture->filename = strdup(filename); + picture->offset.seconds = timestamp - dive->when + shift_time; + picture_load_exif_data(picture); + + dive_add_picture(dive, picture); + dive_set_geodata_from_picture(dive, picture); + invalidate_dive_cache(dive); +} + +void dive_add_picture(struct dive *dive, struct picture *newpic) +{ + struct picture **pic_ptr = &dive->picture_list; + /* let's keep the list sorted by time */ + while (*pic_ptr && (*pic_ptr)->offset.seconds <= newpic->offset.seconds) + pic_ptr = &(*pic_ptr)->next; + newpic->next = *pic_ptr; + *pic_ptr = newpic; + cache_picture(newpic); + return; +} + +unsigned int dive_get_picture_count(struct dive *dive) +{ + unsigned int i = 0; + FOR_EACH_PICTURE (dive) + i++; + return i; +} + +void dive_set_geodata_from_picture(struct dive *dive, struct picture *picture) +{ + struct dive_site *ds = get_dive_site_by_uuid(dive->dive_site_uuid); + if (!dive_site_has_gps_location(ds) && (picture->latitude.udeg || picture->longitude.udeg)) { + if (ds) { + ds->latitude = picture->latitude; + ds->longitude = picture->longitude; + } else { + dive->dive_site_uuid = create_dive_site_with_gps("", picture->latitude, picture->longitude, dive->when); + invalidate_dive_cache(dive); + } + } +} + +void picture_free(struct picture *picture) +{ + if (!picture) + return; + free(picture->filename); + free(picture->hash); + free(picture); +} + +// When handling pictures in different threads, we need to copy them so we don't +// run into problems when the main thread frees the picture. + +struct picture *clone_picture(struct picture *src) +{ + struct picture *dst; + + dst = alloc_picture(); + copy_pl(src, dst); + return dst; +} + +void dive_remove_picture(char *filename) +{ + struct picture **picture = ¤t_dive->picture_list; + while (picture && !same_string((*picture)->filename, filename)) + picture = &(*picture)->next; + if (picture) { + struct picture *temp = (*picture)->next; + picture_free(*picture); + *picture = temp; + invalidate_dive_cache(current_dive); + } +} + +/* this always acts on the current divecomputer of the current dive */ +void make_first_dc() +{ + struct divecomputer *dc = ¤t_dive->dc; + struct divecomputer *newdc = malloc(sizeof(*newdc)); + struct divecomputer *cur_dc = current_dc; /* needs to be in a local variable so the macro isn't re-executed */ + + /* skip the current DC in the linked list */ + while (dc && dc->next != cur_dc) + dc = dc->next; + if (!dc) { + free(newdc); + fprintf(stderr, "data inconsistent: can't find the current DC"); + return; + } + dc->next = cur_dc->next; + *newdc = current_dive->dc; + current_dive->dc = *cur_dc; + current_dive->dc.next = newdc; + free(cur_dc); + invalidate_dive_cache(current_dive); +} + +/* always acts on the current dive */ +unsigned int count_divecomputers(void) +{ + int ret = 1; + struct divecomputer *dc = current_dive->dc.next; + while (dc) { + ret++; + dc = dc->next; + } + return ret; +} + +/* always acts on the current dive */ +void delete_current_divecomputer(void) +{ + struct divecomputer *dc = current_dc; + + if (dc == ¤t_dive->dc) { + /* remove the first one, so copy the second one in place of the first and free the second one + * be careful about freeing the no longer needed structures - since we copy things around we can't use free_dc()*/ + struct divecomputer *fdc = dc->next; + free(dc->sample); + free((void *)dc->model); + free_events(dc->events); + memcpy(dc, fdc, sizeof(struct divecomputer)); + free(fdc); + } else { + struct divecomputer *pdc = ¤t_dive->dc; + while (pdc->next != dc && pdc->next) + pdc = pdc->next; + if (pdc->next == dc) { + pdc->next = dc->next; + free_dc(dc); + } + } + if (dc_number == count_divecomputers()) + dc_number--; + invalidate_dive_cache(current_dive); +} + +/* helper function to make it easier to work with our structures + * we don't interpolate here, just use the value from the last sample up to that time */ +int get_depth_at_time(struct divecomputer *dc, unsigned int time) +{ + int depth = 0; + if (dc && dc->sample) + for (int i = 0; i < dc->samples; i++) { + if (dc->sample[i].time.seconds > time) + break; + depth = dc->sample[i].depth.mm; + } + return depth; +} diff --git a/core/dive.h b/core/dive.h new file mode 100644 index 000000000..ae24b7409 --- /dev/null +++ b/core/dive.h @@ -0,0 +1,913 @@ +#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 double gas_compressibility_factor(struct gasmix *gas, double bar); + + +static inline int get_o2(const struct gasmix *mix) +{ + return mix->o2.permille ?: O2_IN_AIR; +} + +static inline int get_he(const struct gasmix *mix) +{ + return mix->he.permille; +} + +struct gas_pressures { + double o2, n2, he; +}; + +extern void fill_pressures(struct gas_pressures *pressures, const double amb_pressure, const struct gasmix *mix, double po2, enum dive_comp_type dctype); + +extern void sanitize_gasmix(struct gasmix *mix); +extern int gasmix_distance(const struct gasmix *a, const struct gasmix *b); +extern struct gasmix *get_gasmix_from_event(struct event *ev); + +static inline bool gasmix_is_air(const struct gasmix *gasmix) +{ + int o2 = gasmix->o2.permille; + int he = gasmix->he.permille; + return (he == 0) && (o2 == 0 || ((o2 >= O2_IN_AIR - 1) && (o2 <= O2_IN_AIR + 1))); +} + +/* Linear interpolation between 'a' and 'b', when we are 'part'way into the 'whole' distance from a to b */ +static inline int interpolate(int a, int b, int part, int whole) +{ + /* It is doubtful that we actually need floating point for this, but whatever */ + double x = (double)a * (whole - part) + (double)b * part; + return rint(x / whole); +} + +void get_gas_string(const struct gasmix *gasmix, char *text, int len); +const char *gasname(const struct gasmix *gasmix); + +struct sample // BASE TYPE BYTES UNITS RANGE DESCRIPTION +{ // --------- ----- ----- ----- ----------- + duration_t time; // uint32_t 4 seconds (0-68 yrs) elapsed dive time up to this sample + duration_t stoptime; // uint32_t 4 seconds (0-18 h) time duration of next deco stop + duration_t ndl; // uint32_t 4 seconds (0-18 h) time duration before no-deco limit + duration_t tts; // uint32_t 4 seconds (0-18 h) time duration to reach the surface + duration_t rbt; // uint32_t 4 seconds (0-18 h) remaining bottom time + depth_t depth; // int32_t 4 mm (0-2000 km) dive depth of this sample + depth_t stopdepth; // int32_t 4 mm (0-2000 km) depth of next deco stop + temperature_t temperature; // int32_t 4 mdegrK (0-2 MdegK) ambient temperature + pressure_t cylinderpressure; // int32_t 4 mbar (0-2 Mbar) main cylinder pressure + pressure_t o2cylinderpressure; // int32_t 4 mbar (0-2 Mbar) CCR o2 cylinder pressure (rebreather) + o2pressure_t setpoint; // uint16_t 2 mbar (0-65 bar) O2 partial pressure (will be setpoint) + o2pressure_t o2sensor[3]; // uint16_t 6 mbar (0-65 bar) Up to 3 PO2 sensor values (rebreather) + bearing_t bearing; // int16_t 2 degrees (-32k to 32k deg) compass bearing + uint8_t sensor; // uint8_t 1 sensorID (0-255) ID of cylinder pressure sensor + uint8_t cns; // uint8_t 1 % (0-255 %) cns% accumulated + uint8_t heartbeat; // uint8_t 1 beats/m (0-255) heart rate measurement + volume_t sac; // 4 ml/min predefined SAC + bool in_deco; // bool 1 y/n y/n this sample is part of deco + bool manually_entered; // bool 1 y/n y/n this sample was entered by the user, + // not calculated when planning a dive +}; // Total size of structure: 57 bytes, excluding padding at end + +struct divetag { + /* + * The name of the divetag. If a translation is available, name contains + * the translated tag + */ + char *name; + /* + * If a translation is available, we write the original tag to source. + * This enables us to write a non-localized tag to the xml file. + */ + char *source; +}; + +struct tag_entry { + struct divetag *tag; + struct tag_entry *next; +}; + +/* + * divetags are only stored once, each dive only contains + * a list of tag_entries which then point to the divetags + * in the global g_tag_list + */ + +extern struct tag_entry *g_tag_list; + +struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag); +struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list); +void dump_taglist(const char *intro, struct tag_entry *tl); + +/* + * Writes all divetags in tag_list to buffer, limited by the buffer's (len)gth. + * Returns the characters written + */ +int taglist_get_tagstring(struct tag_entry *tag_list, char *buffer, int len); + +/* cleans up a list: removes empty tags and duplicates */ +void taglist_cleanup(struct tag_entry **tag_list); + +void taglist_init_global(); +void taglist_free(struct tag_entry *tag_list); + +bool taglist_contains(struct tag_entry *tag_list, const char *tag); +bool taglist_equal(struct tag_entry *tl1, struct tag_entry *tl2); +int count_dives_with_tag(const char *tag); +int count_dives_with_person(const char *person); +int count_dives_with_location(const char *location); +int count_dives_with_suit(const char *suit); + +struct extra_data { + const char *key; + const char *value; + struct extra_data *next; +}; + +/* + * NOTE! The deviceid and diveid are model-specific *hashes* of + * whatever device identification that model may have. Different + * dive computers will have different identifying data, it could + * be a firmware number or a serial ID (in either string or in + * numeric format), and we do not care. + * + * The only thing we care about is that subsurface will hash + * that information the same way. So then you can check the ID + * of a dive computer by comparing the hashes for equality. + * + * A deviceid or diveid of zero is assumed to be "no ID". + */ +struct divecomputer { + timestamp_t when; + duration_t duration, surfacetime; + depth_t maxdepth, meandepth; + temperature_t airtemp, watertemp; + pressure_t surface_pressure; + enum dive_comp_type divemode; // dive computer type: OC(default) or CCR + uint8_t no_o2sensors; // rebreathers: number of O2 sensors used + int salinity; // kg per 10000 l + const char *model, *serial, *fw_version; + uint32_t deviceid, diveid; + int samples, alloc_samples; + struct sample *sample; + struct event *events; + struct extra_data *extra_data; + struct divecomputer *next; +}; + +#define MAX_CYLINDERS (8) +#define MAX_WEIGHTSYSTEMS (6) +#define W_IDX_PRIMARY 0 +#define W_IDX_SECONDARY 1 + +typedef enum { + TF_NONE, + NO_TRIP, + IN_TRIP, + ASSIGNED_TRIP, + NUM_TRIPFLAGS +} tripflag_t; + +typedef struct dive_trip +{ + timestamp_t when; + char *location; + char *notes; + struct dive *dives; + int nrdives; + int index; + unsigned expanded : 1, selected : 1, autogen : 1, fixup : 1; + struct dive_trip *next; +} dive_trip_t; + +/* List of dive trips (sorted by date) */ +extern dive_trip_t *dive_trip_list; +struct picture; +struct dive { + int number; + tripflag_t tripflag; + dive_trip_t *divetrip; + struct dive *next, **pprev; + bool selected; + bool hidden_by_filter; + bool downloaded; + timestamp_t when; + uint32_t dive_site_uuid; + char *notes; + char *divemaster, *buddy; + int rating; + int visibility; /* 0 - 5 star rating */ + cylinder_t cylinder[MAX_CYLINDERS]; + weightsystem_t weightsystem[MAX_WEIGHTSYSTEMS]; + char *suit; + int sac, otu, cns, maxcns; + + /* Calculated based on dive computer data */ + temperature_t mintemp, maxtemp, watertemp, airtemp; + depth_t maxdepth, meandepth; + pressure_t surface_pressure; + duration_t duration; + int salinity; // kg per 10000 l + + struct tag_entry *tag_list; + struct divecomputer dc; + int id; // unique ID for this dive + struct picture *picture_list; + int oxygen_cylinder_index, diluent_cylinder_index; // CCR dive cylinder indices + unsigned char git_id[20]; +}; + +static inline void invalidate_dive_cache(struct dive *dive) +{ + memset(dive->git_id, 0, 20); +} + +static inline bool dive_cache_is_valid(const struct dive *dive) +{ + static const unsigned char null_id[20] = { 0, }; + return !!memcmp(dive->git_id, null_id, 20); +} + +extern int get_cylinder_idx_by_use(struct dive *dive, enum cylinderuse cylinder_use_type); +extern void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, int mapping[]); + +/* when selectively copying dive information, which parts should be copied? */ +struct dive_components { + unsigned int divesite : 1; + unsigned int notes : 1; + unsigned int divemaster : 1; + unsigned int buddy : 1; + unsigned int suit : 1; + unsigned int rating : 1; + unsigned int visibility : 1; + unsigned int tags : 1; + unsigned int cylinders : 1; + unsigned int weights : 1; +}; + +/* picture list and methods related to dive picture handling */ +struct picture { + char *filename; + char *hash; + offset_t offset; + degrees_t latitude; + degrees_t longitude; + struct picture *next; +}; + +#define FOR_EACH_PICTURE(_dive) \ + if (_dive) \ + for (struct picture *picture = (_dive)->picture_list; picture; picture = picture->next) + +#define FOR_EACH_PICTURE_NON_PTR(_divestruct) \ + for (struct picture *picture = (_divestruct).picture_list; picture; picture = picture->next) + +extern struct picture *alloc_picture(); +extern struct picture *clone_picture(struct picture *src); +extern bool dive_check_picture_time(struct dive *d, int shift_time, timestamp_t timestamp); +extern void dive_create_picture(struct dive *d, char *filename, int shift_time, bool match_all); +extern void dive_add_picture(struct dive *d, struct picture *newpic); +extern void dive_remove_picture(char *filename); +extern unsigned int dive_get_picture_count(struct dive *d); +extern bool picture_check_valid(char *filename, int shift_time); +extern void picture_load_exif_data(struct picture *p); +extern timestamp_t picture_get_timestamp(char *filename); +extern void dive_set_geodata_from_picture(struct dive *d, struct picture *pic); +extern void picture_free(struct picture *picture); + +extern int explicit_first_cylinder(struct dive *dive, struct divecomputer *dc); +extern int get_depth_at_time(struct divecomputer *dc, unsigned int time); + +static inline int get_surface_pressure_in_mbar(const struct dive *dive, bool non_null) +{ + int mbar = dive->surface_pressure.mbar; + if (!mbar && non_null) + mbar = SURFACE_PRESSURE; + return mbar; +} + +/* Pa = N/m^2 - so we determine the weight (in N) of the mass of 10m + * of water (and use standard salt water at 1.03kg per liter if we don't know salinity) + * and add that to the surface pressure (or to 1013 if that's unknown) */ +static inline int calculate_depth_to_mbar(int depth, pressure_t surface_pressure, int salinity) +{ + double specific_weight; + int mbar = surface_pressure.mbar; + + if (!mbar) + mbar = SURFACE_PRESSURE; + if (!salinity) + salinity = SEAWATER_SALINITY; + if (salinity < 500) + salinity += FRESHWATER_SALINITY; + specific_weight = salinity / 10000.0 * 0.981; + mbar += rint(depth / 10.0 * specific_weight); + return mbar; +} + +static inline int depth_to_mbar(int depth, struct dive *dive) +{ + return calculate_depth_to_mbar(depth, dive->surface_pressure, dive->salinity); +} + +static inline double depth_to_bar(int depth, struct dive *dive) +{ + return depth_to_mbar(depth, dive) / 1000.0; +} + +static inline double depth_to_atm(int depth, struct dive *dive) +{ + return mbar_to_atm(depth_to_mbar(depth, dive)); +} + +/* for the inverse calculation we use just the relative pressure + * (that's the one that some dive computers like the Uemis Zurich + * provide - for the other models that do this libdivecomputer has to + * take care of this, but the Uemis we support natively */ +static inline int rel_mbar_to_depth(int mbar, struct dive *dive) +{ + int cm; + double specific_weight = 1.03 * 0.981; + if (dive->dc.salinity) + specific_weight = dive->dc.salinity / 10000.0 * 0.981; + /* whole mbar gives us cm precision */ + cm = rint(mbar / specific_weight); + return cm * 10; +} + +static inline int mbar_to_depth(int mbar, struct dive *dive) +{ + pressure_t surface_pressure; + if (dive->surface_pressure.mbar) + surface_pressure = dive->surface_pressure; + else + surface_pressure.mbar = SURFACE_PRESSURE; + return rel_mbar_to_depth(mbar - surface_pressure.mbar, dive); +} + +/* MOD rounded to multiples of roundto mm */ +static inline depth_t gas_mod(struct gasmix *mix, pressure_t po2_limit, struct dive *dive, int roundto) { + depth_t rounded_depth; + + double depth = (double) mbar_to_depth(po2_limit.mbar * 1000 / get_o2(mix), dive); + rounded_depth.mm = rint(depth / roundto) * roundto; + return rounded_depth; +} + +#define SURFACE_THRESHOLD 750 /* somewhat arbitrary: only below 75cm is it really diving */ + +/* this is a global spot for a temporary dive structure that we use to + * be able to edit a dive without unintended side effects */ +extern struct dive edit_dive; + +extern short autogroup; +/* random threashold: three days without diving -> new trip + * this works very well for people who usually dive as part of a trip and don't + * regularly dive at a local facility; this is why trips are an optional feature */ +#define TRIP_THRESHOLD 3600 * 24 * 3 + +#define UNGROUPED_DIVE(_dive) ((_dive)->tripflag == NO_TRIP) +#define DIVE_IN_TRIP(_dive) ((_dive)->tripflag == IN_TRIP || (_dive)->tripflag == ASSIGNED_TRIP) +#define DIVE_NEEDS_TRIP(_dive) ((_dive)->tripflag == TF_NONE) + +extern void add_dive_to_trip(struct dive *, dive_trip_t *); + +extern void delete_single_dive(int idx); +extern void add_single_dive(int idx, struct dive *dive); + +extern void insert_trip(dive_trip_t **trip); + + +extern const struct units SI_units, IMPERIAL_units; +extern struct units xml_parsing_units; + +extern struct units *get_units(void); +extern int run_survey, verbose, quit, force_root; + +struct dive_table { + int nr, allocated, preexisting; + struct dive **dives; +}; + +extern struct dive_table dive_table, downloadTable; +extern struct dive displayed_dive; +extern struct dive_site displayed_dive_site; +extern int selected_dive; +extern unsigned int dc_number; +#define current_dive (get_dive(selected_dive)) +#define current_dc (get_dive_dc(current_dive, dc_number)) + +static inline struct dive *get_dive(int nr) +{ + if (nr >= dive_table.nr || nr < 0) + return NULL; + return dive_table.dives[nr]; +} + +static inline struct dive *get_dive_from_table(int nr, struct dive_table *dt) +{ + if (nr >= dt->nr || nr < 0) + return NULL; + return dt->dives[nr]; +} + +static inline struct dive_site *get_dive_site_for_dive(struct dive *dive) +{ + if (dive) + return get_dive_site_by_uuid(dive->dive_site_uuid); + return NULL; +} + +static inline char *get_dive_location(struct dive *dive) +{ + struct dive_site *ds = get_dive_site_by_uuid(dive->dive_site_uuid); + if (ds && ds->name) + return ds->name; + return NULL; +} + +static inline unsigned int number_of_computers(struct dive *dive) +{ + unsigned int total_number = 0; + struct divecomputer *dc = &dive->dc; + + if (!dive) + return 1; + + do { + total_number++; + dc = dc->next; + } while (dc); + return total_number; +} + +static inline struct divecomputer *get_dive_dc(struct dive *dive, int nr) +{ + struct divecomputer *dc = &dive->dc; + + while (nr-- > 0) { + dc = dc->next; + if (!dc) + return &dive->dc; + } + return dc; +} + +extern timestamp_t dive_endtime(const struct dive *dive); + +extern void make_first_dc(void); +extern unsigned int count_divecomputers(void); +extern void delete_current_divecomputer(void); + +/* + * Iterate over each dive, with the first parameter being the index + * iterator variable, and the second one being the dive one. + * + * I don't think anybody really wants the index, and we could make + * it local to the for-loop, but that would make us requires C99. + */ +#define for_each_dive(_i, _x) \ + for ((_i) = 0; ((_x) = get_dive(_i)) != NULL; (_i)++) + +#define for_each_dc(_dive, _dc) \ + for (_dc = &_dive->dc; _dc; _dc = _dc->next) + +#define for_each_gps_location(_i, _x) \ + for ((_i) = 0; ((_x) = get_gps_location(_i, &gps_location_table)) != NULL; (_i)++) + +static inline struct dive *get_dive_by_uniq_id(int id) +{ + int i; + struct dive *dive = NULL; + + for_each_dive (i, dive) { + if (dive->id == id) + break; + } +#ifdef DEBUG + if (dive == NULL) { + fprintf(stderr, "Invalid id %x passed to get_dive_by_diveid, try to fix the code\n", id); + exit(1); + } +#endif + return dive; +} + +static inline int get_idx_by_uniq_id(int id) +{ + int i; + struct dive *dive = NULL; + + for_each_dive (i, dive) { + if (dive->id == id) + break; + } +#ifdef DEBUG + if (dive == NULL) { + fprintf(stderr, "Invalid id %x passed to get_dive_by_diveid, try to fix the code\n", id); + exit(1); + } +#endif + return i; +} + +static inline bool dive_site_has_gps_location(struct dive_site *ds) +{ + return ds && (ds->latitude.udeg || ds->longitude.udeg); +} + +static inline int dive_has_gps_location(struct dive *dive) +{ + if (!dive) + return false; + return dive_site_has_gps_location(get_dive_site_by_uuid(dive->dive_site_uuid)); +} + +#ifdef __cplusplus +extern "C" { +#endif + +extern int report_error(const char *fmt, ...); +extern void report_message(const char *msg); +extern const char *get_error_string(void); + +extern struct dive *find_dive_including(timestamp_t when); +extern bool dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset); +extern bool time_during_dive_with_offset(struct dive *dive, timestamp_t when, timestamp_t offset); +struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset); + +/* Check if two dive computer entries are the exact same dive (-1=no/0=maybe/1=yes) */ +extern int match_one_dc(struct divecomputer *a, struct divecomputer *b); + +extern void parse_xml_init(void); +extern int parse_xml_buffer(const char *url, const char *buf, int size, struct dive_table *table, const char **params); +extern void parse_xml_exit(void); +extern void set_filename(const char *filename, bool force); + +extern int parse_dm4_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); +extern int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); +extern int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); +extern int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); +extern int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); +extern int parse_dlf_buffer(unsigned char *buffer, size_t size); + +extern int check_git_sha(const char *filename); +extern int parse_file(const char *filename); +extern int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate); +extern int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate); +extern int parse_txt_file(const char *filename, const char *csv); +extern int parse_manual_file(const char *filename, char **params, int pnr); +extern int save_dives(const char *filename); +extern int save_dives_logic(const char *filename, bool select_only); +extern int save_dive(FILE *f, struct dive *dive); +extern int export_dives_xslt(const char *filename, const bool selected, const int units, const char *export_xslt); + +struct membuffer; +extern void save_one_dive_to_mb(struct membuffer *b, struct dive *dive); + +int cylinderuse_from_text(const char *text); + + +struct user_info { + const char *name; + const char *email; +}; + +extern void subsurface_user_info(struct user_info *); +extern int subsurface_rename(const char *path, const char *newpath); +extern int subsurface_dir_rename(const char *path, const char *newpath); +extern int subsurface_open(const char *path, int oflags, mode_t mode); +extern FILE *subsurface_fopen(const char *path, const char *mode); +extern void *subsurface_opendir(const char *path); +extern int subsurface_access(const char *path, int mode); +extern struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp); +extern int subsurface_zip_close(struct zip *zip); +extern void subsurface_console_init(bool dedicated); +extern void subsurface_console_exit(void); +extern bool subsurface_user_is_root(void); + +extern void shift_times(const timestamp_t amount); +extern timestamp_t get_times(); + +extern xsltStylesheetPtr get_stylesheet(const char *name); + +extern timestamp_t utc_mktime(struct tm *tm); +extern void utc_mkdate(timestamp_t, struct tm *tm); + +extern struct dive *alloc_dive(void); +extern void record_dive_to_table(struct dive *dive, struct dive_table *table); +extern void record_dive(struct dive *dive); +extern void clear_dive(struct dive *dive); +extern void copy_dive(struct dive *s, struct dive *d); +extern void selective_copy_dive(struct dive *s, struct dive *d, struct dive_components what, bool clear); +extern struct dive *clone_dive(struct dive *s); + +extern void clear_table(struct dive_table *table); + +extern struct sample *prepare_sample(struct divecomputer *dc); +extern void finish_sample(struct divecomputer *dc); + +extern bool has_hr_data(struct divecomputer *dc); + +extern void sort_table(struct dive_table *table); +extern struct dive *fixup_dive(struct dive *dive); +extern void fixup_dc_duration(struct divecomputer *dc); +extern int dive_getUniqID(struct dive *d); +extern unsigned int dc_airtemp(struct divecomputer *dc); +extern unsigned int dc_watertemp(struct divecomputer *dc); +extern int split_dive(struct dive *); +extern struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer_downloaded); +extern struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded); +extern struct event *clone_event(const struct event *src_ev); +extern void copy_events(struct divecomputer *s, struct divecomputer *d); +extern void free_events(struct event *ev); +extern void copy_cylinders(struct dive *s, struct dive *d, bool used_only); +extern void copy_samples(struct divecomputer *s, struct divecomputer *d); +extern bool is_cylinder_used(struct dive *dive, int idx); +extern void fill_default_cylinder(cylinder_t *cyl); +extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int time, int idx); +extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); +extern void remove_event(struct event *event); +extern void update_event_name(struct dive *d, struct event* event, char *name); +extern void add_extra_data(struct divecomputer *dc, const char *key, const char *value); +extern void per_cylinder_mean_depth(struct dive *dive, struct divecomputer *dc, int *mean, int *duration); +extern int get_cylinder_index(struct dive *dive, struct event *ev); +extern int nr_cylinders(struct dive *dive); +extern int nr_weightsystems(struct dive *dive); + +/* UI related protopypes */ + +// extern void report_error(GError* error); + +extern void add_cylinder_description(cylinder_type_t *); +extern void add_weightsystem_description(weightsystem_t *); +extern void remember_event(const char *eventname); +extern void invalidate_dive_cache(struct dive *dc); + +#if WE_DONT_USE_THIS /* this is a missing feature in Qt - selecting which events to display */ +extern int evn_foreach(void (*callback)(const char *, bool *, void *), void *data); +#endif /* WE_DONT_USE_THIS */ + +extern void clear_events(void); + +extern void set_dc_nickname(struct dive *dive); +extern void set_autogroup(bool value); +extern int total_weight(struct dive *); + +#ifdef __cplusplus +} +#endif + +#define DIVE_ERROR_PARSE 1 +#define DIVE_ERROR_PLAN 2 + +const char *weekday(int wday); +const char *monthname(int mon); + +#define UTF8_DEGREE "\xc2\xb0" +#define UTF8_DELTA "\xce\x94" +#define UTF8_UPWARDS_ARROW "\xE2\x86\x91" +#define UTF8_DOWNWARDS_ARROW "\xE2\x86\x93" +#define UTF8_AVERAGE "\xc3\xb8" +#define UTF8_SUBSCRIPT_2 "\xe2\x82\x82" +#define UTF8_WHITESTAR "\xe2\x98\x86" +#define UTF8_BLACKSTAR "\xe2\x98\x85" + +extern const char *existing_filename; +extern void subsurface_command_line_init(int *, char ***); +extern void subsurface_command_line_exit(int *, char ***); + +#define FRACTION(n, x) ((unsigned)(n) / (x)), ((unsigned)(n) % (x)) + +extern void add_segment(double pressure, const struct gasmix *gasmix, int period_in_seconds, int setpoint, const struct dive *dive, int sac); +extern void clear_deco(double surface_pressure); +extern void dump_tissues(void); +extern void set_gf(short gflow, short gfhigh, bool gf_low_at_maxdepth); +extern void cache_deco_state(char **datap); +extern void restore_deco_state(char *data); +extern void nuclear_regeneration(double time); +extern void vpmb_start_gradient(); +extern void vpmb_next_gradient(double deco_time, double surface_pressure); +extern double tissue_tolerance_calc(const struct dive *dive, double pressure); + +/* this should be converted to use our types */ +struct divedatapoint { + int time; + int depth; + struct gasmix gasmix; + int setpoint; + bool entered; + struct divedatapoint *next; +}; + +struct diveplan { + timestamp_t when; + int surface_pressure; /* mbar */ + int bottomsac; /* ml/min */ + int decosac; /* ml/min */ + int salinity; + short gflow; + short gfhigh; + struct divedatapoint *dp; +}; + +struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, struct gasmix gasmix, int po2, bool entered); +struct divedatapoint *create_dp(int time_incr, int depth, struct gasmix gasmix, int po2); +#if DEBUG_PLAN +void dump_plan(struct diveplan *diveplan); +#endif +bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer); +void calc_crushing_pressure(double pressure); +void vpmb_start_gradient(); + +void delete_single_dive(int idx); + +struct event *get_next_event(struct event *event, const char *name); + + +/* these structs holds the information that + * describes the cylinders / weight systems. + * they are global variables initialized in equipment.c + * used to fill the combobox in the add/edit cylinder + * dialog + */ + +struct tank_info_t { + const char *name; + int cuft, ml, psi, bar; +}; +extern struct tank_info_t tank_info[100]; + +struct ws_info_t { + const char *name; + int grams; +}; +extern struct ws_info_t ws_info[100]; + +extern bool cylinder_nodata(cylinder_t *cyl); +extern bool cylinder_none(void *_data); +extern bool weightsystem_none(void *_data); +extern bool no_weightsystems(weightsystem_t *ws); +extern bool weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2); +extern void remove_cylinder(struct dive *dive, int idx); +extern void remove_weightsystem(struct dive *dive, int idx); +extern void reset_cylinders(struct dive *dive, bool track_gas); + +/* + * String handling. + */ +#define STRTOD_NO_SIGN 0x01 +#define STRTOD_NO_DOT 0x02 +#define STRTOD_NO_COMMA 0x04 +#define STRTOD_NO_EXPONENT 0x08 +extern double strtod_flags(const char *str, const char **ptr, unsigned int flags); + +#define STRTOD_ASCII (STRTOD_NO_COMMA) + +#define ascii_strtod(str, ptr) strtod_flags(str, ptr, STRTOD_ASCII) + +extern void set_save_userid_local(short value); +extern void set_userid(char *user_id); +extern void set_informational_units(char *units); + +extern const char *get_dive_date_c_string(timestamp_t when); +extern void update_setpoint_events(struct divecomputer *dc); +#ifdef __cplusplus +} +#endif + +extern weight_t string_to_weight(const char *str); +extern depth_t string_to_depth(const char *str); +extern pressure_t string_to_pressure(const char *str); +extern volume_t string_to_volume(const char *str, pressure_t workp); +extern fraction_t string_to_fraction(const char *str); +extern void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_depth); + +#include "pref.h" + +#endif // DIVE_H diff --git a/core/divecomputer.cpp b/core/divecomputer.cpp new file mode 100644 index 000000000..e4081e1cd --- /dev/null +++ b/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/core/divecomputer.h b/core/divecomputer.h new file mode 100644 index 000000000..98d12ab8b --- /dev/null +++ b/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/core/divelist.c b/core/divelist.c new file mode 100644 index 000000000..543d9e17b --- /dev/null +++ b/core/divelist.c @@ -0,0 +1,1207 @@ +/* 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" +#include "git-access.h" + +static short dive_list_changed = false; + +short autogroup = false; + +dive_trip_t *dive_trip_list; + +unsigned int amount_selected; + +#if DEBUG_SELECTION_TRACKING +void dump_selection(void) +{ + int i; + struct dive *dive; + + printf("currently selected are %u dives:", amount_selected); + for_each_dive(i, dive) { + if (dive->selected) + printf(" %d", i); + } + printf("\n"); +} +#endif + +void set_autogroup(bool value) +{ + /* if we keep the UI paradigm, this needs to toggle + * the checkbox on the autogroup menu item */ + autogroup = value; +} + +dive_trip_t *find_trip_by_idx(int idx) +{ + dive_trip_t *trip = dive_trip_list; + + if (idx >= 0) + return NULL; + idx = -idx; + while (trip) { + if (trip->index == idx) + return trip; + trip = trip->next; + } + return NULL; +} + +int trip_has_selected_dives(dive_trip_t *trip) +{ + struct dive *dive; + for (dive = trip->dives; dive; dive = dive->next) { + if (dive->selected) + return 1; + } + return 0; +} + +/* + * Get "maximal" dive gas for a dive. + * Rules: + * - Trimix trumps nitrox (highest He wins, O2 breaks ties) + * - Nitrox trumps air (even if hypoxic) + * These are the same rules as the inter-dive sorting rules. + */ +void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2max_p) +{ + int i; + int maxo2 = -1, maxhe = -1, mino2 = 1000; + + + for (i = 0; i < MAX_CYLINDERS; i++) { + cylinder_t *cyl = dive->cylinder + i; + int o2 = get_o2(&cyl->gasmix); + int he = get_he(&cyl->gasmix); + + if (!is_cylinder_used(dive, i)) + continue; + if (cylinder_none(cyl)) + continue; + if (o2 > maxo2) + maxo2 = o2; + if (he > maxhe) + goto newmax; + if (he < maxhe) + continue; + if (o2 <= maxo2) + continue; + newmax: + maxhe = he; + mino2 = o2; + } + /* All air? Show/sort as "air"/zero */ + if ((!maxhe && maxo2 == O2_IN_AIR && mino2 == maxo2) || + (maxo2 == -1 && maxhe == -1 && mino2 == 1000)) + maxo2 = mino2 = 0; + *o2_p = mino2; + *he_p = maxhe; + *o2max_p = maxo2; +} + +int total_weight(struct dive *dive) +{ + int i, total_grams = 0; + + if (dive) + for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) + total_grams += dive->weightsystem[i].weight.grams; + return total_grams; +} + +static int active_o2(struct dive *dive, struct divecomputer *dc, duration_t time) +{ + struct gasmix gas; + get_gas_at_time(dive, dc, time, &gas); + return get_o2(&gas); +} + +/* calculate OTU for a dive - this only takes the first divecomputer into account */ +static int calculate_otu(struct dive *dive) +{ + int i; + double otu = 0.0; + struct divecomputer *dc = &dive->dc; + + for (i = 1; i < dc->samples; i++) { + int t; + int po2; + struct sample *sample = dc->sample + i; + struct sample *psample = sample - 1; + t = sample->time.seconds - psample->time.seconds; + if (sample->setpoint.mbar) { + po2 = sample->setpoint.mbar; + } else { + int o2 = active_o2(dive, dc, sample->time); + po2 = o2 * depth_to_atm(sample->depth.mm, dive); + } + if (po2 >= 500) + otu += pow((po2 - 500) / 1000.0, 0.83) * t / 30.0; + } + return rint(otu); +} +/* calculate CNS for a dive - this only takes the first divecomputer into account */ +int const cns_table[][3] = { + /* po2, Maximum Single Exposure, Maximum 24 hour Exposure */ + { 1600, 45 * 60, 150 * 60 }, + { 1500, 120 * 60, 180 * 60 }, + { 1400, 150 * 60, 180 * 60 }, + { 1300, 180 * 60, 210 * 60 }, + { 1200, 210 * 60, 240 * 60 }, + { 1100, 240 * 60, 270 * 60 }, + { 1000, 300 * 60, 300 * 60 }, + { 900, 360 * 60, 360 * 60 }, + { 800, 450 * 60, 450 * 60 }, + { 700, 570 * 60, 570 * 60 }, + { 600, 720 * 60, 720 * 60 } +}; + +/* this only gets called if dive->maxcns == 0 which means we know that + * none of the divecomputers has tracked any CNS for us + * so we calculated it "by hand" */ +static int calculate_cns(struct dive *dive) +{ + int i, divenr; + size_t j; + double cns = 0.0; + struct divecomputer *dc = &dive->dc; + struct dive *prev_dive; + timestamp_t endtime; + + /* shortcut */ + if (dive->cns) + return dive->cns; + /* + * Do we start with a cns loading from a previous dive? + * Check if we did a dive 12 hours prior, and what cns we had from that. + * Then apply ha 90min halftime to see whats left. + */ + divenr = get_divenr(dive); + if (divenr) { + prev_dive = get_dive(divenr - 1); + if (prev_dive) { + endtime = prev_dive->when + prev_dive->duration.seconds; + if (dive->when < (endtime + 3600 * 12)) { + cns = calculate_cns(prev_dive); + cns = cns * 1 / pow(2, (dive->when - endtime) / (90.0 * 60.0)); + } + } + } + /* Caclulate the cns for each sample in this dive and sum them */ + for (i = 1; i < dc->samples; i++) { + int t; + int po2; + struct sample *sample = dc->sample + i; + struct sample *psample = sample - 1; + t = sample->time.seconds - psample->time.seconds; + if (sample->setpoint.mbar) { + po2 = sample->setpoint.mbar; + } else { + int o2 = active_o2(dive, dc, sample->time); + po2 = o2 * depth_to_atm(sample->depth.mm, dive); + } + /* CNS don't increse when below 500 matm */ + if (po2 < 500) + continue; + /* Find what table-row we should calculate % for */ + for (j = 1; j < sizeof(cns_table) / (sizeof(int) * 3); j++) + if (po2 > cns_table[j][0]) + break; + j--; + cns += ((double)t) / ((double)cns_table[j][1]) * 100; + } + /* save calculated cns in dive struct */ + dive->cns = cns; + return dive->cns; +} +/* + * Return air usage (in liters). + */ +static double calculate_airuse(struct dive *dive) +{ + int airuse = 0; + int i; + + for (i = 0; i < MAX_CYLINDERS; i++) { + pressure_t start, end; + cylinder_t *cyl = dive->cylinder + i; + + start = cyl->start.mbar ? cyl->start : cyl->sample_start; + end = cyl->end.mbar ? cyl->end : cyl->sample_end; + if (!end.mbar || start.mbar <= end.mbar) + continue; + + airuse += gas_volume(cyl, start) - gas_volume(cyl, end); + } + return airuse / 1000.0; +} + +/* this only uses the first divecomputer to calculate the SAC rate */ +static int calculate_sac(struct dive *dive) +{ + struct divecomputer *dc = &dive->dc; + double airuse, pressure, sac; + int duration, meandepth; + + airuse = calculate_airuse(dive); + if (!airuse) + return 0; + + duration = dc->duration.seconds; + if (!duration) + return 0; + + meandepth = dc->meandepth.mm; + if (!meandepth) + return 0; + + /* Mean pressure in ATM (SAC calculations are in atm*l/min) */ + pressure = depth_to_atm(meandepth, dive); + sac = airuse / pressure * 60 / duration; + + /* milliliters per minute.. */ + return sac * 1000; +} + +/* for now we do this based on the first divecomputer */ +static void add_dive_to_deco(struct dive *dive) +{ + struct divecomputer *dc = &dive->dc; + int i; + + if (!dc) + return; + for (i = 1; i < dc->samples; i++) { + struct sample *psample = dc->sample + i - 1; + struct sample *sample = dc->sample + i; + int t0 = psample->time.seconds; + int t1 = sample->time.seconds; + int j; + + for (j = t0; j < t1; j++) { + int depth = interpolate(psample->depth.mm, sample->depth.mm, j - t0, t1 - t0); + add_segment(depth_to_bar(depth, dive), + &dive->cylinder[sample->sensor].gasmix, 1, sample->setpoint.mbar, dive, dive->sac); + } + } +} + +int get_divenr(struct dive *dive) +{ + int i; + struct dive *d; + // tempting as it may be, don't die when called with dive=NULL + if (dive) + for_each_dive(i, d) { + if (d->id == dive->id) // don't compare pointers, we could be passing in a copy of the dive + return i; + } + return -1; +} + +int get_divesite_idx(struct dive_site *ds) +{ + int i; + struct dive_site *d; + // tempting as it may be, don't die when called with dive=NULL + if (ds) + for_each_dive_site(i, d) { + if (d->uuid == ds->uuid) // don't compare pointers, we could be passing in a copy of the dive + return i; + } + return -1; +} + +static struct gasmix air = { .o2.permille = O2_IN_AIR, .he.permille = 0 }; + +/* take into account previous dives until there is a 48h gap between dives */ +double init_decompression(struct dive *dive) +{ + int i, divenr = -1; + unsigned int surface_time; + timestamp_t when, lasttime = 0, laststart = 0; + bool deco_init = false; + double surface_pressure; + + if (!dive) + return 0.0; + + surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; + divenr = get_divenr(dive); + when = dive->when; + i = divenr; + if (i < 0) { + i = dive_table.nr - 1; + while (i >= 0 && get_dive(i)->when > when) + --i; + i++; + } + while (i--) { + struct dive *pdive = get_dive(i); + /* we don't want to mix dives from different trips as we keep looking + * for how far back we need to go */ + if (dive->divetrip && pdive->divetrip != dive->divetrip) + continue; + if (!pdive || pdive->when >= when || pdive->when + pdive->duration.seconds + 48 * 60 * 60 < when) + break; + /* For simultaneous dives, only consider the first */ + if (pdive->when == laststart) + continue; + when = pdive->when; + lasttime = when + pdive->duration.seconds; + } + while (++i < (divenr >= 0 ? divenr : dive_table.nr)) { + struct dive *pdive = get_dive(i); + /* again skip dives from different trips */ + if (dive->divetrip && dive->divetrip != pdive->divetrip) + continue; + surface_pressure = get_surface_pressure_in_mbar(pdive, true) / 1000.0; + if (!deco_init) { + clear_deco(surface_pressure); + deco_init = true; +#if DECO_CALC_DEBUG & 2 + dump_tissues(); +#endif + } + add_dive_to_deco(pdive); + laststart = pdive->when; +#if DECO_CALC_DEBUG & 2 + printf("added dive #%d\n", pdive->number); + dump_tissues(); +#endif + if (pdive->when > lasttime) { + surface_time = pdive->when - lasttime; + lasttime = pdive->when + pdive->duration.seconds; + add_segment(surface_pressure, &air, surface_time, 0, dive, prefs.decosac); +#if DECO_CALC_DEBUG & 2 + printf("after surface intervall of %d:%02u\n", FRACTION(surface_time, 60)); + dump_tissues(); +#endif + } + } + /* add the final surface time */ + if (lasttime && dive->when > lasttime) { + surface_time = dive->when - lasttime; + surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; + add_segment(surface_pressure, &air, surface_time, 0, dive, prefs.decosac); +#if DECO_CALC_DEBUG & 2 + printf("after surface intervall of %d:%02u\n", FRACTION(surface_time, 60)); + dump_tissues(); +#endif + } + if (!deco_init) { + surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; + clear_deco(surface_pressure); +#if DECO_CALC_DEBUG & 2 + printf("no previous dive\n"); + dump_tissues(); +#endif + } + return tissue_tolerance_calc(dive, surface_pressure); +} + +void update_cylinder_related_info(struct dive *dive) +{ + if (dive != NULL) { + dive->sac = calculate_sac(dive); + dive->otu = calculate_otu(dive); + if (dive->maxcns == 0) + dive->maxcns = calculate_cns(dive); + } +} + +#define MAX_GAS_STRING 80 +#define UTF8_ELLIPSIS "\xE2\x80\xA6" + +/* callers needs to free the string */ +char *get_dive_gas_string(struct dive *dive) +{ + int o2, he, o2max; + char *buffer = malloc(MAX_GAS_STRING); + + if (buffer) { + get_dive_gas(dive, &o2, &he, &o2max); + o2 = (o2 + 5) / 10; + he = (he + 5) / 10; + o2max = (o2max + 5) / 10; + + if (he) + if (o2 == o2max) + snprintf(buffer, MAX_GAS_STRING, "%d/%d", o2, he); + else + snprintf(buffer, MAX_GAS_STRING, "%d/%d" UTF8_ELLIPSIS "%d%%", o2, he, o2max); + else if (o2) + if (o2 == o2max) + snprintf(buffer, MAX_GAS_STRING, "%d%%", o2); + else + snprintf(buffer, MAX_GAS_STRING, "%d" UTF8_ELLIPSIS "%d%%", o2, o2max); + else + strcpy(buffer, translate("gettextFromC", "air")); + } + return buffer; +} + +/* + * helper functions for dive_trip handling + */ +#ifdef DEBUG_TRIP +void dump_trip_list(void) +{ + dive_trip_t *trip; + int i = 0; + timestamp_t last_time = 0; + + for (trip = dive_trip_list; trip; trip = trip->next) { + struct tm tm; + utc_mkdate(trip->when, &tm); + if (trip->when < last_time) + printf("\n\ndive_trip_list OUT OF ORDER!!!\n\n\n"); + printf("%s trip %d to \"%s\" on %04u-%02u-%02u %02u:%02u:%02u (%d dives - %p)\n", + trip->autogen ? "autogen " : "", + ++i, trip->location, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, + trip->nrdives, trip); + last_time = trip->when; + } + printf("-----\n"); +} +#endif + +/* this finds the last trip that at or before the time given */ +dive_trip_t *find_matching_trip(timestamp_t when) +{ + dive_trip_t *trip = dive_trip_list; + + if (!trip || trip->when > when) { +#ifdef DEBUG_TRIP + printf("no matching trip\n"); +#endif + return NULL; + } + while (trip->next && trip->next->when <= when) + trip = trip->next; +#ifdef DEBUG_TRIP + { + struct tm tm; + utc_mkdate(trip->when, &tm); + printf("found trip %p @ %04d-%02d-%02d %02d:%02d:%02d\n", + trip, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + } +#endif + return trip; +} + +/* insert the trip into the dive_trip_list - but ensure you don't have + * two trips for the same date; but if you have, make sure you don't + * keep the one with less information */ +void insert_trip(dive_trip_t **dive_trip_p) +{ + dive_trip_t *dive_trip = *dive_trip_p; + dive_trip_t **p = &dive_trip_list; + dive_trip_t *trip; + struct dive *divep; + + /* Walk the dive trip list looking for the right location.. */ + while ((trip = *p) != NULL && trip->when < dive_trip->when) + p = &trip->next; + + if (trip && trip->when == dive_trip->when) { + if (!trip->location) + trip->location = dive_trip->location; + if (!trip->notes) + trip->notes = dive_trip->notes; + divep = dive_trip->dives; + while (divep) { + add_dive_to_trip(divep, trip); + divep = divep->next; + } + *dive_trip_p = trip; + } else { + dive_trip->next = trip; + *p = dive_trip; + } +#ifdef DEBUG_TRIP + dump_trip_list(); +#endif +} + +static void delete_trip(dive_trip_t *trip) +{ + dive_trip_t **p, *tmp; + + assert(!trip->dives); + + /* Remove the trip from the list of trips */ + p = &dive_trip_list; + while ((tmp = *p) != NULL) { + if (tmp == trip) { + *p = trip->next; + break; + } + p = &tmp->next; + } + + /* .. and free it */ + free(trip->location); + free(trip->notes); + free(trip); +} + +void find_new_trip_start_time(dive_trip_t *trip) +{ + struct dive *dive = trip->dives; + timestamp_t when = dive->when; + + while ((dive = dive->next) != NULL) { + if (dive->when < when) + when = dive->when; + } + trip->when = when; +} + +/* check if we have a trip right before / after this dive */ +bool is_trip_before_after(struct dive *dive, bool before) +{ + int idx = get_idx_by_uniq_id(dive->id); + if (before) { + if (idx > 0 && get_dive(idx - 1)->divetrip) + return true; + } else { + if (idx < dive_table.nr - 1 && get_dive(idx + 1)->divetrip) + return true; + } + return false; +} + +struct dive *first_selected_dive() +{ + int idx; + struct dive *d; + + for_each_dive (idx, d) { + if (d->selected) + return d; + } + return NULL; +} + +struct dive *last_selected_dive() +{ + int idx; + struct dive *d, *ret = NULL; + + for_each_dive (idx, d) { + if (d->selected) + ret = d; + } + return ret; +} + +void remove_dive_from_trip(struct dive *dive, short was_autogen) +{ + struct dive *next, **pprev; + dive_trip_t *trip = dive->divetrip; + + if (!trip) + return; + + /* Remove the dive from the trip's list of dives */ + next = dive->next; + pprev = dive->pprev; + *pprev = next; + if (next) + next->pprev = pprev; + + dive->divetrip = NULL; + if (was_autogen) + dive->tripflag = TF_NONE; + else + dive->tripflag = NO_TRIP; + assert(trip->nrdives > 0); + if (!--trip->nrdives) + delete_trip(trip); + else if (trip->when == dive->when) + find_new_trip_start_time(trip); +} + +void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) +{ + if (dive->divetrip == trip) + return; + remove_dive_from_trip(dive, false); + trip->nrdives++; + dive->divetrip = trip; + dive->tripflag = ASSIGNED_TRIP; + + /* Add it to the trip's list of dives*/ + dive->next = trip->dives; + if (dive->next) + dive->next->pprev = &dive->next; + trip->dives = dive; + dive->pprev = &trip->dives; + + if (dive->when && trip->when > dive->when) + trip->when = dive->when; +} + +dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) +{ + dive_trip_t *dive_trip = calloc(1, sizeof(dive_trip_t)); + + dive_trip->when = dive->when; + dive_trip->location = copy_string(get_dive_location(dive)); + insert_trip(&dive_trip); + + dive->tripflag = IN_TRIP; + add_dive_to_trip(dive, dive_trip); + return dive_trip; +} + +/* + * Walk the dives from the oldest dive, and see if we can autogroup them + */ +void autogroup_dives(void) +{ + int i; + struct dive *dive, *lastdive = NULL; + + for_each_dive(i, dive) { + dive_trip_t *trip; + + if (dive->divetrip) { + lastdive = dive; + continue; + } + + if (!DIVE_NEEDS_TRIP(dive)) { + lastdive = NULL; + continue; + } + + /* Do we have a trip we can combine this into? */ + if (lastdive && dive->when < lastdive->when + TRIP_THRESHOLD) { + dive_trip_t *trip = lastdive->divetrip; + add_dive_to_trip(dive, trip); + if (get_dive_location(dive) && !trip->location) + trip->location = copy_string(get_dive_location(dive)); + lastdive = dive; + continue; + } + + lastdive = dive; + trip = create_and_hookup_trip_from_dive(dive); + trip->autogen = 1; + } + +#ifdef DEBUG_TRIP + dump_trip_list(); +#endif +} + +/* this implements the mechanics of removing the dive from the table, + * but doesn't deal with updating dive trips, etc */ +void delete_single_dive(int idx) +{ + int i; + struct dive *dive = get_dive(idx); + if (!dive) + return; /* this should never happen */ + remove_dive_from_trip(dive, false); + if (dive->selected) + deselect_dive(idx); + for (i = idx; i < dive_table.nr - 1; i++) + dive_table.dives[i] = dive_table.dives[i + 1]; + dive_table.dives[--dive_table.nr] = NULL; + /* free all allocations */ + free(dive->dc.sample); + free((void *)dive->notes); + free((void *)dive->divemaster); + free((void *)dive->buddy); + free((void *)dive->suit); + taglist_free(dive->tag_list); + free(dive); +} + +struct dive **grow_dive_table(struct dive_table *table) +{ + int nr = table->nr, allocated = table->allocated; + struct dive **dives = table->dives; + + if (nr >= allocated) { + allocated = (nr + 32) * 3 / 2; + dives = realloc(dives, allocated * sizeof(struct dive *)); + if (!dives) + exit(1); + table->dives = dives; + table->allocated = allocated; + } + return dives; +} + +void add_single_dive(int idx, struct dive *dive) +{ + int i; + grow_dive_table(&dive_table); + dive_table.nr++; + if (dive->selected) + amount_selected++; + + if (idx < 0) { + // convert an idx of -1 so we do insert-in-chronological-order + idx = dive_table.nr - 1; + for (int i = 0; i < dive_table.nr - 1; i++) { + if (dive->when <= dive_table.dives[i]->when) { + idx = i; + break; + } + } + } + + for (i = idx; i < dive_table.nr; i++) { + struct dive *tmp = dive_table.dives[i]; + dive_table.dives[i] = dive; + dive = tmp; + } +} + +bool consecutive_selected() +{ + struct dive *d; + int i; + bool consecutive = true; + bool firstfound = false; + bool lastfound = false; + + if (amount_selected == 0 || amount_selected == 1) + return true; + + for_each_dive(i, d) { + if (d->selected) { + if (!firstfound) + firstfound = true; + else if (lastfound) + consecutive = false; + } else if (firstfound) { + lastfound = true; + } + } + return consecutive; +} + +/* + * Merge two dives. 'a' is always before 'b' in the dive list + * (and thus in time). + */ +struct dive *merge_two_dives(struct dive *a, struct dive *b) +{ + struct dive *res; + int i, j, nr, nrdiff; + int id; + + if (!a || !b) + return NULL; + + id = a->id; + i = get_divenr(a); + j = get_divenr(b); + if (i < 0 || j < 0) + // something is wrong with those dives. Bail + return NULL; + res = merge_dives(a, b, b->when - a->when, false); + if (!res) + return NULL; + + /* + * If 'a' and 'b' were numbered, and in proper order, + * then the resulting dive will get the first number, + * and the subsequent dives will be renumbered by the + * difference. + * + * So if you had a dive list 1 3 6 7 8, and you + * merge 1 and 3, the resulting numbered list will + * be 1 4 5 6, because we assume that there were + * some missing dives (originally dives 4 and 5), + * that now will still be missing (dives 2 and 3 + * in the renumbered world). + * + * Obviously the normal case is that everything is + * consecutive, and the difference will be 1, so the + * above example is not supposed to be normal. + */ + nrdiff = 0; + nr = a->number; + if (a->number && b->number > a->number) { + res->number = nr; + nrdiff = b->number - nr; + } + + add_single_dive(i, res); + delete_single_dive(i + 1); + delete_single_dive(j); + // now make sure that we keep the id of the first dive. + // why? + // because this way one of the previously selected ids is still around + res->id = id; + + // renumber dives from merged one in advance by difference between + // merged dives numbers. Do not renumber if actual number is zero. + for (; j < dive_table.nr; j++) { + struct dive *dive = dive_table.dives[j]; + int newnr; + + if (!dive->number) + continue; + newnr = dive->number - nrdiff; + + /* + * Don't renumber stuff that isn't in order! + * + * So if the new dive number isn't larger than the + * previous dive number, just stop here. + */ + if (newnr <= nr) + break; + dive->number = newnr; + nr = newnr; + } + + mark_divelist_changed(true); + return res; +} + +void select_dive(int idx) +{ + struct dive *dive = get_dive(idx); + if (dive) { + /* never select an invalid dive that isn't displayed */ + if (!dive->selected) { + dive->selected = 1; + amount_selected++; + } + selected_dive = idx; + } +} + +void deselect_dive(int idx) +{ + struct dive *dive = get_dive(idx); + if (dive && dive->selected) { + dive->selected = 0; + if (amount_selected) + amount_selected--; + if (selected_dive == idx && amount_selected > 0) { + /* pick a different dive as selected */ + while (--selected_dive >= 0) { + dive = get_dive(selected_dive); + if (dive && dive->selected) + return; + } + selected_dive = idx; + while (++selected_dive < dive_table.nr) { + dive = get_dive(selected_dive); + if (dive && dive->selected) + return; + } + } + if (amount_selected == 0) + selected_dive = -1; + } +} + +void deselect_dives_in_trip(struct dive_trip *trip) +{ + struct dive *dive; + if (!trip) + return; + for (dive = trip->dives; dive; dive = dive->next) + deselect_dive(get_divenr(dive)); +} + +void select_dives_in_trip(struct dive_trip *trip) +{ + struct dive *dive; + if (!trip) + return; + for (dive = trip->dives; dive; dive = dive->next) + if (!dive->hidden_by_filter) + select_dive(get_divenr(dive)); +} + +void filter_dive(struct dive *d, bool shown) +{ + if (!d) + return; + d->hidden_by_filter = !shown; + if (!shown && d->selected) + deselect_dive(get_divenr(d)); +} + + +/* This only gets called with non-NULL trips. + * It does not combine notes or location, just picks the first one + * (or the second one if the first one is empty */ +void combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b) +{ + if (same_string(trip_a->location, "") && trip_b->location) { + free(trip_a->location); + trip_a->location = strdup(trip_b->location); + } + if (same_string(trip_a->notes, "") && trip_b->notes) { + free(trip_a->notes); + trip_a->notes = strdup(trip_b->notes); + } + /* this also removes the dives from trip_b and eventually + * calls delete_trip(trip_b) when the last dive has been moved */ + while (trip_b->dives) + add_dive_to_trip(trip_b->dives, trip_a); +} + +void mark_divelist_changed(int changed) +{ + dive_list_changed = changed; + updateWindowTitle(); +} + +int unsaved_changes() +{ + return dive_list_changed; +} + +void remove_autogen_trips() +{ + int i; + struct dive *dive; + + for_each_dive(i, dive) { + dive_trip_t *trip = dive->divetrip; + + if (trip && trip->autogen) + remove_dive_from_trip(dive, true); + } +} + +/* + * When adding dives to the dive table, we try to renumber + * the new dives based on any old dives in the dive table. + * + * But we only do it if: + * + * - there are no dives in the dive table + * + * OR + * + * - the last dive in the old dive table was numbered + * + * - all the new dives are strictly at the end (so the + * "last dive" is at the same location in the dive table + * after re-sorting the dives. + * + * - none of the new dives have any numbers + * + * This catches the common case of importing new dives from + * a dive computer, and gives them proper numbers based on + * your old dive list. But it tries to be very conservative + * and not give numbers if there is *any* question about + * what the numbers should be - in which case you need to do + * a manual re-numbering. + */ +static void try_to_renumber(struct dive *last, int preexisting) +{ + int i, nr; + + /* + * If the new dives aren't all strictly at the end, + * we're going to expect the user to do a manual + * renumbering. + */ + if (preexisting && get_dive(preexisting - 1) != last) + return; + + /* + * If any of the new dives already had a number, + * we'll have to do a manual renumbering. + */ + for (i = preexisting; i < dive_table.nr; i++) { + struct dive *dive = get_dive(i); + if (dive->number) + return; + } + + /* + * Ok, renumber.. + */ + if (last) + nr = last->number; + else + nr = 0; + for (i = preexisting; i < dive_table.nr; i++) { + struct dive *dive = get_dive(i); + dive->number = ++nr; + } +} + +void process_dives(bool is_imported, bool prefer_imported) +{ + int i; + int preexisting = dive_table.preexisting; + bool did_merge = false; + struct dive *last; + + /* check if we need a nickname for the divecomputer for newly downloaded dives; + * since we know they all came from the same divecomputer we just check for the + * first one */ + if (preexisting < dive_table.nr && dive_table.dives[preexisting]->downloaded) + set_dc_nickname(dive_table.dives[preexisting]); + else + /* they aren't downloaded, so record / check all new ones */ + for (i = preexisting; i < dive_table.nr; i++) + set_dc_nickname(dive_table.dives[i]); + + for (i = preexisting; i < dive_table.nr; i++) + dive_table.dives[i]->downloaded = true; + + /* This does the right thing for -1: NULL */ + last = get_dive(preexisting - 1); + + sort_table(&dive_table); + + for (i = 1; i < dive_table.nr; i++) { + struct dive **pp = &dive_table.dives[i - 1]; + struct dive *prev = pp[0]; + struct dive *dive = pp[1]; + struct dive *merged; + int id; + + /* only try to merge overlapping dives - or if one of the dives has + * zero duration (that might be a gps marker from the webservice) */ + if (prev->duration.seconds && dive->duration.seconds && + prev->when + prev->duration.seconds < dive->when) + continue; + + merged = try_to_merge(prev, dive, prefer_imported); + if (!merged) + continue; + + // remember the earlier dive's id + id = prev->id; + + /* careful - we might free the dive that last points to. Oops... */ + if (last == prev || last == dive) + last = merged; + + /* Redo the new 'i'th dive */ + i--; + add_single_dive(i, merged); + delete_single_dive(i + 1); + delete_single_dive(i + 1); + // keep the id or the first dive for the merged dive + merged->id = id; + + /* this means the table was changed */ + did_merge = true; + } + /* make sure no dives are still marked as downloaded */ + for (i = 1; i < dive_table.nr; i++) + dive_table.dives[i]->downloaded = false; + + if (is_imported) { + /* If there are dives in the table, are they numbered */ + if (!last || last->number) + try_to_renumber(last, preexisting); + + /* did we add dives or divecomputers to the dive table? */ + if (did_merge || preexisting < dive_table.nr) { + mark_divelist_changed(true); + } + } +} + +void set_dive_nr_for_current_dive() +{ + if (dive_table.nr == 1) + current_dive->number = 1; + else if (selected_dive == dive_table.nr - 1 && get_dive(dive_table.nr - 2)->number) + current_dive->number = get_dive(dive_table.nr - 2)->number + 1; +} + +static int min_datafile_version; + +int get_min_datafile_version() +{ + return min_datafile_version; +} + +void reset_min_datafile_version() +{ + min_datafile_version = 0; +} + +void report_datafile_version(int version) +{ + if (min_datafile_version == 0 || min_datafile_version > version) + min_datafile_version = version; +} + +void clear_dive_file_data() +{ + while (dive_table.nr) + delete_single_dive(0); + while (dive_site_table.nr) + delete_dive_site(get_dive_site(0)->uuid); + + clear_dive(&displayed_dive); + clear_dive_site(&displayed_dive_site); + + free((void *)existing_filename); + existing_filename = NULL; + + reset_min_datafile_version(); + saved_git_id = ""; +} diff --git a/core/divelist.h b/core/divelist.h new file mode 100644 index 000000000..5bae09cff --- /dev/null +++ b/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/core/divelogexportlogic.cpp b/core/divelogexportlogic.cpp new file mode 100644 index 000000000..af5157f4a --- /dev/null +++ b/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/core/divelogexportlogic.h b/core/divelogexportlogic.h new file mode 100644 index 000000000..84f09c362 --- /dev/null +++ b/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/core/divesite.c b/core/divesite.c new file mode 100644 index 000000000..e9eed2a07 --- /dev/null +++ b/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/core/divesite.cpp b/core/divesite.cpp new file mode 100644 index 000000000..ae102a14b --- /dev/null +++ b/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/core/divesite.h b/core/divesite.h new file mode 100644 index 000000000..f18b2e8e8 --- /dev/null +++ b/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/core/divesitehelpers.cpp b/core/divesitehelpers.cpp new file mode 100644 index 000000000..3542f96fa --- /dev/null +++ b/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/core/divesitehelpers.h b/core/divesitehelpers.h new file mode 100644 index 000000000..a08069bc0 --- /dev/null +++ b/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/core/equipment.c b/core/equipment.c new file mode 100644 index 000000000..9f3e49039 --- /dev/null +++ b/core/equipment.c @@ -0,0 +1,238 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +/* 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/core/exif.cpp b/core/exif.cpp new file mode 100644 index 000000000..0b1cda2bc --- /dev/null +++ b/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/core/exif.h b/core/exif.h new file mode 100644 index 000000000..0fb3a7d4a --- /dev/null +++ b/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/core/file.c b/core/file.c new file mode 100644 index 000000000..1337da3a2 --- /dev/null +++ b/core/file.c @@ -0,0 +1,1115 @@ +#include +#include +#include +#include +#include +#include +#include "gettext.h" +#include +#include + +#include "dive.h" +#include "divelist.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 == (int)mem->size) // converting to int loses a bit but size will never be that big + goto out; + errno = EIO; + ret = -1; +free: + free(mem->buffer); + mem->buffer = NULL; + mem->size = 0; +out: + close(fd); + return ret; +} + + +static void zip_read(struct zip_file *file, const char *filename) +{ + int size = 1024, n, read = 0; + char *mem = malloc(size); + + while ((n = zip_fread(file, mem + read, size - read)) > 0) { + read += n; + size = read * 3 / 2; + mem = realloc(mem, size); + } + mem[read] = 0; + (void) parse_xml_buffer(filename, mem, read, &dive_table, NULL); + free(mem); +} + +int try_to_open_zip(const char *filename) +{ + int success = 0; + /* Grr. libzip needs to re-open the file, it can't take a buffer */ + struct zip *zip = subsurface_zip_open_readonly(filename, ZIP_CHECKCONS, NULL); + + if (zip) { + int index; + for (index = 0;; index++) { + struct zip_file *file = zip_fopen_index(zip, index, 0); + if (!file) + break; + /* skip parsing the divelogs.de pictures */ + if (strstr(zip_get_name(zip, index, 0), "pictures/")) + continue; + zip_read(file, filename); + zip_fclose(file); + success++; + } + subsurface_zip_close(zip); + + if (!success) + return report_error(translate("gettextFromC", "No dives in the input file '%s'"), filename); + } + return success; +} + +static int try_to_xslt_open_csv(const char *filename, struct memblock *mem, const char *tag) +{ + char *buf; + + if (mem->size == 0 && readfile(filename, mem) < 0) + return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); + + /* Surround the CSV file content with XML tags to enable XSLT + * parsing + * + * Tag markers take: strlen("<>") = 5 + */ + buf = realloc(mem->buffer, mem->size + 7 + strlen(tag) * 2); + if (buf != NULL) { + char *starttag = NULL; + char *endtag = NULL; + + starttag = malloc(3 + strlen(tag)); + endtag = malloc(5 + strlen(tag)); + + if (starttag == NULL || endtag == NULL) { + /* this is fairly silly - so the malloc fails, but we strdup the error? + * let's complete the silliness by freeing the two pointers in case one malloc succeeded + * and the other one failed - this will make static analysis tools happy */ + free(starttag); + free(endtag); + free(buf); + return report_error("Memory allocation failed in %s", __func__); + } + + sprintf(starttag, "<%s>", tag); + sprintf(endtag, "\n", tag); + + memmove(buf + 2 + strlen(tag), buf, mem->size); + memcpy(buf, starttag, 2 + strlen(tag)); + memcpy(buf + mem->size + 2 + strlen(tag), endtag, 5 + strlen(tag)); + mem->size += (6 + 2 * strlen(tag)); + mem->buffer = buf; + + free(starttag); + free(endtag); + } else { + free(mem->buffer); + return report_error("realloc failed in %s", __func__); + } + + return 0; +} + +int db_test_func(void *param, int columns, char **data, char **column) +{ + (void) param; + (void) columns; + (void) column; + return *data[0] == '0'; +} + + +static int try_to_open_db(const char *filename, struct memblock *mem) +{ + sqlite3 *handle; + char dm4_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%ProfileBlob%'"; + char dm5_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%SampleBlob%'"; + char shearwater_test[] = "select count(*) from sqlite_master where type='table' and name='system' and sql like '%dbVersion%'"; + char cobalt_test[] = "select count(*) from sqlite_master where type='table' and name='TrackPoints' and sql like '%DepthPressure%'"; + char divinglog_test[] = "select count(*) from sqlite_master where type='table' and name='DBInfo' and sql like '%PrgName%'"; + int retval; + + retval = sqlite3_open(filename, &handle); + + if (retval) { + fprintf(stderr, "Database connection failed '%s'.\n", filename); + return 1; + } + + /* Testing if DB schema resembles Suunto DM5 database format */ + retval = sqlite3_exec(handle, dm5_test, &db_test_func, 0, NULL); + if (!retval) { + retval = parse_dm5_buffer(handle, filename, mem->buffer, mem->size, &dive_table); + sqlite3_close(handle); + return retval; + } + + /* Testing if DB schema resembles Suunto DM4 database format */ + retval = sqlite3_exec(handle, dm4_test, &db_test_func, 0, NULL); + if (!retval) { + retval = parse_dm4_buffer(handle, filename, mem->buffer, mem->size, &dive_table); + sqlite3_close(handle); + return retval; + } + + /* Testing if DB schema resembles Shearwater database format */ + retval = sqlite3_exec(handle, shearwater_test, &db_test_func, 0, NULL); + if (!retval) { + retval = parse_shearwater_buffer(handle, filename, mem->buffer, mem->size, &dive_table); + sqlite3_close(handle); + return retval; + } + + /* Testing if DB schema resembles Atomic Cobalt database format */ + retval = sqlite3_exec(handle, cobalt_test, &db_test_func, 0, NULL); + if (!retval) { + retval = parse_cobalt_buffer(handle, filename, mem->buffer, mem->size, &dive_table); + sqlite3_close(handle); + return retval; + } + + /* Testing if DB schema resembles Divinglog database format */ + retval = sqlite3_exec(handle, divinglog_test, &db_test_func, 0, NULL); + if (!retval) { + retval = parse_divinglog_buffer(handle, filename, mem->buffer, mem->size, &dive_table); + sqlite3_close(handle); + return retval; + } + + sqlite3_close(handle); + + return retval; +} + +timestamp_t parse_date(const char *date) +{ + int hour, min, sec; + struct tm tm; + char *p; + + memset(&tm, 0, sizeof(tm)); + tm.tm_mday = strtol(date, &p, 10); + if (tm.tm_mday < 1 || tm.tm_mday > 31) + return 0; + for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) { + if (!memcmp(p, monthname(tm.tm_mon), 3)) + break; + } + if (tm.tm_mon > 11) + return 0; + date = p + 3; + tm.tm_year = strtol(date, &p, 10); + if (date == p) + return 0; + if (tm.tm_year < 70) + tm.tm_year += 2000; + if (tm.tm_year < 100) + tm.tm_year += 1900; + if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3) + return 0; + tm.tm_hour = hour; + tm.tm_min = min; + tm.tm_sec = sec; + return utc_mktime(&tm); +} + +enum csv_format { + CSV_DEPTH, + CSV_TEMP, + CSV_PRESSURE, + POSEIDON_DEPTH, + POSEIDON_TEMP, + POSEIDON_SETPOINT, + POSEIDON_SENSOR1, + POSEIDON_SENSOR2, + POSEIDON_PRESSURE, + POSEIDON_O2CYLINDER, + POSEIDON_NDL, + POSEIDON_CEILING +}; + +static void add_sample_data(struct sample *sample, enum csv_format type, double val) +{ + switch (type) { + case CSV_DEPTH: + sample->depth.mm = feet_to_mm(val); + break; + case CSV_TEMP: + sample->temperature.mkelvin = F_to_mkelvin(val); + break; + case CSV_PRESSURE: + sample->cylinderpressure.mbar = psi_to_mbar(val * 4); + break; + case POSEIDON_DEPTH: + sample->depth.mm = val * 0.5 *1000; + break; + case POSEIDON_TEMP: + sample->temperature.mkelvin = C_to_mkelvin(val * 0.2); + break; + case POSEIDON_SETPOINT: + sample->setpoint.mbar = val * 10; + break; + case POSEIDON_SENSOR1: + sample->o2sensor[0].mbar = val * 10; + break; + case POSEIDON_SENSOR2: + sample->o2sensor[1].mbar = val * 10; + break; + case POSEIDON_PRESSURE: + sample->cylinderpressure.mbar = val * 1000; + break; + case POSEIDON_O2CYLINDER: + sample->o2cylinderpressure.mbar = val * 1000; + break; + case POSEIDON_NDL: + sample->ndl.seconds = val * 60; + break; + case POSEIDON_CEILING: + sample->stopdepth.mm = val * 1000; + break; + } +} + +/* + * Cochran comma-separated values: depth in feet, temperature in F, pressure in psi. + * + * They start with eight comma-separated fields like: + * + * filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can} + * divenr: %d + * datetime: {03Sep11 16:37:22},{15Dec11 18:27:02} + * ??: 1 + * serialnr??: {CCI134},{CCI207} + * computer??: {GeminiII},{CommanderIII} + * computer??: {GeminiII},{CommanderIII} + * ??: 1 + * + * Followed by the data values (all comma-separated, all one long line). + */ +static int try_to_open_csv(struct memblock *mem, enum csv_format type) +{ + char *p = mem->buffer; + char *header[8]; + int i, time; + timestamp_t date; + struct dive *dive; + struct divecomputer *dc; + + for (i = 0; i < 8; i++) { + header[i] = p; + p = strchr(p, ','); + if (!p) + return 0; + p++; + } + + date = parse_date(header[2]); + if (!date) + return 0; + + dive = alloc_dive(); + dive->when = date; + dive->number = atoi(header[1]); + dc = &dive->dc; + + time = 0; + for (;;) { + char *end; + double val; + struct sample *sample; + + errno = 0; + val = strtod(p, &end); // FIXME == localization issue + if (end == p) + break; + if (errno) + break; + + sample = prepare_sample(dc); + sample->time.seconds = time; + add_sample_data(sample, type, val); + finish_sample(dc); + + time++; + dc->duration.seconds = time; + if (*end != ',') + break; + p = end + 1; + } + record_dive(dive); + return 1; +} + +static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem) +{ + // hack to be able to provide a comment for the translated string + static char *csv_warning = QT_TRANSLATE_NOOP3("gettextFromC", + "Cannot open CSV file %s; please use Import log file dialog", + "'Import log file' should be the same text as corresponding label in Import menu"); + + /* Suunto Dive Manager files: SDE, ZIP; divelogs.de files: DLD */ + if (!strcasecmp(fmt, "SDE") || !strcasecmp(fmt, "ZIP") || !strcasecmp(fmt, "DLD")) + return try_to_open_zip(filename); + + /* CSV files */ + if (!strcasecmp(fmt, "CSV")) + return report_error(translate("gettextFromC", csv_warning), filename); + /* Truly nasty intentionally obfuscated Cochran Anal software */ + if (!strcasecmp(fmt, "CAN")) + return try_to_open_cochran(filename, mem); + /* Cochran export comma-separated-value files */ + if (!strcasecmp(fmt, "DPT")) + return try_to_open_csv(mem, CSV_DEPTH); + if (!strcasecmp(fmt, "LVD")) + return try_to_open_liquivision(filename, mem); + if (!strcasecmp(fmt, "TMP")) + return try_to_open_csv(mem, CSV_TEMP); + if (!strcasecmp(fmt, "HP1")) + return try_to_open_csv(mem, CSV_PRESSURE); + + return 0; +} + +static int parse_file_buffer(const char *filename, struct memblock *mem) +{ + int ret; + char *fmt = strrchr(filename, '.'); + if (fmt && (ret = open_by_filename(filename, fmt + 1, mem)) != 0) + return ret; + + if (!mem->size || !mem->buffer) + return report_error("Out of memory parsing file %s\n", filename); + + return parse_xml_buffer(filename, mem->buffer, mem->size, &dive_table, NULL); +} + +int check_git_sha(const char *filename) +{ + struct git_repository *git; + const char *branch = NULL; + + git = is_git_repository(filename, &branch, NULL, false); + if (prefs.cloud_git_url && + strstr(filename, prefs.cloud_git_url) + && git == dummy_git_repository) + /* opening the cloud storage repository failed for some reason, + * so we don't know if there is additional data in the remote */ + return 1; + + /* if this is a git repository, do we already have this exact state loaded ? + * get the SHA and compare with what we currently have */ + if (git && git != dummy_git_repository) { + const char *sha = get_sha(git, branch); + if (!same_string(sha, "") && + same_string(sha, saved_git_id)) { + fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha); + return 0; + } + } + return 1; +} + +int parse_file(const char *filename) +{ + struct git_repository *git; + const char *branch = NULL; + char *current_sha = copy_string(saved_git_id); + struct memblock mem; + char *fmt; + int ret; + + git = is_git_repository(filename, &branch, NULL, false); + if (prefs.cloud_git_url && + strstr(filename, prefs.cloud_git_url) + && git == dummy_git_repository) { + /* opening the cloud storage repository failed for some reason + * give up here and don't send errors about git repositories */ + free(current_sha); + return 0; + } + /* if this is a git repository, do we already have this exact state loaded ? + * get the SHA and compare with what we currently have */ + if (git && git != dummy_git_repository) { + const char *sha = get_sha(git, branch); + if (!same_string(sha, "") && + same_string(sha, current_sha) && + !unsaved_changes()) { + fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha); + free(current_sha); + return 0; + } + } + free(current_sha); + if (git) + return git_load_dives(git, branch); + + if ((ret = readfile(filename, &mem)) < 0) { + /* we don't want to display an error if this was the default file or the cloud storage */ + if ((prefs.default_filename && !strcmp(filename, prefs.default_filename)) || + isCloudUrl(filename)) + return 0; + + return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); + } else if (ret == 0) { + return report_error(translate("gettextFromC", "Empty file '%s'"), filename); + } + + fmt = strrchr(filename, '.'); + if (fmt && (!strcasecmp(fmt + 1, "DB") || !strcasecmp(fmt + 1, "BAK") || !strcasecmp(fmt + 1, "SQL"))) { + if (!try_to_open_db(filename, &mem)) { + free(mem.buffer); + return 0; + } + } + + /* Divesoft Freedom */ + if (fmt && (!strcasecmp(fmt + 1, "DLF"))) { + if (!parse_dlf_buffer(mem.buffer, mem.size)) { + free(mem.buffer); + return 0; + } + return -1; + } + + /* DataTrak/Wlog */ + if (fmt && !strcasecmp(fmt + 1, "LOG")) { + datatrak_import(filename, &dive_table); + return 0; + } + + /* OSTCtools */ + if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) { + ostctools_import(filename, &dive_table); + return 0; + } + + ret = parse_file_buffer(filename, &mem); + free(mem.buffer); + return ret; +} + +#define MATCH(buffer, pattern) \ + memcmp(buffer, pattern, strlen(pattern)) + +char *parse_mkvi_value(const char *haystack, const char *needle) +{ + char *lineptr, *valueptr, *endptr, *ret = NULL; + + if ((lineptr = strstr(haystack, needle)) != NULL) { + if ((valueptr = strstr(lineptr, ": ")) != NULL) { + valueptr += 2; + } + if ((endptr = strstr(lineptr, "\n")) != NULL) { + char terminator = '\n'; + if (*(endptr - 1) == '\r') { + --endptr; + terminator = '\r'; + } + *endptr = 0; + ret = copy_string(valueptr); + *endptr = terminator; + + } + } + return ret; +} + +char *next_mkvi_key(const char *haystack) +{ + char *valueptr, *endptr, *ret = NULL; + + if ((valueptr = strstr(haystack, "\n")) != NULL) { + valueptr += 1; + if ((endptr = strstr(valueptr, ": ")) != NULL) { + *endptr = 0; + ret = strdup(valueptr); + *endptr = ':'; + } + } + return ret; +} + +int parse_txt_file(const char *filename, const char *csv) +{ + struct memblock memtxt, memcsv; + + if (readfile(filename, &memtxt) < 0) { + return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); + } + + /* + * MkVI stores some information in .txt file but the whole profile and events are stored in .csv file. First + * make sure the input .txt looks like proper MkVI file, then start parsing the .csv. + */ + if (MATCH(memtxt.buffer, "MkVI_Config") == 0) { + int d, m, y, he; + int hh = 0, mm = 0, ss = 0; + int prev_depth = 0, cur_sampletime = 0, prev_setpoint = -1, prev_ndl = -1; + bool has_depth = false, has_setpoint = false, has_ndl = false; + char *lineptr, *key, *value; + int o2cylinder_pressure = 0, cylinder_pressure = 0, cur_cylinder_index = 0; + unsigned int prev_time = 0; + + struct dive *dive; + struct divecomputer *dc; + struct tm cur_tm; + + value = parse_mkvi_value(memtxt.buffer, "Dive started at"); + if (sscanf(value, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) != 6) { + free(value); + return -1; + } + free(value); + cur_tm.tm_year = y; + cur_tm.tm_mon = m - 1; + cur_tm.tm_mday = d; + cur_tm.tm_hour = hh; + cur_tm.tm_min = mm; + cur_tm.tm_sec = ss; + + dive = alloc_dive(); + dive->when = utc_mktime(&cur_tm);; + dive->dc.model = strdup("Poseidon MkVI Discovery"); + value = parse_mkvi_value(memtxt.buffer, "Rig Serial number"); + dive->dc.deviceid = atoi(value); + free(value); + dive->dc.divemode = CCR; + dive->dc.no_o2sensors = 2; + + dive->cylinder[cur_cylinder_index].cylinder_use = OXYGEN; + dive->cylinder[cur_cylinder_index].type.size.mliter = 3000; + dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000; + dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6"); + dive->cylinder[cur_cylinder_index].gasmix.o2.permille = 1000; + cur_cylinder_index++; + + dive->cylinder[cur_cylinder_index].cylinder_use = DILUENT; + dive->cylinder[cur_cylinder_index].type.size.mliter = 3000; + dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000; + dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6"); + value = parse_mkvi_value(memtxt.buffer, "Helium percentage"); + he = atoi(value); + free(value); + value = parse_mkvi_value(memtxt.buffer, "Nitrogen percentage"); + dive->cylinder[cur_cylinder_index].gasmix.o2.permille = (100 - atoi(value) - he) * 10; + free(value); + dive->cylinder[cur_cylinder_index].gasmix.he.permille = he * 10; + cur_cylinder_index++; + + lineptr = strstr(memtxt.buffer, "Dive started at"); + while (lineptr && *lineptr && (lineptr = strchr(lineptr, '\n')) && ++lineptr) { + key = next_mkvi_key(lineptr); + if (!key) + break; + value = parse_mkvi_value(lineptr, key); + if (!value) { + free(key); + break; + } + add_extra_data(&dive->dc, key, value); + free(key); + free(value); + } + dc = &dive->dc; + + /* + * Read samples from the CSV file. A sample contains all the lines with same timestamp. The CSV file has + * the following format: + * + * timestamp, type, value + * + * And following fields are of interest to us: + * + * 6 sensor1 + * 7 sensor2 + * 8 depth + * 13 o2 tank pressure + * 14 diluent tank pressure + * 20 o2 setpoint + * 39 water temp + */ + + if (readfile(csv, &memcsv) < 0) { + free(dive); + return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv); + } + lineptr = memcsv.buffer; + for (;;) { + struct sample *sample; + int type; + int value; + int sampletime; + int gaschange = 0; + + /* Collect all the information for one sample */ + sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value); + + has_depth = false; + has_setpoint = false; + has_ndl = false; + sample = prepare_sample(dc); + + /* + * There was a bug in MKVI download tool that resulted in erroneous sample + * times. This fix should work similarly as the vendor's own. + */ + + sample->time.seconds = cur_sampletime < 0xFFFF * 3 / 4 ? cur_sampletime : prev_time; + prev_time = sample->time.seconds; + + do { + int i = sscanf(lineptr, "%d,%d,%d", &sampletime, &type, &value); + switch (i) { + case 3: + switch (type) { + case 0: + //Mouth piece position event: 0=OC, 1=CC, 2=UN, 3=NC + switch (value) { + case 0: + add_event(dc, cur_sampletime, 0, 0, 0, + QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position OC")); + break; + case 1: + add_event(dc, cur_sampletime, 0, 0, 0, + QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position CC")); + break; + case 2: + add_event(dc, cur_sampletime, 0, 0, 0, + QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position unknown")); + break; + case 3: + add_event(dc, cur_sampletime, 0, 0, 0, + QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position not connected")); + break; + } + break; + case 3: + //Power Off event + add_event(dc, cur_sampletime, 0, 0, 0, + QT_TRANSLATE_NOOP("gettextFromC", "Power off")); + break; + case 4: + //Battery State of Charge in % +#ifdef SAMPLE_EVENT_BATTERY + add_event(dc, cur_sampletime, SAMPLE_EVENT_BATTERY, 0, + value, QT_TRANSLATE_NOOP("gettextFromC", "battery")); +#endif + break; + case 6: + //PO2 Cell 1 Average + add_sample_data(sample, POSEIDON_SENSOR1, value); + break; + case 7: + //PO2 Cell 2 Average + add_sample_data(sample, POSEIDON_SENSOR2, value); + break; + case 8: + //Depth * 2 + has_depth = true; + prev_depth = value; + add_sample_data(sample, POSEIDON_DEPTH, value); + break; + //9 Max Depth * 2 + //10 Ascent/Descent Rate * 2 + case 11: + //Ascent Rate Alert >10 m/s + add_event(dc, cur_sampletime, SAMPLE_EVENT_ASCENT, 0, 0, + QT_TRANSLATE_NOOP("gettextFromC", "ascent")); + break; + case 13: + //O2 Tank Pressure + add_sample_data(sample, POSEIDON_O2CYLINDER, value); + if (!o2cylinder_pressure) { + dive->cylinder[0].sample_start.mbar = value * 1000; + o2cylinder_pressure = value; + } else + o2cylinder_pressure = value; + break; + case 14: + //Diluent Tank Pressure + add_sample_data(sample, POSEIDON_PRESSURE, value); + if (!cylinder_pressure) { + dive->cylinder[1].sample_start.mbar = value * 1000; + cylinder_pressure = value; + } else + cylinder_pressure = value; + break; + //16 Remaining dive time #1? + //17 related to O2 injection + case 20: + //PO2 Setpoint + has_setpoint = true; + prev_setpoint = value; + add_sample_data(sample, POSEIDON_SETPOINT, value); + break; + case 22: + //End of O2 calibration Event: 0 = OK, 2 = Failed, rest of dive setpoint 1.0 + if (value == 2) + add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0, + QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration failed")); + add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0, + QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration")); + break; + case 25: + //25 Max Ascent depth + add_sample_data(sample, POSEIDON_CEILING, value); + break; + case 31: + //Start of O2 calibration Event + add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_BEGIN, 0, + QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration")); + break; + case 37: + //Remaining dive time #2? + has_ndl = true; + prev_ndl = value; + add_sample_data(sample, POSEIDON_NDL, value); + break; + case 39: + // Water Temperature in Celcius + add_sample_data(sample, POSEIDON_TEMP, value); + break; + case 85: + //He diluent part in % + gaschange += value << 16; + break; + case 86: + //O2 diluent part in % + gaschange += value; + break; + //239 Unknown, maybe PO2 at sensor validation? + //240 Unknown, maybe PO2 at sensor validation? + //247 Unknown, maybe PO2 Cell 1 during pressure test + //248 Unknown, maybe PO2 Cell 2 during pressure test + //250 PO2 Cell 1 + //251 PO2 Cell 2 + default: + break; + } /* sample types */ + break; + case EOF: + break; + default: + printf("Unable to parse input: %s\n", lineptr); + break; + } + + lineptr = strchr(lineptr, '\n'); + if (!lineptr || !*lineptr) + break; + lineptr++; + + /* Grabbing next sample time */ + sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value); + } while (sampletime == cur_sampletime); + + if (gaschange) + add_event(dc, cur_sampletime, SAMPLE_EVENT_GASCHANGE2, 0, gaschange, + QT_TRANSLATE_NOOP("gettextFromC", "gaschange")); + if (!has_depth) + add_sample_data(sample, POSEIDON_DEPTH, prev_depth); + if (!has_setpoint && prev_setpoint >= 0) + add_sample_data(sample, POSEIDON_SETPOINT, prev_setpoint); + if (!has_ndl && prev_ndl >= 0) + add_sample_data(sample, POSEIDON_NDL, prev_ndl); + if (cylinder_pressure) + dive->cylinder[1].sample_end.mbar = cylinder_pressure * 1000; + if (o2cylinder_pressure) + dive->cylinder[0].sample_end.mbar = o2cylinder_pressure * 1000; + finish_sample(dc); + + if (!lineptr || !*lineptr) + break; + } + record_dive(dive); + return 1; + } else { + return report_error(translate("gettextFromC", "No matching DC found for file '%s'"), csv); + } + + return 0; +} + +#define MAXCOLDIGITS 10 +#define DATESTR 9 +#define TIMESTR 6 + +int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate) +{ + int ret, i; + struct memblock mem; + time_t now; + struct tm *timep = NULL; + char tmpbuf[MAXCOLDIGITS]; + + /* Increase the limits for recursion and variables on XSLT + * parsing */ + xsltMaxDepth = 30000; +#if LIBXSLT_VERSION > 10126 + xsltMaxVars = 150000; +#endif + + if (filename == NULL) + return report_error("No CSV filename"); + + time(&now); + timep = localtime(&now); + + strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep); + params[pnr++] = "date"; + params[pnr++] = strdup(tmpbuf); + + /* As the parameter is numeric, we need to ensure that the leading zero + * is not discarded during the transform, thus prepend time with 1 */ + + strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep); + params[pnr++] = "time"; + params[pnr++] = strdup(tmpbuf); + params[pnr++] = NULL; + + mem.size = 0; + if (try_to_xslt_open_csv(filename, &mem, csvtemplate)) + return -1; + + /* + * Lets print command line for manual testing with xsltproc if + * verbosity level is high enough. The printed line needs the + * input file added as last parameter. + */ + +#ifndef SUBSURFACE_MOBILE + if (verbose >= 2) { + fprintf(stderr, "(echo ''; 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); + } +#endif + ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params); + + free(mem.buffer); + for (i = 0; params[i]; i += 2) + free(params[i + 1]); + + return ret; +} + +#define SBPARAMS 40 +int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate) +{ + int ret, i; + struct memblock mem; + time_t now; + struct tm *timep = NULL; + char *ptr, *ptr_old = NULL; + char *NL = NULL; + char tmpbuf[MAXCOLDIGITS]; + + /* Increase the limits for recursion and variables on XSLT + * parsing */ + xsltMaxDepth = 30000; +#if LIBXSLT_VERSION > 10126 + xsltMaxVars = 150000; +#endif + + time(&now); + timep = localtime(&now); + + strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep); + params[pnr++] = "date"; + params[pnr++] = strdup(tmpbuf); + + /* As the parameter is numeric, we need to ensure that the leading zero + * is not discarded during the transform, thus prepend time with 1 */ + strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep); + params[pnr++] = "time"; + params[pnr++] = strdup(tmpbuf); + + + if (filename == NULL) + return report_error("No CSV filename"); + + if (readfile(filename, &mem) < 0) + return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); + + /* Determine NL (new line) character and the start of CSV data */ + ptr = mem.buffer; + while ((ptr = strstr(ptr, "\r\n\r\n")) != NULL) { + ptr_old = ptr; + ptr += 1; + NL = "\r\n"; + } + + if (!ptr_old) { + ptr = mem.buffer; + while ((ptr = strstr(ptr, "\n\n")) != NULL) { + ptr_old = ptr; + ptr += 1; + NL = "\n"; + } + ptr_old += 2; + } else + ptr_old += 4; + + /* + * If file does not contain empty lines, it is not a valid + * Seabear CSV file. + */ + if (NL == NULL) + return -1; + + /* + * On my current sample of Seabear DC log file, the date is + * without any identifier. Thus we must search for the previous + * line and step through from there. That is the line after + * Serial number. + */ + ptr = strstr(mem.buffer, "Serial number:"); + if (ptr) + ptr = strstr(ptr, NL); + + /* + * Write date and time values to params array, if available in + * the CSV header + */ + + if (ptr) { + ptr += strlen(NL) + 2; + /* + * pnr is the index of NULL on the params as filled by + * the init function. The two last entries should be + * date and time. Here we overwrite them with the data + * from the CSV header. + */ + + memcpy(params[pnr - 3], ptr, 4); + memcpy(params[pnr - 3] + 4, ptr + 5, 2); + memcpy(params[pnr - 3] + 6, ptr + 8, 2); + params[pnr - 3][8] = 0; + + memcpy(params[pnr - 1] + 1, ptr + 11, 2); + memcpy(params[pnr - 1] + 3, ptr + 14, 2); + params[pnr - 1][5] = 0; + } + + params[pnr++] = NULL; + + /* Move the CSV data to the start of mem buffer */ + memmove(mem.buffer, ptr_old, mem.size - (ptr_old - (char*)mem.buffer)); + mem.size = (int)mem.size - (ptr_old - (char*)mem.buffer); + + if (try_to_xslt_open_csv(filename, &mem, csvtemplate)) + return -1; + + /* + * Lets print command line for manual testing with xsltproc if + * verbosity level is high enough. The printed line needs the + * input file added as last parameter. + */ + + if (verbose >= 2) { + fprintf(stderr, "xsltproc "); + for (i=0; params[i]; i+=2) + fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]); + fprintf(stderr, "xslt/csv2xml.xslt\n"); + } + + ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params); + free(mem.buffer); + for (i = 0; params[i]; i += 2) + free(params[i + 1]); + + return ret; +} + +int parse_manual_file(const char *filename, char **params, int pnr) +{ + struct memblock mem; + time_t now; + struct tm *timep; + char curdate[9]; + char curtime[6]; + int ret, i; + + + time(&now); + timep = localtime(&now); + strftime(curdate, DATESTR, "%Y%m%d", timep); + + /* As the parameter is numeric, we need to ensure that the leading zero + * is not discarded during the transform, thus prepend time with 1 */ + strftime(curtime, TIMESTR, "1%H%M", timep); + + + params[pnr++] = strdup("date"); + params[pnr++] = strdup(curdate); + params[pnr++] = strdup("time"); + params[pnr++] = strdup(curtime); + params[pnr++] = NULL; + + if (filename == NULL) + return report_error("No manual CSV filename"); + + mem.size = 0; + if (try_to_xslt_open_csv(filename, &mem, "manualCSV")) + return -1; + + ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params); + + free(mem.buffer); + for (i = 0; i < pnr - 2; ++i) + free(params[i]); + return ret; +} diff --git a/core/file.h b/core/file.h new file mode 100644 index 000000000..1c1dfc116 --- /dev/null +++ b/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); +#ifdef __cplusplus +} +#endif + +#endif // FILE_H diff --git a/core/gas-model.c b/core/gas-model.c new file mode 100644 index 000000000..ad1160f3b --- /dev/null +++ b/core/gas-model.c @@ -0,0 +1,64 @@ +/* gas-model.c */ +/* gas compressibility model */ +#include +#include +#include "dive.h" + +/* "Virial minus one" - the virial cubic form without the initial 1.0 */ +#define virial_m1(C, x1, x2, x3) (C[0]*x1+C[1]*x2+C[2]*x3) + +/* + * Cubic virial least-square coefficients for O2/N2/He based on data from + * + * PERRY’S CHEMICAL ENGINEERS’ HANDBOOK SEVENTH EDITION + * + * with the lookup and curve fitting by Lubomir. + * + * The "virial" form of the compression factor polynomial is + * + * Z = 1.0 + C[0]*P + C[1]*P^2 + C[2]*P^3 ... + * + * and these tables do not contain the initial 1.0 term. + * + * NOTE! Helium coefficients are a linear mix operation between the + * 323K and one for 273K isotherms, to make everything be at 300K. + */ +double gas_compressibility_factor(struct gasmix *gas, double bar) +{ + static const double o2_coefficients[3] = { + -7.18092073703e-04, + +2.81852572808e-06, + -1.50290620492e-09 + }; + static const double n2_coefficients[3] = { + -2.19260353292e-04, + +2.92844845532e-06, + -2.07613482075e-09 + }; + static const double he_coefficients[3] = { + +4.87320026468e-04, + -8.83632921053e-08, + +5.33304543646e-11 + }; + int o2, he; + double x1, x2, x3; + double Z; + + o2 = get_o2(gas); + he = get_he(gas); + + x1 = bar; x2 = x1*x1; x3 = x2*x1; + + Z = virial_m1(o2_coefficients, x1, x2, x3) * o2 + + virial_m1(he_coefficients, x1, x2, x3) * he + + virial_m1(n2_coefficients, x1, x2, x3) * (1000 - o2 - he); + + /* + * We add the 1.0 at the very end - the linear mixing of the + * three 1.0 terms is still 1.0 regardless of the gas mix. + * + * The * 0.001 is because we did the linear mixing using the + * raw permille gas values. + */ + return Z * 0.001 + 1.0; +} diff --git a/core/gaspressures.c b/core/gaspressures.c new file mode 100644 index 000000000..5d3fc9791 --- /dev/null +++ b/core/gaspressures.c @@ -0,0 +1,430 @@ +/* gaspressures.c + * --------------- + * This file contains the routines to calculate the gas pressures in the cylinders. + * The functions below support the code in profile.c. + * The high-level function is populate_pressure_information(), called by function + * create_plot_info_new() in profile.c. The other functions below are, in turn, + * called by populate_pressure_information(). The calling sequence is as follows: + * + * populate_pressure_information() -> calc_pressure_time() + * -> fill_missing_tank_pressures() -> fill_missing_segment_pressures() + * -> get_pr_interpolate_data() + * + * The pr_track_t related functions below implement a linked list that is used by + * the majority of the functions below. The linked list covers a part of the dive profile + * for which there are no cylinder pressure data. Each element in the linked list + * represents a segment between two consecutive points on the dive profile. + * pr_track_t is defined in gaspressures.h + */ + +#include "dive.h" +#include "display.h" +#include "profile.h" +#include "gaspressures.h" + +static pr_track_t *pr_track_alloc(int start, int t_start) +{ + pr_track_t *pt = malloc(sizeof(pr_track_t)); + pt->start = start; + pt->end = 0; + pt->t_start = pt->t_end = t_start; + pt->pressure_time = 0; + pt->next = NULL; + return pt; +} + +/* poor man's linked list */ +static pr_track_t *list_last(pr_track_t *list) +{ + pr_track_t *tail = list; + if (!tail) + return NULL; + while (tail->next) { + tail = tail->next; + } + return tail; +} + +static pr_track_t *list_add(pr_track_t *list, pr_track_t *element) +{ + pr_track_t *tail = list_last(list); + if (!tail) + return element; + tail->next = element; + return list; +} + +static void list_free(pr_track_t *list) +{ + if (!list) + return; + list_free(list->next); + free(list); +} + +#ifdef DEBUG_PR_TRACK +static void dump_pr_track(pr_track_t **track_pr) +{ + int cyl; + pr_track_t *list; + + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { + list = track_pr[cyl]; + while (list) { + printf("cyl%d: start %d end %d t_start %d t_end %d pt %d\n", cyl, + list->start, list->end, list->t_start, list->t_end, list->pressure_time); + list = list->next; + } + } +} +#endif + +/* + * This looks at the pressures for one cylinder, and + * calculates any missing beginning/end pressures for + * each segment by taking the over-all SAC-rate into + * account for that cylinder. + * + * NOTE! Many segments have full pressure information + * (both beginning and ending pressure). But if we have + * switched away from a cylinder, we will have the + * beginning pressure for the first segment with a + * missing end pressure. We may then have one or more + * segments without beginning or end pressures, until + * we finally have a segment with an end pressure. + * + * We want to spread out the pressure over these missing + * segments according to how big of a time_pressure area + * they have. + */ +static void fill_missing_segment_pressures(pr_track_t *list, enum interpolation_strategy strategy) +{ + double magic; + + while (list) { + int start = list->start, end; + pr_track_t *tmp = list; + int pt_sum = 0, pt = 0; + + for (;;) { + pt_sum += tmp->pressure_time; + end = tmp->end; + if (end) + break; + end = start; + if (!tmp->next) + break; + tmp = tmp->next; + } + + if (!start) + start = end; + + /* + * Now 'start' and 'end' contain the pressure values + * for the set of segments described by 'list'..'tmp'. + * pt_sum is the sum of all the pressure-times of the + * segments. + * + * Now dole out the pressures relative to pressure-time. + */ + list->start = start; + tmp->end = end; + switch (strategy) { + case SAC: + for (;;) { + int pressure; + pt += list->pressure_time; + pressure = start; + if (pt_sum) + pressure -= (start - end) * (double)pt / pt_sum; + list->end = pressure; + if (list == tmp) + break; + list = list->next; + list->start = pressure; + } + break; + case TIME: + if (list->t_end && (tmp->t_start - tmp->t_end)) { + magic = (list->t_start - tmp->t_end) / (tmp->t_start - tmp->t_end); + list->end = rint(start - (start - end) * magic); + } else { + list->end = start; + } + break; + case CONSTANT: + list->end = start; + } + + /* Ok, we've done that set of segments */ + list = list->next; + } +} + +#ifdef DEBUG_PR_INTERPOLATE +void dump_pr_interpolate(int i, pr_interpolate_t interpolate_pr) +{ + printf("Interpolate for entry %d: start %d - end %d - pt %d - acc_pt %d\n", i, + interpolate_pr.start, interpolate_pr.end, interpolate_pr.pressure_time, interpolate_pr.acc_pressure_time); +} +#endif + + +static struct pr_interpolate_struct get_pr_interpolate_data(pr_track_t *segment, struct plot_info *pi, int cur) +{ // cur = index to pi->entry corresponding to t_end of segment; + struct pr_interpolate_struct interpolate; + int i; + struct plot_data *entry; + + interpolate.start = segment->start; + interpolate.end = segment->end; + interpolate.acc_pressure_time = 0; + interpolate.pressure_time = 0; + + for (i = 0; i < pi->nr; i++) { + entry = pi->entry + i; + + if (entry->sec < segment->t_start) + continue; + interpolate.pressure_time += entry->pressure_time; + if (entry->sec >= segment->t_end) + break; + if (i <= cur) + interpolate.acc_pressure_time += entry->pressure_time; + } + return interpolate; +} + +static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, pr_track_t **track_pr, bool o2_flag) +{ + int cyl, i; + struct plot_data *entry; + pr_interpolate_t interpolate = { 0, 0, 0, 0 }; + pr_track_t *last_segment = NULL; + int cur_pr[MAX_CYLINDERS]; // cur_pr[MAX_CYLINDERS] is the CCR diluent cylinder + + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { + enum interpolation_strategy strategy; + if (!track_pr[cyl]) { + /* no segment where this cylinder is used */ + cur_pr[cyl] = -1; + continue; + } + if (dive->cylinder[cyl].cylinder_use == OC_GAS) + strategy = SAC; + else + strategy = TIME; + fill_missing_segment_pressures(track_pr[cyl], strategy); // Interpolate the missing tank pressure values .. + cur_pr[cyl] = track_pr[cyl]->start; // in the pr_track_t lists of structures + } // and keep the starting pressure for each cylinder. + +#ifdef DEBUG_PR_TRACK + /* another great debugging tool */ + dump_pr_track(track_pr); +#endif + + /* Transfer interpolated cylinder pressures from pr_track strucktures to plotdata + * Go down the list of tank pressures in plot_info. Align them with the start & + * end times of each profile segment represented by a pr_track_t structure. Get + * the accumulated pressure_depths from the pr_track_t structures and then + * interpolate the pressure where these do not exist in the plot_info pressure + * variables. Pressure values are transferred from the pr_track_t structures + * to the plot_info structure, allowing us to plot the tank pressure. + * + * The first two pi structures are "fillers", but in case we don't have a sample + * at time 0 we need to process the second of them here, therefore i=1 */ + for (i = 1; i < pi->nr; i++) { // For each point on the profile: + double magic; + pr_track_t *segment; + int pressure; + int *save_pressure, *save_interpolated; + + entry = pi->entry + i; + + if (o2_flag) { + // Find the cylinder index (cyl) and pressure + cyl = dive->oxygen_cylinder_index; + if (cyl < 0) + return; // Can we do this?!? + pressure = O2CYLINDER_PRESSURE(entry); + save_pressure = &(entry->o2cylinderpressure[SENSOR_PR]); + save_interpolated = &(entry->o2cylinderpressure[INTERPOLATED_PR]); + } else { + pressure = SENSOR_PRESSURE(entry); + save_pressure = &(entry->pressure[SENSOR_PR]); + save_interpolated = &(entry->pressure[INTERPOLATED_PR]); + cyl = entry->cylinderindex; + } + + if (pressure) { // If there is a valid pressure value, + last_segment = NULL; // get rid of interpolation data, + cur_pr[cyl] = pressure; // set current pressure + continue; // and skip to next point. + } + // If there is NO valid pressure value.. + // Find the pressure segment corresponding to this entry.. + segment = track_pr[cyl]; + while (segment && segment->t_end < entry->sec) // Find the track_pr with end time.. + segment = segment->next; // ..that matches the plot_info time (entry->sec) + + if (!segment || !segment->pressure_time) { // No (or empty) segment? + *save_pressure = cur_pr[cyl]; // Just use our current pressure + continue; // and skip to next point. + } + + // If there is a valid segment but no tank pressure .. + if (segment == last_segment) { + interpolate.acc_pressure_time += entry->pressure_time; + } else { + // Set up an interpolation structure + interpolate = get_pr_interpolate_data(segment, pi, i); + last_segment = segment; + } + + if(dive->cylinder[cyl].cylinder_use == OC_GAS) { + + /* if this segment has pressure_time, then calculate a new interpolated pressure */ + if (interpolate.pressure_time) { + /* Overall pressure change over total pressure-time for this segment*/ + magic = (interpolate.end - interpolate.start) / (double)interpolate.pressure_time; + + /* Use that overall pressure change to update the current pressure */ + cur_pr[cyl] = rint(interpolate.start + magic * interpolate.acc_pressure_time); + } + } else { + magic = (interpolate.end - interpolate.start) / (segment->t_end - segment->t_start); + cur_pr[cyl] = rint(segment->start + magic * (entry->sec - segment->t_start)); + } + *save_interpolated = cur_pr[cyl]; // and store the interpolated data in plot_info + } +} + + +/* + * What's the pressure-time between two plot data entries? + * We're calculating the integral of pressure over time by + * adding these up. + * + * The units won't matter as long as everybody agrees about + * them, since they'll cancel out - we use this to calculate + * a constant SAC-rate-equivalent, but we only use it to + * scale pressures, so it ends up being a unitless scaling + * factor. + */ +static inline int calc_pressure_time(struct dive *dive, struct plot_data *a, struct plot_data *b) +{ + int time = b->sec - a->sec; + int depth = (a->depth + b->depth) / 2; + + if (depth <= SURFACE_THRESHOLD) + return 0; + + return depth_to_mbar(depth, dive) * time; +} + +#ifdef PRINT_PRESSURES_DEBUG +// A CCR debugging tool that prints the gas pressures in cylinder 0 and in the diluent cylinder, used in populate_pressure_information(): +static void debug_print_pressures(struct plot_info *pi) +{ + int i; + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = pi->entry + i; + printf("%5d |%9d | %9d || %9d | %9d |\n", i, SENSOR_PRESSURE(entry), INTERPOLATED_PRESSURE(entry), DILUENT_PRESSURE(entry), INTERPOLATED_DILUENT_PRESSURE(entry)); + } +} +#endif + +/* This function goes through the list of tank pressures, either SENSOR_PRESSURE(entry) or O2CYLINDER_PRESSURE(entry), + * of structure plot_info for the dive profile where each item in the list corresponds to one point (node) of the + * profile. It finds values for which there are no tank pressures (pressure==0). For each missing item (node) of + * tank pressure it creates a pr_track_alloc structure that represents a segment on the dive profile and that + * contains tank pressures. There is a linked list of pr_track_alloc structures for each cylinder. These pr_track_alloc + * structures ultimately allow for filling the missing tank pressure values on the dive profile using the depth_pressure + * of the dive. To do this, it calculates the summed pressure-time value for the duration of the dive and stores these + * in the pr_track_alloc structures. If diluent_flag = 1, then DILUENT_PRESSURE(entry) is used instead of SENSOR_PRESSURE. + * This function is called by create_plot_info_new() in profile.c + */ +void populate_pressure_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, int o2_flag) +{ + (void) dc; + int i, cylinderid, cylinderindex = -1; + pr_track_t *track_pr[MAX_CYLINDERS] = { NULL, }; + pr_track_t *current = NULL; + bool missing_pr = false; + bool found_any_pr_data = false; + + /* if we have no pressure data whatsoever, this is pointless, so let's just return */ + for (i = 0; i < MAX_CYLINDERS; i++) { + if (dive->cylinder[i].start.mbar || dive->cylinder[i].sample_start.mbar || + dive->cylinder[i].end.mbar || dive->cylinder[i].sample_end.mbar) { + found_any_pr_data = true; + break; + } + } + if (!found_any_pr_data) + return; + + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = pi->entry + i; + unsigned pressure; + if (o2_flag) { // if this is a diluent cylinder: + pressure = O2CYLINDER_PRESSURE(entry); + cylinderid = dive->oxygen_cylinder_index; + if (cylinderid < 0) + goto GIVE_UP; + } else { + pressure = SENSOR_PRESSURE(entry); + cylinderid = entry->cylinderindex; + } + /* If track_pr structure already exists, then update it: */ + /* discrete integration of pressure over time to get the SAC rate equivalent */ + if (current) { + entry->pressure_time = calc_pressure_time(dive, entry - 1, entry); + current->pressure_time += entry->pressure_time; + current->t_end = entry->sec; + } + + /* If 1st record or different cylinder: Create a new track_pr structure: */ + /* track the segments per cylinder and their pressure/time integral */ + if (cylinderid != cylinderindex) { + if (o2_flag) // For CCR dives: + cylinderindex = dive->oxygen_cylinder_index; // indicate o2 cylinder + else + cylinderindex = entry->cylinderindex; + current = pr_track_alloc(pressure, entry->sec); + track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current); + continue; + } + + if (!pressure) { + missing_pr = 1; + continue; + } + if (current) + current->end = pressure; + + /* Was it continuous? */ + if ((o2_flag) && (O2CYLINDER_PRESSURE(entry - 1))) // in the case of CCR o2 pressure + continue; + else if (SENSOR_PRESSURE(entry - 1)) // for all other cylinders + continue; + + /* transmitter stopped transmitting cylinder pressure data */ + current = pr_track_alloc(pressure, entry->sec); + if (cylinderindex >= 0) + track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current); + } + + if (missing_pr) { + fill_missing_tank_pressures(dive, pi, track_pr, o2_flag); + } + +#ifdef PRINT_PRESSURES_DEBUG + debug_print_pressures(pi); +#endif + +GIVE_UP: + for (i = 0; i < MAX_CYLINDERS; i++) + list_free(track_pr[i]); +} diff --git a/core/gaspressures.h b/core/gaspressures.h new file mode 100644 index 000000000..420c117a2 --- /dev/null +++ b/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/core/gettext.h b/core/gettext.h new file mode 100644 index 000000000..43ff023c7 --- /dev/null +++ b/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/core/gettextfromc.cpp b/core/gettextfromc.cpp new file mode 100644 index 000000000..43ee8da50 --- /dev/null +++ b/core/gettextfromc.cpp @@ -0,0 +1,27 @@ +#include +#include +#include "gettextfromc.h" + +const char *gettextFromC::trGettext(const char *text) +{ + QByteArray &result = translationCache[QByteArray(text)]; + if (result.isEmpty()) + result = translationCache[QByteArray(text)] = trUtf8(text).toUtf8(); + return result.constData(); +} + +void gettextFromC::reset(void) +{ + translationCache.clear(); +} + +gettextFromC *gettextFromC::instance() +{ + static QScopedPointer self(new gettextFromC()); + return self.data(); +} + +extern "C" const char *trGettext(const char *text) +{ + return gettextFromC::instance()->trGettext(text); +} diff --git a/core/gettextfromc.h b/core/gettextfromc.h new file mode 100644 index 000000000..53df18365 --- /dev/null +++ b/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/core/git-access.c b/core/git-access.c new file mode 100644 index 000000000..d10139d3d --- /dev/null +++ b/core/git-access.c @@ -0,0 +1,929 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#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" + +bool is_subsurface_cloud = false; + +int (*update_progress_cb)(int, const char *) = NULL; + +void set_git_update_cb(int(*cb)(int, const char *)) +{ + update_progress_cb = cb; +} + +// total overkill, but this allows us to get good timing in various scenarios; +// the various parts of interacting with the local and remote git repositories send +// us updates which indicate progress (and no, this is not smooth and definitely not +// proportional - some parts are based on compute performance, some on network speed) +// they also provide information where in the process we are so we can analyze the log +// to understand which parts of the process take how much time. + +// last_git_storage_update_val is used to detect when we suddenly go back to smaller +// "percentage" value because we are back to executing earlier code a second (or third +// time) in that case a negative percentage value is sent to the callback function as a +// special case to mark that situation. Overall this ensures monotonous percentage values +int last_git_storage_update_val; + +int git_storage_update_progress(int percent, const char *text) +{ + static int delta = 0; + + if (percent == 0) { + delta = 0; + } else if (percent > 0 && percent < last_git_storage_update_val) { + delta = last_git_storage_update_val + delta; + if (update_progress_cb) + (*update_progress_cb)(-delta, "DELTA"); + if (verbose) + fprintf(stderr, "set git storage percentage delta to %d\n", delta); + } + + last_git_storage_update_val = percent; + + percent += delta; + + int ret = 0; + if (update_progress_cb) + ret = (*update_progress_cb)(percent, text); + return ret; +} + +// the checkout_progress_cb doesn't allow canceling of the operation +// map the git progress to 70..90% of overall progress +static void progress_cb(const char *path, size_t completed_steps, size_t total_steps, void *payload) +{ + (void) path; + (void) payload; + + int percent = 0; + if (total_steps) + percent = 70 + 20 * completed_steps / total_steps; + (void)git_storage_update_progress(percent, "checkout_progress_cb"); +} + +// this randomly assumes that 80% of the time is spent on the objects and 20% on the deltas +// map the git progress to 70..90% of overall progress +// if the user cancels the dialog this is passed back to libgit2 +static int transfer_progress_cb(const git_transfer_progress *stats, void *payload) +{ + (void) payload; + + int percent = 0; + if (stats->total_objects) + percent = 70 + 16 * stats->received_objects / stats->total_objects; + if (stats->total_deltas) + percent += 4 * stats->indexed_deltas / stats->total_deltas; + /* for debugging this is useful + char buf[100]; + snprintf(buf, 100, "transfer cb rec_obj %d tot_obj %d idx_delta %d total_delta %d local obj %d", stats->received_objects, stats->total_objects, stats->indexed_deltas, stats->total_deltas, stats->local_objects); + return git_storage_update_progress(percent, buf); + */ + return git_storage_update_progress(percent, "transfer cb"); +} + +// the initial push to sync the repos is mapped to 10..15% of overall progress +static int push_transfer_progress_cb(unsigned int current, unsigned int total, size_t bytes, void *payload) +{ + (void) bytes; + (void) payload; + + int percent = 0; + if (total != 0) + percent = 12 + 5 * current / total; + return git_storage_update_progress(percent, "push trasfer cb"); +} + +char *get_local_dir(const char *remote, const char *branch) +{ + SHA_CTX ctx; + unsigned char hash[20]; + + // That zero-byte update is so that we don't get hash + // collisions for "repo1 branch" vs "repo 1branch". + SHA1_Init(&ctx); + SHA1_Update(&ctx, remote, strlen(remote)); + SHA1_Update(&ctx, "", 1); + SHA1_Update(&ctx, branch, strlen(branch)); + SHA1_Final(hash, &ctx); + + return format_string("%s/cloudstorage/%02x%02x%02x%02x%02x%02x%02x%02x", + system_default_directory(), + hash[0], hash[1], hash[2], hash[3], + hash[4], hash[5], hash[6], hash[7]); +} + +static char *move_local_cache(const char *remote, const char *branch) +{ + char *old_path = get_local_dir(remote, branch); + return move_away(old_path); +} + +static int check_clean(const char *path, unsigned int status, void *payload) +{ + (void) payload; + status &= ~GIT_STATUS_CURRENT | GIT_STATUS_IGNORED; + if (!status) + return 0; + if (is_subsurface_cloud) + report_error(translate("gettextFromC", "Local cache directory %s corrupted - can't sync with Subsurface cloud storage"), path); + else + report_error("WARNING: Git cache directory modified (path %s) status %0x", path, status); + return 1; +} + +/* + * The remote is strictly newer than the local branch. + */ +static int reset_to_remote(git_repository *repo, git_reference *local, const git_oid *new_id) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + opts.progress_cb = &progress_cb; + git_object *target; + + if (verbose) + fprintf(stderr, "git storage: reset to remote\n"); + + // If it's not checked out (bare or not HEAD), just update the reference */ + if (git_repository_is_bare(repo) || git_branch_is_head(local) != 1) { + git_reference *out; + + if (git_reference_set_target(&out, local, new_id, "Update to remote")) + return report_error(translate("gettextFromC", "Could not update local cache to newer remote data")); + + git_reference_free(out); + +#ifdef DEBUG + // Not really an error, just informational + report_error("Updated local branch from remote"); +#endif + return 0; + } + + if (git_object_lookup(&target, repo, new_id, GIT_OBJ_COMMIT)) { + if (is_subsurface_cloud) + return report_error(translate("gettextFromC", "Subsurface cloud storage corrupted")); + else + return report_error("Could not look up remote commit"); + } + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + if (git_reset(repo, target, GIT_RESET_HARD, &opts)) { + if (is_subsurface_cloud) + return report_error(translate("gettextFromC", "Could not update local cache to newer remote data")); + else + return report_error("Local head checkout failed after update"); + } + // Not really an error, just informational +#ifdef DEBUG + report_error("Updated local information from remote"); +#endif + return 0; +} + +int credential_ssh_cb(git_cred **out, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *payload) +{ + (void) url; + (void) allowed_types; + (void) payload; + + const char *priv_key = format_string("%s/%s", system_default_directory(), "ssrf_remote.key"); + const char *passphrase = prefs.cloud_storage_password ? strdup(prefs.cloud_storage_password) : strdup(""); + return git_cred_ssh_key_new(out, username_from_url, NULL, priv_key, passphrase); +} + +int credential_https_cb(git_cred **out, + const char *url, + const char *username_from_url, + unsigned int allowed_types, + void *payload) +{ + (void) url; + (void) username_from_url; + (void) payload; + (void) allowed_types; + const char *username = prefs.cloud_storage_email_encoded; + const char *password = prefs.cloud_storage_password ? strdup(prefs.cloud_storage_password) : strdup(""); + return git_cred_userpass_plaintext_new(out, username, password); +} + +#define KNOWN_CERT "\xfd\xb8\xf7\x73\x76\xe2\x75\x53\x93\x37\xdc\xfe\x1e\x55\x43\x3d\xf2\x2c\x18\x2c" +int certificate_check_cb(git_cert *cert, int valid, const char *host, void *payload) +{ + (void) payload; + if (same_string(host, "cloud.subsurface-divelog.org") && cert->cert_type == GIT_CERT_X509) { + SHA_CTX ctx; + unsigned char hash[21]; + git_cert_x509 *cert509 = (git_cert_x509 *)cert; + SHA1_Init(&ctx); + SHA1_Update(&ctx, cert509->data, cert509->len); + SHA1_Final(hash, &ctx); + hash[20] = 0; + if (verbose > 1) + if (same_string((char *)hash, KNOWN_CERT)) { + fprintf(stderr, "cloud certificate considered %s, forcing it valid\n", + valid ? "valid" : "not valid"); + return 1; + } + } + return valid; +} + +static int update_remote(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, enum remote_transport rt) +{ + (void) repo; + (void) remote; + + git_push_options opts = GIT_PUSH_OPTIONS_INIT; + git_strarray refspec; + const char *name = git_reference_name(local); + + if (verbose) + fprintf(stderr, "git storage: update remote\n"); + + refspec.count = 1; + refspec.strings = (char **)&name; + + opts.callbacks.push_transfer_progress = &push_transfer_progress_cb; + if (rt == RT_SSH) + opts.callbacks.credentials = credential_ssh_cb; + else if (rt == RT_HTTPS) + opts.callbacks.credentials = credential_https_cb; + opts.callbacks.certificate_check = certificate_check_cb; + + if (git_remote_push(origin, &refspec, &opts)) { + if (is_subsurface_cloud) + return report_error(translate("gettextFromC", "Could not update Subsurface cloud storage, try again later")); + else + return report_error("Unable to update remote with current local cache state (%s)", giterr_last()->message); + } + return 0; +} + +extern int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree); + +static int try_to_git_merge(git_repository *repo, git_reference **local_p, git_reference *remote, git_oid *base, const git_oid *local_id, const git_oid *remote_id) +{ + (void) remote; + git_tree *local_tree, *remote_tree, *base_tree; + git_commit *local_commit, *remote_commit, *base_commit; + git_index *merged_index; + git_merge_options merge_options; + + if (verbose) { + char outlocal[41], outremote[41]; + outlocal[40] = outremote[40] = 0; + git_oid_fmt(outlocal, local_id); + git_oid_fmt(outremote, remote_id); + fprintf(stderr, "trying to merge local SHA %s remote SHA %s\n", outlocal, outremote); + } + + git_merge_init_options(&merge_options, GIT_MERGE_OPTIONS_VERSION); + merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; + merge_options.file_favor = GIT_MERGE_FILE_FAVOR_UNION; + merge_options.rename_threshold = 100; + if (git_commit_lookup(&local_commit, repo, local_id)) { + fprintf(stderr, "Remote storage and local data diverged. Error: can't get commit (%s)", giterr_last()->message); + goto diverged_error; + } + if (git_commit_tree(&local_tree, local_commit)) { + fprintf(stderr, "Remote storage and local data diverged. Error: failed local tree lookup (%s)", giterr_last()->message); + goto diverged_error; + } + if (git_commit_lookup(&remote_commit, repo, remote_id)) { + fprintf(stderr, "Remote storage and local data diverged. Error: can't get commit (%s)", giterr_last()->message); + goto diverged_error; + } + if (git_commit_tree(&remote_tree, remote_commit)) { + fprintf(stderr, "Remote storage and local data diverged. Error: failed local tree lookup (%s)", giterr_last()->message); + goto diverged_error; + } + if (git_commit_lookup(&base_commit, repo, base)) { + fprintf(stderr, "Remote storage and local data diverged. Error: can't get commit (%s)", giterr_last()->message); + goto diverged_error; + } + if (git_commit_tree(&base_tree, base_commit)) { + fprintf(stderr, "Remote storage and local data diverged. Error: failed base tree lookup (%s)", giterr_last()->message); + goto diverged_error; + } + if (git_merge_trees(&merged_index, repo, base_tree, local_tree, remote_tree, &merge_options)) { + fprintf(stderr, "Remote storage and local data diverged. Error: merge failed (%s)", giterr_last()->message); + // this is the one where I want to report more detail to the user - can't quite explain why + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge failed (%s)"), giterr_last()->message); + } + if (git_index_has_conflicts(merged_index)) { + int error; + const git_index_entry *ancestor = NULL, + *ours = NULL, + *theirs = NULL; + git_index_conflict_iterator *iter = NULL; + error = git_index_conflict_iterator_new(&iter, merged_index); + while (git_index_conflict_next(&ancestor, &ours, &theirs, iter) + != GIT_ITEROVER) { + /* Mark this conflict as resolved */ + fprintf(stderr, "conflict in %s / %s / %s -- ", + ours ? ours->path : "-", + theirs ? theirs->path : "-", + ancestor ? ancestor->path : "-"); + if ((!ours && theirs && ancestor) || + (ours && !theirs && ancestor)) { + // the file was removed on one side or the other - just remove it + fprintf(stderr, "looks like a delete on one side; removing the file from the index\n"); + error = git_index_remove(merged_index, ours ? ours->path : theirs->path, GIT_INDEX_STAGE_ANY); + } else if (ancestor) { + error = git_index_conflict_remove(merged_index, ours ? ours->path : theirs ? theirs->path : ancestor->path); + } + if (error) { + fprintf(stderr, "error at conflict resplution (%s)", giterr_last()->message); + } + } + git_index_conflict_cleanup(merged_index); + git_index_conflict_iterator_free(iter); + report_error(translate("gettextFromC", "Remote storage and local data diverged. Cannot combine local and remote changes")); + } + git_oid merge_oid, commit_oid; + git_tree *merged_tree; + git_signature *author; + git_commit *commit; + + if (git_index_write_tree_to(&merge_oid, merged_index, repo)) + goto write_error; + if (git_tree_lookup(&merged_tree, repo, &merge_oid)) + goto write_error; + if (git_signature_default(&author, repo) < 0) + if (git_signature_now(&author, "Subsurface", "noemail@given") < 0) + goto write_error; + if (git_commit_create_v(&commit_oid, repo, NULL, author, author, NULL, "automatic merge", merged_tree, 2, local_commit, remote_commit)) + goto write_error; + if (git_commit_lookup(&commit, repo, &commit_oid)) + goto write_error; + if (git_branch_is_head(*local_p) && !git_repository_is_bare(repo)) { + git_object *parent; + git_reference_peel(&parent, *local_p, GIT_OBJ_COMMIT); + if (update_git_checkout(repo, parent, merged_tree)) { + goto write_error; + } + } + if (git_reference_set_target(local_p, *local_p, &commit_oid, "Subsurface merge event")) + goto write_error; + set_git_id(&commit_oid); + git_signature_free(author); + if (verbose) + fprintf(stderr, "Successfully merged repositories"); + return 0; + +diverged_error: + return report_error(translate("gettextFromC", "Remote storage and local data diverged")); + +write_error: + return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: writing the data failed (%s)"), giterr_last()->message); +} + +// if accessing the local cache of Subsurface cloud storage fails, we simplify things +// for the user and simply move the cache away (in case they want to try and extract data) +// and ask them to retry the operation (which will then refresh the data from the cloud server) +static int cleanup_local_cache(const char *remote_url, const char *branch) +{ + char *backup_path = move_local_cache(remote_url, branch); + report_error(translate("gettextFromC", "Problems with local cache of Subsurface cloud data")); + report_error(translate("gettextFromC", "Moved cache data to %s. Please try the operation again."), backup_path); + free(backup_path); + return -1; +} +static int try_to_update(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, + const char *remote_url, const char *branch, enum remote_transport rt) +{ + git_oid base; + const git_oid *local_id, *remote_id; + int ret = 0; + + if (verbose) + fprintf(stderr, "git storage: try to update\n"); + git_storage_update_progress(9, "try to update"); + if (!git_reference_cmp(local, remote)) + return 0; + + // Dirty modified state in the working tree? We're not going + // to update either way + if (git_status_foreach(repo, check_clean, NULL)) { + if (is_subsurface_cloud) + goto cloud_data_error; + else + return report_error("local cached copy is dirty, skipping update"); + } + local_id = git_reference_target(local); + remote_id = git_reference_target(remote); + + if (!local_id || !remote_id) { + if (is_subsurface_cloud) + goto cloud_data_error; + else + return report_error("Unable to get local or remote SHA1"); + } + if (git_merge_base(&base, repo, local_id, remote_id)) { + if (is_subsurface_cloud) + goto cloud_data_error; + else + return report_error("Unable to find common commit of local and remote branches"); + } + /* Is the remote strictly newer? Use it */ + if (git_oid_equal(&base, local_id)) + return reset_to_remote(repo, local, remote_id); + + /* Is the local repo the more recent one? See if we can update upstream */ + if (git_oid_equal(&base, remote_id)) { + if (verbose) + fprintf(stderr, "local is newer than remote, update remote\n"); + git_storage_update_progress(10, "git_update_remote, local was newer"); + return update_remote(repo, origin, local, remote, rt); + } + /* Merging a bare repository always needs user action */ + if (git_repository_is_bare(repo)) { + if (is_subsurface_cloud) + goto cloud_data_error; + else + return report_error("Local and remote have diverged, merge of bare branch needed"); + } + /* Merging will definitely need the head branch too */ + if (git_branch_is_head(local) != 1) { + if (is_subsurface_cloud) + goto cloud_data_error; + else + return report_error("Local and remote do not match, local branch not HEAD - cannot update"); + } + /* Ok, let's try to merge these */ + git_storage_update_progress(11, "try to merge"); + ret = try_to_git_merge(repo, &local, remote, &base, local_id, remote_id); + if (ret == 0) + return update_remote(repo, origin, local, remote, rt); + else + return ret; + +cloud_data_error: + // since we are working with Subsurface cloud storage we want to make the user interaction + // as painless as possible. So if something went wrong with the local cache, tell the user + // about it an move it away + return cleanup_local_cache(remote_url, branch); +} + +static int check_remote_status(git_repository *repo, git_remote *origin, const char *remote, const char *branch, enum remote_transport rt) +{ + int error = 0; + + git_reference *local_ref, *remote_ref; + + if (verbose) + fprintf(stderr, "git storage: check remote status\n"); + git_storage_update_progress(7, "git check remote status"); + + if (git_branch_lookup(&local_ref, repo, branch, GIT_BRANCH_LOCAL)) { + if (is_subsurface_cloud) + return cleanup_local_cache(remote, branch); + else + return report_error("Git cache branch %s no longer exists", branch); + } + if (git_branch_upstream(&remote_ref, local_ref)) { + /* so there is no upstream branch for our branch; that's a problem. + * let's push our branch */ + git_strarray refspec; + git_reference_list(&refspec, repo); + git_push_options opts = GIT_PUSH_OPTIONS_INIT; + opts.callbacks.transfer_progress = &transfer_progress_cb; + if (rt == RT_SSH) + opts.callbacks.credentials = credential_ssh_cb; + else if (rt == RT_HTTPS) + opts.callbacks.credentials = credential_https_cb; + opts.callbacks.certificate_check = certificate_check_cb; + git_storage_update_progress(8, "git remote push (no remote existed)"); + error = git_remote_push(origin, &refspec, &opts); + } else { + error = try_to_update(repo, origin, local_ref, remote_ref, remote, branch, rt); + git_reference_free(remote_ref); + } + git_reference_free(local_ref); + return error; +} + +int sync_with_remote(git_repository *repo, const char *remote, const char *branch, enum remote_transport rt) +{ + int error; + git_remote *origin; + char *proxy_string; + git_config *conf; + + if (prefs.git_local_only) { + if (verbose) + fprintf(stderr, "don't sync with remote - read from cache only\n"); + return 0; + } + if (verbose) + fprintf(stderr, "sync with remote %s[%s]\n", remote, branch); + git_storage_update_progress(2, "sync with remote"); + git_repository_config(&conf, repo); + if (rt == RT_HTTPS && getProxyString(&proxy_string)) { + if (verbose) + fprintf(stderr, "set proxy to \"%s\"\n", proxy_string); + git_config_set_string(conf, "http.proxy", proxy_string); + free(proxy_string); + } else { + if (verbose) + fprintf(stderr, "delete proxy setting\n"); + git_config_delete_entry(conf, "http.proxy"); + } + + /* + * NOTE! Remote errors are reported, but are nonfatal: + * we still successfully return the local repository. + */ + error = git_remote_lookup(&origin, repo, "origin"); + if (error) { + if (!is_subsurface_cloud) + report_error("Repository '%s' origin lookup failed (%s)", remote, giterr_last()->message); + return 0; + } + + if (rt == RT_HTTPS && !canReachCloudServer()) { + // this is not an error, just a warning message, so return 0 + report_error("Cannot connect to cloud server, working with local copy"); + git_storage_update_progress(18, "can't reach cloud server, working with local copy"); + return 0; + } + if (verbose) + fprintf(stderr, "git storage: fetch remote\n"); + git_fetch_options opts = GIT_FETCH_OPTIONS_INIT; + opts.callbacks.transfer_progress = &transfer_progress_cb; + if (rt == RT_SSH) + opts.callbacks.credentials = credential_ssh_cb; + else if (rt == RT_HTTPS) + opts.callbacks.credentials = credential_https_cb; + opts.callbacks.certificate_check = certificate_check_cb; + git_storage_update_progress(6, "git fetch remote"); + error = git_remote_fetch(origin, NULL, &opts, NULL); + // NOTE! A fetch error is not fatal, we just report it + if (error) { + if (is_subsurface_cloud) + report_error("Cannot sync with cloud server, working with offline copy"); + else + report_error("Unable to fetch remote '%s'", remote); + if (verbose) + fprintf(stderr, "remote fetch failed (%s)\n", giterr_last()->message); + error = 0; + } else { + error = check_remote_status(repo, origin, remote, branch, rt); + } + git_remote_free(origin); + git_storage_update_progress(18, "done with sync with remote"); + return error; +} + +static git_repository *update_local_repo(const char *localdir, const char *remote, const char *branch, enum remote_transport rt) +{ + int error; + git_repository *repo = NULL; + + if (verbose) + fprintf(stderr, "git storage: update local repo\n"); + + error = git_repository_open(&repo, localdir); + if (error) { + if (is_subsurface_cloud) + (void)cleanup_local_cache(remote, branch); + else + report_error("Unable to open git cache repository at %s: %s", localdir, giterr_last()->message); + return NULL; + } + sync_with_remote(repo, remote, branch, rt); + return repo; +} + +static int repository_create_cb(git_repository **out, const char *path, int bare, void *payload) +{ + (void) payload; + char *proxy_string; + git_config *conf; + + int ret = git_repository_init(out, path, bare); + + git_repository_config(&conf, *out); + if (getProxyString(&proxy_string)) { + if (verbose) + fprintf(stderr, "set proxy to \"%s\"\n", proxy_string); + git_config_set_string(conf, "http.proxy", proxy_string); + free(proxy_string); + } else { + if (verbose) + fprintf(stderr, "delete proxy setting\n"); + git_config_delete_entry(conf, "http.proxy"); + } + return ret; +} + +/* this should correctly initialize both the local and remote + * repository for the Subsurface cloud storage */ +static git_repository *create_and_push_remote(const char *localdir, const char *remote, const char *branch) +{ + git_repository *repo; + git_config *conf; + int len; + char *variable_name, *merge_head; + + if (verbose) + fprintf(stderr, "git storage: create and push remote\n"); + + /* first make sure the directory for the local cache exists */ + subsurface_mkdir(localdir); + + /* set up the origin to point to our remote */ + git_repository_init_options init_opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + init_opts.origin_url = remote; + + /* now initialize the repository with */ + git_repository_init_ext(&repo, localdir, &init_opts); + + /* create a config so we can set the remote tracking branch */ + git_repository_config(&conf, repo); + len = sizeof("branch..remote") + strlen(branch); + variable_name = malloc(len); + snprintf(variable_name, len, "branch.%s.remote", branch); + git_config_set_string(conf, variable_name, "origin"); + /* we know this is shorter than the previous one, so we reuse the variable*/ + snprintf(variable_name, len, "branch.%s.merge", branch); + len = sizeof("refs/heads/") + strlen(branch); + merge_head = malloc(len); + snprintf(merge_head, len, "refs/heads/%s", branch); + git_config_set_string(conf, variable_name, merge_head); + + /* finally create an empty commit and push it to the remote */ + if (do_git_save(repo, branch, remote, false, true)) + return NULL; + return(repo); +} + +static git_repository *create_local_repo(const char *localdir, const char *remote, const char *branch, enum remote_transport rt) +{ + int error; + git_repository *cloned_repo = NULL; + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + + if (verbose) + fprintf(stderr, "git storage: create_local_repo\n"); + + opts.fetch_opts.callbacks.transfer_progress = &transfer_progress_cb; + if (rt == RT_SSH) + opts.fetch_opts.callbacks.credentials = credential_ssh_cb; + else if (rt == RT_HTTPS) + opts.fetch_opts.callbacks.credentials = credential_https_cb; + opts.repository_cb = repository_create_cb; + opts.fetch_opts.callbacks.certificate_check = certificate_check_cb; + + opts.checkout_branch = branch; + if (rt == RT_HTTPS && !canReachCloudServer()) + return 0; + if (verbose > 1) + fprintf(stderr, "git storage: calling git_clone()\n"); + error = git_clone(&cloned_repo, remote, localdir, &opts); + if (verbose > 1) + fprintf(stderr, "git storage: returned from git_clone() with error %d\n", error); + if (error) { + char *msg = giterr_last()->message; + int len = sizeof("Reference 'refs/remotes/origin/' not found") + strlen(branch); + char *pattern = malloc(len); + snprintf(pattern, len, "Reference 'refs/remotes/origin/%s' not found", branch); + if (strstr(remote, prefs.cloud_git_url) && strstr(msg, pattern)) { + /* we're trying to open the remote branch that corresponds + * to our cloud storage and the branch doesn't exist. + * So we need to create the branch and push it to the remote */ + cloned_repo = create_and_push_remote(localdir, remote, branch); +#if !defined(DEBUG) && !defined(SUBSURFACE_MOBILE) + } else if (is_subsurface_cloud) { + report_error(translate("gettextFromC", "Error connecting to Subsurface cloud storage")); +#endif + } else { + report_error(translate("gettextFromC", "git clone of %s failed (%s)"), remote, msg); + } + free(pattern); + } + return cloned_repo; +} + +static struct git_repository *get_remote_repo(const char *localdir, const char *remote, const char *branch) +{ + struct stat st; + enum remote_transport rt; + + /* figure out the remote transport */ + if (strncmp(remote, "ssh://", 6) == 0) + rt = RT_SSH; + else if (strncmp(remote, "https://", 8) == 0) + rt = RT_HTTPS; + else + rt = RT_OTHER; + + if (verbose > 1) { + fprintf(stderr, "git_remote_repo: accessing %s\n", remote); + } + git_storage_update_progress(1, "start git interaction"); + /* Do we already have a local cache? */ + if (!stat(localdir, &st)) { + if (!S_ISDIR(st.st_mode)) { + if (is_subsurface_cloud) + (void)cleanup_local_cache(remote, branch); + else + report_error("local git cache at '%s' is corrupt"); + return NULL; + } + return update_local_repo(localdir, remote, branch, rt); + } + if (!prefs.git_local_only) + return create_local_repo(localdir, remote, branch, rt); + else + return 0; + +} + +/* + * This turns a remote repository into a local one if possible. + * + * The recognized formats are + * git://host/repo[branch] + * ssh://host/repo[branch] + * http://host/repo[branch] + * https://host/repo[branch] + * file://repo[branch] + */ +static struct git_repository *is_remote_git_repository(char *remote, const char *branch) +{ + char c, *localdir; + const char *p = remote; + + while ((c = *p++) >= 'a' && c <= 'z') + /* nothing */; + if (c != ':') + return NULL; + if (*p++ != '/' || *p++ != '/') + return NULL; + + /* Special-case "file://", since it's already local */ + if (!strncmp(remote, "file://", 7)) + remote += 7; + + /* + * Ok, we found "[a-z]*://", we've simplified the + * local repo case (because libgit2 is insanely slow + * for that), and we think we have a real "remote + * git" format. + * + * We now create the SHA1 hash of the whole thing, + * including the branch name. That will be our unique + * unique local repository name. + * + * NOTE! We will create a local repository per branch, + * because + * + * (a) libgit2 remote tracking branch support seems to + * be a bit lacking + * (b) we'll actually check the branch out so that we + * can do merges etc too. + * + * so even if you have a single remote git repo with + * multiple branches for different people, the local + * caches will sadly force that to split into multiple + * individual repositories. + */ + + /* + * next we need to make sure that any encoded username + * has been extracted from an https:// based URL + */ + if (!strncmp(remote, "https://", 8)) { + char *at = strchr(remote, '@'); + if (at) { + /* was this the @ that denotes an account? that means it was before the + * first '/' after the https:// - so let's find a '/' after that and compare */ + char *slash = strchr(remote + 8, '/'); + if (slash && slash > at) { + /* grab the part between "https://" and "@" as encoded email address + * (that's our username) and move the rest of the URL forward, remembering + * to copy the closing NUL as well */ + prefs.cloud_storage_email_encoded = strndup(remote + 8, at - remote - 8); + memmove(remote + 8, at + 1, strlen(at + 1) + 1); + } + } + } + localdir = get_local_dir(remote, branch); + if (!localdir) + return NULL; + + /* remember if the current git storage we are working on is our cloud storage + * this is used to create more user friendly error message and warnings */ + is_subsurface_cloud = strstr(remote, prefs.cloud_git_url) != NULL; + + return get_remote_repo(localdir, remote, branch); +} + +/* + * If it's not a git repo, return NULL. Be very conservative. + */ +struct git_repository *is_git_repository(const char *filename, const char **branchp, const char **remote, bool dry_run) +{ + int flen, blen, ret; + int offset = 1; + struct stat st; + git_repository *repo; + char *loc, *branch; + + flen = strlen(filename); + if (!flen || filename[--flen] != ']') + return NULL; + + /* Find the matching '[' */ + blen = 0; + while (flen && filename[--flen] != '[') + blen++; + + /* Ignore slashes at the end of the repo name */ + while (flen && filename[flen-1] == '/') { + flen--; + offset++; + } + + if (!flen) + return NULL; + + /* + * This is the "point of no return": the name matches + * the git repository name rules, and we will no longer + * return NULL. + * + * We will either return "dummy_git_repository" and the + * branch pointer will have the _whole_ filename in it, + * or we will return a real git repository with the + * branch pointer being filled in with just the branch + * name. + * + * The actual git reading/writing routines can use this + * to generate proper error messages. + */ + *branchp = filename; + loc = format_string("%.*s", flen, filename); + if (!loc) + return dummy_git_repository; + + branch = format_string("%.*s", blen, filename + flen + offset); + if (!branch) { + free(loc); + return dummy_git_repository; + } + + if (dry_run) { + *branchp = branch; + *remote = loc; + return dummy_git_repository; + } + repo = is_remote_git_repository(loc, branch); + if (repo) { + if (remote) + *remote = loc; + else + free(loc); + *branchp = branch; + return repo; + } + + if (stat(loc, &st) < 0 || !S_ISDIR(st.st_mode)) { + free(loc); + free(branch); + return dummy_git_repository; + } + + ret = git_repository_open(&repo, loc); + free(loc); + if (ret < 0) { + free(branch); + return dummy_git_repository; + } + if (remote) + *remote = NULL; + *branchp = branch; + return repo; +} diff --git a/core/git-access.h b/core/git-access.h new file mode 100644 index 000000000..3a0c5160a --- /dev/null +++ b/core/git-access.h @@ -0,0 +1,36 @@ +#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 const char *get_sha(git_repository *repo, const char *branch); +extern int do_git_save(git_repository *repo, const char *branch, const char *remote, bool select_only, bool create_empty); +extern const char *saved_git_id; +extern void clear_git_id(void); +extern void set_git_id(const struct git_oid *); +void set_git_update_cb(int (*)(int, const char *)); +int git_storage_update_progress(int percent, const char *text); +char *get_local_dir(const char *remote, const char *branch); + +extern int last_git_storage_update_val; + +#ifdef __cplusplus +} +#endif +#endif // GITACCESS_H + diff --git a/core/gpslocation.cpp b/core/gpslocation.cpp new file mode 100644 index 000000000..8595cc45d --- /dev/null +++ b/core/gpslocation.cpp @@ -0,0 +1,606 @@ +#include "core/gpslocation.h" +#include "qt-models/gpslistmodel.h" +#include "core/pref.h" +#include "core/dive.h" +#include "core/helpers.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define GPS_FIX_ADD_URL "http://api.subsurface-divelog.org/api/dive/add/" +#define GPS_FIX_DELETE_URL "http://api.subsurface-divelog.org/api/dive/delete/" +#define GPS_FIX_DOWNLOAD_URL "http://api.subsurface-divelog.org/api/dive/get/" +#define GET_WEBSERVICE_UID_URL "https://cloud.subsurface-divelog.org/webuserid/" + +GpsLocation *GpsLocation::m_Instance = NULL; + +GpsLocation::GpsLocation(void (*showMsgCB)(const char *), QObject *parent) : QObject(parent) +{ + Q_ASSERT_X(m_Instance == NULL, "GpsLocation", "GpsLocation recreated"); + m_Instance = this; + m_GpsSource = 0; + waitingForPosition = false; + showMessageCB = showMsgCB; + // create a QSettings object that's separate from the main application settings + geoSettings = new QSettings(QSettings::NativeFormat, QSettings::UserScope, + QString("org.subsurfacedivelog"), QString("subsurfacelocation"), this); +#ifdef SUBSURFACE_MOBILE + if (hasLocationsSource()) + status(QString("Found GPS with positioning methods %1").arg(QString::number(m_GpsSource->supportedPositioningMethods(), 16))); +#endif + userAgent = getUserAgent(); + loadFromStorage(); +} + +GpsLocation *GpsLocation::instance() +{ + Q_ASSERT(m_Instance != NULL); + + return m_Instance; +} + +GpsLocation::~GpsLocation() +{ + m_Instance = NULL; +} + +QGeoPositionInfoSource *GpsLocation::getGpsSource() +{ + if (!m_GpsSource) { + m_GpsSource = QGeoPositionInfoSource::createDefaultSource(this); + if (m_GpsSource != 0) { +#ifndef SUBSURFACE_MOBILE + if (verbose) +#endif + status(QString("Created position source %1").arg(m_GpsSource->sourceName())); + connect(m_GpsSource, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(newPosition(QGeoPositionInfo))); + connect(m_GpsSource, SIGNAL(updateTimeout()), this, SLOT(updateTimeout())); + m_GpsSource->setUpdateInterval(5 * 60 * 1000); // 5 minutes so the device doesn't drain the battery + } else { +#ifdef SUBSURFACE_MOBILE + status("don't have GPS source"); +#endif + } + } + return m_GpsSource; +} + +bool GpsLocation::hasLocationsSource() +{ + QGeoPositionInfoSource *gpsSource = getGpsSource(); + return gpsSource != 0 && (gpsSource->supportedPositioningMethods() & QGeoPositionInfoSource::SatellitePositioningMethods); +} + +void GpsLocation::serviceEnable(bool toggle) +{ + QGeoPositionInfoSource *gpsSource = getGpsSource(); + if (!gpsSource) { + if (toggle) + status("Can't start location service, no location source available"); + return; + } + if (toggle) { + gpsSource->startUpdates(); + status(QString("Starting Subsurface GPS service with update interval %1").arg(gpsSource->updateInterval())); + } else { + gpsSource->stopUpdates(); + status("Stopping Subsurface GPS service"); + } +} + +QString GpsLocation::currentPosition() +{ + if (!hasLocationsSource()) + return tr("Unknown GPS location"); + if (m_trackers.count() && m_trackers.lastKey() + 300 >= QDateTime::currentMSecsSinceEpoch() / 1000) { + // we can simply use the last position that we tracked + gpsTracker gt = m_trackers.last(); + QString gpsString = printGPSCoords(gt.latitude.udeg, gt.longitude.udeg); + qDebug() << "returning last position" << gpsString; + return gpsString; + } + qDebug() << "requesting new GPS position"; + m_GpsSource->requestUpdate(); + // ok, we need to get the current position and somehow in the callback update the location in the QML UI + // punting right now + waitingForPosition = true; + return QString("waiting for the next gps location"); +} + +void GpsLocation::newPosition(QGeoPositionInfo pos) +{ + int64_t lastTime = 0; + QGeoCoordinate lastCoord; + int nr = m_trackers.count(); + if (nr) { + gpsTracker gt = m_trackers.last(); + lastCoord.setLatitude(gt.latitude.udeg / 1000000.0); + lastCoord.setLongitude(gt.longitude.udeg / 1000000.0); + lastTime = gt.when; + } + // if we are waiting for a position update or + // if we have no record stored or if at least the configured minimum + // time has passed or we moved at least the configured minimum distance + int64_t delta = (int64_t)pos.timestamp().toTime_t() + gettimezoneoffset() - lastTime; + if (!nr || waitingForPosition || delta > prefs.time_threshold || + lastCoord.distanceTo(pos.coordinate()) > prefs.distance_threshold) { + QString msg("received new position %1 after delta %2 threshold %3"); + status(qPrintable(msg.arg(pos.coordinate().toString()).arg(delta).arg(prefs.time_threshold))); + waitingForPosition = false; + gpsTracker gt; + gt.when = pos.timestamp().toTime_t(); + gt.when += gettimezoneoffset(gt.when); + gt.latitude.udeg = rint(pos.coordinate().latitude() * 1000000); + gt.longitude.udeg = rint(pos.coordinate().longitude() * 1000000); + addFixToStorage(gt); + } +} + +void GpsLocation::updateTimeout() +{ + status("request to get new position timed out"); +} + +void GpsLocation::status(QString msg) +{ + qDebug() << msg; + if (showMessageCB) + (*showMessageCB)(qPrintable(msg)); +} + +QString GpsLocation::getUserid(QString user, QString passwd) +{ + qDebug() << "called getUserid"; + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + + QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); + QUrl url(GET_WEBSERVICE_UID_URL); + QString data; + data = user + " " + passwd; + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + request.setRawHeader("Accept", "text/html"); + request.setRawHeader("Content-type", "application/x-www-form-urlencoded"); + reply = manager->post(request, data.toUtf8()); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(getUseridError(QNetworkReply::NetworkError))); + timer.start(10000); + loop.exec(); + if (timer.isActive()) { + timer.stop(); + if (reply->error() == QNetworkReply::NoError) { + QString result = reply->readAll(); + status(QString("received ") + result); + result.remove("WebserviceID:"); + reply->deleteLater(); + return result; + } + } else { + status("Getting Web service ID timed out"); + } + reply->deleteLater(); + return QString(); +} + +int GpsLocation::getGpsNum() const +{ + return m_trackers.count(); +} + +static void copy_gps_location(struct gpsTracker &gps, struct dive *d) +{ + struct dive_site *ds = get_dive_site_by_uuid(d->dive_site_uuid); + if (!ds) { + d->dive_site_uuid = create_dive_site(qPrintable(gps.name), gps.when); + ds = get_dive_site_by_uuid(d->dive_site_uuid); + } + ds->latitude = gps.latitude; + ds->longitude = gps.longitude; +} + +#define SAME_GROUP 6 * 3600 /* six hours */ +bool GpsLocation::applyLocations() +{ + int i; + bool changed = false; + int last = 0; + int cnt = m_trackers.count(); + if (cnt == 0) + return false; + + // create a table with the GPS information + QList gpsTable = m_trackers.values(); + + // now walk the dive table and see if we can fill in missing gps data + struct dive *d; + for_each_dive(i, d) { + if (dive_has_gps_location(d)) + continue; + for (int j = last; j < cnt; j++) { + if (time_during_dive_with_offset(d, gpsTable[j].when, SAME_GROUP)) { + if (verbose) + qDebug() << "processing gpsFix @" << get_dive_date_string(gpsTable[j].when) << + "which is withing six hours of dive from" << + get_dive_date_string(d->when) << "until" << + get_dive_date_string(d->when + d->duration.seconds); + /* + * If position is fixed during dive. This is the good one. + * Asign and mark position, and end gps_location loop + */ + if (time_during_dive_with_offset(d, gpsTable[j].when, 0)) { + if (verbose) + qDebug() << "gpsFix is during the dive, pick that one"; + copy_gps_location(gpsTable[j], d); + changed = true; + last = j; + break; + } else { + /* + * If it is not, check if there are more position fixes in SAME_GROUP range + */ + if (j + 1 < cnt && time_during_dive_with_offset(d, gpsTable[j+1].when, SAME_GROUP)) { + if (verbose) + qDebug() << "look at the next gps fix @" << get_dive_date_string(gpsTable[j+1].when); + /* first let's test if this one is during the dive */ + if (time_during_dive_with_offset(d, gpsTable[j+1].when, 0)) { + if (verbose) + qDebug() << "which is during the dive, pick that one"; + copy_gps_location(gpsTable[j+1], d); + changed = true; + last = j + 1; + break; + } + /* we know the gps fixes are sorted; if they are both before the dive, ignore the first, + * if theay are both after the dive, take the first, + * if the first is before and the second is after, take the closer one */ + if (gpsTable[j+1].when < d->when) { + if (verbose) + qDebug() << "which is closer to the start of the dive, do continue with that"; + continue; + } else if (gpsTable[j].when > d->when + d->duration.seconds) { + if (verbose) + qDebug() << "which is even later after the end of the dive, so pick the previous one"; + copy_gps_location(gpsTable[j], d); + changed = true; + last = j; + break; + } else { + /* ok, gpsFix is before, nextgpsFix is after */ + if (d->when - gpsTable[j].when <= gpsTable[j+1].when - (d->when + d->duration.seconds)) { + if (verbose) + qDebug() << "pick the one before as it's closer to the start"; + copy_gps_location(gpsTable[j], d); + changed = true; + last = j; + break; + } else { + if (verbose) + qDebug() << "pick the one after as it's closer to the start"; + copy_gps_location(gpsTable[j+1], d); + changed = true; + last = j + 1; + break; + } + } + /* + * If no more positions in range, the actual is the one. Asign, mark and end loop. + */ + } else { + if (verbose) + qDebug() << "which seems to be the best one for this dive, so pick it"; + copy_gps_location(gpsTable[j], d); + changed = true; + last = j; + break; + } + } + } else { + /* If position is out of SAME_GROUP range and in the future, mark position for + * next dive iteration and end the gps_location loop + */ + if (gpsTable[j].when >= d->when + d->duration.seconds + SAME_GROUP) { + last = j; + break; + } + } + + } + } + if (changed) + mark_divelist_changed(true); + return changed; +} + +QMap GpsLocation::currentGPSInfo() const +{ + return m_trackers; +} + +void GpsLocation::loadFromStorage() +{ + int nr = geoSettings->value(QString("count")).toInt(); + for (int i = 0; i < nr; i++) { + struct gpsTracker gt; + gt.when = geoSettings->value(QString("gpsFix%1_time").arg(i)).toLongLong(); + gt.latitude.udeg = geoSettings->value(QString("gpsFix%1_lat").arg(i)).toInt(); + gt.longitude.udeg = geoSettings->value(QString("gpsFix%1_lon").arg(i)).toInt(); + gt.name = geoSettings->value(QString("gpsFix%1_name").arg(i)).toString(); + gt.idx = i; + m_trackers.insert(gt.when, gt); + } +} + +void GpsLocation::replaceFixToStorage(gpsTracker >) +{ + if (!m_trackers.keys().contains(gt.when)) { + addFixToStorage(gt); + return; + } + gpsTracker replacedTracker = m_trackers.value(gt.when); + geoSettings->setValue(QString("gpsFix%1_time").arg(replacedTracker.idx), gt.when); + geoSettings->setValue(QString("gpsFix%1_lat").arg(replacedTracker.idx), gt.latitude.udeg); + geoSettings->setValue(QString("gpsFix%1_lon").arg(replacedTracker.idx), gt.longitude.udeg); + geoSettings->setValue(QString("gpsFix%1_name").arg(replacedTracker.idx), gt.name); + replacedTracker.latitude = gt.latitude; + replacedTracker.longitude = gt.longitude; + replacedTracker.name = gt.name; +} + +void GpsLocation::addFixToStorage(gpsTracker >) +{ + int nr = m_trackers.count(); + geoSettings->setValue("count", nr + 1); + geoSettings->setValue(QString("gpsFix%1_time").arg(nr), gt.when); + geoSettings->setValue(QString("gpsFix%1_lat").arg(nr), gt.latitude.udeg); + geoSettings->setValue(QString("gpsFix%1_lon").arg(nr), gt.longitude.udeg); + geoSettings->setValue(QString("gpsFix%1_name").arg(nr), gt.name); + gt.idx = nr; + geoSettings->sync(); + m_trackers.insert(gt.when, gt); +} + +void GpsLocation::deleteFixFromStorage(gpsTracker >) +{ + qint64 when = gt.when; + int cnt = m_trackers.count(); + if (cnt == 0 || !m_trackers.keys().contains(when)) { + qDebug() << "no gps fix with timestamp" << when; + return; + } + if (geoSettings->value(QString("gpsFix%1_time").arg(gt.idx)).toLongLong() != when) { + qDebug() << "uh oh - index for tracker has gotten out of sync..."; + return; + } + if (cnt > 1) { + // now we move the last tracker into that spot (so our settings stay consecutive) + // and delete the last settings entry + when = geoSettings->value(QString("gpsFix%1_time").arg(cnt - 1)).toLongLong(); + struct gpsTracker movedTracker = m_trackers.value(when); + movedTracker.idx = gt.idx; + m_trackers.remove(movedTracker.when); + m_trackers.insert(movedTracker.when, movedTracker); + geoSettings->setValue(QString("gpsFix%1_time").arg(movedTracker.idx), when); + geoSettings->setValue(QString("gpsFix%1_lat").arg(movedTracker.idx), movedTracker.latitude.udeg); + geoSettings->setValue(QString("gpsFix%1_lon").arg(movedTracker.idx), movedTracker.longitude.udeg); + geoSettings->setValue(QString("gpsFix%1_name").arg(movedTracker.idx), movedTracker.name); + geoSettings->remove(QString("gpsFix%1_lat").arg(cnt - 1)); + geoSettings->remove(QString("gpsFix%1_lon").arg(cnt - 1)); + geoSettings->remove(QString("gpsFix%1_time").arg(cnt - 1)); + geoSettings->remove(QString("gpsFix%1_name").arg(cnt - 1)); + } + geoSettings->setValue("count", cnt - 1); + geoSettings->sync(); + m_trackers.remove(gt.when); +} + +void GpsLocation::deleteGpsFix(qint64 when) +{ + struct gpsTracker defaultTracker; + defaultTracker.when = 0; + struct gpsTracker deletedTracker = m_trackers.value(when, defaultTracker); + if (deletedTracker.when != when) { + qDebug() << "can't find tracker for timestamp" << when; + return; + } + deleteFixFromStorage(deletedTracker); + m_deletedTrackers.append(deletedTracker); +} + +void GpsLocation::clearGpsData() +{ + m_trackers.clear(); + geoSettings->clear(); + geoSettings->sync(); +} + +void GpsLocation::postError(QNetworkReply::NetworkError error) +{ + Q_UNUSED(error); + status(QString("error when sending a GPS fix: %1").arg(reply->errorString())); +} + +void GpsLocation::getUseridError(QNetworkReply::NetworkError error) +{ + Q_UNUSED(error); + status(QString("error when retrieving Subsurface webservice user id: %1").arg(reply->errorString())); +} + +void GpsLocation::deleteFixesFromServer() +{ + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + + QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); + QUrl url(GPS_FIX_DELETE_URL); + QList keys = m_trackers.keys(); + while (!m_deletedTrackers.isEmpty()) { + gpsTracker gt = m_deletedTrackers.takeFirst(); + QDateTime dt; + QUrlQuery data; + dt.setTime_t(gt.when - gettimezoneoffset(gt.when)); + data.addQueryItem("login", prefs.userid); + data.addQueryItem("dive_date", dt.toString("yyyy-MM-dd")); + data.addQueryItem("dive_time", dt.toString("hh:mm")); + status(data.toString(QUrl::FullyEncoded).toUtf8()); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + request.setRawHeader("Accept", "text/json"); + request.setRawHeader("Content-type", "application/x-www-form-urlencoded"); + reply = manager->post(request, data.toString(QUrl::FullyEncoded).toUtf8()); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(postError(QNetworkReply::NetworkError))); + timer.start(10000); + loop.exec(); + if (timer.isActive()) { + timer.stop(); + if (reply->error() != QNetworkReply::NoError) { + QString response = reply->readAll(); + status(QString("Server response:") + reply->readAll()); + } + } else { + status("Deleting on the server timed out - try again later"); + m_deletedTrackers.prepend(gt); + break; + } + reply->deleteLater(); + status(QString("completed deleting gps fix %1 - response: ").arg(gt.idx) + reply->readAll()); + } +} + +void GpsLocation::uploadToServer() +{ + // we want to do this one at a time (the server prefers that) + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + + QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); + QUrl url(GPS_FIX_ADD_URL); + Q_FOREACH(qint64 key, m_trackers.keys()) { + struct gpsTracker gt = m_trackers.value(key); + QDateTime dt; + QUrlQuery data; + dt.setTime_t(gt.when - gettimezoneoffset(gt.when)); + data.addQueryItem("login", prefs.userid); + data.addQueryItem("dive_date", dt.toString("yyyy-MM-dd")); + data.addQueryItem("dive_time", dt.toString("hh:mm")); + data.addQueryItem("dive_latitude", QString::number(gt.latitude.udeg / 1000000.0, 'f', 9)); + data.addQueryItem("dive_longitude", QString::number(gt.longitude.udeg / 1000000.0, 'f', 9)); + if (gt.name.isEmpty()) + gt.name = "Auto-created dive"; + data.addQueryItem("dive_name", gt.name); + status(data.toString(QUrl::FullyEncoded).toUtf8()); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + request.setRawHeader("Accept", "text/json"); + request.setRawHeader("Content-type", "application/x-www-form-urlencoded"); + reply = manager->post(request, data.toString(QUrl::FullyEncoded).toUtf8()); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + // somehoe I cannot get this to work with the new connect syntax: + // connect(reply, &QNetworkReply::error, this, &GpsLocation::postError); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(postError(QNetworkReply::NetworkError))); + timer.start(10000); + loop.exec(); + if (timer.isActive()) { + timer.stop(); + if (reply->error() != QNetworkReply::NoError) { + QString response = reply->readAll(); + if (!response.contains("Duplicate entry")) { + status(QString("Server response:") + reply->readAll()); + break; + } + } + } else { + status("Uploading to server timed out"); + break; + } + reply->deleteLater(); + status(QString("completed sending gps fix %1 - response: ").arg(gt.idx) + reply->readAll()); + } + // and now remove the ones that were locally deleted + deleteFixesFromServer(); +} + +void GpsLocation::downloadFromServer() +{ + QEventLoop loop; + QTimer timer; + timer.setSingleShot(true); + QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); + QUrl url(QString(GPS_FIX_DOWNLOAD_URL "?login=%1").arg(prefs.userid)); + QNetworkRequest request; + request.setUrl(url); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + request.setRawHeader("Accept", "text/json"); + request.setRawHeader("Content-type", "text/html"); + qDebug() << "downloadFromServer accessing" << url; + reply = manager->get(request); + connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); + connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(getUseridError(QNetworkReply::NetworkError))); + timer.start(10000); + loop.exec(); + if (timer.isActive()) { + timer.stop(); + if (!reply->error()) { + QString response = reply->readAll(); + QJsonDocument json = QJsonDocument::fromJson(response.toLocal8Bit()); + QJsonObject object = json.object(); + if (object.value("download").toString() != "ok") { + qDebug() << "problems downloading GPS fixes"; + return; + } + qDebug() << "already have" << m_trackers.count() << "GPS fixes"; + QJsonArray downloadedFixes = object.value("dives").toArray(); + qDebug() << downloadedFixes.count() << "GPS fixes downloaded"; + for (int i = 0; i < downloadedFixes.count(); i++) { + QJsonObject fix = downloadedFixes[i].toObject(); + QString date = fix.value("date").toString(); + QString time = fix.value("time").toString(); + QString name = fix.value("name").toString(); + QString latitude = fix.value("latitude").toString(); + QString longitude = fix.value("longitude").toString(); + QDateTime timestamp = QDateTime::fromString(date + " " + time, "yyyy-M-d hh:m:s"); + + struct gpsTracker gt; + gt.when = timestamp.toMSecsSinceEpoch() / 1000 + gettimezoneoffset(timestamp.toMSecsSinceEpoch() / 1000); + gt.latitude.udeg = latitude.toDouble() * 1000000; + gt.longitude.udeg = longitude.toDouble() * 1000000; + gt.name = name; + // add this GPS fix to the QMap and the settings (remove existing fix at the same timestamp first) + if (m_trackers.keys().contains(gt.when)) { + qDebug() << "already have a fix at time stamp" << gt.when; + replaceFixToStorage(gt); + } else { + addFixToStorage(gt); + } + } + } else { + qDebug() << "network error" << reply->error() << reply->errorString() << reply->readAll(); + } + } else { + qDebug() << "download timed out"; + status("Download from server timed out"); + } + reply->deleteLater(); +} diff --git a/core/gpslocation.h b/core/gpslocation.h new file mode 100644 index 000000000..82b51a291 --- /dev/null +++ b/core/gpslocation.h @@ -0,0 +1,66 @@ +#ifndef GPSLOCATION_H +#define GPSLOCATION_H + +#include "units.h" +#include +#include +#include +#include +#include +#include +#include + +struct gpsTracker { + degrees_t latitude; + degrees_t longitude; + qint64 when; + QString name; + int idx; +}; + +class GpsLocation : QObject { + Q_OBJECT +public: + GpsLocation(void (*showMsgCB)(const char *msg), QObject *parent); + ~GpsLocation(); + static GpsLocation *instance(); + bool applyLocations(); + int getGpsNum() const; + QString getUserid(QString user, QString passwd); + bool hasLocationsSource(); + QString currentPosition(); + + QMap currentGPSInfo() const; + +private: + QGeoPositionInfo lastPos; + QGeoPositionInfoSource *getGpsSource(); + QGeoPositionInfoSource *m_GpsSource; + void status(QString msg); + QSettings *geoSettings; + QNetworkReply *reply; + QString userAgent; + void (*showMessageCB)(const char *msg); + static GpsLocation *m_Instance; + bool waitingForPosition; + QMap m_trackers; + QList m_deletedTrackers; + void loadFromStorage(); + void addFixToStorage(gpsTracker >); + void replaceFixToStorage(gpsTracker >); + void deleteFixFromStorage(gpsTracker >); + void deleteFixesFromServer(); + +public slots: + void serviceEnable(bool toggle); + void newPosition(QGeoPositionInfo pos); + void updateTimeout(); + void uploadToServer(); + void downloadFromServer(); + void postError(QNetworkReply::NetworkError error); + void getUseridError(QNetworkReply::NetworkError error); + void clearGpsData(); + void deleteGpsFix(qint64 when); +}; + +#endif // GPSLOCATION_H diff --git a/core/helpers.h b/core/helpers.h new file mode 100644 index 000000000..f88da015c --- /dev/null +++ b/core/helpers.h @@ -0,0 +1,56 @@ +/* + * 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); +QString get_volume_unit(); +QString get_pressure_string(pressure_t pressure, bool showunit = false); +QString get_pressure_unit(); +void set_default_dive_computer(const char *vendor, const char *product); +void set_default_dive_computer_device(const char *name); +void set_default_dive_computer_download_mode(int downloadMode); +QString getSubsurfaceDataPath(QString folderToFind); +QString getPrintingTemplatePathUser(); +QString getPrintingTemplatePathBundle(); +void copyPath(QString src, QString dst); +extern const QString get_dc_nickname(const char *model, uint32_t deviceid); +int gettimezoneoffset(timestamp_t when = 0); +int parseLengthToMm(const QString &text); +int parseTemperatureToMkelvin(const QString &text); +int parseWeightToGrams(const QString &text); +int parsePressureToMbar(const QString &text); +int parseGasMixO2(const QString &text); +int parseGasMixHE(const QString &text); +QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText); +QString get_dive_date_string(timestamp_t when); +QString get_short_dive_date_string(timestamp_t when); +bool is_same_day (timestamp_t trip_when, timestamp_t dive_when); +QString get_trip_date_string(timestamp_t when, int nr, bool getday); +QString uiLanguage(QLocale *callerLoc); +QLocale getLocale(); +void selectedDivesGasUsed(QVector > &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/core/imagedownloader.cpp b/core/imagedownloader.cpp new file mode 100644 index 000000000..f406ee45a --- /dev/null +++ b/core/imagedownloader.cpp @@ -0,0 +1,113 @@ +#include "dive.h" +#include "metrics.h" +#include "divelist.h" +#include "qthelper.h" +#include "imagedownloader.h" +#include + +#include + +QUrl cloudImageURL(const char *hash) +{ + return QUrl::fromUserInput(QString("https://cloud.subsurface-divelog.org/images/").append(hash)); +} + +ImageDownloader::ImageDownloader(struct picture *pic) +{ + picture = pic; +} + +ImageDownloader::~ImageDownloader() +{ + picture_free(picture); +} + +void ImageDownloader::load(bool fromHash){ + QUrl url; + loadFromHash = fromHash; + if(fromHash) + url = cloudImageURL(picture->hash); + else + url = QUrl::fromUserInput(QString(picture->filename)); + if (url.isValid()) { + QEventLoop loop; + QNetworkRequest request(url); + connect(&manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(saveImage(QNetworkReply *))); + QNetworkReply *reply = manager.get(request); + while (reply->isRunning()) { + loop.processEvents(); + sleep(1); + } + } +} + +void ImageDownloader::saveImage(QNetworkReply *reply) +{ + QByteArray imageData = reply->readAll(); + QImage image = QImage(); + image.loadFromData(imageData); + if (image.isNull()) { + if (loadFromHash) + load(false); + return; + } + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(imageData); + QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first(); + QDir dir(path); + if (!dir.exists()) + dir.mkpath(path); + QFile imageFile(path.append("/").append(hash.result().toHex())); + if (imageFile.open(QIODevice::WriteOnly)) { + QDataStream stream(&imageFile); + stream.writeRawData(imageData.data(), imageData.length()); + imageFile.waitForBytesWritten(-1); + imageFile.close(); + add_hash(imageFile.fileName(), hash.result()); + learnHash(picture, hash.result()); + } + reply->manager()->deleteLater(); + reply->deleteLater(); + // This should be called to make the picture actually show. + // Problem is DivePictureModel is not in core. + // Nevertheless, the image shows when the dive is selected the next time. + // DivePictureModel::instance()->updateDivePictures(); + +} + +void loadPicture(struct picture *picture, bool fromHash) +{ + ImageDownloader download(picture); + download.load(fromHash); +} + +SHashedImage::SHashedImage(struct picture *picture) : QImage() +{ + QUrl url = QUrl::fromUserInput(localFilePath(QString(picture->filename))); + if(url.isLocalFile()) + load(url.toLocalFile()); + if (isNull()) { + // This did not load anything. Let's try to get the image from other sources + // Let's try to load it locally via its hash + QString filename = fileFromHash(picture->hash); + if (filename.isNull()) { + // That didn't produce a local filename. + // Try the cloud server + QtConcurrent::run(loadPicture, clone_picture(picture), true); + } else { + // Load locally from translated file name + load(filename); + if (!isNull()) { + // Make sure the hash still matches the image file + QtConcurrent::run(updateHash, clone_picture(picture)); + } else { + // Interpret filename as URL + QtConcurrent::run(loadPicture, clone_picture(picture), false); + } + } + } else { + // We loaded successfully. Now, make sure hash is up to date. + QtConcurrent::run(hashPicture, clone_picture(picture)); + } +} + diff --git a/core/imagedownloader.h b/core/imagedownloader.h new file mode 100644 index 000000000..f4e3df875 --- /dev/null +++ b/core/imagedownloader.h @@ -0,0 +1,34 @@ +#ifndef IMAGEDOWNLOADER_H +#define IMAGEDOWNLOADER_H + +#include +#include +#include + +typedef QPair SHashedFilename; + +extern QUrl cloudImageURL(const char *hash); + + +class ImageDownloader : public QObject { + Q_OBJECT; +public: + ImageDownloader(struct picture *picture); + ~ImageDownloader(); + void load(bool fromHash); + +private: + struct picture *picture; + QNetworkAccessManager manager; + bool loadFromHash; + +private slots: + void saveImage(QNetworkReply *reply); +}; + +class SHashedImage : public QImage { +public: + SHashedImage(struct picture *picture); +}; + +#endif // IMAGEDOWNLOADER_H diff --git a/core/isocialnetworkintegration.cpp b/core/isocialnetworkintegration.cpp new file mode 100644 index 000000000..eb1e82a49 --- /dev/null +++ b/core/isocialnetworkintegration.cpp @@ -0,0 +1,6 @@ +#include "isocialnetworkintegration.h" + +//Hack for moc. +ISocialNetworkIntegration::ISocialNetworkIntegration(QObject* parent) : QObject(parent) +{ +} diff --git a/core/isocialnetworkintegration.h b/core/isocialnetworkintegration.h new file mode 100644 index 000000000..70ea3d9ab --- /dev/null +++ b/core/isocialnetworkintegration.h @@ -0,0 +1,73 @@ +#ifndef ISOCIALNETWORKINTEGRATION_H +#define ISOCIALNETWORKINTEGRATION_H + +#include + +/* This Interface represents a Plugin for Social Network integration, + * with it you may be able to create plugins for facebook, instagram, + * twitpic, google plus and any other thing you may imagine. + * + * We bundle facebook integration as an example. + */ + +class ISocialNetworkIntegration : public QObject { + Q_OBJECT +public: + ISocialNetworkIntegration(QObject* parent = 0); + + /*! + * @name socialNetworkName + * @brief The name of this social network + * @return The name of this social network + * + * The name of this social network will be used to populate the Menu to toggle states + * between connected/disconnected, and also submit stuff to it. + */ + virtual QString socialNetworkName() const = 0; + + /*! + * @name socialNetworkIcon + * @brief The icon of this social network + * @return The icon of this social network + * + * The icon of this social network will be used to populate the menu, and can also be + * used on a toolbar if requested. + */ + virtual QString socialNetworkIcon() const = 0; + + /*! + * @name isConnected + * @brief returns true if connected to this social network, false otherwise + * @return true if connected to this social network, false otherwise + */ + virtual bool isConnected() = 0; + + /*! + * @name requestLogin + * @brief try to login on this social network. + * + * Try to login on this social network. All widget implementation that + * manages login should be done inside this function. + */ + virtual void requestLogin() = 0; + + /*! + * @name requestLogoff + * @brief tries to logoff from this social network + * + * Try to logoff from this social network. + */ + virtual void requestLogoff() = 0; + + /*! + * @name uploadCurrentDive + * @brief send the current dive info to the Social Network + * + * Should format all the options and pixmaps from the current dive + * to update to the social network. All widget stuff related to sendint + * dive information should be executed inside this function. + */ + virtual void requestUpload() = 0; +}; + +#endif \ No newline at end of file diff --git a/core/libdivecomputer.c b/core/libdivecomputer.c new file mode 100644 index 000000000..549b894ce --- /dev/null +++ b/core/libdivecomputer.c @@ -0,0 +1,1081 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#include +#include +#include +#include +#include "gettext.h" +#include "dive.h" +#include "device.h" +#include "divelist.h" +#include "display.h" + +#include +#include +#include +#include "libdivecomputer.h" + +/* Christ. Libdivecomputer has the worst configuration system ever. */ +#ifdef HW_FROG_H +#define NOT_FROG , 0 +#define LIBDIVECOMPUTER_SUPPORTS_FROG +#else +#define NOT_FROG +#endif + +char *dumpfile_name; +char *logfile_name; +const char *progress_bar_text = ""; +double progress_bar_fraction = 0.0; + +static int stoptime, stopdepth, ndl, po2, cns; +static bool in_deco, first_temp_is_air; + +/* + * Directly taken from libdivecomputer's examples/common.c to improve + * the error messages resulting from libdc's return codes + */ +const char *errmsg (dc_status_t rc) +{ + switch (rc) { + case DC_STATUS_SUCCESS: + return "Success"; + case DC_STATUS_UNSUPPORTED: + return "Unsupported operation"; + case DC_STATUS_INVALIDARGS: + return "Invalid arguments"; + case DC_STATUS_NOMEMORY: + return "Out of memory"; + case DC_STATUS_NODEVICE: + return "No device found"; + case DC_STATUS_NOACCESS: + return "Access denied"; + case DC_STATUS_IO: + return "Input/output error"; + case DC_STATUS_TIMEOUT: + return "Timeout"; + case DC_STATUS_PROTOCOL: + return "Protocol error"; + case DC_STATUS_DATAFORMAT: + return "Data format error"; + case DC_STATUS_CANCELLED: + return "Cancelled"; + default: + return "Unknown error"; + } +} + +static dc_status_t create_parser(device_data_t *devdata, dc_parser_t **parser) +{ + return dc_parser_new(parser, devdata->device); +} + +static int parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_parser_t *parser, unsigned int ngases) +{ + static bool shown_warning = false; + unsigned int i; + int rc; + +#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) + unsigned int ntanks = 0; + rc = dc_parser_get_field(parser, DC_FIELD_TANK_COUNT, 0, &ntanks); + if (rc == DC_STATUS_SUCCESS) { + if (ntanks && ntanks != ngases) { + shown_warning = true; + report_error("different number of gases (%d) and tanks (%d)", ngases, ntanks); + } + } + dc_tank_t tank = { 0 }; +#endif + + for (i = 0; i < ngases; i++) { + dc_gasmix_t gasmix = { 0 }; + int o2, he; + bool no_volume = true; + + rc = dc_parser_get_field(parser, DC_FIELD_GASMIX, i, &gasmix); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) + return rc; + + if (i >= MAX_CYLINDERS) + continue; + + o2 = rint(gasmix.oxygen * 1000); + he = rint(gasmix.helium * 1000); + + /* Ignore bogus data - libdivecomputer does some crazy stuff */ + if (o2 + he <= O2_IN_AIR || o2 > 1000) { + if (!shown_warning) { + shown_warning = true; + report_error("unlikely dive gas data from libdivecomputer: o2 = %d he = %d", o2, he); + } + o2 = 0; + } + if (he < 0 || o2 + he > 1000) { + if (!shown_warning) { + shown_warning = true; + report_error("unlikely dive gas data from libdivecomputer: o2 = %d he = %d", o2, he); + } + he = 0; + } + dive->cylinder[i].gasmix.o2.permille = o2; + dive->cylinder[i].gasmix.he.permille = he; + +#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) + tank.volume = 0.0; + if (i < ntanks) { + rc = dc_parser_get_field(parser, DC_FIELD_TANK, i, &tank); + if (rc == DC_STATUS_SUCCESS) { + if (tank.type == DC_TANKVOLUME_IMPERIAL) { + dive->cylinder[i].type.size.mliter = rint(tank.volume * 1000); + dive->cylinder[i].type.workingpressure.mbar = rint(tank.workpressure * 1000); + if (same_string(devdata->model, "Suunto EON Steel")) { + /* Suunto EON Steele gets this wrong. Badly. + * but on the plus side it only supports a few imperial sizes, + * so let's try and guess at least the most common ones. + * First, the pressures are off by a constant factor. WTF? + * Then we can round the wet sizes so we get to multiples of 10 + * for cuft sizes (as that's all that you can enter) */ + dive->cylinder[i].type.workingpressure.mbar *= 206.843 / 206.7; + char name_buffer[9]; + int rounded_size = ml_to_cuft(gas_volume(&dive->cylinder[i], + dive->cylinder[i].type.workingpressure)); + rounded_size = (int)((rounded_size + 5) / 10) * 10; + switch (dive->cylinder[i].type.workingpressure.mbar) { + case 206843: + snprintf(name_buffer, 9, "AL%d", rounded_size); + break; + case 234422: /* this is wrong - HP tanks tend to be 3440, but Suunto only allows 3400 */ + snprintf(name_buffer, 9, "HP%d", rounded_size); + break; + case 179263: + snprintf(name_buffer, 9, "LP+%d", rounded_size); + break; + case 165474: + snprintf(name_buffer, 9, "LP%d", rounded_size); + break; + default: + snprintf(name_buffer, 9, "%d cuft", rounded_size); + break; + } + dive->cylinder[i].type.description = copy_string(name_buffer); + dive->cylinder[i].type.size.mliter = cuft_to_l(rounded_size) * 1000 / + mbar_to_atm(dive->cylinder[i].type.workingpressure.mbar); + } + } else if (tank.type == DC_TANKVOLUME_METRIC) { + dive->cylinder[i].type.size.mliter = rint(tank.volume * 1000); + } + if (tank.gasmix != i) { // we don't handle this, yet + shown_warning = true; + report_error("gasmix %d for tank %d doesn't match", tank.gasmix, i); + } + } + } + if (!IS_FP_SAME(tank.volume, 0.0)) + no_volume = false; + + // this new API also gives us the beginning and end pressure for the tank + if (!IS_FP_SAME(tank.beginpressure, 0.0) && !IS_FP_SAME(tank.endpressure, 0.0)) { + dive->cylinder[i].start.mbar = tank.beginpressure * 1000; + dive->cylinder[i].end.mbar = tank.endpressure * 1000; + } +#endif + if (no_volume) { + /* for the first tank, if there is no tanksize available from the + * dive computer, fill in the default tank information (if set) */ + fill_default_cylinder(&dive->cylinder[i]); + } + /* whatever happens, make sure there is a name for the cylinder */ + if (same_string(dive->cylinder[i].type.description, "")) + dive->cylinder[i].type.description = strdup(translate("gettextFromC", "unknown")); + } + return DC_STATUS_SUCCESS; +} + +static void handle_event(struct divecomputer *dc, struct sample *sample, dc_sample_value_t value) +{ + int type, time; + /* we mark these for translation here, but we store the untranslated strings + * and only translate them when they are displayed on screen */ + static const char *events[] = { + QT_TRANSLATE_NOOP("gettextFromC", "none"), QT_TRANSLATE_NOOP("gettextFromC", "deco stop"), QT_TRANSLATE_NOOP("gettextFromC", "rbt"), QT_TRANSLATE_NOOP("gettextFromC", "ascent"), QT_TRANSLATE_NOOP("gettextFromC", "ceiling"), QT_TRANSLATE_NOOP("gettextFromC", "workload"), + QT_TRANSLATE_NOOP("gettextFromC", "transmitter"), QT_TRANSLATE_NOOP("gettextFromC", "violation"), QT_TRANSLATE_NOOP("gettextFromC", "bookmark"), QT_TRANSLATE_NOOP("gettextFromC", "surface"), QT_TRANSLATE_NOOP("gettextFromC", "safety stop"), + QT_TRANSLATE_NOOP("gettextFromC", "gaschange"), QT_TRANSLATE_NOOP("gettextFromC", "safety stop (voluntary)"), QT_TRANSLATE_NOOP("gettextFromC", "safety stop (mandatory)"), + QT_TRANSLATE_NOOP("gettextFromC", "deepstop"), QT_TRANSLATE_NOOP("gettextFromC", "ceiling (safety stop)"), QT_TRANSLATE_NOOP3("gettextFromC", "below floor", "event showing dive is below deco floor and adding deco time"), QT_TRANSLATE_NOOP("gettextFromC", "divetime"), + QT_TRANSLATE_NOOP("gettextFromC", "maxdepth"), QT_TRANSLATE_NOOP("gettextFromC", "OLF"), QT_TRANSLATE_NOOP("gettextFromC", "pO₂"), QT_TRANSLATE_NOOP("gettextFromC", "airtime"), QT_TRANSLATE_NOOP("gettextFromC", "rgbm"), QT_TRANSLATE_NOOP("gettextFromC", "heading"), + QT_TRANSLATE_NOOP("gettextFromC", "tissue level warning"), QT_TRANSLATE_NOOP("gettextFromC", "gaschange"), QT_TRANSLATE_NOOP("gettextFromC", "non stop time") + }; + const int nr_events = sizeof(events) / sizeof(const char *); + const char *name; + /* + * Just ignore surface events. They are pointless. What "surface" + * means depends on the dive computer (and possibly even settings + * in the dive computer). It does *not* necessarily mean "depth 0", + * so don't even turn it into that. + */ + if (value.event.type == SAMPLE_EVENT_SURFACE) + return; + + /* + * Other evens might be more interesting, but for now we just print them out. + */ + type = value.event.type; + name = QT_TRANSLATE_NOOP("gettextFromC", "invalid event number"); + if (type < nr_events) + name = events[type]; + + time = value.event.time; + if (sample) + time += sample->time.seconds; + + add_event(dc, time, type, value.event.flags, value.event.value, name); +} + +void +sample_cb(dc_sample_type_t type, dc_sample_value_t value, void *userdata) +{ + static unsigned int nsensor = 0; + struct divecomputer *dc = userdata; + struct sample *sample; + + /* + * We fill in the "previous" sample - except for DC_SAMPLE_TIME, + * which creates a new one. + */ + sample = dc->samples ? dc->sample + dc->samples - 1 : NULL; + + /* + * Ok, sanity check. + * If first sample is not a DC_SAMPLE_TIME, Allocate a sample for us + */ + if (sample == NULL && type != DC_SAMPLE_TIME) + sample = prepare_sample(dc); + + switch (type) { + case DC_SAMPLE_TIME: + nsensor = 0; + + // The previous sample gets some sticky values + // that may have been around from before, even + // if there was no new data + if (sample) { + sample->in_deco = in_deco; + sample->ndl.seconds = ndl; + sample->stoptime.seconds = stoptime; + sample->stopdepth.mm = stopdepth; + sample->setpoint.mbar = po2; + sample->cns = cns; + } + // Create a new sample. + // Mark depth as negative + sample = prepare_sample(dc); + sample->time.seconds = value.time; + sample->depth.mm = -1; + finish_sample(dc); + break; + case DC_SAMPLE_DEPTH: + sample->depth.mm = rint(value.depth * 1000); + break; + case DC_SAMPLE_PRESSURE: + sample->sensor = value.pressure.tank; + sample->cylinderpressure.mbar = rint(value.pressure.value * 1000); + break; + case DC_SAMPLE_TEMPERATURE: + sample->temperature.mkelvin = C_to_mkelvin(value.temperature); + break; + case DC_SAMPLE_EVENT: + handle_event(dc, sample, value); + break; + case DC_SAMPLE_RBT: + sample->rbt.seconds = (!strncasecmp(dc->model, "suunto", 6)) ? value.rbt : value.rbt * 60; + break; + case DC_SAMPLE_HEARTBEAT: + sample->heartbeat = value.heartbeat; + break; + case DC_SAMPLE_BEARING: + sample->bearing.degrees = value.bearing; + break; +#ifdef DEBUG_DC_VENDOR + case DC_SAMPLE_VENDOR: + printf(" ", 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, ...) +{ + (void) devdata; + static char buffer[1024]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + progress_bar_text = buffer; +} + +static int import_dive_number = 0; + +static int parse_samples(device_data_t *devdata, struct divecomputer *dc, dc_parser_t *parser) +{ + (void) devdata; + // Parse the sample data. + return dc_parser_samples_foreach(parser, sample_cb, dc); +} + +static int might_be_same_dc(struct divecomputer *a, struct divecomputer *b) +{ + if (!a->model || !b->model) + return 1; + if (strcasecmp(a->model, b->model)) + return 0; + if (!a->deviceid || !b->deviceid) + return 1; + return a->deviceid == b->deviceid; +} + +static int match_one_dive(struct divecomputer *a, struct dive *dive) +{ + struct divecomputer *b = &dive->dc; + + /* + * Walk the existing dive computer data, + * see if we have a match (or an anti-match: + * the same dive computer but a different + * dive ID). + */ + do { + int match = match_one_dc(a, b); + if (match) + return match > 0; + b = b->next; + } while (b); + + /* Ok, no exact dive computer match. Does the date match? */ + b = &dive->dc; + do { + if (a->when == b->when && might_be_same_dc(a, b)) + return 1; + b = b->next; + } while (b); + + return 0; +} + +/* + * Check if this dive already existed before the import + */ +static int find_dive(struct divecomputer *match) +{ + int i; + + for (i = 0; i < dive_table.preexisting; i++) { + struct dive *old = dive_table.dives[i]; + + if (match_one_dive(match, old)) + return 1; + } + return 0; +} + +/* + * Like g_strdup_printf(), but without the stupid g_malloc/g_free confusion. + * And we limit the string to some arbitrary size. + */ +static char *str_printf(const char *fmt, ...) +{ + va_list args; + char buf[1024]; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf) - 1, fmt, args); + va_end(args); + buf[sizeof(buf) - 1] = 0; + return strdup(buf); +} + +/* + * The dive ID for libdivecomputer dives is the first word of the + * SHA1 of the fingerprint, if it exists. + * + * NOTE! This is byte-order dependent, and I don't care. + */ +static uint32_t calculate_diveid(const unsigned char *fingerprint, unsigned int fsize) +{ + uint32_t csum[5]; + + if (!fingerprint || !fsize) + return 0; + + SHA1(fingerprint, fsize, (unsigned char *)csum); + return csum[0]; +} + +#ifdef DC_FIELD_STRING +static uint32_t calculate_string_hash(const char *str) +{ + return calculate_diveid((const unsigned char *)str, strlen(str)); +} + +static void parse_string_field(struct dive *dive, dc_field_string_t *str) +{ + // Our dive ID is the string hash of the "Dive ID" string + if (!strcmp(str->desc, "Dive ID")) { + if (!dive->dc.diveid) + dive->dc.diveid = calculate_string_hash(str->value); + return; + } + add_extra_data(&dive->dc, str->desc, str->value); + if (!strcmp(str->desc, "Serial")) { + dive->dc.serial = strdup(str->value); + /* should we just overwrite this whenever we have the "Serial" field? + * It's a much better deviceid then what we have so far... for now I'm leaving it as is */ + if (!dive->dc.deviceid) + dive->dc.deviceid = calculate_string_hash(str->value); + return; + } + if (!strcmp(str->desc, "FW Version")) { + dive->dc.fw_version = strdup(str->value); + return; + } +} +#endif + +static dc_status_t libdc_header_parser(dc_parser_t *parser, struct device_data_t *devdata, struct dive *dive) +{ + dc_status_t rc = 0; + dc_datetime_t dt = { 0 }; + struct tm tm; + + rc = dc_parser_get_datetime(parser, &dt); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + dev_info(devdata, translate("gettextFromC", "Error parsing the datetime")); + return rc; + } + + dive->dc.deviceid = devdata->deviceid; + + if (rc == DC_STATUS_SUCCESS) { + tm.tm_year = dt.year; + tm.tm_mon = dt.month - 1; + tm.tm_mday = dt.day; + tm.tm_hour = dt.hour; + tm.tm_min = dt.minute; + tm.tm_sec = dt.second; + dive->when = dive->dc.when = utc_mktime(&tm); + } + + // Parse the divetime. + const char *date_string = get_dive_date_c_string(dive->when); + dev_info(devdata, translate("gettextFromC", "Dive %d: %s"), import_dive_number, date_string); + free((void *)date_string); + + unsigned int divetime = 0; + rc = dc_parser_get_field(parser, DC_FIELD_DIVETIME, 0, &divetime); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + dev_info(devdata, translate("gettextFromC", "Error parsing the divetime")); + return rc; + } + if (rc == DC_STATUS_SUCCESS) + dive->dc.duration.seconds = divetime; + + // Parse the maxdepth. + double maxdepth = 0.0; + rc = dc_parser_get_field(parser, DC_FIELD_MAXDEPTH, 0, &maxdepth); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + dev_info(devdata, translate("gettextFromC", "Error parsing the maxdepth")); + return rc; + } + if (rc == DC_STATUS_SUCCESS) + dive->dc.maxdepth.mm = rint(maxdepth * 1000); + +#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) + // if this is defined then we have a fairly late version of libdivecomputer + // from the 0.5 development cylcle - most likely temperatures and tank sizes + // are supported + + // Parse temperatures + double temperature; + dc_field_type_t temp_fields[] = {DC_FIELD_TEMPERATURE_SURFACE, + DC_FIELD_TEMPERATURE_MAXIMUM, + DC_FIELD_TEMPERATURE_MINIMUM}; + for (int i = 0; i < 3; i++) { + rc = dc_parser_get_field(parser, temp_fields[i], 0, &temperature); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + dev_info(devdata, translate("gettextFromC", "Error parsing temperature")); + return rc; + } + if (rc == DC_STATUS_SUCCESS) + switch(i) { + case 0: + dive->dc.airtemp.mkelvin = C_to_mkelvin(temperature); + break; + case 1: // we don't distinguish min and max water temp here, so take min if given, max otherwise + case 2: + dive->dc.watertemp.mkelvin = C_to_mkelvin(temperature); + break; + } + } +#endif + + // Parse the gas mixes. + unsigned int ngases = 0; + rc = dc_parser_get_field(parser, DC_FIELD_GASMIX_COUNT, 0, &ngases); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + dev_info(devdata, translate("gettextFromC", "Error parsing the gas mix count")); + return rc; + } + +#if DC_VERSION_CHECK(0, 3, 0) + // Check if the libdivecomputer version already supports salinity & atmospheric + dc_salinity_t salinity = { + .type = DC_WATER_SALT, + .density = SEAWATER_SALINITY / 10.0 + }; + rc = dc_parser_get_field(parser, DC_FIELD_SALINITY, 0, &salinity); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + dev_info(devdata, translate("gettextFromC", "Error obtaining water salinity")); + return rc; + } + if (rc == DC_STATUS_SUCCESS) + dive->dc.salinity = rint(salinity.density * 10.0); + + double surface_pressure = 0; + rc = dc_parser_get_field(parser, DC_FIELD_ATMOSPHERIC, 0, &surface_pressure); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + dev_info(devdata, translate("gettextFromC", "Error obtaining surface pressure")); + return rc; + } + if (rc == DC_STATUS_SUCCESS) + dive->dc.surface_pressure.mbar = rint(surface_pressure * 1000.0); +#endif + +#ifdef DC_FIELD_STRING + // The dive parsing may give us more device information + int idx; + for (idx = 0; idx < 100; idx++) { + dc_field_string_t str = { NULL }; + rc = dc_parser_get_field(parser, DC_FIELD_STRING, idx, &str); + if (rc != DC_STATUS_SUCCESS) + break; + if (!str.desc || !str.value) + break; + parse_string_field(dive, &str); + } +#endif + +#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) + dc_divemode_t divemode; + rc = dc_parser_get_field(parser, DC_FIELD_DIVEMODE, 0, &divemode); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + dev_info(devdata, translate("gettextFromC", "Error obtaining divemode")); + return rc; + } + if (rc == DC_STATUS_SUCCESS) + switch(divemode) { + case DC_DIVEMODE_FREEDIVE: + dive->dc.divemode = FREEDIVE; + break; + case DC_DIVEMODE_GAUGE: + case DC_DIVEMODE_OC: /* Open circuit */ + dive->dc.divemode = OC; + break; + case DC_DIVEMODE_CC: /* Closed circuit */ + dive->dc.divemode = CCR; + break; + } +#endif + + rc = parse_gasmixes(devdata, dive, parser, ngases); + if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { + dev_info(devdata, translate("gettextFromC", "Error parsing the gas mix")); + return rc; + } + + return DC_STATUS_SUCCESS; +} + +/* returns true if we want libdivecomputer's dc_device_foreach() to continue, + * false otherwise */ +static int dive_cb(const unsigned char *data, unsigned int size, + const unsigned char *fingerprint, unsigned int fsize, + void *userdata) +{ + int rc; + dc_parser_t *parser = NULL; + device_data_t *devdata = userdata; + struct dive *dive = NULL; + + /* reset the deco / ndl data */ + ndl = stoptime = stopdepth = 0; + in_deco = false; + + rc = create_parser(devdata, &parser); + if (rc != DC_STATUS_SUCCESS) { + dev_info(devdata, translate("gettextFromC", "Unable to create parser for %s %s"), devdata->vendor, devdata->product); + return false; + } + + rc = dc_parser_set_data(parser, data, size); + if (rc != DC_STATUS_SUCCESS) { + dev_info(devdata, translate("gettextFromC", "Error registering the data")); + goto error_exit; + } + + import_dive_number++; + dive = alloc_dive(); + + // Parse the dive's header data + rc = libdc_header_parser (parser, devdata, dive); + if (rc != DC_STATUS_SUCCESS) { + dev_info(devdata, translate("getextFromC", "Error parsing the header")); + goto error_exit; + } + + dive->dc.model = strdup(devdata->model); + dive->dc.diveid = calculate_diveid(fingerprint, fsize); + + // Initialize the sample data. + rc = parse_samples(devdata, &dive->dc, parser); + if (rc != DC_STATUS_SUCCESS) { + dev_info(devdata, translate("gettextFromC", "Error parsing the samples")); + goto error_exit; + } + + /* If we already saw this dive, abort. */ + if (!devdata->force_download && find_dive(&dive->dc)) + goto error_exit; + + dc_parser_destroy(parser); + + /* Various libdivecomputer interface fixups */ + if (dive->dc.airtemp.mkelvin == 0 && first_temp_is_air && dive->dc.samples) { + dive->dc.airtemp = dive->dc.sample[0].temperature; + dive->dc.sample[0].temperature.mkelvin = 0; + } + + if (devdata->create_new_trip) { + if (!devdata->trip) + devdata->trip = create_and_hookup_trip_from_dive(dive); + else + add_dive_to_trip(dive, devdata->trip); + } + + dive->downloaded = true; + record_dive_to_table(dive, devdata->download_table); + mark_divelist_changed(true); + return true; + +error_exit: + dc_parser_destroy(parser); + free(dive); + return false; + +} + +/* + * The device ID for libdivecomputer devices is the first 32-bit word + * of the SHA1 hash of the model/firmware/serial numbers. + * + * NOTE! This is byte-order-dependent. And I can't find it in myself to + * care. + */ +static uint32_t calculate_sha1(unsigned int model, unsigned int firmware, unsigned int serial) +{ + SHA_CTX ctx; + uint32_t csum[5]; + + SHA1_Init(&ctx); + SHA1_Update(&ctx, &model, sizeof(model)); + SHA1_Update(&ctx, &firmware, sizeof(firmware)); + SHA1_Update(&ctx, &serial, sizeof(serial)); + SHA1_Final((unsigned char *)csum, &ctx); + return csum[0]; +} + +/* + * libdivecomputer has returned two different serial numbers for the + * same device in different versions. First it used to just do the four + * bytes as one 32-bit number, then it turned it into a decimal number + * with each byte giving two digits (0-99). + * + * The only way we can tell is by looking at the format of the number, + * so we'll just fix it to the first format. + */ +static unsigned int undo_libdivecomputer_suunto_nr_changes(unsigned int serial) +{ + unsigned char b0, b1, b2, b3; + + /* + * The second format will never have more than 8 decimal + * digits, so do a cheap check first + */ + if (serial >= 100000000) + return serial; + + /* The original format seems to be four bytes of values 00-99 */ + b0 = (serial >> 0) & 0xff; + b1 = (serial >> 8) & 0xff; + b2 = (serial >> 16) & 0xff; + b3 = (serial >> 24) & 0xff; + + /* Looks like an old-style libdivecomputer serial number */ + if ((b0 < 100) && (b1 < 100) && (b2 < 100) && (b3 < 100)) + return serial; + + /* Nope, it was converted. */ + b0 = serial % 100; + serial /= 100; + b1 = serial % 100; + serial /= 100; + b2 = serial % 100; + serial /= 100; + b3 = serial % 100; + + serial = b0 + (b1 << 8) + (b2 << 16) + (b3 << 24); + return serial; +} + +static unsigned int fixup_suunto_versions(device_data_t *devdata, const dc_event_devinfo_t *devinfo) +{ + unsigned int serial = devinfo->serial; + char serial_nr[13] = ""; + char firmware[13] = ""; + + first_temp_is_air = 1; + + serial = undo_libdivecomputer_suunto_nr_changes(serial); + + if (serial) { + snprintf(serial_nr, sizeof(serial_nr), "%02d%02d%02d%02d", + (devinfo->serial >> 24) & 0xff, + (devinfo->serial >> 16) & 0xff, + (devinfo->serial >> 8) & 0xff, + (devinfo->serial >> 0) & 0xff); + } + if (devinfo->firmware) { + snprintf(firmware, sizeof(firmware), "%d.%d.%d", + (devinfo->firmware >> 16) & 0xff, + (devinfo->firmware >> 8) & 0xff, + (devinfo->firmware >> 0) & 0xff); + } + create_device_node(devdata->model, devdata->deviceid, serial_nr, firmware, ""); + + return serial; +} + +static void event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) +{ + (void) device; + const dc_event_progress_t *progress = data; + const dc_event_devinfo_t *devinfo = data; + const dc_event_clock_t *clock = data; + const dc_event_vendor_t *vendor = data; + device_data_t *devdata = userdata; + unsigned int serial; + + switch (event) { + case DC_EVENT_WAITING: + dev_info(devdata, translate("gettextFromC", "Event: waiting for user action")); + break; + case DC_EVENT_PROGRESS: + if (!progress->maximum) + break; + progress_bar_fraction = (double)progress->current / (double)progress->maximum; + break; + case DC_EVENT_DEVINFO: + dev_info(devdata, translate("gettextFromC", "model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)"), + devinfo->model, devinfo->model, + devinfo->firmware, devinfo->firmware, + devinfo->serial, devinfo->serial); + if (devdata->libdc_logfile) { + fprintf(devdata->libdc_logfile, "Event: model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)\n", + devinfo->model, devinfo->model, + devinfo->firmware, devinfo->firmware, + devinfo->serial, devinfo->serial); + } + /* + * libdivecomputer doesn't give serial numbers in the proper string form, + * so we have to see if we can do some vendor-specific munging. + */ + serial = devinfo->serial; + if (!strcmp(devdata->vendor, "Suunto")) + serial = fixup_suunto_versions(devdata, devinfo); + devdata->deviceid = calculate_sha1(devinfo->model, devinfo->firmware, serial); + /* really, serial and firmware version are NOT numbers. We'll try to save them here + * in something that might work, but this really needs to be handled with the + * DC_FIELD_STRING interface instead */ + devdata->libdc_serial = devinfo->serial; + devdata->libdc_firmware = devinfo->firmware; + break; + case DC_EVENT_CLOCK: + dev_info(devdata, translate("gettextFromC", "Event: systime=%" PRId64 ", devtime=%u\n"), + (uint64_t)clock->systime, clock->devtime); + if (devdata->libdc_logfile) { + fprintf(devdata->libdc_logfile, "Event: systime=%" PRId64 ", devtime=%u\n", + (uint64_t)clock->systime, clock->devtime); + } + break; + case DC_EVENT_VENDOR: + if (devdata->libdc_logfile) { + fprintf(devdata->libdc_logfile, "Event: vendor="); + for (unsigned int i = 0; i < vendor->size; ++i) + fprintf(devdata->libdc_logfile, "%02X", vendor->data[i]); + fprintf(devdata->libdc_logfile, "\n"); + } + break; + default: + break; + } +} + +int import_thread_cancelled; + +static int cancel_cb(void *userdata) +{ + (void) userdata; + return import_thread_cancelled; +} + +static const char *do_device_import(device_data_t *data) +{ + dc_status_t rc; + dc_device_t *device = data->device; + + data->model = str_printf("%s %s", data->vendor, data->product); + + // Register the event handler. + int events = DC_EVENT_WAITING | DC_EVENT_PROGRESS | DC_EVENT_DEVINFO | DC_EVENT_CLOCK | DC_EVENT_VENDOR; + rc = dc_device_set_events(device, events, event_cb, data); + if (rc != DC_STATUS_SUCCESS) + return translate("gettextFromC", "Error registering the event handler."); + + // Register the cancellation handler. + rc = dc_device_set_cancel(device, cancel_cb, data); + if (rc != DC_STATUS_SUCCESS) + return translate("gettextFromC", "Error registering the cancellation handler."); + + if (data->libdc_dump) { + dc_buffer_t *buffer = dc_buffer_new(0); + + rc = dc_device_dump(device, buffer); + if (rc == DC_STATUS_SUCCESS && dumpfile_name) { + FILE *fp = subsurface_fopen(dumpfile_name, "wb"); + if (fp != NULL) { + fwrite(dc_buffer_get_data(buffer), 1, dc_buffer_get_size(buffer), fp); + fclose(fp); + } + } + + dc_buffer_free(buffer); + } else { + rc = dc_device_foreach(device, dive_cb, data); + } + + if (rc != DC_STATUS_SUCCESS) { + progress_bar_fraction = 0.0; + return translate("gettextFromC", "Dive data import error"); + } + + /* All good */ + return NULL; +} + +void logfunc(dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *msg, void *userdata) +{ + (void) context; + const char *loglevels[] = { "NONE", "ERROR", "WARNING", "INFO", "DEBUG", "ALL" }; + + FILE *fp = (FILE *)userdata; + + if (loglevel == DC_LOGLEVEL_ERROR || loglevel == DC_LOGLEVEL_WARNING) { + fprintf(fp, "%s: %s [in %s:%d (%s)]\n", loglevels[loglevel], msg, file, line, function); + } else { + fprintf(fp, "%s: %s\n", loglevels[loglevel], msg); + } +} + +const char *do_libdivecomputer_import(device_data_t *data) +{ + dc_status_t rc; + const char *err; + FILE *fp = NULL; + + import_dive_number = 0; + first_temp_is_air = 0; + data->device = NULL; + data->context = NULL; + + if (data->libdc_log && logfile_name) + fp = subsurface_fopen(logfile_name, "w"); + + data->libdc_logfile = fp; + + rc = dc_context_new(&data->context); + if (rc != DC_STATUS_SUCCESS) + return translate("gettextFromC", "Unable to create libdivecomputer context"); + + if (fp) { + dc_context_set_loglevel(data->context, DC_LOGLEVEL_ALL); + dc_context_set_logfunc(data->context, logfunc, fp); + } + + err = translate("gettextFromC", "Unable to open %s %s (%s)"); + +#if defined(SSRF_CUSTOM_SERIAL) + dc_serial_t *serial_device = NULL; + + if (data->bluetooth_mode) { +#if defined(BT_SUPPORT) && defined(SSRF_CUSTOM_SERIAL) + rc = dc_serial_qt_open(&serial_device, data->context, data->devname); +#endif +#ifdef SERIAL_FTDI + } else if (!strcmp(data->devname, "ftdi")) { + rc = dc_serial_ftdi_open(&serial_device, data->context); +#endif + } + + if (rc != DC_STATUS_SUCCESS) { + report_error(errmsg(rc)); + } else if (serial_device) { + rc = dc_device_custom_open(&data->device, data->context, data->descriptor, serial_device); + } else { +#else + { +#endif + rc = dc_device_open(&data->device, data->context, data->descriptor, data->devname); + + if (rc != DC_STATUS_SUCCESS && subsurface_access(data->devname, R_OK | W_OK) != 0) + err = translate("gettextFromC", "Insufficient privileges to open the device %s %s (%s)"); + } + + if (rc == DC_STATUS_SUCCESS) { + err = do_device_import(data); + /* TODO: Show the logfile to the user on error. */ + dc_device_close(data->device); + data->device = NULL; + } + + dc_context_free(data->context); + data->context = NULL; + + if (fp) { + fclose(fp); + } + + return err; +} + +/* + * Parse data buffers instead of dc devices downloaded data. + * Intended to be used to parse profile data from binary files during import tasks. + * Actually included Uwatec families because of works on datatrak and smartrak logs + * and OSTC families for OSTCTools logs import. + * For others, simply include them in the switch (check parameters). + * Note that dc_descriptor_t in data *must* have been filled using dc_descriptor_iterator() + * calls. + */ +dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned char *buffer, int size) +{ + dc_status_t rc; + dc_parser_t *parser = NULL; + + switch (data->descriptor->type) { + case DC_FAMILY_UWATEC_ALADIN: + case DC_FAMILY_UWATEC_MEMOMOUSE: + rc = uwatec_memomouse_parser_create(&parser, data->context, 0, 0); + break; + case DC_FAMILY_UWATEC_SMART: + case DC_FAMILY_UWATEC_MERIDIAN: + rc = uwatec_smart_parser_create (&parser, data->context, data->descriptor->model, 0, 0); + break; + case DC_FAMILY_HW_OSTC: +#if defined(SSRF_CUSTOM_SERIAL) + rc = hw_ostc_parser_create (&parser, data->context, data->deviceid, 0); +#else + rc = hw_ostc_parser_create (&parser, data->context, data->deviceid); +#endif + break; + case DC_FAMILY_HW_FROG: + case DC_FAMILY_HW_OSTC3: +#if defined(SSRF_CUSTOM_SERIAL) + rc = hw_ostc_parser_create (&parser, data->context, data->deviceid, 1); +#else + rc = hw_ostc_parser_create (&parser, data->context, data->deviceid); +#endif + break; + default: + report_error("Device type not handled!"); + return DC_STATUS_UNSUPPORTED; + } + if (rc != DC_STATUS_SUCCESS) { + report_error("Error creating parser."); + dc_parser_destroy (parser); + return rc; + } + rc = dc_parser_set_data(parser, buffer, size); + if (rc != DC_STATUS_SUCCESS) { + report_error("Error registering the data."); + dc_parser_destroy (parser); + return rc; + } + // Do not parse Aladin/Memomouse headers as they are fakes + // Do not return on error, we can still parse the samples + if (data->descriptor->type != DC_FAMILY_UWATEC_ALADIN && data->descriptor->type != DC_FAMILY_UWATEC_MEMOMOUSE) { + rc = libdc_header_parser (parser, data, dive); + if (rc != DC_STATUS_SUCCESS) { + report_error("Error parsing the dive header data. Dive # %d\nStatus = %s", dive->number, errmsg(rc)); + } + } + rc = dc_parser_samples_foreach (parser, sample_cb, &dive->dc); + if (rc != DC_STATUS_SUCCESS) { + report_error("Error parsing the sample data. Dive # %d\nStatus = %s", dive->number, errmsg(rc)); + dc_parser_destroy (parser); + return rc; + } + dc_parser_destroy(parser); + return(DC_STATUS_SUCCESS); +} diff --git a/core/libdivecomputer.h b/core/libdivecomputer.h new file mode 100644 index 000000000..99f1c2490 --- /dev/null +++ b/core/libdivecomputer.h @@ -0,0 +1,72 @@ +#ifndef LIBDIVECOMPUTER_H +#define LIBDIVECOMPUTER_H + + +/* libdivecomputer */ + +#ifdef DC_VERSION /* prevent a warning with wingdi.h */ +#undef DC_VERSION +#endif +#ifdef HAVE_LIBDIVECOMPUTER +#include +#endif +#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/core/linux.c b/core/linux.c new file mode 100644 index 000000000..b81f6bf53 --- /dev/null +++ b/core/linux.c @@ -0,0 +1,232 @@ +/* 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 + (void)font; + return false; +} + +void subsurface_user_info(struct user_info *user) +{ + struct passwd *pwd = getpwuid(getuid()); + const char *username = getenv("USER"); + + if (pwd) { + if (pwd->pw_gecos && *pwd->pw_gecos) + user->name = pwd->pw_gecos; + if (!username) + username = pwd->pw_name; + } + if (username && *username) { + char hostname[64]; + struct membuffer mb = {}; + gethostname(hostname, sizeof(hostname)); + put_format(&mb, "%s@%s", username, hostname); + user->email = mb_cstring(&mb); + } +} + +static const char *system_default_path_append(const char *append) +{ + const char *home = getenv("HOME"); + if (!home) + home = "~"; + const char *path = "/.subsurface"; + + int len = strlen(home) + strlen(path) + 1; + if (append) + len += strlen(append) + 1; + + char *buffer = (char *)malloc(len); + memset(buffer, 0, len); + strcat(buffer, home); + strcat(buffer, path); + if (append) { + strcat(buffer, "/"); + strcat(buffer, append); + } + + return buffer; +} + +const char *system_default_directory(void) +{ + static const char *path = NULL; + if (!path) + path = system_default_path_append(NULL); + return path; +} + +const char *system_default_filename(void) +{ + static char *filename = NULL; + if (!filename) { + const char *user = getenv("LOGNAME"); + if (same_string(user, "")) + user = "username"; + filename = calloc(strlen(user) + 5, 1); + strcat(filename, user); + strcat(filename, ".xml"); + } + static const char *path = NULL; + if (!path) + path = system_default_path_append(filename); + return path; +} + +int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) +{ + int index = -1, entries = 0; + DIR *dp = NULL; + struct dirent *ep = NULL; + size_t i; + FILE *file; + char *line = NULL; + char *fname; + size_t len; + if (dc_type != DC_TYPE_UEMIS) { + const char *dirname = "/dev"; + const char *patterns[] = { + "ttyUSB*", + "ttyS*", + "ttyACM*", + "rfcomm*", + NULL + }; + + dp = opendir(dirname); + if (dp == NULL) { + return -1; + } + + while ((ep = readdir(dp)) != NULL) { + for (i = 0; patterns[i] != NULL; ++i) { + if (fnmatch(patterns[i], ep->d_name, 0) == 0) { + char filename[1024]; + int n = snprintf(filename, sizeof(filename), "%s/%s", dirname, ep->d_name); + if (n >= (int)sizeof(filename)) { + closedir(dp); + return -1; + } + callback(filename, userdata); + if (is_default_dive_computer_device(filename)) + index = entries; + entries++; + break; + } + } + } + closedir(dp); + } + if (dc_type != DC_TYPE_SERIAL) { + int num_uemis = 0; + file = fopen("/proc/mounts", "r"); + if (file == NULL) + return index; + + while ((getline(&line, &len, file)) != -1) { + char *ptr = strstr(line, "UEMISSDA"); + if (ptr) { + char *end = ptr, *start = ptr; + while (start > line && *start != ' ') + start--; + if (*start == ' ') + start++; + while (*end != ' ' && *end != '\0') + end++; + + *end = '\0'; + fname = strdup(start); + + callback(fname, userdata); + + if (is_default_dive_computer_device(fname)) + index = entries; + entries++; + num_uemis++; + free((void *)fname); + } + } + free(line); + fclose(file); + if (num_uemis == 1 && entries == 1) /* if we found only one and it's a mounted Uemis, pick it */ + index = 0; + } + return index; +} + +/* NOP wrappers to comform with windows.c */ +int subsurface_rename(const char *path, const char *newpath) +{ + return rename(path, newpath); +} + +int subsurface_open(const char *path, int oflags, mode_t mode) +{ + return open(path, oflags, mode); +} + +FILE *subsurface_fopen(const char *path, const char *mode) +{ + return fopen(path, mode); +} + +void *subsurface_opendir(const char *path) +{ + return (void *)opendir(path); +} + +int subsurface_access(const char *path, int mode) +{ + return access(path, mode); +} + +struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp) +{ + return zip_open(path, flags, errorp); +} + +int subsurface_zip_close(struct zip *zip) +{ + return zip_close(zip); +} + +/* win32 console */ +void subsurface_console_init(bool dedicated) +{ + (void)dedicated; + /* NOP */ +} + +void subsurface_console_exit(void) +{ + /* NOP */ +} + +bool subsurface_user_is_root() +{ + return (geteuid() == 0); +} diff --git a/core/liquivision.c b/core/liquivision.c new file mode 100644 index 000000000..9347a724a --- /dev/null +++ b/core/liquivision.c @@ -0,0 +1,420 @@ +#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) +{ + (void) code; + (void) ps; + (void) ps_ptr; + (void) event; + + // Skip 4 bytes + return 4; +} + + +static int handle_event_ver3(int code, const unsigned char *ps, unsigned int ps_ptr, struct lv_event *event) +{ + int skip = 4; + uint16_t current_sensor; + + switch (code) { + case 0x0002: // Unknown + case 0x0004: // Unknown + skip = 4; + break; + case 0x0005: // Unknown + skip = 6; + break; + case 0x0007: // Gas + // 4 byte time + // 1 byte O2, 1 bye He + skip = 6; + break; + case 0x0008: + // 4 byte time + // 2 byte gas set point 2 + skip = 6; + break; + case 0x000f: + // Tank pressure + event->time = array_uint32_le(ps + ps_ptr); + + /* As far as I know, Liquivision supports 2 sensors, own and buddie's. This is my + * best guess how it is represented. */ + + current_sensor = array_uint16_le(ps + ps_ptr + 4); + if (primary_sensor == 0) { + primary_sensor = current_sensor; + } + if (current_sensor == primary_sensor) { + event->pressure.sensor = 0; + event->pressure.mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb + } else { + /* Ignoring the buddy sensor for no as we cannot draw it on the profile. + event->pressure.sensor = 1; + event->pressure.mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb + */ + } + // 1 byte PSR + // 1 byte ST + skip = 10; + break; + case 0x0010: + skip = 26; + break; + case 0x0015: // Unknown + skip = 2; + break; + default: + skip = 4; + break; + } + + return skip; +} + +static void parse_dives (int log_version, const unsigned char *buf, unsigned int buf_size) +{ + unsigned int ptr = 0; + unsigned char model; + + struct dive *dive; + struct divecomputer *dc; + struct sample *sample; + + while (ptr < buf_size) { + int i; + bool found_divesite = false; + dive = alloc_dive(); + primary_sensor = 0; + dc = &dive->dc; + + /* Just the main cylinder until we can handle the buddy cylinder porperly */ + for (i = 0; i < 1; i++) + fill_default_cylinder(&dive->cylinder[i]); + + // Model 0=Xen, 1,2=Xeo, 4=Lynx, other=Liquivision + model = *(buf + ptr); + switch (model) { + case 0: + dc->model = strdup("Xen"); + break; + case 1: + case 2: + dc->model = strdup("Xeo"); + break; + case 4: + dc->model = strdup("Lynx"); + break; + default: + dc->model = strdup("Liquivision"); + break; + } + ptr++; + + // Dive location, assemble Location and Place + unsigned int len, place_len; + char *location; + len = array_uint32_le(buf + ptr); + ptr += 4; + place_len = array_uint32_le(buf + ptr + len); + + if (len && place_len) { + location = malloc(len + place_len + 4); + memset(location, 0, len + place_len + 4); + memcpy(location, buf + ptr, len); + memcpy(location + len, ", ", 2); + memcpy(location + len + 2, buf + ptr + len + 4, place_len); + } else if (len) { + location = strndup((char *)buf + ptr, len); + } else if (place_len) { + location = strndup((char *)buf + ptr + len + 4, place_len); + } + + /* Store the location only if we have one */ + if (len || place_len) + found_divesite = true; + + ptr += len + 4 + place_len; + + // Dive comment + len = array_uint32_le(buf + ptr); + ptr += 4; + + // Blank notes are better than the default text + if (len && strncmp((char *)buf + ptr, "Comment ...", 11)) { + dive->notes = strndup((char *)buf + ptr, len); + } + ptr += len; + + dive->id = array_uint32_le(buf + ptr); + ptr += 4; + + dive->number = array_uint16_le(buf + ptr) + 1; + ptr += 2; + + dive->duration.seconds = array_uint32_le(buf + ptr); // seconds + ptr += 4; + + dive->maxdepth.mm = array_uint16_le(buf + ptr) * 10; // cm->mm + ptr += 2; + + dive->meandepth.mm = array_uint16_le(buf + ptr) * 10; // cm->mm + ptr += 2; + + dive->when = array_uint32_le(buf + ptr); + ptr += 4; + + // now that we have the dive time we can store the divesite + // (we need the dive time to create deterministic uuids) + if (found_divesite) { + dive->dive_site_uuid = find_or_create_dive_site_with_name(location, dive->when); + free(location); + } + //unsigned int end_time = array_uint32_le(buf + ptr); + ptr += 4; + + //unsigned int sit = array_uint32_le(buf + ptr); + ptr += 4; + //if (sit == 0xffffffff) { + //} + + dive->surface_pressure.mbar = array_uint16_le(buf + ptr); // ??? + ptr += 2; + + //unsigned int rep_dive = array_uint16_le(buf + ptr); + ptr += 2; + + dive->mintemp.mkelvin = C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK + ptr += 2; + + dive->maxtemp.mkelvin = C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK + ptr += 2; + + dive->salinity = *(buf + ptr); // ??? + ptr += 1; + + unsigned int sample_count = array_uint32_le(buf + ptr); + ptr += 4; + + // Sample interval + unsigned char sample_interval; + sample_interval = 1; + + unsigned char intervals[6] = {1,2,5,10,30,60}; + if (*(buf + ptr) < 6) + sample_interval = intervals[*(buf + ptr)]; + ptr += 1; + + float start_cns = 0; + unsigned char dive_mode = 0, algorithm = 0; + if (array_uint32_le(buf + ptr) != sample_count) { + // Xeo, with CNS and OTU + start_cns = *(float *) (buf + ptr); + ptr += 4; + dive->cns = *(float *) (buf + ptr); // end cns + ptr += 4; + dive->otu = *(float *) (buf + ptr); + ptr += 4; + dive_mode = *(buf + ptr++); // 0=Deco, 1=Gauge, 2=None + algorithm = *(buf + ptr++); // 0=ZH-L16C+GF + sample_count = array_uint32_le(buf + ptr); + } + // we aren't using the start_cns, dive_mode, and algorithm, yet + (void)start_cns; + (void)dive_mode; + (void)algorithm; + + ptr += 4; + + // Parse dive samples + const unsigned char *ds = buf + ptr; + const unsigned char *ts = buf + ptr + sample_count * 2 + 4; + const unsigned char *ps = buf + ptr + sample_count * 4 + 4; + unsigned int ps_count = array_uint32_le(ps); + ps += 4; + + // Bump ptr + ptr += sample_count * 4 + 4; + + // Handle events + unsigned int ps_ptr; + ps_ptr = 0; + + unsigned int event_code, d = 0, e; + struct lv_event event; + + // Loop through events + for (e = 0; e < ps_count; e++) { + // Get event + event_code = array_uint16_le(ps + ps_ptr); + ps_ptr += 2; + + if (log_version == 3) { + ps_ptr += handle_event_ver3(event_code, ps, ps_ptr, &event); + if (event_code != 0xf) + continue; // ignore all by pressure sensor event + } else { // version 2 + ps_ptr += handle_event_ver2(event_code, ps, ps_ptr, &event); + continue; // ignore all events + } + int sample_time, last_time; + int depth_mm, last_depth, temp_mk, last_temp; + + while (true) { + sample = prepare_sample(dc); + + // Get sample times + sample_time = d * sample_interval; + depth_mm = array_uint16_le(ds + d * 2) * 10; // cm->mm + temp_mk = C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10); // dC->mK + last_time = (d ? (d - 1) * sample_interval : 0); + + if (d == sample_count) { + // We still have events to record + sample->time.seconds = event.time; + sample->depth.mm = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm + sample->temperature.mkelvin = C_to_mkelvin((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK + sample->sensor = event.pressure.sensor; + sample->cylinderpressure.mbar = event.pressure.mbar; + finish_sample(dc); + + break; + } else if (event.time > sample_time) { + // Record sample and loop + sample->time.seconds = sample_time; + sample->depth.mm = depth_mm; + sample->temperature.mkelvin = temp_mk; + finish_sample(dc); + d++; + + continue; + } else if (event.time == sample_time) { + sample->time.seconds = sample_time; + sample->depth.mm = depth_mm; + sample->temperature.mkelvin = temp_mk; + sample->sensor = event.pressure.sensor; + sample->cylinderpressure.mbar = event.pressure.mbar; + finish_sample(dc); + + break; + } else { // Event is prior to sample + sample->time.seconds = event.time; + sample->sensor = event.pressure.sensor; + sample->cylinderpressure.mbar = event.pressure.mbar; + if (last_time == sample_time) { + sample->depth.mm = depth_mm; + sample->temperature.mkelvin = temp_mk; + } else { + // Extrapolate + last_depth = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm + last_temp = C_to_mkelvin((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK + sample->depth.mm = last_depth + (depth_mm - last_depth) + * (event.time - last_time) / sample_interval; + sample->temperature.mkelvin = last_temp + (temp_mk - last_temp) + * (event.time - last_time) / sample_interval; + } + finish_sample(dc); + + break; + } + } // while (true); + } // for each event sample + + // record trailing depth samples + for ( ;d < sample_count; d++) { + sample = prepare_sample(dc); + sample->time.seconds = d * sample_interval; + + sample->depth.mm = array_uint16_le(ds + d * 2) * 10; // cm->mm + sample->temperature.mkelvin = + C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10); + finish_sample(dc); + } + + if (log_version == 3 && model == 4) { + // Advance to begin of next dive + switch (array_uint16_le(ps + ps_ptr)) { + case 0x0000: + ps_ptr += 5; + break; + case 0x0100: + ps_ptr += 7; + break; + case 0x0200: + ps_ptr += 9; + break; + case 0x0300: + ps_ptr += 11; + break; + case 0x0b0b: + ps_ptr += 27; + break; + } + + while (*(ps + ps_ptr) != 0x04) + ps_ptr++; + } + + // End dive + dive->downloaded = true; + record_dive(dive); + mark_divelist_changed(true); + + // Advance ptr for next dive + ptr += ps_ptr + 4; + } // while + + //DEBUG save_dives("/tmp/test.xml"); +} + +int try_to_open_liquivision(const char *filename, struct memblock *mem) +{ + (void) filename; + const unsigned char *buf = mem->buffer; + unsigned int buf_size = mem->size; + unsigned int ptr; + int log_version; + + // Get name length + unsigned int len = array_uint32_le(buf); + // Ignore length field and the name + ptr = 4 + len; + + unsigned int dive_count = array_uint32_le(buf + ptr); + if (dive_count == 0xffffffff) { + // File version 3.0 + log_version = 3; + ptr += 6; + dive_count = array_uint32_le(buf + ptr); + } else { + log_version = 2; + } + ptr += 4; + + parse_dives(log_version, buf + ptr, buf_size - ptr); + + return 1; +} diff --git a/core/load-git.c b/core/load-git.c new file mode 100644 index 000000000..a1f2d2031 --- /dev/null +++ b/core/load-git.c @@ -0,0 +1,1709 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#include +#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) +{ + (void) str; + uint32_t uuid; + degrees_t latitude = parse_degrees(line, &line); + degrees_t longitude = parse_degrees(line, &line); + struct dive *dive = _dive; + struct dive_site *ds = get_dive_site_for_dive(dive); + if (!ds) { + uuid = get_dive_site_uuid_by_gps(latitude, longitude, NULL); + if (!uuid) + uuid = create_dive_site_with_gps("", latitude, longitude, dive->when); + dive->dive_site_uuid = uuid; + } else { + if (dive_site_has_gps_location(ds) && + (ds->latitude.udeg != latitude.udeg || ds->longitude.udeg != longitude.udeg)) { + const char *coords = printGPSCoords(latitude.udeg, longitude.udeg); + // we have a dive site that already has GPS coordinates + ds->notes = add_to_string(ds->notes, translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords); + free((void *)coords); + } + ds->latitude = latitude; + ds->longitude = longitude; + } + +} + +static void parse_dive_location(char *line, struct membuffer *str, void *_dive) +{ + (void) line; + uint32_t uuid; + char *name = get_utf8(str); + struct dive *dive = _dive; + struct dive_site *ds = get_dive_site_for_dive(dive); + if (!ds) { + uuid = get_dive_site_uuid_by_name(name, NULL); + if (!uuid) + uuid = create_dive_site(name, dive->when); + dive->dive_site_uuid = uuid; + } else { + // we already had a dive site linked to the dive + if (same_string(ds->name, "")) { + ds->name = strdup(name); + } else { + // and that dive site had a name. that's weird - if our name is different, add it to the notes + if (!same_string(ds->name, name)) + ds->notes = add_to_string(ds->notes, translate("gettextFromC", "additional name for site: %s\n"), name); + } + } + free(name); +} + +static void parse_dive_divemaster(char *line, struct membuffer *str, void *_dive) +{ (void) line; struct dive *dive = _dive; dive->divemaster = get_utf8(str); } + +static void parse_dive_buddy(char *line, struct membuffer *str, void *_dive) +{ (void) line; struct dive *dive = _dive; dive->buddy = get_utf8(str); } + +static void parse_dive_suit(char *line, struct membuffer *str, void *_dive) +{ (void) line; struct dive *dive = _dive; dive->suit = get_utf8(str); } + +static void parse_dive_notes(char *line, struct membuffer *str, void *_dive) +{ (void) line; struct dive *dive = _dive; dive->notes = get_utf8(str); } + +static void parse_dive_divesiteid(char *line, struct membuffer *str, void *_dive) +{ (void) str; struct dive *dive = _dive; dive->dive_site_uuid = get_hex(line); } + +/* + * We can have multiple tags in the membuffer. They are separated by + * NUL bytes. + */ +static void parse_dive_tags(char *line, struct membuffer *str, void *_dive) +{ + (void) line; + struct dive *dive = _dive; + const char *tag; + int len = str->len; + + if (!len) + return; + + /* Make sure there is a NUL at the end too */ + tag = mb_cstring(str); + for (;;) { + int taglen = strlen(tag); + if (taglen) + taglist_add_tag(&dive->tag_list, tag); + len -= taglen; + if (!len) + return; + tag += taglen+1; + len--; + } +} + +static void parse_dive_airtemp(char *line, struct membuffer *str, void *_dive) +{ (void) str; struct dive *dive = _dive; dive->airtemp = get_temperature(line); } + +static void parse_dive_watertemp(char *line, struct membuffer *str, void *_dive) +{ (void) str; struct dive *dive = _dive; dive->watertemp = get_temperature(line); } + +static void parse_dive_duration(char *line, struct membuffer *str, void *_dive) +{ (void) str; struct dive *dive = _dive; dive->duration = get_duration(line); } + +static void parse_dive_rating(char *line, struct membuffer *str, void *_dive) +{ (void) str; struct dive *dive = _dive; dive->rating = get_index(line); } + +static void parse_dive_visibility(char *line, struct membuffer *str, void *_dive) +{ (void) str; struct dive *dive = _dive; dive->visibility = get_index(line); } + +static void parse_dive_notrip(char *line, struct membuffer *str, void *_dive) +{ + (void) str; + (void) line; + struct dive *dive = _dive; dive->tripflag = NO_TRIP; +} + +static void parse_site_description(char *line, struct membuffer *str, void *_ds) +{ (void) line; struct dive_site *ds = _ds; ds->description = strdup(mb_cstring(str)); } + +static void parse_site_name(char *line, struct membuffer *str, void *_ds) +{ (void) line; struct dive_site *ds = _ds; ds->name = strdup(mb_cstring(str)); } + +static void parse_site_notes(char *line, struct membuffer *str, void *_ds) +{ (void) line; struct dive_site *ds = _ds; ds->notes = strdup(mb_cstring(str)); } + +extern degrees_t parse_degrees(char *buf, char **end); +static void parse_site_gps(char *line, struct membuffer *str, void *_ds) +{ + (void) str; + struct dive_site *ds = _ds; + + ds->latitude = parse_degrees(line, &line); + ds->longitude = parse_degrees(line, &line); +} + +static void parse_site_geo(char *line, struct membuffer *str, void *_ds) +{ + struct dive_site *ds = _ds; + if (ds->taxonomy.category == NULL) + ds->taxonomy.category = alloc_taxonomy(); + int nr = ds->taxonomy.nr; + if (nr < TC_NR_CATEGORIES) { + struct taxonomy *t = &ds->taxonomy.category[nr]; + t->value = strdup(mb_cstring(str)); + sscanf(line, "cat %d origin %d \"", &t->category, (int *)&t->origin); + ds->taxonomy.nr++; + } +} + +/* Parse key=val parts of samples and cylinders etc */ +static char *parse_keyvalue_entry(void (*fn)(void *, const char *, const char *), void *fndata, char *line) +{ + char *key = line, *val, c; + + while ((c = *line) != 0) { + if (isspace(c) || c == '=') + break; + line++; + } + + if (c == '=') + *line++ = 0; + val = line; + + while ((c = *line) != 0) { + if (isspace(c)) + break; + line++; + } + if (c) + *line++ = 0; + + fn(fndata, key, val); + return line; +} + +static int cylinder_index, weightsystem_index; + +static void parse_cylinder_keyvalue(void *_cylinder, const char *key, const char *value) +{ + cylinder_t *cylinder = _cylinder; + if (!strcmp(key, "vol")) { + cylinder->type.size = get_volume(value); + return; + } + if (!strcmp(key, "workpressure")) { + cylinder->type.workingpressure = get_pressure(value); + return; + } + /* This is handled by the "get_utf8()" */ + if (!strcmp(key, "description")) + return; + if (!strcmp(key, "o2")) { + cylinder->gasmix.o2 = get_fraction(value); + return; + } + if (!strcmp(key, "he")) { + cylinder->gasmix.he = get_fraction(value); + return; + } + if (!strcmp(key, "start")) { + cylinder->start = get_pressure(value); + return; + } + if (!strcmp(key, "end")) { + cylinder->end = get_pressure(value); + return; + } + if (!strcmp(key, "use")) { + cylinder->cylinder_use = cylinderuse_from_text(value); + return; + } + report_error("Unknown cylinder key/value pair (%s/%s)", key, value); +} + +static void parse_dive_cylinder(char *line, struct membuffer *str, void *_dive) +{ + struct dive *dive = _dive; + cylinder_t *cylinder = dive->cylinder + cylinder_index; + + cylinder_index++; + cylinder->type.description = get_utf8(str); + for (;;) { + char c; + while (isspace(c = *line)) + line++; + if (!c) + break; + line = parse_keyvalue_entry(parse_cylinder_keyvalue, cylinder, line); + } +} + +static void parse_weightsystem_keyvalue(void *_ws, const char *key, const char *value) +{ + weightsystem_t *ws = _ws; + if (!strcmp(key, "weight")) { + ws->weight = get_weight(value); + return; + } + /* This is handled by the "get_utf8()" */ + if (!strcmp(key, "description")) + return; + report_error("Unknown weightsystem key/value pair (%s/%s)", key, value); +} + +static void parse_dive_weightsystem(char *line, struct membuffer *str, void *_dive) +{ + struct dive *dive = _dive; + weightsystem_t *ws = dive->weightsystem + weightsystem_index; + + weightsystem_index++; + ws->description = get_utf8(str); + for (;;) { + char c; + while (isspace(c = *line)) + line++; + if (!c) + break; + line = parse_keyvalue_entry(parse_weightsystem_keyvalue, ws, line); + } +} + +static int match_action(char *line, struct membuffer *str, void *data, + struct keyword_action *action, unsigned nr_action) +{ + char *p = line, c; + unsigned low, high; + + while ((c = *p) >= 'a' && c <= 'z') // skip over 1st word + p++; // Extract the second word from the line: + if (p == line) + return -1; + switch (c) { + case 0: // if 2nd word is C-terminated + break; + case ' ': // =end of 2nd word? + *p++ = 0; // then C-terminate that word + break; + default: + return -1; + } + + /* Standard binary search in a table */ + low = 0; + high = nr_action; + while (low < high) { + unsigned mid = (low+high)/2; + struct keyword_action *a = action + mid; + int cmp = strcmp(line, a->keyword); + if (!cmp) { // attribute found: + a->fn(p, str, data); // Execute appropriate function, + return 0; // .. passing 2n word from above + } // (p) as a function argument. + if (cmp < 0) + high = mid; + else + low = mid+1; + } +report_error("Unmatched action '%s'", line); + return -1; +} + +/* FIXME! We should do the array thing here too. */ +static void parse_sample_keyvalue(void *_sample, const char *key, const char *value) +{ + struct sample *sample = _sample; + + if (!strcmp(key, "sensor")) { + sample->sensor = atoi(value); + return; + } + if (!strcmp(key, "ndl")) { + sample->ndl = get_duration(value); + return; + } + if (!strcmp(key, "tts")) { + sample->tts = get_duration(value); + return; + } + if (!strcmp(key, "in_deco")) { + sample->in_deco = atoi(value); + return; + } + if (!strcmp(key, "stoptime")) { + sample->stoptime = get_duration(value); + return; + } + if (!strcmp(key, "stopdepth")) { + sample->stopdepth = get_depth(value); + return; + } + if (!strcmp(key, "cns")) { + sample->cns = atoi(value); + return; + } + + if (!strcmp(key, "rbt")) { + sample->rbt = get_duration(value); + return; + } + + if (!strcmp(key, "po2")) { + pressure_t p = get_pressure(value); + sample->setpoint.mbar = p.mbar; + return; + } + if (!strcmp(key, "sensor1")) { + pressure_t p = get_pressure(value); + sample->o2sensor[0].mbar = p.mbar; + return; + } + if (!strcmp(key, "sensor2")) { + pressure_t p = get_pressure(value); + sample->o2sensor[1].mbar = p.mbar; + return; + } + if (!strcmp(key, "sensor3")) { + pressure_t p = get_pressure(value); + sample->o2sensor[2].mbar = p.mbar; + return; + } + if (!strcmp(key, "o2pressure")) { + pressure_t p = get_pressure(value); + sample->o2cylinderpressure.mbar = p.mbar; + return; + } + if (!strcmp(key, "heartbeat")) { + sample->heartbeat = atoi(value); + return; + } + if (!strcmp(key, "bearing")) { + sample->bearing.degrees = atoi(value); + return; + } + + report_error("Unexpected sample key/value pair (%s/%s)", key, value); +} + +static char *parse_sample_unit(struct sample *sample, double val, char *unit) +{ + char *end = unit, c; + + /* Skip over the unit */ + while ((c = *end) != 0) { + if (isspace(c)) { + *end++ = 0; + break; + } + end++; + } + + /* The units are "°C", "m" or "bar", so let's just look at the first character */ + switch (*unit) { + case 'm': + sample->depth.mm = rint(1000*val); + break; + case 'b': + sample->cylinderpressure.mbar = rint(1000*val); + break; + default: + sample->temperature.mkelvin = C_to_mkelvin(val); + break; + } + + return end; +} + +/* + * By default the sample data does not change unless the + * save-file gives an explicit new value. So we copy the + * data from the previous sample if one exists, and then + * the parsing will update it as necessary. + * + * There are a few exceptions, like the sample pressure: + * missing sample pressure doesn't mean "same as last + * time", but "interpolate". We clear those ones + * explicitly. + */ +static struct sample *new_sample(struct divecomputer *dc) +{ + struct sample *sample = prepare_sample(dc); + if (sample != dc->sample) { + memcpy(sample, sample-1, sizeof(struct sample)); + sample->cylinderpressure.mbar = 0; + } + return sample; +} + +static void sample_parser(char *line, struct divecomputer *dc) +{ + int m, s = 0; + struct sample *sample = new_sample(dc); + + m = strtol(line, &line, 10); + if (*line == ':') + s = strtol(line+1, &line, 10); + sample->time.seconds = m*60+s; + + for (;;) { + char c; + + while (isspace(c = *line)) + line++; + if (!c) + break; + /* Less common sample entries have a name */ + if (c >= 'a' && c <= 'z') { + line = parse_keyvalue_entry(parse_sample_keyvalue, sample, line); + } else { + const char *end; + double val = ascii_strtod(line, &end); + if (end == line) { + report_error("Odd sample data: %s", line); + break; + } + line = (char *)end; + line = parse_sample_unit(sample, val, line); + } + } + finish_sample(dc); +} + +static void parse_dc_airtemp(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->airtemp = get_temperature(line); } + +static void parse_dc_date(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; update_date(&dc->when, line); } + +static void parse_dc_deviceid(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->deviceid = get_hex(line); } + +static void parse_dc_diveid(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->diveid = get_hex(line); } + +static void parse_dc_duration(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->duration = get_duration(line); } + +static void parse_dc_dctype(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->divemode = get_dctype(line); } + +static void parse_dc_maxdepth(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->maxdepth = get_depth(line); } + +static void parse_dc_meandepth(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->meandepth = get_depth(line); } + +static void parse_dc_model(char *line, struct membuffer *str, void *_dc) +{ (void) line; struct divecomputer *dc = _dc; dc->model = get_utf8(str); } + +static void parse_dc_numberofoxygensensors(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->no_o2sensors = get_index(line); } + +static void parse_dc_surfacepressure(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->surface_pressure = get_pressure(line); } + +static void parse_dc_salinity(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->salinity = get_salinity(line); } + +static void parse_dc_surfacetime(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->surfacetime = get_duration(line); } + +static void parse_dc_time(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; update_time(&dc->when, line); } + +static void parse_dc_watertemp(char *line, struct membuffer *str, void *_dc) +{ (void) str; struct divecomputer *dc = _dc; dc->watertemp = get_temperature(line); } + +static void parse_event_keyvalue(void *_event, const char *key, const char *value) +{ + struct event *event = _event; + int val = atoi(value); + + if (!strcmp(key, "type")) { + event->type = val; + } else if (!strcmp(key, "flags")) { + event->flags = val; + } else if (!strcmp(key, "value")) { + event->value = val; + } else if (!strcmp(key, "name")) { + /* We get the name from the string handling */ + } else if (!strcmp(key, "cylinder")) { + /* NOTE! We add one here as a marker that "yes, we got a cylinder index" */ + event->gas.index = 1+get_index(value); + } else if (!strcmp(key, "o2")) { + event->gas.mix.o2 = get_fraction(value); + } else if (!strcmp(key, "he")) { + event->gas.mix.he = get_fraction(value); + } else + report_error("Unexpected event key/value pair (%s/%s)", key, value); +} + +/* keyvalue "key" "value" + * so we have two strings (possibly empty) in the membuffer, separated by a '\0' */ +static void parse_dc_keyvalue(char *line, struct membuffer *str, void *_dc) +{ + const char *key, *value; + struct divecomputer *dc = _dc; + + // Let's make sure we have two strings... + int string_counter = 0; + while(*line) { + if (*line == '"') + string_counter++; + line++; + } + if (string_counter != 2) + return; + + // stupidly the second string in the membuffer isn't NUL terminated; + // asking for a cstring fixes that; interestingly enough, given that there are two + // strings in the mb, the next command at the same time assigns a pointer to the + // first string to 'key' and NUL terminates the second string (which then goes to 'value') + key = mb_cstring(str); + value = key + strlen(key) + 1; + add_extra_data(dc, key, value); +} + +static void parse_dc_event(char *line, struct membuffer *str, void *_dc) +{ + int m, s = 0; + const char *name; + struct divecomputer *dc = _dc; + struct event event = { 0 }, *ev; + + m = strtol(line, &line, 10); + if (*line == ':') + s = strtol(line+1, &line, 10); + event.time.seconds = m*60+s; + + for (;;) { + char c; + while (isspace(c = *line)) + line++; + if (!c) + break; + line = parse_keyvalue_entry(parse_event_keyvalue, &event, line); + } + + name = ""; + if (str->len) + name = mb_cstring(str); + ev = add_event(dc, event.time.seconds, event.type, event.flags, event.value, name); + + /* + * Older logs might mark the dive to be CCR by having an "SP change" event at time 0:00. + * Better to mark them being CCR on import so no need for special treatments elsewhere on + * the code. + */ + if (ev && event.time.seconds == 0 && event.type == SAMPLE_EVENT_PO2 && dc->divemode==OC) { + dc->divemode = CCR; + } + + if (ev && event_is_gaschange(ev)) { + /* + * We subtract one here because "0" is "no index", + * and the parsing will add one for actual cylinder + * index data (see parse_event_keyvalue) + */ + ev->gas.index = event.gas.index-1; + if (event.gas.mix.o2.permille || event.gas.mix.he.permille) + ev->gas.mix = event.gas.mix; + } +} + +static void parse_trip_date(char *line, struct membuffer *str, void *_trip) +{ (void) str; dive_trip_t *trip = _trip; update_date(&trip->when, line); } + +static void parse_trip_time(char *line, struct membuffer *str, void *_trip) +{ (void) str; dive_trip_t *trip = _trip; update_time(&trip->when, line); } + +static void parse_trip_location(char *line, struct membuffer *str, void *_trip) +{ (void) line; dive_trip_t *trip = _trip; trip->location = get_utf8(str); } + +static void parse_trip_notes(char *line, struct membuffer *str, void *_trip) +{ (void) line; dive_trip_t *trip = _trip; trip->notes = get_utf8(str); } + +static void parse_settings_autogroup(char *line, struct membuffer *str, void *_unused) +{ + (void) line; + (void) str; + (void) _unused; + set_autogroup(1); +} + +static void parse_settings_units(char *line, struct membuffer *str, void *unused) +{ + (void) str; + (void) unused; + if (line) + set_informational_units(line); +} + +static void parse_settings_userid(char *line, struct membuffer *str, void *_unused) +{ + (void) str; + (void) _unused; + if (line) { + set_save_userid_local(true); + set_userid(line); + } +} + +/* + * Our versioning is a joke right now, but this is more of an example of what we + * *can* do some day. And if we do change the version, this warning will show if + * you read with a version of subsurface that doesn't know about it. + * We MUST keep this in sync with the XML version (so we can report a consistent + * minimum datafile version) + */ +static void parse_settings_version(char *line, struct membuffer *str, void *_unused) +{ + (void) str; + (void) _unused; + int version = atoi(line); + report_datafile_version(version); + if (version > DATAFORMAT_VERSION) + report_error("Git save file version %d is newer than version %d I know about", version, DATAFORMAT_VERSION); +} + +/* The string in the membuffer is the version string of subsurface that saved things, just FYI */ +static void parse_settings_subsurface(char *line, struct membuffer *str, void *_unused) +{ + (void) line; + (void) str; + (void) _unused; +} + +struct divecomputerid { + const char *model; + const char *nickname; + const char *firmware; + const char *serial; + const char *cstr; + unsigned int deviceid; +}; + +static void parse_divecomputerid_keyvalue(void *_cid, const char *key, const char *value) +{ + struct divecomputerid *cid = _cid; + + if (*value == '"') { + value = cid->cstr; + cid->cstr += strlen(cid->cstr)+1; + } + if (!strcmp(key, "deviceid")) { + cid->deviceid = get_hex(value); + return; + } + if (!strcmp(key, "serial")) { + cid->serial = value; + return; + } + if (!strcmp(key, "firmware")) { + cid->firmware = value; + return; + } + if (!strcmp(key, "nickname")) { + cid->nickname = value; + return; + } + report_error("Unknow divecomputerid key/value pair (%s/%s)", key, value); +} + +/* + * The 'divecomputerid' is a bit harder to parse than some other things, because + * it can have multiple strings (but see the tag parsing for another example of + * that) in addition to the non-string entries. + * + * We keep the "next" string in "id.cstr" and update it as we use it. + */ +static void parse_settings_divecomputerid(char *line, struct membuffer *str, void *_unused) +{ + (void) _unused; + struct divecomputerid id = { mb_cstring(str) }; + + id.cstr = id.model + strlen(id.model) + 1; + + /* Skip the '"' that stood for the model string */ + line++; + + for (;;) { + char c; + while (isspace(c = *line)) + line++; + if (!c) + break; + line = parse_keyvalue_entry(parse_divecomputerid_keyvalue, &id, line); + } + create_device_node(id.model, id.deviceid, id.serial, id.firmware, id.nickname); +} + +static void parse_picture_filename(char *line, struct membuffer *str, void *_pic) +{ + (void) line; + struct picture *pic = _pic; + pic->filename = get_utf8(str); +} + +static void parse_picture_gps(char *line, struct membuffer *str, void *_pic) +{ + (void) str; + struct picture *pic = _pic; + + pic->latitude = parse_degrees(line, &line); + pic->longitude = parse_degrees(line, &line); +} + +static void parse_picture_hash(char *line, struct membuffer *str, void *_pic) +{ + (void) line; + struct picture *pic = _pic; + pic->hash = get_utf8(str); +} + +/* These need to be sorted! */ +struct keyword_action dc_action[] = { +#undef D +#define D(x) { #x, parse_dc_ ## x } + D(airtemp), D(date), D(dctype), D(deviceid), D(diveid), D(duration), + D(event), D(keyvalue), D(maxdepth), D(meandepth), D(model), D(numberofoxygensensors), + D(salinity), D(surfacepressure), D(surfacetime), D(time), D(watertemp) +}; + +/* Sample lines start with a space or a number */ +static void divecomputer_parser(char *line, struct membuffer *str, void *_dc) +{ + char c = *line; + if (c < 'a' || c > 'z') + sample_parser(line, _dc); + match_action(line, str, _dc, dc_action, ARRAY_SIZE(dc_action)); +} + +/* These need to be sorted! */ +struct keyword_action dive_action[] = { +#undef D +#define D(x) { #x, parse_dive_ ## x } + D(airtemp), D(buddy), D(cylinder), D(divemaster), D(divesiteid), D(duration), + D(gps), D(location), D(notes), D(notrip), D(rating), D(suit), + D(tags), D(visibility), D(watertemp), D(weightsystem) +}; + +static void dive_parser(char *line, struct membuffer *str, void *_dive) +{ + match_action(line, str, _dive, dive_action, ARRAY_SIZE(dive_action)); +} + +/* These need to be sorted! */ +struct keyword_action site_action[] = { +#undef D +#define D(x) { #x, parse_site_ ## x } + D(description), D(geo), D(gps), D(name), D(notes) +}; + +static void site_parser(char *line, struct membuffer *str, void *_ds) +{ + match_action(line, str, _ds, site_action, ARRAY_SIZE(site_action)); +} + +/* These need to be sorted! */ +struct keyword_action trip_action[] = { +#undef D +#define D(x) { #x, parse_trip_ ## x } + D(date), D(location), D(notes), D(time), +}; + +static void trip_parser(char *line, struct membuffer *str, void *_trip) +{ + match_action(line, str, _trip, trip_action, ARRAY_SIZE(trip_action)); +} + +/* These need to be sorted! */ +static struct keyword_action settings_action[] = { +#undef D +#define D(x) { #x, parse_settings_ ## x } + D(autogroup), D(divecomputerid), D(subsurface), D(units), D(userid), D(version), +}; + +static void settings_parser(char *line, struct membuffer *str, void *_unused) +{ + (void) _unused; + match_action(line, str, NULL, settings_action, ARRAY_SIZE(settings_action)); +} + +/* These need to be sorted! */ +static struct keyword_action picture_action[] = { +#undef D +#define D(x) { #x, parse_picture_ ## x } + D(filename), D(gps), D(hash) +}; + +static void picture_parser(char *line, struct membuffer *str, void *_pic) +{ + match_action(line, str, _pic, picture_action, ARRAY_SIZE(picture_action)); +} + +/* + * We have a very simple line-based interface, with the small + * complication that lines can have strings in the middle, and + * a string can be multiple lines. + * + * The UTF-8 string escaping is *very* simple, though: + * + * - a string starts and ends with double quotes (") + * + * - inside the string we escape: + * (a) double quotes with '\"' + * (b) backslash (\) with '\\' + * + * - additionally, for human readability, we escape + * newlines with '\n\t', with the exception that + * consecutive newlines are left unescaped (so an + * empty line doesn't become a line with just a tab + * on it). + * + * Also, while the UTF-8 string can have arbitrarily + * long lines, the non-string parts of the lines are + * never long, so we can use a small temporary buffer + * on stack for that part. + * + * Also, note that if a line has one or more strings + * in it: + * + * - each string will be represented as a single '"' + * character in the output. + * + * - all string will exist in the same 'membuffer', + * separated by NUL characters (that cannot exist + * in a string, not even quoted). + */ +static const char *parse_one_string(const char *buf, const char *end, struct membuffer *b) +{ + const char *p = buf; + + /* + * We turn multiple strings one one line (think dive tags) into one + * membuffer that has NUL characters in between strings. + */ + if (b->len) + put_bytes(b, "", 1); + + while (p < end) { + char replace; + + switch (*p++) { + default: + continue; + case '\n': + if (p < end && *p == '\t') { + replace = '\n'; + break; + } + continue; + case '\\': + if (p < end) { + replace = *p; + break; + } + continue; + case '"': + replace = 0; + break; + } + put_bytes(b, buf, p - buf - 1); + if (!replace) + break; + put_bytes(b, &replace, 1); + buf = ++p; + } + return p; +} + +typedef void (line_fn_t)(char *, struct membuffer *, void *); +#define MAXLINE 500 +static unsigned parse_one_line(const char *buf, unsigned size, line_fn_t *fn, void *fndata, struct membuffer *b) +{ + const char *end = buf + size; + const char *p = buf; + char line[MAXLINE+1]; + int off = 0; + + while (p < end) { + char c = *p++; + if (c == '\n') + break; + line[off] = c; + off++; + if (off > MAXLINE) + off = MAXLINE; + if (c == '"') + p = parse_one_string(p, end, b); + } + line[off] = 0; + fn(line, b, fndata); + return p - buf; +} + +/* + * We keep on re-using the membuffer that we use for + * strings, but the callback function can "steal" it by + * saving its value and just clear the original. + */ +static void for_each_line(git_blob *blob, line_fn_t *fn, void *fndata) +{ + const char *content = git_blob_rawcontent(blob); + unsigned int size = git_blob_rawsize(blob); + struct membuffer str = { 0 }; + + while (size) { + unsigned int n = parse_one_line(content, size, fn, fndata, &str); + content += n; + size -= n; + + /* Re-use the allocation, but forget the data */ + str.len = 0; + } + free_buffer(&str); +} + +#define GIT_WALK_OK 0 +#define GIT_WALK_SKIP 1 + +static struct divecomputer *active_dc; +static struct dive *active_dive; +static dive_trip_t *active_trip; + +static void finish_active_trip(void) +{ + dive_trip_t *trip = active_trip; + + if (trip) { + active_trip = NULL; + insert_trip(&trip); + } +} + +static void finish_active_dive(void) +{ + struct dive *dive = active_dive; + + if (dive) { + /* check if we need to save pictures */ + FOR_EACH_PICTURE(dive) { + if (!picture_exists(picture)) + save_picture_from_git(picture); + } + /* free any memory we allocated to track pictures */ + while (pel) { + free(pel->data); + void *lastone = pel; + pel = pel->next; + free(lastone); + } + active_dive = NULL; + record_dive(dive); + } +} + +static struct dive *create_new_dive(timestamp_t when) +{ + struct dive *dive = alloc_dive(); + + /* We'll fill in more data from the dive file */ + dive->when = when; + + if (active_trip) + add_dive_to_trip(dive, active_trip); + return dive; +} + +static dive_trip_t *create_new_trip(int yyyy, int mm, int dd) +{ + dive_trip_t *trip = calloc(1, sizeof(dive_trip_t)); + struct tm tm = { 0 }; + + /* We'll fill in the real data from the trip descriptor file */ + tm.tm_year = yyyy; + tm.tm_mon = mm-1; + tm.tm_mday = dd; + trip->when = utc_mktime(&tm); + + return trip; +} + +static bool validate_date(int yyyy, int mm, int dd) +{ + return yyyy > 1970 && yyyy < 3000 && + mm > 0 && mm < 13 && + dd > 0 && dd < 32; +} + +static bool validate_time(int h, int m, int s) +{ + return h >= 0 && h < 24 && + m >= 0 && m < 60 && + s >=0 && s <= 60; +} + +/* + * Dive trip directory, name is 'nn-alphabetic[~hex]' + */ +static int dive_trip_directory(const char *root, const char *name) +{ + int yyyy = -1, mm = -1, dd = -1; + + if (sscanf(root, "%d/%d", &yyyy, &mm) != 2) + return GIT_WALK_SKIP; + dd = atoi(name); + if (!validate_date(yyyy, mm, dd)) + return GIT_WALK_SKIP; + finish_active_trip(); + active_trip = create_new_trip(yyyy, mm, dd); + return GIT_WALK_OK; +} + +/* + * Dive directory, name is [[yyyy-]mm-]nn-ddd-hh:mm:ss[~hex] in older git repositories + * but [[yyyy-]mm-]nn-ddd-hh=mm=ss[~hex] in newer repos as ':' is an illegal character for Windows files + * and 'timeoff' points to what should be the time part of + * the name (the first digit of the hour). + * + * The root path will be of the form yyyy/mm[/tripdir], + */ +static int dive_directory(const char *root, const git_tree_entry *entry, const char *name, int timeoff) +{ + int yyyy = -1, mm = -1, dd = -1; + int h, m, s; + int mday_off, month_off, year_off; + struct tm tm; + + /* Skip the '-' before the time */ + mday_off = timeoff; + if (!mday_off || name[--mday_off] != '-') + return GIT_WALK_SKIP; + /* Skip the day name */ + while (mday_off > 0 && name[--mday_off] != '-') + /* nothing */; + + mday_off = mday_off - 2; + month_off = mday_off - 3; + year_off = month_off - 5; + if (mday_off < 0) + return GIT_WALK_SKIP; + + /* Get the time of day -- parse both time formats so we can read old repos when not on Windows */ + if (sscanf(name+timeoff, "%d:%d:%d", &h, &m, &s) != 3 && sscanf(name+timeoff, "%d=%d=%d", &h, &m, &s) != 3) + return GIT_WALK_SKIP; + if (!validate_time(h, m, s)) + return GIT_WALK_SKIP; + + /* + * Using the "git_tree_walk()" interface is simple, but + * it kind of sucks as an interface because there is + * no sane way to pass the hierarchy to the callbacks. + * The "payload" is a fixed one-time thing: we'd like + * the "current trip" to be passed down to the dives + * that get parsed under that trip, but we can't. + * + * So "active_trip" is not the trip that is in the hierarchy + * _above_ us, it's just the trip that was _before_ us. But + * if a dive is not in a trip at all, we can't tell. + * + * We could just do a better walker that passes the + * return value around, but we hack around this by + * instead looking at the one hierarchical piece of + * data we have: the pathname to the current entry. + * + * This is pretty hacky. The magic '8' is the length + * of a pathname of the form 'yyyy/mm/'. + */ + if (strlen(root) == 8) + finish_active_trip(); + + /* + * Get the date. The day of the month is in the dive directory + * name, the year and month might be in the path leading up + * to it. + */ + dd = atoi(name + mday_off); + if (year_off < 0) { + if (sscanf(root, "%d/%d", &yyyy, &mm) != 2) + return GIT_WALK_SKIP; + } else + yyyy = atoi(name + year_off); + if (month_off >= 0) + mm = atoi(name + month_off); + + if (!validate_date(yyyy, mm, dd)) + return GIT_WALK_SKIP; + + /* Ok, close enough. We've gotten sufficient information */ + memset(&tm, 0, sizeof(tm)); + tm.tm_hour = h; + tm.tm_min = m; + tm.tm_sec = s; + tm.tm_year = yyyy - 1900; + tm.tm_mon = mm-1; + tm.tm_mday = dd; + + finish_active_dive(); + active_dive = create_new_dive(utc_mktime(&tm)); + memcpy(active_dive->git_id, git_tree_entry_id(entry)->id, 20); + return GIT_WALK_OK; +} + +static int picture_directory(const char *root, const char *name) +{ + (void) root; + (void) name; + if (!active_dive) + return GIT_WALK_SKIP; + return GIT_WALK_OK; +} + +/* + * Return the length of the string without the unique part. + */ +static int nonunique_length(const char *str) +{ + int len = 0; + + for (;;) { + char c = *str++; + if (!c || c == '~') + return len; + len++; + } +} + +/* + * When hitting a directory node, we have a couple of cases: + * + * - It's just a date entry - all numeric (either year or month): + * + * [yyyy|mm] + * + * We don't do anything with these, we just traverse into them. + * The numeric data will show up as part of the full path when + * we hit more interesting entries. + * + * - It's a trip directory. The name will be of the form + * + * nn-alphabetic[~hex] + * + * where 'nn' is the day of the month (year and month will be + * encoded in the path leading up to this). + * + * - It's a dive directory. The name will be of the form + * + * [[yyyy-]mm-]nn-ddd-hh=mm=ss[~hex] + * + * (older versions had this as [[yyyy-]mm-]nn-ddd-hh:mm:ss[~hex] + * but that faile on Windows) + * + * which describes the date and time of a dive (yyyy and mm + * are optional, and may be encoded in the path leading up to + * the dive). + * + * - It is a per-dive picture directory ("Pictures") + * + * - It's some random non-dive-data directory. + * + * If it doesn't match the above patterns, we'll ignore them + * for dive loading purposes, and not even recurse into them. + */ +static int walk_tree_directory(const char *root, const git_tree_entry *entry) +{ + const char *name = git_tree_entry_name(entry); + int digits = 0, len; + char c; + + if (!strcmp(name, "Pictures")) + return picture_directory(root, name); + + if (!strcmp(name, "01-Divesites")) + return GIT_WALK_OK; + + while (isdigit(c = name[digits])) + digits++; + + /* Doesn't start with two or four digits? Skip */ + if (digits != 4 && digits != 2) + return GIT_WALK_SKIP; + + /* Only digits? Do nothing, but recurse into it */ + if (!c) + return GIT_WALK_OK; + + /* All valid cases need to have a slash following */ + if (c != '-') + return GIT_WALK_SKIP; + + /* Do a quick check for a common dive case */ + len = nonunique_length(name); + + /* + * We know the len is at least 3, because we had at least + * two digits and a dash + */ + if (name[len-3] == ':' || name[len-3] == '=') + return dive_directory(root, entry, name, len-8); + + if (digits != 2) + return GIT_WALK_SKIP; + + return dive_trip_directory(root, name); +} + +git_blob *git_tree_entry_blob(git_repository *repo, const git_tree_entry *entry) +{ + const git_oid *id = git_tree_entry_id(entry); + git_blob *blob; + + if (git_blob_lookup(&blob, repo, id)) + return NULL; + return blob; +} + +static struct divecomputer *create_new_dc(struct dive *dive) +{ + struct divecomputer *dc = &dive->dc; + + while (dc->next) + dc = dc->next; + /* Did we already fill that in? */ + if (dc->samples || dc->model || dc->when) { + struct divecomputer *newdc = calloc(1, sizeof(*newdc)); + if (!newdc) + return NULL; + dc->next = newdc; + dc = newdc; + } + dc->when = dive->when; + dc->duration = dive->duration; + return dc; +} + +/* + * We should *really* try to delay the dive computer data parsing + * until necessary, in order to reduce load-time. The parsing is + * cheap, but the loading of the git blob into memory can be pretty + * costly. + */ +static int parse_divecomputer_entry(git_repository *repo, const git_tree_entry *entry, const char *suffix) +{ + (void) suffix; + git_blob *blob = git_tree_entry_blob(repo, entry); + + if (!blob) + return report_error("Unable to read divecomputer file"); + + active_dc = create_new_dc(active_dive); + for_each_line(blob, divecomputer_parser, active_dc); + git_blob_free(blob); + active_dc = NULL; + return 0; +} + +/* + * NOTE! The "git_id" for the dive is the hash for the whole dive directory. + * As such, it covers not just the dive, but the divecomputers and the + * pictures too. So if any of the dive computers change, the dive cache + * has to be invalidated too. + */ +static int parse_dive_entry(git_repository *repo, const git_tree_entry *entry, const char *suffix) +{ + struct dive *dive = active_dive; + git_blob *blob = git_tree_entry_blob(repo, entry); + if (!blob) + return report_error("Unable to read dive file"); + if (*suffix) + dive->number = atoi(suffix+1); + cylinder_index = weightsystem_index = 0; + for_each_line(blob, dive_parser, active_dive); + git_blob_free(blob); + return 0; +} + +static int parse_site_entry(git_repository *repo, const git_tree_entry *entry, const char *suffix) +{ + if (*suffix == '\0') + return report_error("Dive site without uuid"); + uint32_t uuid = strtoul(suffix, NULL, 16); + struct dive_site *ds = alloc_or_get_dive_site(uuid); + git_blob *blob = git_tree_entry_blob(repo, entry); + if (!blob) + return report_error("Unable to read dive site file"); + for_each_line(blob, site_parser, ds); + git_blob_free(blob); + return 0; +} + +static int parse_trip_entry(git_repository *repo, const git_tree_entry *entry) +{ + git_blob *blob = git_tree_entry_blob(repo, entry); + if (!blob) + return report_error("Unable to read trip file"); + for_each_line(blob, trip_parser, active_trip); + git_blob_free(blob); + return 0; +} + +static int parse_settings_entry(git_repository *repo, const git_tree_entry *entry) +{ + git_blob *blob = git_tree_entry_blob(repo, entry); + if (!blob) + return report_error("Unable to read settings file"); + set_save_userid_local(false); + for_each_line(blob, settings_parser, NULL); + git_blob_free(blob); + return 0; +} + +static int parse_picture_file(git_repository *repo, const git_tree_entry *entry, const char *name) +{ + /* remember the picture data so we can handle it when all dive data has been loaded + * the name of the git file is PIC- */ + git_blob *blob = git_tree_entry_blob(repo, entry); + const void *rawdata = git_blob_rawcontent(blob); + int len = git_blob_rawsize(blob); + struct picture_entry_list *new_pel = malloc(sizeof(struct picture_entry_list)); + new_pel->next = pel; + pel = new_pel; + pel->data = malloc(len); + memcpy(pel->data, rawdata, len); + pel->len = len; + pel->hash = strdup(name + 4); + git_blob_free(blob); + return 0; +} + +static int parse_picture_entry(git_repository *repo, const git_tree_entry *entry, const char *name) +{ + git_blob *blob; + struct picture *pic; + int hh, mm, ss, offset; + char sign; + + /* + * The format of the picture name files is just the offset within + * the dive in form [[+-]hh=mm=ss (previously [[+-]hh:mm:ss, but + * that didn't work on Windows), possibly followed by a hash to + * make the filename unique (which we can just ignore). + */ + if (sscanf(name, "%c%d:%d:%d", &sign, &hh, &mm, &ss) != 4 && + sscanf(name, "%c%d=%d=%d", &sign, &hh, &mm, &ss) != 4) + return report_error("Unknown file name %s", name); + offset = ss + 60*(mm + 60*hh); + if (sign == '-') + offset = -offset; + + blob = git_tree_entry_blob(repo, entry); + if (!blob) + return report_error("Unable to read picture file"); + + pic = alloc_picture(); + pic->offset.seconds = offset; + + for_each_line(blob, picture_parser, pic); + dive_add_picture(active_dive, pic); + git_blob_free(blob); + return 0; +} + +static int walk_tree_file(const char *root, const git_tree_entry *entry, git_repository *repo) +{ + struct dive *dive = active_dive; + dive_trip_t *trip = active_trip; + const char *name = git_tree_entry_name(entry); + if (verbose > 1) + fprintf(stderr, "git load handling file %s\n", name); + switch (*name) { + /* Picture file? They are saved as time offsets in the dive */ + case '-': case '+': + if (dive) + return parse_picture_entry(repo, entry, name); + break; + case 'D': + if (dive && !strncmp(name, "Divecomputer", 12)) + return parse_divecomputer_entry(repo, entry, name+12); + if (dive && !strncmp(name, "Dive", 4)) + return parse_dive_entry(repo, entry, name+4); + break; + case 'S': + if (!strncmp(name, "Site", 4)) + return parse_site_entry(repo, entry, name + 5); + break; + case '0': + if (trip && !strcmp(name, "00-Trip")) + return parse_trip_entry(repo, entry); + if (!strcmp(name, "00-Subsurface")) + return parse_settings_entry(repo, entry); + break; + case 'P': + if (dive && !strncmp(name, "PIC-", 4)) + return parse_picture_file(repo, entry, name); + break; + } + report_error("Unknown file %s%s (%p %p)", root, name, dive, trip); + return GIT_WALK_SKIP; +} + +static int walk_tree_cb(const char *root, const git_tree_entry *entry, void *payload) +{ + git_repository *repo = payload; + git_filemode_t mode = git_tree_entry_filemode(entry); + + if (mode == GIT_FILEMODE_TREE) + return walk_tree_directory(root, entry); + + walk_tree_file(root, entry, repo); + /* Ignore failed blob loads */ + return GIT_WALK_OK; +} + +static int load_dives_from_tree(git_repository *repo, git_tree *tree) +{ + git_tree_walk(tree, GIT_TREEWALK_PRE, walk_tree_cb, repo); + return 0; +} + +void clear_git_id(void) +{ + saved_git_id = NULL; +} + +void set_git_id(const struct git_oid * id) +{ + static char git_id_buffer[GIT_OID_HEXSZ+1]; + + git_oid_tostr(git_id_buffer, sizeof(git_id_buffer), id); + saved_git_id = git_id_buffer; +} + +static int find_commit(git_repository *repo, const char *branch, git_commit **commit_p) +{ + git_object *object; + + if (git_revparse_single(&object, repo, branch)) + return report_error("Unable to look up revision '%s'", branch); + if (git_object_peel((git_object **)commit_p, object, GIT_OBJ_COMMIT)) + return report_error("Revision '%s' is not a valid commit", branch); + return 0; +} + +static int do_git_load(git_repository *repo, const char *branch) +{ + int ret; + git_commit *commit; + git_tree *tree; + + ret = find_commit(repo, branch, &commit); + if (ret) + return ret; + if (git_commit_tree(&tree, commit)) + return report_error("Could not look up tree of commit in branch '%s'", branch); + ret = load_dives_from_tree(repo, tree); + if (!ret) + set_git_id(git_commit_id(commit)); + git_object_free((git_object *)tree); + return ret; +} + +const char *get_sha(git_repository *repo, const char *branch) +{ + static char git_id_buffer[GIT_OID_HEXSZ+1]; + git_commit *commit; + if (find_commit(repo, branch, &commit)) + return NULL; + git_oid_tostr(git_id_buffer, sizeof(git_id_buffer), (const git_oid *)commit); + return git_id_buffer; +} + +/* + * Like git_save_dives(), this silently returns a negative + * value if it's not a git repository at all (so that you + * can try to load it some other way. + * + * If it is a git repository, we return zero for success, + * or report an error and return 1 if the load failed. + */ +int git_load_dives(struct git_repository *repo, const char *branch) +{ + int ret; + + if (repo == dummy_git_repository) + return report_error("Unable to open git repository at '%s'", branch); + ret = do_git_load(repo, branch); + git_repository_free(repo); + free((void *)branch); + finish_active_dive(); + finish_active_trip(); + return ret; +} diff --git a/core/macos.c b/core/macos.c new file mode 100644 index 000000000..500412cd8 --- /dev/null +++ b/core/macos.c @@ -0,0 +1,218 @@ +/* macos.c */ +/* implements Mac OS X specific functions */ +#include +#include +#include +#include "dive.h" +#include "display.h" +#include +#if !defined(__IPHONE_5_0) +#include +#endif +#include +#include +#include +#include +#include + +void subsurface_user_info(struct user_info *info) +{ + (void) info; + /* Nothing, let's use libgit2-20 on MacOS */ +} + +/* macos defines CFSTR to create a CFString object from a constant, + * but no similar macros if a C string variable is supposed to be + * the argument. We add this here (hardcoding the default allocator + * and MacRoman encoding */ +#define CFSTR_VAR(_var) CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, \ + (_var), kCFStringEncodingMacRoman, \ + kCFAllocatorNull) + +#define SUBSURFACE_PREFERENCES CFSTR("org.hohndel.subsurface") +#define ICON_NAME "Subsurface.icns" +#define UI_FONT "Arial 12" + +const char mac_system_divelist_default_font[] = "Arial"; +const char *system_divelist_default_font = mac_system_divelist_default_font; +double system_divelist_default_font_size = -1.0; + +void subsurface_OS_pref_setup(void) +{ + // nothing +} + +bool subsurface_ignore_font(const char *font) +{ + (void) font; + // there are no old default fonts to ignore + return false; +} + +static const char *system_default_path_append(const char *append) +{ + const char *home = getenv("HOME"); + const char *path = "/Library/Application Support/Subsurface"; + + int len = strlen(home) + strlen(path) + 1; + if (append) + len += strlen(append) + 1; + + char *buffer = (char *)malloc(len); + memset(buffer, 0, len); + strcat(buffer, home); + strcat(buffer, path); + if (append) { + strcat(buffer, "/"); + strcat(buffer, append); + } + + return buffer; +} + +const char *system_default_directory(void) +{ + static const char *path = NULL; + if (!path) + path = system_default_path_append(NULL); + return path; +} + +const char *system_default_filename(void) +{ + static char *filename = NULL; + if (!filename) { + const char *user = getenv("LOGNAME"); + if (same_string(user, "")) + user = "username"; + filename = calloc(strlen(user) + 5, 1); + strcat(filename, user); + strcat(filename, ".xml"); + } + static const char *path = NULL; + if (!path) + path = system_default_path_append(filename); + return path; +} + +int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) +{ + int index = -1, entries = 0; + DIR *dp = NULL; + struct dirent *ep = NULL; + size_t i; + if (dc_type != DC_TYPE_UEMIS) { + const char *dirname = "/dev"; + const char *patterns[] = { + "tty.*", + "usbserial", + NULL + }; + + dp = opendir(dirname); + if (dp == NULL) { + return -1; + } + + while ((ep = readdir(dp)) != NULL) { + for (i = 0; patterns[i] != NULL; ++i) { + if (fnmatch(patterns[i], ep->d_name, 0) == 0) { + char filename[1024]; + int n = snprintf(filename, sizeof(filename), "%s/%s", dirname, ep->d_name); + if (n >= (int)sizeof(filename)) { + closedir(dp); + return -1; + } + callback(filename, userdata); + if (is_default_dive_computer_device(filename)) + index = entries; + entries++; + break; + } + } + } + closedir(dp); + } + if (dc_type != DC_TYPE_SERIAL) { + const char *dirname = "/Volumes"; + int num_uemis = 0; + dp = opendir(dirname); + if (dp == NULL) { + return -1; + } + + while ((ep = readdir(dp)) != NULL) { + if (fnmatch("UEMISSDA", ep->d_name, 0) == 0) { + char filename[1024]; + int n = snprintf(filename, sizeof(filename), "%s/%s", dirname, ep->d_name); + if (n >= (int)sizeof(filename)) { + closedir(dp); + return -1; + } + callback(filename, userdata); + if (is_default_dive_computer_device(filename)) + index = entries; + entries++; + num_uemis++; + break; + } + } + closedir(dp); + if (num_uemis == 1 && entries == 1) /* if we find exactly one entry and that's a Uemis, select it */ + index = 0; + } + return index; +} + +/* NOP wrappers to comform with windows.c */ +int subsurface_rename(const char *path, const char *newpath) +{ + return rename(path, newpath); +} + +int subsurface_open(const char *path, int oflags, mode_t mode) +{ + return open(path, oflags, mode); +} + +FILE *subsurface_fopen(const char *path, const char *mode) +{ + return fopen(path, mode); +} + +void *subsurface_opendir(const char *path) +{ + return (void *)opendir(path); +} + +int subsurface_access(const char *path, int mode) +{ + return access(path, mode); +} + +struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp) +{ + return zip_open(path, flags, errorp); +} + +int subsurface_zip_close(struct zip *zip) +{ + return zip_close(zip); +} + +/* win32 console */ +void subsurface_console_init(bool dedicated) +{ + (void) dedicated; + /* NOP */ +} + +void subsurface_console_exit(void) +{ + /* NOP */ +} + +bool subsurface_user_is_root() +{ + return (geteuid() == 0); +} diff --git a/core/membuffer.c b/core/membuffer.c new file mode 100644 index 000000000..053edb8f0 --- /dev/null +++ b/core/membuffer.c @@ -0,0 +1,288 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#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/core/membuffer.h b/core/membuffer.h new file mode 100644 index 000000000..434b34c71 --- /dev/null +++ b/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/core/metrics.cpp b/core/metrics.cpp new file mode 100644 index 000000000..3c66528b8 --- /dev/null +++ b/core/metrics.cpp @@ -0,0 +1,65 @@ +/* + * metrics.cpp + * + * methods to find/compute essential UI metrics + * (font properties, icon sizes, etc) + * + */ + +#include "metrics.h" + +static IconMetrics dfltIconMetrics; + +IconMetrics::IconMetrics() : + sz_small(-1), + sz_med(-1), + sz_big(-1), + sz_pic(-1), + spacing(-1), + dpr(1.0) +{ +} + +QFont defaultModelFont() +{ + QFont font; +// font.setPointSizeF(font.pointSizeF() * 0.8); + return font; +} + +QFontMetrics defaultModelFontMetrics() +{ + return QFontMetrics(defaultModelFont()); +} + +// return the default icon size, computed as the multiple of 16 closest to +// the given height +static int defaultIconSize(int height) +{ + int ret = (height + 8)/16; + ret *= 16; + if (ret < 16) + ret = 16; + return ret; +} + +const IconMetrics & defaultIconMetrics() +{ + if (dfltIconMetrics.sz_small == -1) { + int small = defaultIconSize(defaultModelFontMetrics().height()); + dfltIconMetrics.sz_small = small; + dfltIconMetrics.sz_med = small + small/2; + dfltIconMetrics.sz_big = 2*small; + + dfltIconMetrics.sz_pic = 8*small; + + dfltIconMetrics.spacing = small/8; + } + + return dfltIconMetrics; +} + +void updateDevicePixelRatio(double dpr) +{ + dfltIconMetrics.dpr = dpr; +} diff --git a/core/metrics.h b/core/metrics.h new file mode 100644 index 000000000..ca281b3b1 --- /dev/null +++ b/core/metrics.h @@ -0,0 +1,36 @@ +/* + * metrics.h + * + * header file for common function to find/compute essential UI metrics + * (font properties, icon sizes, etc) + * + */ +#ifndef METRICS_H +#define METRICS_H + +#include +#include +#include + +QFont defaultModelFont(); +QFontMetrics defaultModelFontMetrics(); + +// Collection of icon/picture sizes and other metrics, resolution independent +struct IconMetrics { + // icon sizes + int sz_small; // ex 16px + int sz_med; // ex 24px + int sz_big; // ex 32px + // picture size + int sz_pic; // ex 128px + // icon spacing + int spacing; // ex 2px + // devicePixelRatio + double dpr; // 1.0 for traditional screens, HiDPI screens up to 3.0 + IconMetrics(); +}; + +const IconMetrics & defaultIconMetrics(); +void updateDevicePixelRatio(double dpr); + +#endif // METRICS_H diff --git a/core/ostctools.c b/core/ostctools.c new file mode 100644 index 000000000..9be591b0e --- /dev/null +++ b/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 necessary to iterate once and again on a parsing + * function. Actually there's only one kind of archive for every DC model. + */ +void ostctools_import(const char *file, struct dive_table *divetable) +{ + FILE *archive; + device_data_t *devdata = calloc(1, sizeof(device_data_t)); + dc_family_t dc_fam; + unsigned char *buffer = calloc(65536, 1), *uc_tmp; + char *tmp; + struct dive *ostcdive = alloc_dive(); + dc_status_t rc = 0; + int model, ret, i = 0; + unsigned int serial; + struct extra_data *ptr; + + // Open the archive + if ((archive = subsurface_fopen(file, "rb")) == NULL) { + report_error(translate("gettextFromC", "Failed to read '%s'"), file); + free(ostcdive); + goto out; + } + + // Read dive number from the log + uc_tmp = calloc(2, 1); + fseek(archive, 258, 0); + fread(uc_tmp, 1, 2, archive); + ostcdive->number = uc_tmp[0] + (uc_tmp[1] << 8); + free(uc_tmp); + + // Read device's serial number + uc_tmp = calloc(2, 1); + fseek(archive, 265, 0); + fread(uc_tmp, 1, 2, archive); + serial = uc_tmp[0] + (uc_tmp[1] << 8); + free(uc_tmp); + + // Read dive's raw data, header + profile + fseek(archive, 456, 0); + while (!feof(archive)) { + fread(buffer + i, 1, 1, archive); + if (buffer[i] == 0xFD && buffer[i - 1] == 0xFD) + break; + i++; + } + + // Try to determine the dc family based on the header type + if (buffer[2] == 0x20 || buffer[2] == 0x21) { + dc_fam = DC_FAMILY_HW_OSTC; + } else { + switch (buffer[8]) { + case 0x22: + dc_fam = DC_FAMILY_HW_FROG; + break; + case 0x23: + dc_fam = DC_FAMILY_HW_OSTC3; + break; + default: + report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); + free(ostcdive); + fclose(archive); + goto out; + } + } + + // Try to determine the model based on serial number + switch (dc_fam) { + case DC_FAMILY_HW_OSTC: + if (serial > 7000) + model = 3; //2C + else if (serial > 2048) + model = 2; //2N + else if (serial > 300) + model = 1; //MK2 + else + model = 0; //OSTC + break; + case DC_FAMILY_HW_FROG: + model = 0; + break; + default: + if (serial > 10000) + model = 0x12; //Sport + else + model = 0x0A; //OSTC3 + } + + // Prepare data to pass to libdivecomputer. + ret = ostc_prepare_data(model, dc_fam, devdata); + if (ret == 0) { + report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); + free(ostcdive); + fclose(archive); + goto out; + } + tmp = calloc(strlen(devdata->vendor) + strlen(devdata->model) + 28, 1); + sprintf(tmp, "%s %s (Imported from OSTCTools)", devdata->vendor, devdata->model); + ostcdive->dc.model = copy_string(tmp); + free(tmp); + + // Parse the dive data + rc = libdc_buffer_parser(ostcdive, devdata, buffer, i + 1); + if (rc != DC_STATUS_SUCCESS) + report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), ostcdive->number); + + // Serial number is not part of the header nor the profile, so libdc won't + // catch it. If Serial is part of the extra_data, and set to zero, remove + // it from the list and add again. + tmp = calloc(12, 1); + sprintf(tmp, "%d", serial); + ostcdive->dc.serial = copy_string(tmp); + free(tmp); + + if (ostcdive->dc.extra_data) { + ptr = ostcdive->dc.extra_data; + while (strcmp(ptr->key, "Serial")) + ptr = ptr->next; + if (!strcmp(ptr->value, "0")) { + add_extra_data(&ostcdive->dc, "Serial", ostcdive->dc.serial); + *ptr = *(ptr)->next; + } + } else { + add_extra_data(&ostcdive->dc, "Serial", ostcdive->dc.serial); + } + record_dive_to_table(ostcdive, divetable); + mark_divelist_changed(true); + sort_table(divetable); + fclose(archive); +out: + free(devdata); + free(buffer); +} diff --git a/core/parse-xml.c b/core/parse-xml.c new file mode 100644 index 000000000..e8782251e --- /dev/null +++ b/core/parse-xml.c @@ -0,0 +1,3751 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#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, force_root; +int metric = 1; +int last_xml_version = -1; +int diveid = -1; + +static xmlDoc *test_xslt_transforms(xmlDoc *doc, const char **params); + +/* the dive table holds the overall dive list; target table points at + * the table we are currently filling */ +struct dive_table dive_table; +struct dive_table *target_table = NULL; + +/* Trim a character string by removing leading and trailing white space characters. + * Parameter: a pointer to a null-terminated character string (buffer); + * Return value: length of the trimmed string, excluding the terminal 0x0 byte + * The original pointer (buffer) remains valid after this function has been called + * and points to the trimmed string */ +int trimspace(char *buffer) { + int i, size, start, end; + size = strlen(buffer); + for(start = 0; isspace(buffer[start]); start++) + if (start >= size) return 0; // Find 1st character following leading whitespace + for(end = size - 1; isspace(buffer[end]); end--) // Find last character before trailing whitespace + if (end <= 0) return 0; + for(i = start; i <= end; i++) // Move the nonspace characters to the start of the string + buffer[i-start] = buffer[i]; + size = end - start + 1; + buffer[size] = 0x0; // then terminate the string + return size; // return string length +} + +/* + * Clear a dive_table + */ +void clear_table(struct dive_table *table) +{ + for (int i = 0; i < table->nr; i++) + free(table->dives[i]); + table->nr = 0; +} + +/* + * Add a dive into the dive_table array + */ +void record_dive_to_table(struct dive *dive, struct dive_table *table) +{ + assert(table != NULL); + struct dive **dives = grow_dive_table(table); + int nr = table->nr; + + dives[nr] = fixup_dive(dive); + table->nr = nr + 1; +} + +void record_dive(struct dive *dive) +{ + record_dive_to_table(dive, &dive_table); +} + +static void start_match(const char *type, const char *name, char *buffer) +{ + if (verbose > 2) + printf("Matching %s '%s' (%s)\n", + type, name, buffer); +} + +static void nonmatch(const char *type, const char *name, char *buffer) +{ + if (verbose > 1) + printf("Unable to match %s '%s' (%s)\n", + type, name, buffer); +} + +typedef void (*matchfn_t)(char *buffer, void *); + +static int match(const char *pattern, int plen, + const char *name, + matchfn_t fn, char *buf, void *data) +{ + switch (name[plen]) { + case '\0': + case '.': + break; + default: + return 0; + } + if (memcmp(pattern, name, plen)) + return 0; + fn(buf, data); + return 1; +} + + +struct units xml_parsing_units; +const struct units SI_units = SI_UNITS; +const struct units IMPERIAL_units = IMPERIAL_UNITS; + +/* + * Dive info as it is being built up.. + */ +#define MAX_EVENT_NAME 128 +static struct divecomputer *cur_dc; +static struct dive *cur_dive; +static struct dive_site *cur_dive_site; +degrees_t cur_latitude, cur_longitude; +static dive_trip_t *cur_trip = NULL; +static struct sample *cur_sample; +static struct picture *cur_picture; +static union { + struct event event; + char allocation[sizeof(struct event)+MAX_EVENT_NAME]; +} event_allocation = { .event.deleted = 1 }; +#define cur_event event_allocation.event +static struct { + struct { + const char *model; + uint32_t deviceid; + const char *nickname, *serial_nr, *firmware; + } dc; +} cur_settings; +static bool in_settings = false; +static bool in_userid = false; +static struct tm cur_tm; +static int cur_cylinder_index, cur_ws_index; +static int lastndl, laststoptime, laststopdepth, lastcns, lastpo2, lastindeco; +static int lastcylinderindex, lastsensor, next_o2_sensor; +static struct extra_data cur_extra_data; + +/* + * If we don't have an explicit dive computer, + * we use the implicit one that every dive has.. + */ +static struct divecomputer *get_dc(void) +{ + return cur_dc ?: &cur_dive->dc; +} + +static enum import_source { + UNKNOWN, + LIBDIVECOMPUTER, + DIVINGLOG, + UDDF, + SSRF_WS, +} import_source; + +static void divedate(const char *buffer, timestamp_t *when) +{ + int d, m, y; + int hh, mm, ss; + + hh = 0; + mm = 0; + ss = 0; + if (sscanf(buffer, "%d.%d.%d %d:%d:%d", &d, &m, &y, &hh, &mm, &ss) >= 3) { + /* This is ok, and we got at least the date */ + } else if (sscanf(buffer, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) >= 3) { + /* This is also ok */ + } else { + fprintf(stderr, "Unable to parse date '%s'\n", buffer); + return; + } + cur_tm.tm_year = y; + cur_tm.tm_mon = m - 1; + cur_tm.tm_mday = d; + cur_tm.tm_hour = hh; + cur_tm.tm_min = mm; + cur_tm.tm_sec = ss; + + *when = utc_mktime(&cur_tm); +} + +static void divetime(const char *buffer, timestamp_t *when) +{ + int h, m, s = 0; + + if (sscanf(buffer, "%d:%d:%d", &h, &m, &s) >= 2) { + cur_tm.tm_hour = h; + cur_tm.tm_min = m; + cur_tm.tm_sec = s; + *when = utc_mktime(&cur_tm); + } +} + +/* Libdivecomputer: "2011-03-20 10:22:38" */ +static void divedatetime(char *buffer, timestamp_t *when) +{ + int y, m, d; + int hr, min, sec; + + if (sscanf(buffer, "%d-%d-%d %d:%d:%d", + &y, &m, &d, &hr, &min, &sec) == 6) { + cur_tm.tm_year = y; + cur_tm.tm_mon = m - 1; + cur_tm.tm_mday = d; + cur_tm.tm_hour = hr; + cur_tm.tm_min = min; + cur_tm.tm_sec = sec; + *when = utc_mktime(&cur_tm); + } +} + +enum ParseState { + FINDSTART, + FINDEND +}; +static void divetags(char *buffer, struct tag_entry **tags) +{ + int i = 0, start = 0, end = 0; + enum ParseState state = FINDEND; + int len = buffer ? strlen(buffer) : 0; + + while (i < len) { + if (buffer[i] == ',') { + if (state == FINDSTART) { + /* Detect empty tags */ + } else if (state == FINDEND) { + /* Found end of tag */ + if (i > 0 && buffer[i - 1] != '\\') { + buffer[i] = '\0'; + state = FINDSTART; + taglist_add_tag(tags, buffer + start); + } else { + state = FINDSTART; + } + } + } else if (buffer[i] == ' ') { + /* Handled */ + } else { + /* Found start of tag */ + if (state == FINDSTART) { + state = FINDEND; + start = i; + } else if (state == FINDEND) { + end = i; + } + } + i++; + } + if (state == FINDEND) { + if (end < start) + end = len - 1; + if (len > 0) { + buffer[end + 1] = '\0'; + taglist_add_tag(tags, buffer + start); + } + } +} + +enum number_type { + NEITHER, + FLOAT +}; + +static enum number_type parse_float(const char *buffer, double *res, const char **endp) +{ + double val; + static bool first_time = true; + + errno = 0; + val = ascii_strtod(buffer, endp); + if (errno || *endp == buffer) + return NEITHER; + if (**endp == ',') { + if (IS_FP_SAME(val, rint(val))) { + /* we really want to send an error if this is a Subsurface native file + * as this is likely indication of a bug - but right now we don't have + * that information available */ + if (first_time) { + fprintf(stderr, "Floating point value with decimal comma (%s)?\n", buffer); + first_time = false; + } + /* Try again in permissive mode*/ + val = strtod_flags(buffer, endp, 0); + } + } + + *res = val; + return FLOAT; +} + +union int_or_float { + double fp; +}; + +static enum number_type integer_or_float(char *buffer, union int_or_float *res) +{ + const char *end; + return parse_float(buffer, &res->fp, &end); +} + +static void pressure(char *buffer, pressure_t *pressure) +{ + double mbar = 0.0; + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + /* Just ignore zero values */ + if (!val.fp) + break; + switch (xml_parsing_units.pressure) { + case PASCAL: + mbar = val.fp / 100; + break; + case BAR: + /* Assume mbar, but if it's really small, it's bar */ + mbar = val.fp; + if (fabs(mbar) < 5000) + mbar = mbar * 1000; + break; + case PSI: + mbar = psi_to_mbar(val.fp); + break; + } + if (fabs(mbar) > 5 && fabs(mbar) < 5000000) { + pressure->mbar = rint(mbar); + break; + } + /* fallthrough */ + default: + printf("Strange pressure reading %s\n", buffer); + } +} + +static void cylinder_use(char *buffer, enum cylinderuse *cyl_use) +{ + if (trimspace(buffer)) + *cyl_use = cylinderuse_from_text(buffer); +} + +static void salinity(char *buffer, int *salinity) +{ + union int_or_float val; + switch (integer_or_float(buffer, &val)) { + case FLOAT: + *salinity = rint(val.fp * 10.0); + break; + default: + printf("Strange salinity reading %s\n", buffer); + } +} + +static void depth(char *buffer, depth_t *depth) +{ + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + switch (xml_parsing_units.length) { + case METERS: + depth->mm = rint(val.fp * 1000); + break; + case FEET: + depth->mm = feet_to_mm(val.fp); + break; + } + break; + default: + printf("Strange depth reading %s\n", buffer); + } +} + +static void extra_data_start(void) +{ + memset(&cur_extra_data, 0, sizeof(struct extra_data)); +} + +static void extra_data_end(void) +{ + // don't save partial structures - we must have both key and value + if (cur_extra_data.key && cur_extra_data.value) + add_extra_data(cur_dc, cur_extra_data.key, cur_extra_data.value); +} + +static void weight(char *buffer, weight_t *weight) +{ + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + switch (xml_parsing_units.weight) { + case KG: + weight->grams = rint(val.fp * 1000); + break; + case LBS: + weight->grams = lbs_to_grams(val.fp); + break; + } + break; + default: + printf("Strange weight reading %s\n", buffer); + } +} + +static void temperature(char *buffer, temperature_t *temperature) +{ + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + switch (xml_parsing_units.temperature) { + case KELVIN: + temperature->mkelvin = val.fp * 1000; + break; + case CELSIUS: + temperature->mkelvin = C_to_mkelvin(val.fp); + break; + case FAHRENHEIT: + temperature->mkelvin = F_to_mkelvin(val.fp); + break; + } + break; + default: + printf("Strange temperature reading %s\n", buffer); + } + /* temperatures outside -40C .. +70C should be ignored */ + if (temperature->mkelvin < ZERO_C_IN_MKELVIN - 40000 || + temperature->mkelvin > ZERO_C_IN_MKELVIN + 70000) + temperature->mkelvin = 0; +} + +static void sampletime(char *buffer, duration_t *time) +{ + int i; + int min, sec; + + i = sscanf(buffer, "%d:%d", &min, &sec); + switch (i) { + case 1: + sec = min; + min = 0; + /* fallthrough */ + case 2: + time->seconds = sec + min * 60; + break; + default: + printf("Strange sample time reading %s\n", buffer); + } +} + +static void offsettime(char *buffer, offset_t *time) +{ + duration_t uoffset; + int sign = 1; + if (*buffer == '-') { + sign = -1; + buffer++; + } + /* yes, this could indeed fail if we have an offset > 34yrs + * - too bad */ + sampletime(buffer, &uoffset); + time->seconds = sign * uoffset.seconds; +} + +static void duration(char *buffer, duration_t *time) +{ + /* DivingLog 5.08 (and maybe other versions) appear to sometimes + * store the dive time as 44.00 instead of 44:00; + * This attempts to parse this in a fairly robust way */ + if (!strchr(buffer, ':') && strchr(buffer, '.')) { + char *mybuffer = strdup(buffer); + char *dot = strchr(mybuffer, '.'); + *dot = ':'; + sampletime(mybuffer, time); + free(mybuffer); + } else { + sampletime(buffer, time); + } +} + +static void percent(char *buffer, fraction_t *fraction) +{ + double val; + const char *end; + + switch (parse_float(buffer, &val, &end)) { + case FLOAT: + /* Turn fractions into percent unless explicit.. */ + if (val <= 1.0) { + while (isspace(*end)) + end++; + if (*end != '%') + val *= 100; + } + + /* Then turn percent into our integer permille format */ + if (val >= 0 && val <= 100.0) { + fraction->permille = rint(val * 10); + break; + } + default: + printf(translate("gettextFromC", "Strange percentage reading %s\n"), buffer); + break; + } +} + +static void gasmix(char *buffer, fraction_t *fraction) +{ + /* libdivecomputer does negative percentages. */ + if (*buffer == '-') + return; + if (cur_cylinder_index < MAX_CYLINDERS) + percent(buffer, fraction); +} + +static void gasmix_nitrogen(char *buffer, struct gasmix *gasmix) +{ + (void) buffer; + (void) gasmix; + /* Ignore n2 percentages. There's no value in them. */ +} + +static void cylindersize(char *buffer, volume_t *volume) +{ + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + volume->mliter = rint(val.fp * 1000); + break; + + default: + printf("Strange volume reading %s\n", buffer); + break; + } +} + +static void utf8_string(char *buffer, void *_res) +{ + char **res = _res; + int size; + size = trimspace(buffer); + if(size) + *res = strdup(buffer); +} + +static void event_name(char *buffer, char *name) +{ + int size = trimspace(buffer); + if (size >= MAX_EVENT_NAME) + size = MAX_EVENT_NAME-1; + memcpy(name, buffer, size); + name[size] = 0; +} + +// We don't use gauge as a mode, and pscr doesn't exist as a libdc divemode +const char *libdc_divemode_text[] = { "oc", "cc", "pscr", "freedive", "gauge"}; + +/* Extract the dive computer type from the xml text buffer */ +static void get_dc_type(char *buffer, enum dive_comp_type *dct) +{ + if (trimspace(buffer)) { + for (enum dive_comp_type i = 0; i < NUM_DC_TYPE; i++) { + if (strcmp(buffer, divemode_text[i]) == 0) + *dct = i; + else if (strcmp(buffer, libdc_divemode_text[i]) == 0) + *dct = i; + } + } +} + +#define MATCH(pattern, fn, dest) ({ \ + /* Silly type compatibility test */ \ + if (0) (fn)("test", dest); \ + match(pattern, strlen(pattern), name, (matchfn_t) (fn), buf, dest); }) + +static void get_index(char *buffer, int *i) +{ + *i = atoi(buffer); +} + +static void get_uint8(char *buffer, uint8_t *i) +{ + *i = atoi(buffer); +} + +static void get_bearing(char *buffer, bearing_t *bearing) +{ + bearing->degrees = atoi(buffer); +} + +static void get_rating(char *buffer, int *i) +{ + int j = atoi(buffer); + if (j >= 0 && j <= 5) { + *i = j; + } +} + +static void double_to_o2pressure(char *buffer, o2pressure_t *i) +{ + i->mbar = rint(ascii_strtod(buffer, NULL) * 1000.0); +} + +static void hex_value(char *buffer, uint32_t *i) +{ + *i = strtoul(buffer, NULL, 16); +} + +static void get_tripflag(char *buffer, tripflag_t *tf) +{ + *tf = strcmp(buffer, "NOTRIP") ? TF_NONE : NO_TRIP; +} + +/* + * Divinglog is crazy. The temperatures are in celsius. EXCEPT + * for the sample temperatures, that are in Fahrenheit. + * WTF? + * + * Oh, and I think Diving Log *internally* probably kept them + * in celsius, because I'm seeing entries like + * + * 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) +{ + (void) name; + if (prefs.save_userid_local) + set_userid(buf); +} + +static const char *country, *city; + +static void divinglog_place(char *place, uint32_t *uuid) +{ + char buffer[1024]; + + snprintf(buffer, sizeof(buffer), + "%s%s%s%s%s", + place, + city ? ", " : "", + city ? city : "", + country ? ", " : "", + country ? country : ""); + *uuid = get_dive_site_uuid_by_name(buffer, NULL); + if (*uuid == 0) + *uuid = create_dive_site(buffer, cur_dive->when); + + city = NULL; + country = NULL; +} + +static int divinglog_dive_match(struct dive *dive, const char *name, char *buf) +{ + return MATCH("divedate", divedate, &dive->when) || + MATCH("entrytime", divetime, &dive->when) || + MATCH("divetime", duration, &dive->dc.duration) || + MATCH("depth", depth, &dive->dc.maxdepth) || + MATCH("depthavg", depth, &dive->dc.meandepth) || + MATCH("tanktype", utf8_string, &dive->cylinder[0].type.description) || + MATCH("tanksize", cylindersize, &dive->cylinder[0].type.size) || + MATCH("presw", pressure, &dive->cylinder[0].type.workingpressure) || + MATCH("press", pressure, &dive->cylinder[0].start) || + MATCH("prese", pressure, &dive->cylinder[0].end) || + MATCH("comments", utf8_string, &dive->notes) || + MATCH("names.buddy", utf8_string, &dive->buddy) || + MATCH("name.country", utf8_string, &country) || + MATCH("name.city", utf8_string, &city) || + MATCH("name.place", divinglog_place, &dive->dive_site_uuid) || + 0; +} + +/* + * Uddf specifies ISO 8601 time format. + * + * There are many variations on that. This handles the useful cases. + */ +static void uddf_datetime(char *buffer, timestamp_t *when) +{ + char c; + int y, m, d, hh, mm, ss; + struct tm tm = { 0 }; + int i; + + i = sscanf(buffer, "%d-%d-%d%c%d:%d:%d", &y, &m, &d, &c, &hh, &mm, &ss); + if (i == 7) + goto success; + ss = 0; + if (i == 6) + goto success; + + i = sscanf(buffer, "%04d%02d%02d%c%02d%02d%02d", &y, &m, &d, &c, &hh, &mm, &ss); + if (i == 7) + goto success; + ss = 0; + if (i == 6) + goto success; +bad_date: + printf("Bad date time %s\n", buffer); + return; + +success: + if (c != 'T' && c != ' ') + goto bad_date; + tm.tm_year = y; + tm.tm_mon = m - 1; + tm.tm_mday = d; + tm.tm_hour = hh; + tm.tm_min = mm; + tm.tm_sec = ss; + *when = utc_mktime(&tm); +} + +#define uddf_datedata(name, offset) \ + static void uddf_##name(char *buffer, timestamp_t *when) \ + { \ + cur_tm.tm_##name = atoi(buffer) + offset; \ + *when = utc_mktime(&cur_tm); \ + } + +uddf_datedata(year, 0) +uddf_datedata(mon, -1) +uddf_datedata(mday, 0) +uddf_datedata(hour, 0) +uddf_datedata(min, 0) + +static int uddf_dive_match(struct dive *dive, const char *name, char *buf) +{ + return MATCH("datetime", uddf_datetime, &dive->when) || + MATCH("diveduration", duration, &dive->dc.duration) || + MATCH("greatestdepth", depth, &dive->dc.maxdepth) || + MATCH("year.date", uddf_year, &dive->when) || + MATCH("month.date", uddf_mon, &dive->when) || + MATCH("day.date", uddf_mday, &dive->when) || + MATCH("hour.time", uddf_hour, &dive->when) || + MATCH("minute.time", uddf_min, &dive->when) || + 0; +} + +/* + * This parses "floating point" into micro-degrees. + * We don't do exponentials etc, if somebody does + * GPS locations in that format, they are insane. + */ +degrees_t parse_degrees(char *buf, char **end) +{ + int sign = 1, decimals = 6, value = 0; + degrees_t ret; + + while (isspace(*buf)) + buf++; + switch (*buf) { + case '-': + sign = -1; + /* fallthrough */ + case '+': + buf++; + } + while (isdigit(*buf)) { + value = 10 * value + *buf - '0'; + buf++; + } + + /* Get the first six decimals if they exist */ + if (*buf == '.') + buf++; + do { + value *= 10; + if (isdigit(*buf)) { + value += *buf - '0'; + buf++; + } + } while (--decimals); + + /* Rounding */ + switch (*buf) { + case '5' ... '9': + value++; + } + while (isdigit(*buf)) + buf++; + + *end = buf; + ret.udeg = value * sign; + return ret; +} + +static void gps_lat(char *buffer, struct dive *dive) +{ + char *end; + degrees_t latitude = parse_degrees(buffer, &end); + struct dive_site *ds = get_dive_site_for_dive(dive); + if (!ds) { + dive->dive_site_uuid = create_dive_site_with_gps(NULL, latitude, (degrees_t){0}, dive->when); + } else { + if (ds->latitude.udeg && ds->latitude.udeg != latitude.udeg) + fprintf(stderr, "Oops, changing the latitude of existing dive site id %8x name %s; not good\n", ds->uuid, ds->name ?: "(unknown)"); + ds->latitude = latitude; + } +} + +static void gps_long(char *buffer, struct dive *dive) +{ + char *end; + degrees_t longitude = parse_degrees(buffer, &end); + struct dive_site *ds = get_dive_site_for_dive(dive); + if (!ds) { + dive->dive_site_uuid = create_dive_site_with_gps(NULL, (degrees_t){0}, longitude, dive->when); + } else { + if (ds->longitude.udeg && ds->longitude.udeg != longitude.udeg) + fprintf(stderr, "Oops, changing the longitude of existing dive site id %8x name %s; not good\n", ds->uuid, ds->name ?: "(unknown)"); + ds->longitude = longitude; + } + +} + +static void gps_location(char *buffer, struct dive_site *ds) +{ + char *end; + + ds->latitude = parse_degrees(buffer, &end); + ds->longitude = parse_degrees(end, &end); +} + +/* this is in qthelper.cpp, so including the .h file is a pain */ +extern const char *printGPSCoords(int lat, int lon); + +static void gps_in_dive(char *buffer, struct dive *dive) +{ + char *end; + struct dive_site *ds = NULL; + degrees_t latitude = parse_degrees(buffer, &end); + degrees_t longitude = parse_degrees(end, &end); + uint32_t uuid = dive->dive_site_uuid; + if (uuid == 0) { + // check if we have a dive site within 20 meters of that gps fix + uuid = get_dive_site_uuid_by_gps_proximity(latitude, longitude, 20, &ds); + + if (ds) { + // found a site nearby; in case it turns out this one had a different name let's + // remember the original coordinates so we can create the correct dive site later + cur_latitude = latitude; + cur_longitude = longitude; + dive->dive_site_uuid = uuid; + } else { + dive->dive_site_uuid = create_dive_site_with_gps("", latitude, longitude, dive->when); + ds = get_dive_site_by_uuid(dive->dive_site_uuid); + } + } else { + ds = get_dive_site_by_uuid(uuid); + if (dive_site_has_gps_location(ds) && + (latitude.udeg != 0 || longitude.udeg != 0) && + (ds->latitude.udeg != latitude.udeg || ds->longitude.udeg != longitude.udeg)) { + // Houston, we have a problem + fprintf(stderr, "dive site uuid in dive, but gps location (%10.6f/%10.6f) different from dive location (%10.6f/%10.6f)\n", + ds->latitude.udeg / 1000000.0, ds->longitude.udeg / 1000000.0, + latitude.udeg / 1000000.0, longitude.udeg / 1000000.0); + const char *coords = printGPSCoords(latitude.udeg, longitude.udeg); + ds->notes = add_to_string(ds->notes, translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords); + free((void *)coords); + } else { + ds->latitude = latitude; + ds->longitude = longitude; + } + } +} + +static void add_dive_site(char *ds_name, struct dive *dive) +{ + static int suffix = 1; + char *buffer = ds_name; + char *to_free = NULL; + int size = trimspace(buffer); + if(size) { + uint32_t uuid = dive->dive_site_uuid; + struct dive_site *ds = get_dive_site_by_uuid(uuid); + if (uuid && !ds) { + // that's strange - we have a uuid but it doesn't exist - let's just ignore it + fprintf(stderr, "dive contains a non-existing dive site uuid %x\n", dive->dive_site_uuid); + uuid = 0; + } + if (!uuid) { + // if the dive doesn't have a uuid, check if there's already a dive site by this name + uuid = get_dive_site_uuid_by_name(buffer, &ds); + if (uuid && import_source == SSRF_WS) { + // when downloading GPS fixes from the Subsurface webservice we will often + // get a lot of dives with identical names (the autogenerated fixes). + // So in this case modify the name to make it unique + int name_size = strlen(buffer) + 10; // 8 digits - enough for 100 million sites + to_free = buffer = malloc(name_size); + do { + suffix++; + snprintf(buffer, name_size, "%s %8d", ds_name, suffix); + } while (get_dive_site_uuid_by_name(buffer, NULL) != 0); + ds = NULL; + } + } + if (ds) { + // we have a uuid, let's hope there isn't a different name + if (same_string(ds->name, "")) { + ds->name = copy_string(buffer); + } else if (!same_string(ds->name, buffer)) { + // if it's not the same name, it's not the same dive site + // but wait, we could have gotten this one based on GPS coords and could + // have had two different names for the same site... so let's search the other + // way around + uint32_t exact_match_uuid = get_dive_site_uuid_by_gps_and_name(buffer, ds->latitude, ds->longitude); + if (exact_match_uuid) { + dive->dive_site_uuid = exact_match_uuid; + } else { + dive->dive_site_uuid = create_dive_site(buffer, dive->when); + struct dive_site *newds = get_dive_site_by_uuid(dive->dive_site_uuid); + if (cur_latitude.udeg || cur_longitude.udeg) { + // we started this uuid with GPS data, so lets use those + newds->latitude = cur_latitude; + newds->longitude = cur_longitude; + } else { + newds->latitude = ds->latitude; + newds->longitude = ds->longitude; + } + newds->notes = add_to_string(newds->notes, translate("gettextFromC", "additional name for site: %s\n"), ds->name); + } + } else { + // add the existing dive site to the current dive + dive->dive_site_uuid = uuid; + } + } else { + dive->dive_site_uuid = create_dive_site(buffer, dive->when); + } + } + free(to_free); +} + +static void gps_picture_location(char *buffer, struct picture *pic) +{ + char *end; + + pic->latitude = parse_degrees(buffer, &end); + pic->longitude = parse_degrees(end, &end); +} + +/* We're in the top-level dive xml. Try to convert whatever value to a dive value */ +static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) +{ + start_match("dive", name, buf); + + switch (import_source) { + case DIVINGLOG: + if (divinglog_dive_match(dive, name, buf)) + return; + break; + + case UDDF: + if (uddf_dive_match(dive, name, buf)) + return; + break; + + default: + break; + } + if (MATCH("divesiteid", hex_value, &dive->dive_site_uuid)) + return; + if (MATCH("number", get_index, &dive->number)) + return; + if (MATCH("tags", divetags, &dive->tag_list)) + return; + if (MATCH("tripflag", get_tripflag, &dive->tripflag)) + return; + if (MATCH("date", divedate, &dive->when)) + return; + if (MATCH("time", divetime, &dive->when)) + return; + if (MATCH("datetime", divedatetime, &dive->when)) + return; + /* + * Legacy format note: per-dive depths and duration get saved + * in the first dive computer entry + */ + if (match_dc_data_fields(&dive->dc, name, buf)) + return; + + if (MATCH("filename.picture", utf8_string, &cur_picture->filename)) + return; + if (MATCH("offset.picture", offsettime, &cur_picture->offset)) + return; + if (MATCH("gps.picture", gps_picture_location, cur_picture)) + return; + if (MATCH("hash.picture", utf8_string, &cur_picture->hash)) + return; + if (MATCH("cylinderstartpressure", pressure, &dive->cylinder[0].start)) + return; + if (MATCH("cylinderendpressure", pressure, &dive->cylinder[0].end)) + return; + if (MATCH("gps", gps_in_dive, dive)) + return; + if (MATCH("Place", gps_in_dive, dive)) + return; + if (MATCH("latitude", gps_lat, dive)) + return; + if (MATCH("sitelat", gps_lat, dive)) + return; + if (MATCH("lat", gps_lat, dive)) + return; + if (MATCH("longitude", gps_long, dive)) + return; + if (MATCH("sitelon", gps_long, dive)) + return; + if (MATCH("lon", gps_long, dive)) + return; + if (MATCH("location", add_dive_site, dive)) + return; + if (MATCH("name.dive", add_dive_site, dive)) + return; + if (MATCH("suit", utf8_string, &dive->suit)) + return; + if (MATCH("divesuit", utf8_string, &dive->suit)) + return; + if (MATCH("notes", utf8_string, &dive->notes)) + return; + if (MATCH("divemaster", utf8_string, &dive->divemaster)) + return; + if (MATCH("buddy", utf8_string, &dive->buddy)) + return; + if (MATCH("rating.dive", get_rating, &dive->rating)) + return; + if (MATCH("visibility.dive", get_rating, &dive->visibility)) + return; + if (cur_ws_index < MAX_WEIGHTSYSTEMS) { + if (MATCH("description.weightsystem", utf8_string, &dive->weightsystem[cur_ws_index].description)) + return; + if (MATCH("weight.weightsystem", weight, &dive->weightsystem[cur_ws_index].weight)) + return; + if (MATCH("weight", weight, &dive->weightsystem[cur_ws_index].weight)) + return; + } + if (cur_cylinder_index < MAX_CYLINDERS) { + if (MATCH("size.cylinder", cylindersize, &dive->cylinder[cur_cylinder_index].type.size)) + return; + if (MATCH("workpressure.cylinder", pressure, &dive->cylinder[cur_cylinder_index].type.workingpressure)) + return; + if (MATCH("description.cylinder", utf8_string, &dive->cylinder[cur_cylinder_index].type.description)) + return; + if (MATCH("start.cylinder", pressure, &dive->cylinder[cur_cylinder_index].start)) + return; + if (MATCH("end.cylinder", pressure, &dive->cylinder[cur_cylinder_index].end)) + return; + if (MATCH("use.cylinder", cylinder_use, &dive->cylinder[cur_cylinder_index].cylinder_use)) + return; + if (MATCH("o2", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.o2)) + return; + if (MATCH("o2percent", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.o2)) + return; + if (MATCH("n2", gasmix_nitrogen, &dive->cylinder[cur_cylinder_index].gasmix)) + return; + if (MATCH("he", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.he)) + return; + } + if (MATCH("air.divetemperature", temperature, &dive->airtemp)) + return; + if (MATCH("water.divetemperature", temperature, &dive->watertemp)) + return; + + nonmatch("dive", name, buf); +} + +/* We're in the top-level trip xml. Try to convert whatever value to a trip value */ +static void try_to_fill_trip(dive_trip_t **dive_trip_p, const char *name, char *buf) +{ + start_match("trip", name, buf); + + dive_trip_t *dive_trip = *dive_trip_p; + + if (MATCH("date", divedate, &dive_trip->when)) + return; + if (MATCH("time", divetime, &dive_trip->when)) + return; + if (MATCH("location", utf8_string, &dive_trip->location)) + return; + if (MATCH("notes", utf8_string, &dive_trip->notes)) + return; + + nonmatch("trip", name, buf); +} + +/* We're processing a divesite entry - try to fill the components */ +static void try_to_fill_dive_site(struct dive_site **ds_p, const char *name, char *buf) +{ + start_match("divesite", name, buf); + + struct dive_site *ds = *ds_p; + if (ds->taxonomy.category == NULL) + ds->taxonomy.category = alloc_taxonomy(); + + if (MATCH("uuid", hex_value, &ds->uuid)) + return; + if (MATCH("name", utf8_string, &ds->name)) + return; + if (MATCH("description", utf8_string, &ds->description)) + return; + if (MATCH("notes", utf8_string, &ds->notes)) + return; + if (MATCH("gps", gps_location, ds)) + return; + if (MATCH("cat.geo", get_index, (int *)&ds->taxonomy.category[ds->taxonomy.nr].category)) + return; + if (MATCH("origin.geo", get_index, (int *)&ds->taxonomy.category[ds->taxonomy.nr].origin)) + return; + if (MATCH("value.geo", utf8_string, &ds->taxonomy.category[ds->taxonomy.nr].value)) { + if (ds->taxonomy.nr < TC_NR_CATEGORIES) + ds->taxonomy.nr++; + return; + } + + nonmatch("divesite", name, buf); +} + +/* + * While in some formats file boundaries are dive boundaries, in many + * others (as for example in our native format) there are + * multiple dives per file, so there can be other events too that + * trigger a "new dive" marker and you may get some nesting due + * to that. Just ignore nesting levels. + * On the flipside it is possible that we start an XML file that ends + * up having no dives in it at all - don't create a bogus empty dive + * for those. It's not entirely clear what is the minimum set of data + * to make a dive valid, but if it has no location, no date and no + * samples I'm pretty sure it's useless. + */ +static bool is_dive(void) +{ + return (cur_dive && + (cur_dive->dive_site_uuid || cur_dive->when || cur_dive->dc.samples)); +} + +static void reset_dc_info(struct divecomputer *dc) +{ + /* WARN: reset dc info does't touch the dc? */ + (void) dc; + lastcns = lastpo2 = lastndl = laststoptime = laststopdepth = lastindeco = 0; + lastsensor = lastcylinderindex = 0; +} + +static void reset_dc_settings(void) +{ + free((void *)cur_settings.dc.model); + free((void *)cur_settings.dc.nickname); + free((void *)cur_settings.dc.serial_nr); + free((void *)cur_settings.dc.firmware); + cur_settings.dc.model = NULL; + cur_settings.dc.nickname = NULL; + cur_settings.dc.serial_nr = NULL; + cur_settings.dc.firmware = NULL; + cur_settings.dc.deviceid = 0; +} + +static void settings_start(void) +{ + in_settings = true; +} + +static void settings_end(void) +{ + in_settings = false; +} + +static void dc_settings_start(void) +{ + reset_dc_settings(); +} + +static void dc_settings_end(void) +{ + create_device_node(cur_settings.dc.model, cur_settings.dc.deviceid, cur_settings.dc.serial_nr, + cur_settings.dc.firmware, cur_settings.dc.nickname); + reset_dc_settings(); +} + +static void dive_site_start(void) +{ + if (cur_dive_site) + return; + cur_dive_site = calloc(1, sizeof(struct dive_site)); +} + +static void dive_site_end(void) +{ + if (!cur_dive_site) + return; + if (cur_dive_site->uuid) { + // we intentionally call this with '0' to ensure we get + // a new structure and then copy things into that new + // structure a few lines below (which sets the correct + // uuid) + struct dive_site *ds = alloc_or_get_dive_site(0); + if (cur_dive_site->taxonomy.nr == 0) { + free(cur_dive_site->taxonomy.category); + cur_dive_site->taxonomy.category = NULL; + } + copy_dive_site(cur_dive_site, ds); + + if (verbose > 3) + printf("completed dive site uuid %x8 name {%s}\n", ds->uuid, ds->name); + } + free_taxonomy(&cur_dive_site->taxonomy); + free(cur_dive_site); + cur_dive_site = NULL; +} + +// now we need to add the code to parse the parts of the divesite enry + +static void dive_start(void) +{ + if (cur_dive) + return; + cur_dive = alloc_dive(); + reset_dc_info(&cur_dive->dc); + memset(&cur_tm, 0, sizeof(cur_tm)); + if (cur_trip) { + add_dive_to_trip(cur_dive, cur_trip); + cur_dive->tripflag = IN_TRIP; + } +} + +static void dive_end(void) +{ + if (!cur_dive) + return; + if (!is_dive()) + free(cur_dive); + else + record_dive_to_table(cur_dive, target_table); + cur_dive = NULL; + cur_dc = NULL; + cur_latitude.udeg = 0; + cur_longitude.udeg = 0; + cur_cylinder_index = 0; + cur_ws_index = 0; +} + +static void trip_start(void) +{ + if (cur_trip) + return; + dive_end(); + cur_trip = calloc(1, sizeof(dive_trip_t)); + memset(&cur_tm, 0, sizeof(cur_tm)); +} + +static void trip_end(void) +{ + if (!cur_trip) + return; + insert_trip(&cur_trip); + cur_trip = NULL; +} + +static void event_start(void) +{ + memset(&cur_event, 0, sizeof(cur_event)); + cur_event.deleted = 0; /* Active */ +} + +static void event_end(void) +{ + struct divecomputer *dc = get_dc(); + if (strcmp(cur_event.name, "surface") != 0) { /* 123 is a magic event that we used for a while to encode images in dives */ + if (cur_event.type == 123) { + struct picture *pic = alloc_picture(); + pic->filename = strdup(cur_event.name); + /* theoretically this could fail - but we didn't support multi year offsets */ + pic->offset.seconds = cur_event.time.seconds; + dive_add_picture(cur_dive, pic); + } else { + struct event *ev; + /* At some point gas change events did not have any type. Thus we need to add + * one on import, if we encounter the type one missing. + */ + if (cur_event.type == 0 && strcmp(cur_event.name, "gaschange") == 0) + cur_event.type = cur_event.value >> 16 > 0 ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE; + ev = add_event(dc, cur_event.time.seconds, + cur_event.type, cur_event.flags, + cur_event.value, cur_event.name); + + /* + * Older logs might mark the dive to be CCR by having an "SP change" event at time 0:00. Better + * to mark them being CCR on import so no need for special treatments elsewhere on the code. + */ + if (ev && cur_event.time.seconds == 0 && cur_event.type == SAMPLE_EVENT_PO2 && dc->divemode==OC) { + dc->divemode = CCR; + } + + if (ev && event_is_gaschange(ev)) { + /* See try_to_fill_event() on why the filled-in index is one too big */ + ev->gas.index = cur_event.gas.index-1; + if (cur_event.gas.mix.o2.permille || cur_event.gas.mix.he.permille) + ev->gas.mix = cur_event.gas.mix; + } + } + } + cur_event.deleted = 1; /* No longer active */ +} + +static void picture_start(void) +{ + cur_picture = alloc_picture(); +} + +static void picture_end(void) +{ + dive_add_picture(cur_dive, cur_picture); + cur_picture = NULL; +} + +static void cylinder_start(void) +{ +} + +static void cylinder_end(void) +{ + cur_cylinder_index++; +} + +static void ws_start(void) +{ +} + +static void ws_end(void) +{ + cur_ws_index++; +} + +static void sample_start(void) +{ + cur_sample = prepare_sample(get_dc()); + cur_sample->ndl.seconds = lastndl; + cur_sample->in_deco = lastindeco; + cur_sample->stoptime.seconds = laststoptime; + cur_sample->stopdepth.mm = laststopdepth; + cur_sample->cns = lastcns; + cur_sample->setpoint.mbar = lastpo2; + cur_sample->sensor = lastsensor; + next_o2_sensor = 0; +} + +static void sample_end(void) +{ + if (!cur_dive) + return; + + finish_sample(get_dc()); + lastndl = cur_sample->ndl.seconds; + lastindeco = cur_sample->in_deco; + laststoptime = cur_sample->stoptime.seconds; + laststopdepth = cur_sample->stopdepth.mm; + lastcns = cur_sample->cns; + lastpo2 = cur_sample->setpoint.mbar; + cur_sample = NULL; +} + +static void divecomputer_start(void) +{ + struct divecomputer *dc; + + /* Start from the previous dive computer */ + dc = &cur_dive->dc; + while (dc->next) + dc = dc->next; + + /* Did we already fill that in? */ + if (dc->samples || dc->model || dc->when) { + struct divecomputer *newdc = calloc(1, sizeof(*newdc)); + if (newdc) { + dc->next = newdc; + dc = newdc; + } + } + + /* .. this is the one we'll use */ + cur_dc = dc; + reset_dc_info(dc); +} + +static void divecomputer_end(void) +{ + if (!cur_dc->when) + cur_dc->when = cur_dive->when; + cur_dc = NULL; +} + +static void userid_start(void) +{ + in_userid = true; + set_save_userid_local(true); //if the xml contains userid, keep saving it. +} + +static void userid_stop(void) +{ + in_userid = false; +} + +static bool entry(const char *name, char *buf) +{ + if (!strncmp(name, "version.program", sizeof("version.program") - 1) || + !strncmp(name, "version.divelog", sizeof("version.divelog") - 1)) { + last_xml_version = atoi(buf); + report_datafile_version(last_xml_version); + } + if (in_userid) { + try_to_fill_userid(name, buf); + return true; + } + if (in_settings) { + try_to_fill_dc_settings(name, buf); + try_to_match_autogroup(name, buf); + return true; + } + if (cur_dive_site) { + try_to_fill_dive_site(&cur_dive_site, name, buf); + return true; + } + if (!cur_event.deleted) { + try_to_fill_event(name, buf); + return true; + } + if (cur_sample) { + try_to_fill_sample(cur_sample, name, buf); + return true; + } + if (cur_dc) { + try_to_fill_dc(cur_dc, name, buf); + return true; + } + if (cur_dive) { + try_to_fill_dive(cur_dive, name, buf); + return true; + } + if (cur_trip) { + try_to_fill_trip(&cur_trip, name, buf); + return true; + } + return true; +} + +static const char *nodename(xmlNode *node, char *buf, int len) +{ + int levels = 2; + char *p = buf; + + if (!node || (node->type != XML_CDATA_SECTION_NODE && !node->name)) { + return "root"; + } + + if (node->type == XML_CDATA_SECTION_NODE || (node->parent && !strcmp((const char *)node->name, "text"))) + node = node->parent; + + /* Make sure it's always NUL-terminated */ + p[--len] = 0; + + for (;;) { + const char *name = (const char *)node->name; + char c; + while ((c = *name++) != 0) { + /* Cheaper 'tolower()' for ASCII */ + c = (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c; + *p++ = c; + if (!--len) + return buf; + } + *p = 0; + node = node->parent; + if (!node || !node->name) + return buf; + *p++ = '.'; + if (!--len) + return buf; + if (!--levels) + return buf; + } +} + +#define MAXNAME 32 + +static bool visit_one_node(xmlNode *node) +{ + xmlChar *content; + static char buffer[MAXNAME]; + const char *name; + + content = node->content; + if (!content || xmlIsBlankNode(node)) + return true; + + name = nodename(node, buffer, sizeof(buffer)); + + return entry(name, (char *)content); +} + +static bool traverse(xmlNode *root); + +static bool traverse_properties(xmlNode *node) +{ + xmlAttr *p; + bool ret = true; + + for (p = node->properties; p; p = p->next) + if ((ret = traverse(p->children)) == false) + break; + return ret; +} + +static bool visit(xmlNode *n) +{ + return visit_one_node(n) && traverse_properties(n) && traverse(n->children); +} + +static void DivingLog_importer(void) +{ + import_source = DIVINGLOG; + + /* + * Diving Log units are really strange. + * + * Temperatures are in C, except in samples, + * when they are in Fahrenheit. Depths are in + * meters, an dpressure is in PSI in the samples, + * but in bar when it comes to working pressure. + * + * Crazy f*%^ morons. + */ + xml_parsing_units = SI_units; +} + +static void uddf_importer(void) +{ + import_source = UDDF; + xml_parsing_units = SI_units; + xml_parsing_units.pressure = PASCAL; + xml_parsing_units.temperature = KELVIN; +} + +static void subsurface_webservice(void) +{ + import_source = SSRF_WS; +} + +/* + * I'm sure this could be done as some fancy DTD rules. + * It's just not worth the headache. + */ +static struct nesting { + const char *name; + void (*start)(void), (*end)(void); +} nesting[] = { + { "divecomputerid", dc_settings_start, dc_settings_end }, + { "settings", settings_start, settings_end }, + { "site", dive_site_start, dive_site_end }, + { "dive", dive_start, dive_end }, + { "Dive", dive_start, dive_end }, + { "trip", trip_start, trip_end }, + { "sample", sample_start, sample_end }, + { "waypoint", sample_start, sample_end }, + { "SAMPLE", sample_start, sample_end }, + { "reading", sample_start, sample_end }, + { "event", event_start, event_end }, + { "mix", cylinder_start, cylinder_end }, + { "gasmix", cylinder_start, cylinder_end }, + { "cylinder", cylinder_start, cylinder_end }, + { "weightsystem", ws_start, ws_end }, + { "divecomputer", divecomputer_start, divecomputer_end }, + { "P", sample_start, sample_end }, + { "userid", userid_start, userid_stop}, + { "picture", picture_start, picture_end }, + { "extradata", extra_data_start, extra_data_end }, + + /* Import type recognition */ + { "Divinglog", DivingLog_importer }, + { "uddf", uddf_importer }, + { "output", subsurface_webservice }, + { NULL, } + }; + +static bool traverse(xmlNode *root) +{ + xmlNode *n; + bool ret = true; + + for (n = root; n; n = n->next) { + struct nesting *rule = nesting; + + if (!n->name) { + if ((ret = visit(n)) == false) + break; + continue; + } + + do { + if (!strcmp(rule->name, (const char *)n->name)) + break; + rule++; + } while (rule->name); + + if (rule->start) + rule->start(); + if ((ret = visit(n)) == false) + break; + if (rule->end) + rule->end(); + } + return ret; +} + +/* Per-file reset */ +static void reset_all(void) +{ + /* + * We reset the units for each file. You'd think it was + * a per-dive property, but I'm not going to trust people + * to do per-dive setup. If the xml does have per-dive + * data within one file, we might have to reset it per + * dive for that format. + */ + xml_parsing_units = SI_units; + import_source = UNKNOWN; +} + +/* divelog.de sends us xml files that claim to be iso-8859-1 + * but once we decode the HTML encoded characters they turn + * into UTF-8 instead. So skip the incorrect encoding + * declaration and decode the HTML encoded characters */ +const char *preprocess_divelog_de(const char *buffer) +{ + char *ret = strstr(buffer, ""); + + if (ret) { + xmlParserCtxtPtr ctx; + char buf[] = ""; + size_t i; + + for (i = 0; i < strlen(ret); ++i) + if (!isascii(ret[i])) + return buffer; + + ctx = xmlCreateMemoryParserCtxt(buf, sizeof(buf)); + ret = (char *)xmlStringLenDecodeEntities(ctx, (xmlChar *)ret, strlen(ret), XML_SUBSTITUTE_REF, 0, 0, 0); + + return ret; + } + return buffer; +} + +int parse_xml_buffer(const char *url, const char *buffer, int size, + struct dive_table *table, const char **params) +{ + (void) size; + xmlDoc *doc; + const char *res = preprocess_divelog_de(buffer); + int ret = 0; + + target_table = table; + doc = xmlReadMemory(res, strlen(res), url, NULL, 0); + if (res != buffer) + free((char *)res); + + if (!doc) + return report_error(translate("gettextFromC", "Failed to parse '%s'"), url); + + set_save_userid_local(false); + reset_all(); + dive_start(); + doc = test_xslt_transforms(doc, params); + if (!traverse(xmlDocGetRootElement(doc))) { + // we decided to give up on parsing... why? + ret = -1; + } + dive_end(); + xmlFreeDoc(doc); + return ret; +} + +void parse_mkvi_buffer(struct membuffer *txt, struct membuffer *csv, const char *starttime) +{ + (void) csv; + (void) txt; + dive_start(); + divedate(starttime, &cur_dive->when); + dive_end(); +} + +extern int dm4_events(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + event_start(); + if (data[1]) + cur_event.time.seconds = atoi(data[1]); + + if (data[2]) { + switch (atoi(data[2])) { + case 1: + /* 1 Mandatory Safety Stop */ + strcpy(cur_event.name, "safety stop (mandatory)"); + break; + case 3: + /* 3 Deco */ + /* What is Subsurface's term for going to + * deco? */ + strcpy(cur_event.name, "deco"); + break; + case 4: + /* 4 Ascent warning */ + strcpy(cur_event.name, "ascent"); + break; + case 5: + /* 5 Ceiling broken */ + strcpy(cur_event.name, "violation"); + break; + case 6: + /* 6 Mandatory safety stop ceiling error */ + strcpy(cur_event.name, "violation"); + break; + case 7: + /* 7 Below deco floor */ + strcpy(cur_event.name, "below floor"); + break; + case 8: + /* 8 Dive time alarm */ + strcpy(cur_event.name, "divetime"); + break; + case 9: + /* 9 Depth alarm */ + strcpy(cur_event.name, "maxdepth"); + break; + case 10: + /* 10 OLF 80% */ + case 11: + /* 11 OLF 100% */ + strcpy(cur_event.name, "OLF"); + break; + case 12: + /* 12 High pO₂ */ + strcpy(cur_event.name, "PO2"); + break; + case 13: + /* 13 Air time */ + strcpy(cur_event.name, "airtime"); + break; + case 17: + /* 17 Ascent warning */ + strcpy(cur_event.name, "ascent"); + break; + case 18: + /* 18 Ceiling error */ + strcpy(cur_event.name, "ceiling"); + break; + case 19: + /* 19 Surfaced */ + strcpy(cur_event.name, "surface"); + break; + case 20: + /* 20 Deco */ + strcpy(cur_event.name, "deco"); + break; + case 22: + case 32: + /* 22 Mandatory safety stop violation */ + /* 32 Deep stop violation */ + strcpy(cur_event.name, "violation"); + break; + case 30: + /* Tissue level warning */ + strcpy(cur_event.name, "tissue warning"); + break; + case 37: + /* Tank pressure alarm */ + strcpy(cur_event.name, "tank pressure"); + break; + case 257: + /* 257 Dive active */ + /* This seems to be given after surface when + * descending again. */ + strcpy(cur_event.name, "surface"); + break; + case 258: + /* 258 Bookmark */ + if (data[3]) { + strcpy(cur_event.name, "heading"); + cur_event.value = atoi(data[3]); + } else { + strcpy(cur_event.name, "bookmark"); + } + break; + case 259: + /* Deep stop */ + strcpy(cur_event.name, "Deep stop"); + break; + case 260: + /* Deep stop */ + strcpy(cur_event.name, "Deep stop cleared"); + break; + case 266: + /* Mandatory safety stop activated */ + strcpy(cur_event.name, "safety stop (mandatory)"); + break; + case 267: + /* Mandatory safety stop deactivated */ + /* DM5 shows this only on event list, not on the + * profile so skipping as well for now */ + break; + default: + strcpy(cur_event.name, "unknown"); + cur_event.value = atoi(data[2]); + break; + } + } + event_end(); + + return 0; +} + +extern int dm5_cylinders(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + cylinder_start(); + if (data[7] && atoi(data[7]) > 0 && atoi(data[7]) < 350000) + cur_dive->cylinder[cur_cylinder_index].start.mbar = atoi(data[7]); + if (data[8] && atoi(data[8]) > 0 && atoi(data[8]) < 350000) + cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[8])); + if (data[6]) { + /* DM5 shows tank size of 12 liters when the actual + * value is 0 (and using metric units). So we just use + * the same 12 liters when size is not available */ + if (atof(data[6]) == 0.0 && cur_dive->cylinder[cur_cylinder_index].start.mbar) + cur_dive->cylinder[cur_cylinder_index].type.size.mliter = 12000; + else + cur_dive->cylinder[cur_cylinder_index].type.size.mliter = (atof(data[6])) * 1000; + } + if (data[2]) + cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[2]) * 10; + if (data[3]) + cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[3]) * 10; + cylinder_end(); + return 0; +} + +extern int dm5_gaschange(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + event_start(); + if (data[0]) + cur_event.time.seconds = atoi(data[0]); + if (data[1]) { + strcpy(cur_event.name, "gaschange"); + cur_event.value = atof(data[1]); + } + event_end(); + + return 0; +} + +extern int dm4_tags(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + if (data[0]) + taglist_add_tag(&cur_dive->tag_list, data[0]); + + return 0; +} + +extern int dm4_dive(void *param, int columns, char **data, char **column) +{ + (void) columns; + (void) column; + unsigned int i; + int interval, retval = 0; + sqlite3 *handle = (sqlite3 *)param; + float *profileBlob; + unsigned char *tempBlob; + int *pressureBlob; + char *err = NULL; + char get_events_template[] = "select * from Mark where DiveId = %d"; + char get_tags_template[] = "select Text from DiveTag where DiveId = %d"; + char get_events[64]; + + dive_start(); + cur_dive->number = atoi(data[0]); + + cur_dive->when = (time_t)(atol(data[1])); + if (data[2]) + utf8_string(data[2], &cur_dive->notes); + + /* + * DM4 stores Duration and DiveTime. It looks like DiveTime is + * 10 to 60 seconds shorter than Duration. However, I have no + * idea what is the difference and which one should be used. + * Duration = data[3] + * DiveTime = data[15] + */ + if (data[3]) + cur_dive->duration.seconds = atoi(data[3]); + if (data[15]) + cur_dive->dc.duration.seconds = atoi(data[15]); + + /* + * TODO: the deviceid hash should be calculated here. + */ + settings_start(); + dc_settings_start(); + if (data[4]) + utf8_string(data[4], &cur_settings.dc.serial_nr); + if (data[5]) + utf8_string(data[5], &cur_settings.dc.model); + + cur_settings.dc.deviceid = 0xffffffff; + dc_settings_end(); + settings_end(); + + if (data[6]) + cur_dive->dc.maxdepth.mm = atof(data[6]) * 1000; + if (data[8]) + cur_dive->dc.airtemp.mkelvin = C_to_mkelvin(atoi(data[8])); + if (data[9]) + cur_dive->dc.watertemp.mkelvin = C_to_mkelvin(atoi(data[9])); + + /* + * TODO: handle multiple cylinders + */ + cylinder_start(); + if (data[22] && atoi(data[22]) > 0) + cur_dive->cylinder[cur_cylinder_index].start.mbar = atoi(data[22]); + else if (data[10] && atoi(data[10]) > 0) + cur_dive->cylinder[cur_cylinder_index].start.mbar = atoi(data[10]); + if (data[23] && atoi(data[23]) > 0) + cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[23])); + if (data[11] && atoi(data[11]) > 0) + cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[11])); + if (data[12]) + cur_dive->cylinder[cur_cylinder_index].type.size.mliter = (atof(data[12])) * 1000; + if (data[13]) + cur_dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = (atoi(data[13])); + if (data[20]) + cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[20]) * 10; + if (data[21]) + cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[21]) * 10; + cylinder_end(); + + if (data[14]) + cur_dive->dc.surface_pressure.mbar = (atoi(data[14]) * 1000); + + interval = data[16] ? atoi(data[16]) : 0; + profileBlob = (float *)data[17]; + tempBlob = (unsigned char *)data[18]; + pressureBlob = (int *)data[19]; + for (i = 0; interval && i * interval < cur_dive->duration.seconds; i++) { + sample_start(); + cur_sample->time.seconds = i * interval; + if (profileBlob) + cur_sample->depth.mm = profileBlob[i] * 1000; + else + cur_sample->depth.mm = cur_dive->dc.maxdepth.mm; + + if (data[18] && data[18][0]) + cur_sample->temperature.mkelvin = C_to_mkelvin(tempBlob[i]); + if (data[19] && data[19][0]) + cur_sample->cylinderpressure.mbar = pressureBlob[i]; + sample_end(); + } + + snprintf(get_events, sizeof(get_events) - 1, get_events_template, cur_dive->number); + retval = sqlite3_exec(handle, get_events, &dm4_events, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query dm4_events failed.\n"); + return 1; + } + + snprintf(get_events, sizeof(get_events) - 1, get_tags_template, cur_dive->number); + retval = sqlite3_exec(handle, get_events, &dm4_tags, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query dm4_tags failed.\n"); + return 1; + } + + dive_end(); + + /* + for (i=0; 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) +{ + (void) buffer; + (void) size; + + int retval; + char *err = NULL; + target_table = table; + + /* StartTime is converted from Suunto's nano seconds to standard + * time. We also need epoch, not seconds since year 1. */ + char get_dives[] = "select D.DiveId,StartTime/10000000-62135596800,Note,Duration,SourceSerialNumber,Source,MaxDepth,SampleInterval,StartTemperature,BottomTemperature,D.StartPressure,D.EndPressure,Size,CylinderWorkPressure,SurfacePressure,DiveTime,SampleInterval,ProfileBlob,TemperatureBlob,PressureBlob,Oxygen,Helium,MIX.StartPressure,MIX.EndPressure FROM Dive AS D JOIN DiveMixture AS MIX ON D.DiveId=MIX.DiveId"; + + retval = sqlite3_exec(handle, get_dives, &dm4_dive, handle, &err); + + if (retval != SQLITE_OK) { + fprintf(stderr, "Database query failed '%s'.\n", url); + return 1; + } + + return 0; +} + +int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, + struct dive_table *table) +{ + (void) buffer; + (void) size; + + int retval; + char *err = NULL; + target_table = table; + + /* StartTime is converted from Suunto's nano seconds to standard + * time. We also need epoch, not seconds since year 1. */ + char get_dives[] = "select DiveId,StartTime/10000000-62135596800,Note,Duration,coalesce(SourceSerialNumber,SerialNumber),Source,MaxDepth,SampleInterval,StartTemperature,BottomTemperature,StartPressure,EndPressure,'','',SurfacePressure,DiveTime,SampleInterval,ProfileBlob,TemperatureBlob,PressureBlob,'','','','',SampleBlob FROM Dive where Deleted is null"; + + retval = sqlite3_exec(handle, get_dives, &dm5_dive, handle, &err); + + if (retval != SQLITE_OK) { + fprintf(stderr, "Database query failed '%s'.\n", url); + return 1; + } + + return 0; +} + +extern int shearwater_cylinders(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + cylinder_start(); + if (data[0]) + cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atof(data[0]) * 1000; + if (data[1]) + cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atof(data[1]) * 1000; + cylinder_end(); + + return 0; +} + +extern int shearwater_changes(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + event_start(); + if (data[0]) + cur_event.time.seconds = atoi(data[0]); + if (data[1]) { + strcpy(cur_event.name, "gaschange"); + cur_event.value = atof(data[1]) * 100; + } + event_end(); + + return 0; +} + + +extern int cobalt_profile_sample(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + sample_start(); + if (data[0]) + cur_sample->time.seconds = atoi(data[0]); + if (data[1]) + cur_sample->depth.mm = atoi(data[1]); + if (data[2]) + cur_sample->temperature.mkelvin = metric ? C_to_mkelvin(atof(data[2])) : F_to_mkelvin(atof(data[2])); + sample_end(); + + return 0; +} + + +extern int shearwater_profile_sample(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + sample_start(); + if (data[0]) + cur_sample->time.seconds = atoi(data[0]); + if (data[1]) + cur_sample->depth.mm = metric ? atof(data[1]) * 1000 : feet_to_mm(atof(data[1])); + if (data[2]) + cur_sample->temperature.mkelvin = metric ? C_to_mkelvin(atof(data[2])) : F_to_mkelvin(atof(data[2])); + if (data[3]) { + cur_sample->setpoint.mbar = atof(data[3]) * 1000; + cur_dive->dc.divemode = CCR; + } + if (data[4]) + cur_sample->ndl.seconds = atoi(data[4]) * 60; + if (data[5]) + cur_sample->cns = atoi(data[5]); + if (data[6]) + cur_sample->stopdepth.mm = metric ? atoi(data[6]) * 1000 : feet_to_mm(atoi(data[6])); + + /* We don't actually have data[3], but it should appear in the + * SQL query at some point. + if (data[3]) + cur_sample->cylinderpressure.mbar = metric ? atoi(data[3]) * 1000 : psi_to_mbar(atoi(data[3])); + */ + sample_end(); + + return 0; +} + +extern int shearwater_dive(void *param, int columns, char **data, char **column) +{ + (void) columns; + (void) column; + + int retval = 0; + sqlite3 *handle = (sqlite3 *)param; + char *err = NULL; + char get_profile_template[] = "select currentTime,currentDepth,waterTemp,averagePPO2,currentNdl,CNSPercent,decoCeiling from dive_log_records where diveLogId = %d"; + char get_cylinder_template[] = "select fractionO2,fractionHe from dive_log_records where diveLogId = %d group by fractionO2,fractionHe"; + char get_changes_template[] = "select a.currentTime,a.fractionO2,a.fractionHe from dive_log_records as a,dive_log_records as b where a.diveLogId = %d and b.diveLogId = %d and (a.id - 1) = b.id and (a.fractionO2 != b.fractionO2 or a.fractionHe != b.fractionHe) union select min(currentTime),fractionO2,fractionHe from dive_log_records"; + char get_buffer[1024]; + + dive_start(); + cur_dive->number = atoi(data[0]); + + cur_dive->when = (time_t)(atol(data[1])); + + if (data[2]) + add_dive_site(data[2], cur_dive); + if (data[3]) + utf8_string(data[3], &cur_dive->buddy); + if (data[4]) + utf8_string(data[4], &cur_dive->notes); + + metric = atoi(data[5]) == 1 ? 0 : 1; + + /* TODO: verify that metric calculation is correct */ + if (data[6]) + cur_dive->dc.maxdepth.mm = metric ? atof(data[6]) * 1000 : feet_to_mm(atof(data[6])); + + if (data[7]) + cur_dive->dc.duration.seconds = atoi(data[7]) * 60; + + if (data[8]) + cur_dive->dc.surface_pressure.mbar = atoi(data[8]); + /* + * TODO: the deviceid hash should be calculated here. + */ + settings_start(); + dc_settings_start(); + if (data[9]) + utf8_string(data[9], &cur_settings.dc.serial_nr); + if (data[10]) + utf8_string(data[10], &cur_settings.dc.model); + + cur_settings.dc.deviceid = 0xffffffff; + dc_settings_end(); + settings_end(); + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, cur_dive->number); + retval = sqlite3_exec(handle, get_buffer, &shearwater_cylinders, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query shearwater_cylinders failed.\n"); + return 1; + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_changes_template, cur_dive->number, cur_dive->number); + retval = sqlite3_exec(handle, get_buffer, &shearwater_changes, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query shearwater_changes failed.\n"); + return 1; + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, cur_dive->number); + retval = sqlite3_exec(handle, get_buffer, &shearwater_profile_sample, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query shearwater_profile_sample failed.\n"); + return 1; + } + + dive_end(); + + return SQLITE_OK; +} + +extern int cobalt_cylinders(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + cylinder_start(); + if (data[0]) + cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[0]) * 10; + if (data[1]) + cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[1]) * 10; + if (data[2]) + cur_dive->cylinder[cur_cylinder_index].start.mbar = psi_to_mbar(atoi(data[2])); + if (data[3]) + cur_dive->cylinder[cur_cylinder_index].end.mbar = psi_to_mbar(atoi(data[3])); + if (data[4]) + cur_dive->cylinder[cur_cylinder_index].type.size.mliter = atoi(data[4]) * 100; + if (data[5]) + cur_dive->cylinder[cur_cylinder_index].gas_used.mliter = atoi(data[5]) * 1000; + cylinder_end(); + + return 0; +} + +extern int cobalt_buddies(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + if (data[0]) + utf8_string(data[0], &cur_dive->buddy); + + return 0; +} + +/* + * We still need to figure out how to map free text visibility to + * Subsurface star rating. + */ + +extern int cobalt_visibility(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + (void) data; + return 0; +} + +extern int cobalt_location(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + static char *location = NULL; + if (data[0]) { + if (location) { + char *tmp = malloc(strlen(location) + strlen(data[0]) + 4); + if (!tmp) + return -1; + sprintf(tmp, "%s / %s", location, data[0]); + free(location); + location = NULL; + cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(tmp, cur_dive->when); + free(tmp); + } else { + location = strdup(data[0]); + } + } + return 0; +} + + +extern int cobalt_dive(void *param, int columns, char **data, char **column) +{ + (void) columns; + (void) column; + + int retval = 0; + sqlite3 *handle = (sqlite3 *)param; + char *err = NULL; + char get_profile_template[] = "select runtime*60,(DepthPressure*10000/SurfacePressure)-10000,p.Temperature from Dive AS d JOIN TrackPoints AS p ON d.Id=p.DiveId where d.Id=%d"; + char get_cylinder_template[] = "select FO2,FHe,StartingPressure,EndingPressure,TankSize,TankPressure,TotalConsumption from GasMixes where DiveID=%d and StartingPressure>0 group by FO2,FHe"; + char get_buddy_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=4"; + char get_visibility_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=3"; + char get_location_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=0"; + char get_site_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=1"; + char get_buffer[1024]; + + dive_start(); + cur_dive->number = atoi(data[0]); + + cur_dive->when = (time_t)(atol(data[1])); + + if (data[4]) + utf8_string(data[4], &cur_dive->notes); + + /* data[5] should have information on Units used, but I cannot + * parse it at all based on the sample log I have received. The + * temperatures in the samples are all Imperial, so let's go by + * that. + */ + + metric = 0; + + /* Cobalt stores the pressures, not the depth */ + if (data[6]) + cur_dive->dc.maxdepth.mm = atoi(data[6]); + + if (data[7]) + cur_dive->dc.duration.seconds = atoi(data[7]); + + if (data[8]) + cur_dive->dc.surface_pressure.mbar = atoi(data[8]); + /* + * TODO: the deviceid hash should be calculated here. + */ + settings_start(); + dc_settings_start(); + if (data[9]) { + utf8_string(data[9], &cur_settings.dc.serial_nr); + cur_settings.dc.deviceid = atoi(data[9]); + cur_settings.dc.model = strdup("Cobalt import"); + } + + dc_settings_end(); + settings_end(); + + if (data[9]) { + cur_dive->dc.deviceid = atoi(data[9]); + cur_dive->dc.model = strdup("Cobalt import"); + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, cur_dive->number); + retval = sqlite3_exec(handle, get_buffer, &cobalt_cylinders, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query cobalt_cylinders failed.\n"); + return 1; + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_buddy_template, cur_dive->number); + retval = sqlite3_exec(handle, get_buffer, &cobalt_buddies, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query cobalt_buddies failed.\n"); + return 1; + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_visibility_template, cur_dive->number); + retval = sqlite3_exec(handle, get_buffer, &cobalt_visibility, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query cobalt_visibility failed.\n"); + return 1; + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_location_template, cur_dive->number); + retval = sqlite3_exec(handle, get_buffer, &cobalt_location, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query cobalt_location failed.\n"); + return 1; + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_site_template, cur_dive->number); + retval = sqlite3_exec(handle, get_buffer, &cobalt_location, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query cobalt_location (site) failed.\n"); + return 1; + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, cur_dive->number); + retval = sqlite3_exec(handle, get_buffer, &cobalt_profile_sample, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query cobalt_profile_sample failed.\n"); + return 1; + } + + dive_end(); + + return SQLITE_OK; +} + + +int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, + struct dive_table *table) +{ + (void) buffer; + (void) size; + + int retval; + char *err = NULL; + target_table = table; + + char get_dives[] = "select i.diveId,timestamp,location||' / '||site,buddy,notes,imperialUnits,maxDepth,maxTime,startSurfacePressure,computerSerial,computerModel FROM dive_info AS i JOIN dive_logs AS l ON i.diveId=l.diveId"; + + retval = sqlite3_exec(handle, get_dives, &shearwater_dive, handle, &err); + + if (retval != SQLITE_OK) { + fprintf(stderr, "Database query failed '%s'.\n", url); + return 1; + } + + return 0; +} + +int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, + struct dive_table *table) +{ + (void) buffer; + (void) size; + + int retval; + char *err = NULL; + target_table = table; + + char get_dives[] = "select Id,strftime('%s',DiveStartTime),LocationId,'buddy','notes',Units,(MaxDepthPressure*10000/SurfacePressure)-10000,DiveMinutes,SurfacePressure,SerialNumber,'model' from Dive where IsViewDeleted = 0"; + + retval = sqlite3_exec(handle, get_dives, &cobalt_dive, handle, &err); + + if (retval != SQLITE_OK) { + fprintf(stderr, "Database query failed '%s'.\n", url); + return 1; + } + + return 0; +} + +extern int divinglog_cylinder(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + short dbl = 1; + //char get_cylinder_template[] = "select TankID,TankSize,PresS,PresE,PresW,O2,He,DblTank from Tank where LogID = %d"; + + /* + * Divinglog might have more cylinders than what we support. So + * better to ignore those. + */ + + if (cur_cylinder_index >= MAX_CYLINDERS) + return 0; + + if (data[7] && atoi(data[7]) > 0) + dbl = 2; + + cylinder_start(); + + /* + * Assuming that we have to double the cylinder size, if double + * is set + */ + + if (data[1] && atoi(data[1]) > 0) + cur_dive->cylinder[cur_cylinder_index].type.size.mliter = atol(data[1]) * 1000 * dbl; + + if (data[2] && atoi(data[2]) > 0) + cur_dive->cylinder[cur_cylinder_index].start.mbar = atol(data[2]) * 1000; + if (data[3] && atoi(data[3]) > 0) + cur_dive->cylinder[cur_cylinder_index].end.mbar = atol(data[3]) * 1000; + if (data[4] && atoi(data[4]) > 0) + cur_dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = atol(data[4]) * 1000; + if (data[5] && atoi(data[5]) > 0) + cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atol(data[5]) * 10; + if (data[6] && atoi(data[6]) > 0) + cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atol(data[6]) * 10; + + cylinder_end(); + + return 0; +} + +extern int divinglog_profile(void *handle, int columns, char **data, char **column) +{ + (void) handle; + (void) columns; + (void) column; + + int sinterval = 0; + unsigned long i, len, lenprofile2 = 0; + char *ptr, temp[4], pres[5], hbeat[4], stop[4], stime[4], ndl[4], ppo2_1[4], ppo2_2[4], ppo2_3[4], cns[5], setpoint[3]; + short oldcyl = -1; + + /* We do not have samples */ + if (!data[1]) + return 0; + + if (data[0]) + sinterval = atoi(data[0]); + + /* + * Profile + * + * DDDDDCRASWEE + * D: Depth (in meter with two decimals) + * C: Deco (1 = yes, 0 = no) + * R: RBT (Remaining Bottom Time warning) + * A: Ascent warning + * S: Decostop ignored + * W: Work warning + * E: Extra info (different for every computer) + * + * Example: 004500010000 + * 4.5 m, no deco, no RBT warning, ascanding too fast, no decostop ignored, no work, no extra info + * + * + * Profile2 + * + * TTTFFFFIRRR + * + * T: Temperature (in °C with one decimal) + * F: Tank pressure 1 (in bar with one decimal) + * I: Tank ID (0, 1, 2 ... 9) + * R: RBT (in min) + * + * Example: 25518051099 + * 25.5 °C, 180.5 bar, Tank 1, 99 min RBT + * + */ + + len = strlen(data[1]); + + if (data[2]) + lenprofile2 = strlen(data[2]); + + for (i = 0, ptr = data[1]; i * 12 < len; ++i) { + sample_start(); + + cur_sample->time.seconds = sinterval * i; + cur_sample->in_deco = ptr[5] - '0' ? true : false; + ptr[5] = 0; + cur_sample->depth.mm = atoi(ptr) * 10; + + if (i * 11 < lenprofile2) { + memcpy(temp, &data[2][i * 11], 3); + cur_sample->temperature.mkelvin = C_to_mkelvin(atoi(temp) / 10); + } + + if (data[2]) { + memcpy(pres, &data[2][i * 11 + 3], 4); + cur_sample->cylinderpressure.mbar = atoi(pres) * 100; + } + + if (data[3] && strlen(data[3])) { + memcpy(hbeat, &data[3][i * 14 + 8], 3); + cur_sample->heartbeat = atoi(hbeat); + } + + if (data[4] && strlen(data[4])) { + memcpy(stop, &data[4][i * 9 + 6], 3); + cur_sample->stopdepth.mm = atoi(stop) * 1000; + + memcpy(stime, &data[4][i * 9 + 3], 3); + cur_sample->stoptime.seconds = atoi(stime) * 60; + + /* + * Following value is NDL when not in deco, and + * either 0 or TTS when in deco. + */ + + memcpy(ndl, &data[4][i * 9 + 0], 3); + if (cur_sample->in_deco == false) + cur_sample->ndl.seconds = atoi(ndl) * 60; + else if (atoi(ndl)) + cur_sample->tts.seconds = atoi(ndl) * 60; + + if (cur_sample->in_deco == true) + cur_sample->ndl.seconds = 0; + } + + /* + * AAABBBCCCOOOONNNNSS + * + * A = ppO2 cell 1 (measured) + * B = ppO2 cell 2 (measured) + * C = ppO2 cell 3 (measured) + * O = OTU + * N = CNS + * S = Setpoint + * + * Example: 1121131141548026411 + * 1.12 bar, 1.13 bar, 1.14 bar, OTU = 154.8, CNS = 26.4, Setpoint = 1.1 + */ + + if (data[5] && strlen(data[5])) { + memcpy(ppo2_1, &data[5][i * 19 + 0], 3); + memcpy(ppo2_2, &data[5][i * 19 + 3], 3); + memcpy(ppo2_3, &data[5][i * 19 + 6], 3); + memcpy(cns, &data[5][i * 19 + 13], 4); + memcpy(setpoint, &data[5][i * 19 + 17], 2); + + if (atoi(ppo2_1) > 0) + cur_sample->o2sensor[0].mbar = atoi(ppo2_1) * 100; + if (atoi(ppo2_2) > 0) + cur_sample->o2sensor[1].mbar = atoi(ppo2_2) * 100; + if (atoi(ppo2_3) > 0) + cur_sample->o2sensor[2].mbar = atoi(ppo2_3) * 100; + if (atoi(cns) > 0) + cur_sample->cns = rint(atoi(cns) / 10); + if (atoi(setpoint) > 0) + cur_sample->setpoint.mbar = atoi(setpoint) * 100; + + } + + /* + * My best guess is that if we have o2sensors, then it + * is either CCR or PSCR dive. And the first time we + * have O2 sensor readings, we can count them to get + * the amount O2 sensors. + */ + + if (!cur_dive->dc.no_o2sensors) { + cur_dive->dc.no_o2sensors = cur_sample->o2sensor[0].mbar ? 1 : 0 + + cur_sample->o2sensor[1].mbar ? 1 : 0 + + cur_sample->o2sensor[2].mbar ? 1 : 0; + cur_dive->dc.divemode = CCR; + } + + ptr += 12; + sample_end(); + } + + for (i = 0, ptr = data[1]; i * 12 < len; ++i) { + /* Remaining bottom time warning */ + if (ptr[6] - '0') { + event_start(); + cur_event.time.seconds = sinterval * i; + strcpy(cur_event.name, "rbt"); + event_end(); + } + + /* Ascent warning */ + if (ptr[7] - '0') { + event_start(); + cur_event.time.seconds = sinterval * i; + strcpy(cur_event.name, "ascent"); + event_end(); + } + + /* Deco stop ignored */ + if (ptr[8] - '0') { + event_start(); + cur_event.time.seconds = sinterval * i; + strcpy(cur_event.name, "violation"); + event_end(); + } + + /* Workload warning */ + if (ptr[9] - '0') { + event_start(); + cur_event.time.seconds = sinterval * i; + strcpy(cur_event.name, "workload"); + event_end(); + } + ptr += 12; + } + + for (i = 0; i * 11 < lenprofile2; ++i) { + short tank = data[2][i * 11 + 7] - '0'; + if (oldcyl != tank) { + struct gasmix *mix = &cur_dive->cylinder[tank].gasmix; + int o2 = get_o2(mix); + int he = get_he(mix); + + event_start(); + cur_event.time.seconds = sinterval * i; + strcpy(cur_event.name, "gaschange"); + + o2 = (o2 + 5) / 10; + he = (he + 5) / 10; + cur_event.value = o2 + (he << 16); + + event_end(); + oldcyl = tank; + } + } + + return 0; +} + + +extern int divinglog_dive(void *param, int columns, char **data, char **column) +{ + (void) columns; + (void) column; + + int retval = 0; + sqlite3 *handle = (sqlite3 *)param; + char *err = NULL; + char get_profile_template[] = "select ProfileInt,Profile,Profile2,Profile3,Profile4,Profile5 from Logbook where ID = %d"; + char get_cylinder0_template[] = "select 0,TankSize,PresS,PresE,PresW,O2,He,DblTank from Logbook where ID = %d"; + char get_cylinder_template[] = "select TankID,TankSize,PresS,PresE,PresW,O2,He,DblTank from Tank where LogID = %d order by TankID"; + char get_buffer[1024]; + + dive_start(); + diveid = atoi(data[13]); + cur_dive->number = atoi(data[0]); + + cur_dive->when = (time_t)(atol(data[1])); + + if (data[2]) + cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(data[2], cur_dive->when); + + if (data[3]) + utf8_string(data[3], &cur_dive->buddy); + + if (data[4]) + utf8_string(data[4], &cur_dive->notes); + + if (data[5]) + cur_dive->dc.maxdepth.mm = atof(data[5]) * 1000; + + if (data[6]) + cur_dive->dc.duration.seconds = atoi(data[6]) * 60; + + if (data[7]) + utf8_string(data[7], &cur_dive->divemaster); + + if (data[8]) + cur_dive->airtemp.mkelvin = C_to_mkelvin(atol(data[8])); + + if (data[9]) + cur_dive->watertemp.mkelvin = C_to_mkelvin(atol(data[9])); + + if (data[10]) { + cur_dive->weightsystem[0].weight.grams = atol(data[10]) * 1000; + cur_dive->weightsystem[0].description = strdup(translate("gettextFromC", "unknown")); + } + + if (data[11]) + cur_dive->suit = strdup(data[11]); + + settings_start(); + dc_settings_start(); + + if (data[12]) { + cur_dive->dc.model = strdup(data[12]); + } else { + cur_settings.dc.model = strdup("Divinglog import"); + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder0_template, diveid); + retval = sqlite3_exec(handle, get_buffer, &divinglog_cylinder, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query divinglog_cylinder0 failed.\n"); + return 1; + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, diveid); + retval = sqlite3_exec(handle, get_buffer, &divinglog_cylinder, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query divinglog_cylinder failed.\n"); + return 1; + } + + + dc_settings_end(); + settings_end(); + + if (data[12]) { + cur_dive->dc.model = strdup(data[12]); + } else { + cur_dive->dc.model = strdup("Divinglog import"); + } + + snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, diveid); + retval = sqlite3_exec(handle, get_buffer, &divinglog_profile, 0, &err); + if (retval != SQLITE_OK) { + fprintf(stderr, "%s", "Database query divinglog_profile failed.\n"); + return 1; + } + + dive_end(); + + return SQLITE_OK; +} + + +int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, + struct dive_table *table) +{ + (void) buffer; + (void) size; + + int retval; + char *err = NULL; + target_table = table; + + char get_dives[] = "select Number,strftime('%s',Divedate || ' ' || ifnull(Entrytime,'00:00')),Country || ' - ' || City || ' - ' || Place,Buddy,Comments,Depth,Divetime,Divemaster,Airtemp,Watertemp,Weight,Divesuit,Computer,ID from Logbook where UUID not in (select UUID from DeletedRecords)"; + + retval = sqlite3_exec(handle, get_dives, &divinglog_dive, handle, &err); + + if (retval != SQLITE_OK) { + fprintf(stderr, "Database query failed '%s'.\n", url); + return 1; + } + + return 0; +} + +/* + * Parse a unsigned 32-bit integer in little-endian mode, + * that is seconds since Jan 1, 2000. + */ +static timestamp_t parse_dlf_timestamp(unsigned char *buffer) +{ + timestamp_t offset; + + offset = buffer[3]; + offset = (offset << 8) + buffer[2]; + offset = (offset << 8) + buffer[1]; + offset = (offset << 8) + buffer[0]; + + // Jan 1, 2000 is 946684800 seconds after Jan 1, 1970, which is + // the Unix epoch date that "timestamp_t" uses. + return offset + 946684800; +} + +int parse_dlf_buffer(unsigned char *buffer, size_t size) +{ + unsigned char *ptr = buffer; + unsigned char event; + bool found; + unsigned int time = 0; + int i; + char serial[6]; + + target_table = &dive_table; + + // Check for the correct file magic + if (ptr[0] != 'D' || ptr[1] != 'i' || ptr[2] != 'v' || ptr[3] != 'E') + return -1; + + dive_start(); + divecomputer_start(); + + cur_dc->model = strdup("DLF import"); + // (ptr[7] << 8) + ptr[6] Is "Serial" + snprintf(serial, sizeof(serial), "%d", (ptr[7] << 8) + ptr[6]); + cur_dc->serial = strdup(serial); + cur_dc->when = parse_dlf_timestamp(ptr + 8); + cur_dive->when = cur_dc->when; + + cur_dc->duration.seconds = ((ptr[14] & 0xFE) << 16) + (ptr[13] << 8) + ptr[12]; + + // ptr[14] >> 1 is scrubber used in % + + // 3 bit dive type + switch((ptr[15] & 0x30) >> 3) { + case 0: // unknown + case 1: + cur_dc->divemode = OC; + break; + case 2: + cur_dc->divemode = CCR; + break; + case 3: + cur_dc->divemode = CCR; // mCCR + break; + case 4: + cur_dc->divemode = FREEDIVE; + break; + case 5: + cur_dc->divemode = OC; // Gauge + break; + case 6: + cur_dc->divemode = PSCR; // ASCR + break; + case 7: + cur_dc->divemode = PSCR; + break; + } + + cur_dc->maxdepth.mm = ((ptr[21] << 8) + ptr[20]) * 10; + cur_dc->surface_pressure.mbar = ((ptr[25] << 8) + ptr[24]) / 10; + + /* Done with parsing what we know about the dive header */ + ptr += 32; + + // We're going to interpret ppO2 saved as a sensor value in these modes. + if (cur_dc->divemode == CCR || cur_dc->divemode == PSCR) + cur_dc->no_o2sensors = 1; + + while (ptr < buffer + size) { + time = ((ptr[0] >> 4) & 0x0f) + + ((ptr[1] << 4) & 0xff0) + + (ptr[2] & 0x0f) * 3600; /* hours */ + event = ptr[0] & 0x0f; + switch (event) { + case 0: + /* Regular sample */ + sample_start(); + cur_sample->time.seconds = time; + cur_sample->depth.mm = ((ptr[5] << 8) + ptr[4]) * 10; + // Crazy precision on these stored values... + // Only store value if we're in CCR/PSCR mode, + // because we rather calculate ppo2 our selfs. + if (cur_dc->divemode == CCR || cur_dc->divemode == PSCR) + cur_sample->o2sensor[0].mbar = ((ptr[7] << 8) + ptr[6]) / 10; + // NDL in minutes, 10 bit + cur_sample->ndl.seconds = (((ptr[9] & 0x03) << 8) + ptr[8]) * 60; + // TTS in minutes, 10 bit + cur_sample->tts.seconds = (((ptr[10] & 0x0F) << 6) + (ptr[9] >> 2)) * 60; + // Temperature in 1/10 C, 10 bit signed + cur_sample->temperature.mkelvin = ((ptr[11] & 0x20) ? -1 : 1) * (((ptr[11] & 0x1F) << 4) + (ptr[10] >> 4)) * 100 + ZERO_C_IN_MKELVIN; + // ptr[11] & 0xF0 is unknown, and always 0xC in all checked files + cur_sample->stopdepth.mm = ((ptr[13] << 8) + ptr[12]) * 10; + if (cur_sample->stopdepth.mm) + cur_sample->in_deco = true; + //ptr[14] is helium content, always zero? + //ptr[15] is setpoint, always zero? + sample_end(); + break; + case 1: /* dive event */ + case 2: /* automatic parameter change */ + case 3: /* diver error */ + case 4: /* internal error */ + case 5: /* device activity log */ + event_start(); + cur_event.time.seconds = time; + switch (ptr[4]) { + case 1: + strcpy(cur_event.name, "Setpoint Manual"); + // There is a setpoint value somewhere... + break; + case 2: + strcpy(cur_event.name, "Setpoint Auto"); + // There is a setpoint value somewhere... + switch (ptr[7]) { + case 0: + strcat(cur_event.name, " Manual"); + break; + case 1: + strcat(cur_event.name, " Auto Start"); + break; + case 2: + strcat(cur_event.name, " Auto Hypox"); + break; + case 3: + strcat(cur_event.name, " Auto Timeout"); + break; + case 4: + strcat(cur_event.name, " Auto Ascent"); + break; + case 5: + strcat(cur_event.name, " Auto Stall"); + break; + case 6: + strcat(cur_event.name, " Auto SP Low"); + break; + default: + break; + } + break; + case 3: + // obsolete + strcpy(cur_event.name, "OC"); + break; + case 4: + // obsolete + strcpy(cur_event.name, "CCR"); + break; + case 5: + strcpy(cur_event.name, "gaschange"); + cur_event.type = SAMPLE_EVENT_GASCHANGE2; + cur_event.value = ptr[7] << 8 ^ ptr[6]; + + found = false; + for (i = 0; i < cur_cylinder_index; ++i) { + if (cur_dive->cylinder[i].gasmix.o2.permille == ptr[6] * 10 && cur_dive->cylinder[i].gasmix.he.permille == ptr[7] * 10) { + found = true; + break; + } + } + if (!found) { + cylinder_start(); + cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = ptr[6] * 10; + cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = ptr[7] * 10; + cylinder_end(); + cur_event.gas.index = cur_cylinder_index; + } else { + cur_event.gas.index = i; + } + break; + case 6: + strcpy(cur_event.name, "Start"); + break; + case 7: + strcpy(cur_event.name, "Too Fast"); + break; + case 8: + strcpy(cur_event.name, "Above Ceiling"); + break; + case 9: + strcpy(cur_event.name, "Toxic"); + break; + case 10: + strcpy(cur_event.name, "Hypox"); + break; + case 11: + strcpy(cur_event.name, "Critical"); + break; + case 12: + strcpy(cur_event.name, "Sensor Disabled"); + break; + case 13: + strcpy(cur_event.name, "Sensor Enabled"); + break; + case 14: + strcpy(cur_event.name, "O2 Backup"); + break; + case 15: + strcpy(cur_event.name, "Peer Down"); + break; + case 16: + strcpy(cur_event.name, "HS Down"); + break; + case 17: + strcpy(cur_event.name, "Inconsistent"); + break; + case 18: + // key pressed - probably not + // interesting to view on profile + break; + case 19: + // obsolete + strcpy(cur_event.name, "SCR"); + break; + case 20: + strcpy(cur_event.name, "Above Stop"); + break; + case 21: + strcpy(cur_event.name, "Safety Miss"); + break; + case 22: + strcpy(cur_event.name, "Fatal"); + break; + case 23: + strcpy(cur_event.name, "Diluent"); + break; + case 24: + strcpy(cur_event.name, "gaschange"); + cur_event.type = SAMPLE_EVENT_GASCHANGE2; + cur_event.value = ptr[7] << 8 ^ ptr[6]; + event_end(); + // This is both a mode change and a gas change event + // so we encode it as two separate events. + event_start(); + strcpy(cur_event.name, "Change Mode"); + switch (ptr[8]) { + case 1: + strcat(cur_event.name, ": OC"); + break; + case 2: + strcat(cur_event.name, ": CCR"); + break; + case 3: + strcat(cur_event.name, ": mCCR"); + break; + case 4: + strcat(cur_event.name, ": Free"); + break; + case 5: + strcat(cur_event.name, ": Gauge"); + break; + case 6: + strcat(cur_event.name, ": ASCR"); + break; + case 7: + strcat(cur_event.name, ": PSCR"); + break; + default: + break; + } + event_end(); + break; + case 25: + strcpy(cur_event.name, "CCR O2 solenoid opened/closed"); + break; + case 26: + strcpy(cur_event.name, "User mark"); + break; + case 27: + snprintf(cur_event.name, MAX_EVENT_NAME, "%sGF Switch (%d/%d)", ptr[6] ? "Bailout, ": "", ptr[7], ptr[8]); + break; + case 28: + strcpy(cur_event.name, "Peer Up"); + break; + case 29: + strcpy(cur_event.name, "HS Up"); + break; + case 30: + snprintf(cur_event.name, MAX_EVENT_NAME, "CNS %d%%", ptr[6]); + break; + default: + // No values above 30 had any description + break; + } + event_end(); + break; + case 6: + /* device configuration */ + break; + case 7: + /* measure record */ + /* Po2 sample? Solenoid inject? */ + //fprintf(stderr, "%02X %02X%02X %02X%02X\n", ptr[5], ptr[6], ptr[7], ptr[8], ptr[9]); + break; + default: + /* Unknown... */ + break; + } + ptr += 16; + } + divecomputer_end(); + dive_end(); + return 0; +} + + +void parse_xml_init(void) +{ + LIBXML_TEST_VERSION +} + +void parse_xml_exit(void) +{ + xmlCleanupParser(); +} + +static struct xslt_files { + const char *root; + const char *file; + const char *attribute; +} xslt_files[] = { + { "SUUNTO", "SuuntoSDM.xslt", NULL }, + { "Dive", "SuuntoDM4.xslt", "xmlns" }, + { "Dive", "shearwater.xslt", "version" }, + { "JDiveLog", "jdivelog2subsurface.xslt", NULL }, + { "dives", "MacDive.xslt", NULL }, + { "DIVELOGSDATA", "divelogs.xslt", NULL }, + { "uddf", "uddf.xslt", NULL }, + { "UDDF", "uddf.xslt", NULL }, + { "profile", "udcf.xslt", NULL }, + { "Divinglog", "DivingLog.xslt", NULL }, + { "csv", "csv2xml.xslt", NULL }, + { "sensuscsv", "sensuscsv.xslt", NULL }, + { "SubsurfaceCSV", "subsurfacecsv.xslt", NULL }, + { "manualcsv", "manualcsv2xml.xslt", NULL }, + { "logbook", "DiveLog.xslt", NULL }, + { NULL, } + }; + +static xmlDoc *test_xslt_transforms(xmlDoc *doc, const char **params) +{ + struct xslt_files *info = xslt_files; + xmlDoc *transformed; + xsltStylesheetPtr xslt = NULL; + xmlNode *root_element = xmlDocGetRootElement(doc); + char *attribute; + + while (info->root) { + if ((strcasecmp((const char *)root_element->name, info->root) == 0)) { + if (info->attribute == NULL) + break; + else if (xmlGetProp(root_element, (const xmlChar *)info->attribute) != NULL) + break; + } + info++; + } + + if (info->root) { + attribute = (char *)xmlGetProp(xmlFirstElementChild(root_element), (const xmlChar *)"name"); + if (attribute) { + if (strcasecmp(attribute, "subsurface") == 0) { + free((void *)attribute); + return doc; + } + free((void *)attribute); + } + xmlSubstituteEntitiesDefault(1); + xslt = get_stylesheet(info->file); + if (xslt == NULL) { + report_error(translate("gettextFromC", "Can't open stylesheet %s"), info->file); + return doc; + } + transformed = xsltApplyStylesheet(xslt, doc, params); + xmlFreeDoc(doc); + xsltFreeStylesheet(xslt); + + return transformed; + } + return doc; +} diff --git a/core/planner.c b/core/planner.c new file mode 100644 index 000000000..705aad1cb --- /dev/null +++ b/core/planner.c @@ -0,0 +1,1471 @@ +/* planner.c + * + * code that allows us to plan future dives + * + * (c) Dirk Hohndel 2013 + */ +#include +#include +#include +#include +#include "dive.h" +#include "deco.h" +#include "divelist.h" +#include "planner.h" +#include "gettext.h" +#include "libdivecomputer/parser.h" + +#define TIMESTEP 2 /* second */ +#define DECOTIMESTEP 60 /* seconds. Unit of deco stop times */ + +int decostoplevels_metric[] = { 0, 3000, 6000, 9000, 12000, 15000, 18000, 21000, 24000, 27000, + 30000, 33000, 36000, 39000, 42000, 45000, 48000, 51000, 54000, 57000, + 60000, 63000, 66000, 69000, 72000, 75000, 78000, 81000, 84000, 87000, + 90000, 100000, 110000, 120000, 130000, 140000, 150000, 160000, 170000, + 180000, 190000, 200000, 220000, 240000, 260000, 280000, 300000, + 320000, 340000, 360000, 380000 }; +int decostoplevels_imperial[] = { 0, 3048, 6096, 9144, 12192, 15240, 18288, 21336, 24384, 27432, + 30480, 33528, 36576, 39624, 42672, 45720, 48768, 51816, 54864, 57912, + 60960, 64008, 67056, 70104, 73152, 76200, 79248, 82296, 85344, 88392, + 91440, 101600, 111760, 121920, 132080, 142240, 152400, 162560, 172720, + 182880, 193040, 203200, 223520, 243840, 264160, 284480, 304800, + 325120, 345440, 365760, 386080 }; + +double plangflow, plangfhigh; +bool plan_verbatim, plan_display_runtime, plan_display_duration, plan_display_transitions; + +pressure_t first_ceiling_pressure, max_bottom_ceiling_pressure = {}; + +const char *disclaimer; + +#if DEBUG_PLAN +void dump_plan(struct diveplan *diveplan) +{ + struct divedatapoint *dp; + struct tm tm; + + if (!diveplan) { + printf("Diveplan NULL\n"); + return; + } + utc_mkdate(diveplan->when, &tm); + + printf("\nDiveplan @ %04d-%02d-%02d %02d:%02d:%02d (surfpres %dmbar):\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + diveplan->surface_pressure); + dp = diveplan->dp; + while (dp) { + printf("\t%3u:%02u: %dmm gas: %d o2 %d h2\n", FRACTION(dp->time, 60), dp->depth, get_o2(&dp->gasmix), get_he(&dp->gasmix)); + dp = dp->next; + } +} +#endif + +bool diveplan_empty(struct diveplan *diveplan) +{ + struct divedatapoint *dp; + if (!diveplan || !diveplan->dp) + return true; + dp = diveplan->dp; + while (dp) { + if (dp->time) + return false; + dp = dp->next; + } + return true; +} + +/* get the gas at a certain time during the dive */ +void get_gas_at_time(struct dive *dive, struct divecomputer *dc, duration_t time, struct gasmix *gas) +{ + // we always start with the first gas, so that's our gas + // unless an event tells us otherwise + struct event *event = dc->events; + *gas = dive->cylinder[0].gasmix; + while (event && event->time.seconds <= time.seconds) { + if (!strcmp(event->name, "gaschange")) { + int cylinder_idx = get_cylinder_index(dive, event); + *gas = dive->cylinder[cylinder_idx].gasmix; + } + event = event->next; + } +} + +int get_gasidx(struct dive *dive, struct gasmix *mix) +{ + int gasidx = -1; + + while (++gasidx < MAX_CYLINDERS) + if (gasmix_distance(&dive->cylinder[gasidx].gasmix, mix) < 100) + return gasidx; + return -1; +} + +void interpolate_transition(struct dive *dive, duration_t t0, duration_t t1, depth_t d0, depth_t d1, const struct gasmix *gasmix, o2pressure_t po2) +{ + uint32_t j; + + for (j = t0.seconds; j < t1.seconds; j++) { + int depth = interpolate(d0.mm, d1.mm, j - t0.seconds, t1.seconds - t0.seconds); + add_segment(depth_to_bar(depth, dive), gasmix, 1, po2.mbar, dive, prefs.bottomsac); + } + if (d1.mm > d0.mm) + calc_crushing_pressure(depth_to_bar(d1.mm, &displayed_dive)); +} + +/* returns the tissue tolerance at the end of this (partial) dive */ +void tissue_at_end(struct dive *dive, char **cached_datap) +{ + struct divecomputer *dc; + struct sample *sample, *psample; + int i; + depth_t lastdepth = {}; + duration_t t0 = {}, t1 = {}; + struct gasmix gas; + + if (!dive) + return; + if (*cached_datap) { + restore_deco_state(*cached_datap); + } else { + init_decompression(dive); + cache_deco_state(cached_datap); + } + dc = &dive->dc; + if (!dc->samples) + return; + psample = sample = dc->sample; + + for (i = 0; i < dc->samples; i++, sample++) { + o2pressure_t setpoint; + + if (i) + setpoint = sample[-1].setpoint; + else + setpoint = sample[0].setpoint; + + t1 = sample->time; + get_gas_at_time(dive, dc, t0, &gas); + if (i > 0) + lastdepth = psample->depth; + + /* The ceiling in the deeper portion of a multilevel dive is sometimes critical for the VPM-B + * Boyle's law compensation. We should check the ceiling prior to ascending during the bottom + * portion of the dive. The maximum ceiling might be reached while ascending, but testing indicates + * that it is only marginally deeper than the ceiling at the start of ascent. + * Do not set the first_ceiling_pressure variable (used for the Boyle's law compensation calculation) + * at this stage, because it would interfere with calculating the ceiling at the end of the bottom + * portion of the dive. + * Remember the value for later. + */ + if ((prefs.deco_mode == VPMB) && (lastdepth.mm > sample->depth.mm)) { + pressure_t ceiling_pressure; + nuclear_regeneration(t0.seconds); + vpmb_start_gradient(); + ceiling_pressure.mbar = depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(dive, + depth_to_bar(lastdepth.mm, dive)), + dive->surface_pressure.mbar / 1000.0, + dive, + 1), + dive); + if (ceiling_pressure.mbar > max_bottom_ceiling_pressure.mbar) + max_bottom_ceiling_pressure.mbar = ceiling_pressure.mbar; + } + + interpolate_transition(dive, t0, t1, lastdepth, sample->depth, &gas, setpoint); + psample = sample; + t0 = t1; + } +} + + +/* if a default cylinder is set, use that */ +void fill_default_cylinder(cylinder_t *cyl) +{ + const char *cyl_name = prefs.default_cylinder; + struct tank_info_t *ti = tank_info; + pressure_t pO2 = {.mbar = 1600}; + + if (!cyl_name) + return; + while (ti->name != NULL) { + if (strcmp(ti->name, cyl_name) == 0) + break; + ti++; + } + if (ti->name == NULL) + /* didn't find it */ + return; + cyl->type.description = strdup(ti->name); + if (ti->ml) { + cyl->type.size.mliter = ti->ml; + cyl->type.workingpressure.mbar = ti->bar * 1000; + } else { + cyl->type.workingpressure.mbar = psi_to_mbar(ti->psi); + if (ti->psi) + cyl->type.size.mliter = cuft_to_l(ti->cuft) * 1000 / bar_to_atm(psi_to_bar(ti->psi)); + } + // MOD of air + cyl->depth = gas_mod(&cyl->gasmix, pO2, &displayed_dive, 1); +} + +/* make sure that the gas we are switching to is represented in our + * list of cylinders */ +static int verify_gas_exists(struct gasmix mix_in) +{ + int i; + cylinder_t *cyl; + + for (i = 0; i < MAX_CYLINDERS; i++) { + cyl = displayed_dive.cylinder + i; + if (cylinder_nodata(cyl)) + continue; + if (gasmix_distance(&cyl->gasmix, &mix_in) < 100) + return i; + } + fprintf(stderr, "this gas %s should have been on the cylinder list\nThings will fail now\n", gasname(&mix_in)); + return -1; +} + +/* calculate the new end pressure of the cylinder, based on its current end pressure and the + * latest segment. */ +static void update_cylinder_pressure(struct dive *d, int old_depth, int new_depth, int duration, int sac, cylinder_t *cyl, bool in_deco) +{ + volume_t gas_used; + pressure_t delta_p; + depth_t mean_depth; + int factor = 1000; + + if (d->dc.divemode == PSCR) + factor = prefs.pscr_ratio; + + if (!cyl) + return; + mean_depth.mm = (old_depth + new_depth) / 2; + gas_used.mliter = depth_to_atm(mean_depth.mm, d) * sac / 60 * duration * factor / 1000; + cyl->gas_used.mliter += gas_used.mliter; + if (in_deco) + cyl->deco_gas_used.mliter += gas_used.mliter; + if (cyl->type.size.mliter) { + delta_p.mbar = gas_used.mliter * 1000.0 / cyl->type.size.mliter; + cyl->end.mbar -= delta_p.mbar; + } +} + +/* simply overwrite the data in the displayed_dive + * return false if something goes wrong */ +static void create_dive_from_plan(struct diveplan *diveplan, bool track_gas) +{ + struct divedatapoint *dp; + struct divecomputer *dc; + struct sample *sample; + struct gasmix oldgasmix; + struct event *ev; + cylinder_t *cyl; + int oldpo2 = 0; + int lasttime = 0; + int lastdepth = 0; + enum dive_comp_type type = displayed_dive.dc.divemode; + + if (!diveplan || !diveplan->dp) + return; +#if DEBUG_PLAN & 4 + printf("in create_dive_from_plan\n"); + dump_plan(diveplan); +#endif + displayed_dive.salinity = diveplan->salinity; + // reset the cylinders and clear out the samples and events of the + // displayed dive so we can restart + reset_cylinders(&displayed_dive, track_gas); + dc = &displayed_dive.dc; + dc->when = displayed_dive.when = diveplan->when; + free(dc->sample); + dc->sample = NULL; + dc->samples = 0; + dc->alloc_samples = 0; + while ((ev = dc->events)) { + dc->events = dc->events->next; + free(ev); + } + dp = diveplan->dp; + cyl = &displayed_dive.cylinder[0]; + oldgasmix = cyl->gasmix; + sample = prepare_sample(dc); + sample->setpoint.mbar = dp->setpoint; + sample->sac.mliter = prefs.bottomsac; + oldpo2 = dp->setpoint; + if (track_gas && cyl->type.workingpressure.mbar) + sample->cylinderpressure.mbar = cyl->end.mbar; + sample->manually_entered = true; + finish_sample(dc); + while (dp) { + struct gasmix gasmix = dp->gasmix; + int po2 = dp->setpoint; + if (dp->setpoint) + type = CCR; + int time = dp->time; + int depth = dp->depth; + + if (time == 0) { + /* special entries that just inform the algorithm about + * additional gases that are available */ + if (verify_gas_exists(gasmix) < 0) + goto gas_error_exit; + dp = dp->next; + continue; + } + + /* Check for SetPoint change */ + if (oldpo2 != po2) { + /* this is a bad idea - we should get a different SAMPLE_EVENT type + * reserved for this in libdivecomputer... overloading SMAPLE_EVENT_PO2 + * with a different meaning will only cause confusion elsewhere in the code */ + add_event(dc, lasttime, SAMPLE_EVENT_PO2, 0, po2, "SP change"); + oldpo2 = po2; + } + + /* Make sure we have the new gas, and create a gas change event */ + if (gasmix_distance(&gasmix, &oldgasmix) > 0) { + int idx; + if ((idx = verify_gas_exists(gasmix)) < 0) + goto gas_error_exit; + /* need to insert a first sample for the new gas */ + add_gas_switch_event(&displayed_dive, dc, lasttime + 1, idx); + cyl = &displayed_dive.cylinder[idx]; + sample = prepare_sample(dc); + sample[-1].setpoint.mbar = po2; + sample->time.seconds = lasttime + 1; + sample->depth.mm = lastdepth; + sample->manually_entered = dp->entered; + sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac; + if (track_gas && cyl->type.workingpressure.mbar) + sample->cylinderpressure.mbar = cyl->sample_end.mbar; + finish_sample(dc); + oldgasmix = gasmix; + } + /* Create sample */ + sample = prepare_sample(dc); + /* set po2 at beginning of this segment */ + /* and keep it valid for last sample - where it likely doesn't matter */ + sample[-1].setpoint.mbar = po2; + sample->setpoint.mbar = po2; + sample->time.seconds = lasttime = time; + sample->depth.mm = lastdepth = depth; + sample->manually_entered = dp->entered; + sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac; + if (track_gas && !sample[-1].setpoint.mbar) { /* Don't track gas usage for CCR legs of dive */ + update_cylinder_pressure(&displayed_dive, sample[-1].depth.mm, depth, time - sample[-1].time.seconds, + dp->entered ? diveplan->bottomsac : diveplan->decosac, cyl, !dp->entered); + if (cyl->type.workingpressure.mbar) + sample->cylinderpressure.mbar = cyl->end.mbar; + } + finish_sample(dc); + dp = dp->next; + } + dc->divemode = type; +#if DEBUG_PLAN & 32 + save_dive(stdout, &displayed_dive); +#endif + return; + +gas_error_exit: + report_error(translate("gettextFromC", "Too many gas mixes")); + return; +} + +void free_dps(struct diveplan *diveplan) +{ + if (!diveplan) + return; + struct divedatapoint *dp = diveplan->dp; + while (dp) { + struct divedatapoint *ndp = dp->next; + free(dp); + dp = ndp; + } + diveplan->dp = NULL; +} + +struct divedatapoint *create_dp(int time_incr, int depth, struct gasmix gasmix, int po2) +{ + struct divedatapoint *dp; + + dp = malloc(sizeof(struct divedatapoint)); + dp->time = time_incr; + dp->depth = depth; + dp->gasmix = gasmix; + dp->setpoint = po2; + dp->entered = false; + dp->next = NULL; + return dp; +} + +void add_to_end_of_diveplan(struct diveplan *diveplan, struct divedatapoint *dp) +{ + struct divedatapoint **lastdp = &diveplan->dp; + struct divedatapoint *ldp = *lastdp; + int lasttime = 0; + while (*lastdp) { + ldp = *lastdp; + if (ldp->time > lasttime) + lasttime = ldp->time; + lastdp = &(*lastdp)->next; + } + *lastdp = dp; + if (ldp && dp->time != 0) + dp->time += lasttime; +} + +struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, struct gasmix gasmix, int po2, bool entered) +{ + struct divedatapoint *dp = create_dp(duration, depth, gasmix, po2); + dp->entered = entered; + add_to_end_of_diveplan(diveplan, dp); + return (dp); +} + +struct gaschanges { + int depth; + int gasidx; +}; + + +static struct gaschanges *analyze_gaslist(struct diveplan *diveplan, int *gaschangenr, int depth, int *asc_cylinder) +{ + struct gasmix gas; + int nr = 0; + struct gaschanges *gaschanges = NULL; + struct divedatapoint *dp = diveplan->dp; + int best_depth = displayed_dive.cylinder[*asc_cylinder].depth.mm; + while (dp) { + if (dp->time == 0) { + gas = dp->gasmix; + if (dp->depth <= depth) { + int i = 0; + nr++; + gaschanges = realloc(gaschanges, nr * sizeof(struct gaschanges)); + while (i < nr - 1) { + if (dp->depth < gaschanges[i].depth) { + memmove(gaschanges + i + 1, gaschanges + i, (nr - i - 1) * sizeof(struct gaschanges)); + break; + } + i++; + } + gaschanges[i].depth = dp->depth; + gaschanges[i].gasidx = get_gasidx(&displayed_dive, &gas); + assert(gaschanges[i].gasidx != -1); + } else { + /* is there a better mix to start deco? */ + if (dp->depth < best_depth) { + best_depth = dp->depth; + *asc_cylinder = get_gasidx(&displayed_dive, &gas); + } + } + } + dp = dp->next; + } + *gaschangenr = nr; +#if DEBUG_PLAN & 16 + for (nr = 0; nr < *gaschangenr; nr++) { + int idx = gaschanges[nr].gasidx; + printf("gaschange nr %d: @ %5.2lfm gasidx %d (%s)\n", nr, gaschanges[nr].depth / 1000.0, + idx, gasname(&displayed_dive.cylinder[idx].gasmix)); + } +#endif + return gaschanges; +} + +/* sort all the stops into one ordered list */ +static int *sort_stops(int *dstops, int dnr, struct gaschanges *gstops, int gnr) +{ + int i, gi, di; + int total = dnr + gnr; + int *stoplevels = malloc(total * sizeof(int)); + + /* no gaschanges */ + if (gnr == 0) { + memcpy(stoplevels, dstops, dnr * sizeof(int)); + return stoplevels; + } + i = total - 1; + gi = gnr - 1; + di = dnr - 1; + while (i >= 0) { + if (dstops[di] > gstops[gi].depth) { + stoplevels[i] = dstops[di]; + di--; + } else if (dstops[di] == gstops[gi].depth) { + stoplevels[i] = dstops[di]; + di--; + gi--; + } else { + stoplevels[i] = gstops[gi].depth; + gi--; + } + i--; + if (di < 0) { + while (gi >= 0) + stoplevels[i--] = gstops[gi--].depth; + break; + } + if (gi < 0) { + while (di >= 0) + stoplevels[i--] = dstops[di--]; + break; + } + } + while (i >= 0) + stoplevels[i--] = 0; + +#if DEBUG_PLAN & 16 + int k; + for (k = gnr + dnr - 1; k >= 0; k--) { + printf("stoplevel[%d]: %5.2lfm\n", k, stoplevels[k] / 1000.0); + if (stoplevels[k] == 0) + break; + } +#endif + return stoplevels; +} + +static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, int error) +{ + const unsigned int sz_buffer = 2000000; + const unsigned int sz_temp = 100000; + char *buffer = (char *)malloc(sz_buffer); + char *temp = (char *)malloc(sz_temp); + char *deco, *segmentsymbol; + static char buf[1000]; + int len, lastdepth = 0, lasttime = 0, lastsetpoint = -1, newdepth = 0, lastprintdepth = 0, lastprintsetpoint = -1; + struct gasmix lastprintgasmix = {{ -1 }, { -1 }}; + struct divedatapoint *dp = diveplan->dp; + bool gaschange_after = !plan_verbatim; + bool gaschange_before; + bool lastentered = true; + struct divedatapoint *nextdp = NULL; + + plan_verbatim = prefs.verbatim_plan; + plan_display_runtime = prefs.display_runtime; + plan_display_duration = prefs.display_duration; + plan_display_transitions = prefs.display_transitions; + + if (prefs.deco_mode == VPMB) { + deco = "VPM-B"; + } else { + deco = "BUHLMANN"; + } + + snprintf(buf, sizeof(buf), translate("gettextFromC", "DISCLAIMER / WARNING: THIS IS A NEW IMPLEMENTATION OF THE %s " + "ALGORITHM AND A DIVE PLANNER IMPLEMENTATION BASED ON THAT WHICH HAS " + "RECEIVED ONLY A LIMITED AMOUNT OF TESTING. WE STRONGLY RECOMMEND NOT TO " + "PLAN DIVES SIMPLY BASED ON THE RESULTS GIVEN HERE."), deco); + disclaimer = buf; + + if (!dp) { + free((void *)buffer); + free((void *)temp); + return; + } + + if (error) { + snprintf(temp, sz_temp, "%s", + translate("gettextFromC", "Decompression calculation aborted due to excessive time")); + snprintf(buffer, sz_buffer, "%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 )) { + // Print a symbol to indicate whether segment is an ascent, descent, constant depth (user entered) or deco stop + if (isascent) + segmentsymbol = "➚"; // up-right arrow for ascent + else if (dp->depth > lastdepth) + segmentsymbol = "➘"; // down-right arrow for descent + else if (dp->entered) + segmentsymbol = "➙"; // right arrow for entered entered segment at constant depth + else + segmentsymbol = "➖"; // heavey minus sign for deco stop + + len += snprintf(buffer + len, sz_buffer - len, "", segmentsymbol); + + 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); + if (!plan_verbatim) + len += snprintf(buffer + len, sz_buffer - len, "
%s%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) +{ + (void) bottom_time; + /* We need to make this configurable */ + + /* As an example (and possibly reasonable default) this is the Tech 1 provedure according + * to http://www.globalunderwaterexplorers.org/files/Standards_and_Procedures/SOP_Manual_Ver2.0.2.pdf */ + + if (depth * 4 > avg_depth * 3) { + return prefs.ascrate75; + } else { + if (depth * 2 > avg_depth) { + return prefs.ascrate50; + } else { + if (depth > 6000) + return prefs.ascratestops; + else + return prefs.ascratelast6m; + } + } +} + +void track_ascent_gas(int depth, cylinder_t *cylinder, int avg_depth, int bottom_time, bool safety_stop) +{ + while (depth > 0) { + int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; + if (deltad > depth) + deltad = depth; + update_cylinder_pressure(&displayed_dive, depth, depth - deltad, TIMESTEP, prefs.decosac, cylinder, true); + if (depth <= 5000 && depth >= (5000 - deltad) && safety_stop) { + update_cylinder_pressure(&displayed_dive, 5000, 5000, 180, prefs.decosac, cylinder, true); + safety_stop = false; + } + depth -= deltad; + } +} + +// Determine whether ascending to the next stop will break the ceiling. Return true if the ascent is ok, false if it isn't. +bool trial_ascent(int trial_depth, int stoplevel, int avg_depth, int bottom_time, struct gasmix *gasmix, int po2, double surface_pressure) +{ + + bool clear_to_ascend = true; + char *trial_cache = NULL; + + // For consistency with other VPM-B implementations, we should not start the ascent while the ceiling is + // deeper than the next stop (thus the offgasing during the ascent is ignored). + // However, we still need to make sure we don't break the ceiling due to on-gassing during ascent. + if (prefs.deco_mode == VPMB && (deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, + depth_to_bar(stoplevel, &displayed_dive)), + surface_pressure, &displayed_dive, 1) > stoplevel)) + return false; + + cache_deco_state(&trial_cache); + while (trial_depth > stoplevel) { + int deltad = ascent_velocity(trial_depth, avg_depth, bottom_time) * TIMESTEP; + if (deltad > trial_depth) /* don't test against depth above surface */ + deltad = trial_depth; + add_segment(depth_to_bar(trial_depth, &displayed_dive), + gasmix, + TIMESTEP, po2, &displayed_dive, prefs.decosac); + if (deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, depth_to_bar(trial_depth, &displayed_dive)), + surface_pressure, &displayed_dive, 1) > trial_depth - deltad) { + /* We should have stopped */ + clear_to_ascend = false; + break; + } + trial_depth -= deltad; + } + restore_deco_state(trial_cache); + free(trial_cache); + return clear_to_ascend; +} + +/* Determine if there is enough gas for the dive. Return true if there is enough. + * Also return true if this cannot be calculated because the cylinder doesn't have + * size or a starting pressure. + */ +bool enough_gas(int current_cylinder) +{ + cylinder_t *cyl; + cyl = &displayed_dive.cylinder[current_cylinder]; + + if (!cyl->start.mbar) + return true; + if (cyl->type.size.mliter) + return (float) (cyl->end.mbar - prefs.reserve_gas) * cyl->type.size.mliter / 1000.0 > (float) cyl->deco_gas_used.mliter; + else + return true; +} + +// Work out the stops. Return value is if there were any mandatory stops. + +bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer) +{ + int bottom_depth; + int bottom_gi; + int bottom_stopidx; + bool is_final_plan = true; + int deco_time; + int previous_deco_time; + char *bottom_cache = NULL; + struct sample *sample; + int po2; + int transitiontime, gi; + int current_cylinder; + int stopidx; + int depth; + struct gaschanges *gaschanges = NULL; + int gaschangenr; + int *decostoplevels; + int decostoplevelcount; + int *stoplevels = NULL; + bool stopping = false; + bool pendinggaschange = false; + int clock, previous_point_time; + int avg_depth, max_depth, bottom_time = 0; + int last_ascend_rate; + int best_first_ascend_cylinder; + struct gasmix gas, bottom_gas; + int o2time = 0; + int breaktime = -1; + int breakcylinder = 0; + int error = 0; + bool decodive = false; + + set_gf(diveplan->gflow, diveplan->gfhigh, prefs.gf_low_at_maxdepth); + if (!diveplan->surface_pressure) + diveplan->surface_pressure = SURFACE_PRESSURE; + displayed_dive.surface_pressure.mbar = diveplan->surface_pressure; + clear_deco(displayed_dive.surface_pressure.mbar / 1000.0); + max_bottom_ceiling_pressure.mbar = first_ceiling_pressure.mbar = 0; + create_dive_from_plan(diveplan, is_planner); + + // Do we want deco stop array in metres or feet? + if (prefs.units.length == METERS ) { + decostoplevels = decostoplevels_metric; + decostoplevelcount = sizeof(decostoplevels_metric) / sizeof(int); + } else { + decostoplevels = decostoplevels_imperial; + decostoplevelcount = sizeof(decostoplevels_imperial) / sizeof(int); + } + + /* If the user has selected last stop to be at 6m/20', we need to get rid of the 3m/10' stop. + * Otherwise reinstate the last stop 3m/10' stop. + */ + if (prefs.last_stop) + *(decostoplevels + 1) = 0; + else + *(decostoplevels + 1) = M_OR_FT(3,10); + + /* Let's start at the last 'sample', i.e. the last manually entered waypoint. */ + sample = &displayed_dive.dc.sample[displayed_dive.dc.samples - 1]; + + get_gas_at_time(&displayed_dive, &displayed_dive.dc, sample->time, &gas); + + po2 = sample->setpoint.mbar; + if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) { + report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); + current_cylinder = 0; + } + depth = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].depth.mm; + average_max_depth(diveplan, &avg_depth, &max_depth); + last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); + + /* if all we wanted was the dive just get us back to the surface */ + if (!is_planner) { + transitiontime = depth / 75; /* this still needs to be made configurable */ + plan_add_segment(diveplan, transitiontime, 0, gas, po2, false); + create_dive_from_plan(diveplan, is_planner); + return(false); + } + +#if DEBUG_PLAN & 4 + printf("gas %s\n", gasname(&gas)); + printf("depth %5.2lfm \n", depth / 1000.0); +#endif + + best_first_ascend_cylinder = current_cylinder; + /* Find the gases available for deco */ + + if (po2) { // Don't change gas in CCR mode + gaschanges = NULL; + gaschangenr = 0; + } else { + gaschanges = analyze_gaslist(diveplan, &gaschangenr, depth, &best_first_ascend_cylinder); + } + /* Find the first potential decostopdepth above current depth */ + for (stopidx = 0; stopidx < decostoplevelcount; stopidx++) + if (*(decostoplevels + stopidx) >= depth) + break; + if (stopidx > 0) + stopidx--; + /* Stoplevels are either depths of gas changes or potential deco stop depths. */ + stoplevels = sort_stops(decostoplevels, stopidx + 1, gaschanges, gaschangenr); + stopidx += gaschangenr; + + /* Keep time during the ascend */ + bottom_time = clock = previous_point_time = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].time.seconds; + gi = gaschangenr - 1; + + /* Set tissue tolerance and initial vpmb gradient at start of ascent phase */ + tissue_at_end(&displayed_dive, cached_datap); + nuclear_regeneration(clock); + vpmb_start_gradient(); + + if(prefs.deco_mode == RECREATIONAL) { + bool safety_stop = prefs.safetystop && max_depth >= 10000; + track_ascent_gas(depth, &displayed_dive.cylinder[current_cylinder], avg_depth, bottom_time, safety_stop); + // How long can we stay at the current depth and still directly ascent to the surface? + do { + add_segment(depth_to_bar(depth, &displayed_dive), + &displayed_dive.cylinder[current_cylinder].gasmix, + DECOTIMESTEP, po2, &displayed_dive, prefs.bottomsac); + update_cylinder_pressure(&displayed_dive, depth, depth, DECOTIMESTEP, prefs.bottomsac, &displayed_dive.cylinder[current_cylinder], false); + clock += DECOTIMESTEP; + } while (trial_ascent(depth, 0, avg_depth, bottom_time, &displayed_dive.cylinder[current_cylinder].gasmix, + po2, diveplan->surface_pressure / 1000.0) && + enough_gas(current_cylinder)); + + // We did stay one DECOTIMESTEP too many. + // In the best of all worlds, we would roll back also the last add_segment in terms of caching deco state, but + // let's ignore that since for the eventual ascent in recreational mode, nobody looks at the ceiling anymore, + // so we don't really have to compute the deco state. + update_cylinder_pressure(&displayed_dive, depth, depth, -DECOTIMESTEP, prefs.bottomsac, &displayed_dive.cylinder[current_cylinder], false); + clock -= DECOTIMESTEP; + plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, true); + previous_point_time = clock; + do { + /* Ascend to surface */ + int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; + if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) { + plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); + previous_point_time = clock; + last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); + } + if (depth - deltad < 0) + deltad = depth; + + clock += TIMESTEP; + depth -= deltad; + if (depth <= 5000 && depth >= (5000 - deltad) && safety_stop) { + plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false); + previous_point_time = clock; + clock += 180; + plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false); + previous_point_time = clock; + safety_stop = false; + } + } while (depth > 0); + plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false); + create_dive_from_plan(diveplan, is_planner); + add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error); + fixup_dc_duration(&displayed_dive.dc); + + free(stoplevels); + free(gaschanges); + return(false); + } + + if (best_first_ascend_cylinder != current_cylinder) { + current_cylinder = best_first_ascend_cylinder; + gas = displayed_dive.cylinder[current_cylinder].gasmix; + +#if DEBUG_PLAN & 16 + printf("switch to gas %d (%d/%d) @ %5.2lfm\n", best_first_ascend_cylinder, + (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[best_first_ascend_cylinder].depth / 1000.0); +#endif + } + + // VPM-B or Buehlmann Deco + tissue_at_end(&displayed_dive, cached_datap); + previous_deco_time = 100000000; + deco_time = 10000000; + cache_deco_state(&bottom_cache); // Lets us make several iterations + bottom_depth = depth; + bottom_gi = gi; + bottom_gas = gas; + bottom_stopidx = stopidx; + + //CVA + do { + is_final_plan = (prefs.deco_mode == BUEHLMANN) || (previous_deco_time - deco_time < 10); // CVA time converges + if (deco_time != 10000000) + vpmb_next_gradient(deco_time, diveplan->surface_pressure / 1000.0); + + previous_deco_time = deco_time; + restore_deco_state(bottom_cache); + + depth = bottom_depth; + gi = bottom_gi; + clock = previous_point_time = bottom_time; + gas = bottom_gas; + stopping = false; + decodive = false; + stopidx = bottom_stopidx; + breaktime = -1; + breakcylinder = 0; + o2time = 0; + first_ceiling_pressure.mbar = depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, + depth_to_bar(depth, &displayed_dive)), + diveplan->surface_pressure / 1000.0, + &displayed_dive, + 1), + &displayed_dive); + if (max_bottom_ceiling_pressure.mbar > first_ceiling_pressure.mbar) + first_ceiling_pressure.mbar = max_bottom_ceiling_pressure.mbar; + + last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); + if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) { + report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); + current_cylinder = 0; + } + + while (1) { + /* We will break out when we hit the surface */ + do { + /* Ascend to next stop depth */ + int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; + if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) { + if (is_final_plan) + plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); + previous_point_time = clock; + stopping = false; + last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); + } + if (depth - deltad < stoplevels[stopidx]) + deltad = depth - stoplevels[stopidx]; + + add_segment(depth_to_bar(depth, &displayed_dive), + &displayed_dive.cylinder[current_cylinder].gasmix, + TIMESTEP, po2, &displayed_dive, prefs.decosac); + clock += TIMESTEP; + depth -= deltad; + } while (depth > 0 && depth > stoplevels[stopidx]); + + if (depth <= 0) + break; /* We are at the surface */ + + if (gi >= 0 && stoplevels[stopidx] <= gaschanges[gi].depth) { + /* We have reached a gas change. + * Record this in the dive plan */ + if (is_final_plan) + plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); + previous_point_time = clock; + stopping = true; + + /* Check we need to change cylinder. + * We might not if the cylinder was chosen by the user + * or user has selected only to switch only at required stops. + * If current gas is hypoxic, we want to switch asap */ + + if (current_cylinder != gaschanges[gi].gasidx) { + if (!prefs.switch_at_req_stop || + !trial_ascent(depth, stoplevels[stopidx - 1], avg_depth, bottom_time, + &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0) || get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) < 160) { + current_cylinder = gaschanges[gi].gasidx; + gas = displayed_dive.cylinder[current_cylinder].gasmix; +#if DEBUG_PLAN & 16 + printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi].gasidx, + (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi].depth / 1000.0); +#endif + /* Stop for the minimum duration to switch gas */ + add_segment(depth_to_bar(depth, &displayed_dive), + &displayed_dive.cylinder[current_cylinder].gasmix, + prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac); + clock += prefs.min_switch_duration; + if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) + o2time += prefs.min_switch_duration; + } else { + /* The user has selected the option to switch gas only at required stops. + * Remember that we are waiting to switch gas + */ + pendinggaschange = true; + } + } + gi--; + } + --stopidx; + + /* Save the current state and try to ascend to the next stopdepth */ + while (1) { + /* Check if ascending to next stop is clear, go back and wait if we hit the ceiling on the way */ + if (trial_ascent(depth, stoplevels[stopidx], avg_depth, bottom_time, + &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0)) + break; /* We did not hit the ceiling */ + + /* Add a minute of deco time and then try again */ + decodive = true; + if (!stopping) { + /* The last segment was an ascend segment. + * Add a waypoint for start of this deco stop */ + if (is_final_plan) + plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); + previous_point_time = clock; + stopping = true; + } + + /* Are we waiting to switch gas? + * Occurs when the user has selected the option to switch only at required stops + */ + if (pendinggaschange) { + current_cylinder = gaschanges[gi + 1].gasidx; + gas = displayed_dive.cylinder[current_cylinder].gasmix; +#if DEBUG_PLAN & 16 + printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi + 1].gasidx, + (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi + 1].depth / 1000.0); +#endif + /* Stop for the minimum duration to switch gas */ + add_segment(depth_to_bar(depth, &displayed_dive), + &displayed_dive.cylinder[current_cylinder].gasmix, + prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac); + clock += prefs.min_switch_duration; + if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) + o2time += prefs.min_switch_duration; + pendinggaschange = false; + } + + /* Deco stop should end when runtime is at a whole minute */ + int this_decotimestep; + this_decotimestep = DECOTIMESTEP - clock % DECOTIMESTEP; + + add_segment(depth_to_bar(depth, &displayed_dive), + &displayed_dive.cylinder[current_cylinder].gasmix, + this_decotimestep, po2, &displayed_dive, prefs.decosac); + clock += this_decotimestep; + /* Finish infinite deco */ + if(clock >= 48 * 3600 && depth >= 6000) { + error = LONGDECO; + break; + } + if (prefs.doo2breaks) { + /* The backgas breaks option limits time on oxygen to 12 minutes, followed by 6 minutes on + * backgas (first defined gas). This could be customized if there were demand. + */ + if (get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) { + o2time += DECOTIMESTEP; + if (o2time >= 12 * 60) { + breaktime = 0; + breakcylinder = current_cylinder; + if (is_final_plan) + plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); + previous_point_time = clock; + current_cylinder = 0; + gas = displayed_dive.cylinder[current_cylinder].gasmix; + } + } else { + if (breaktime >= 0) { + breaktime += DECOTIMESTEP; + if (breaktime >= 6 * 60) { + o2time = 0; + if (is_final_plan) + plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); + previous_point_time = clock; + current_cylinder = breakcylinder; + gas = displayed_dive.cylinder[current_cylinder].gasmix; + breaktime = -1; + } + } + } + } + } + if (stopping) { + /* Next we will ascend again. Add a waypoint if we have spend deco time */ + if (is_final_plan) + plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); + previous_point_time = clock; + stopping = false; + } + } + + deco_time = clock - bottom_time; + } while (!is_final_plan); + + plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false); + create_dive_from_plan(diveplan, is_planner); + add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error); + fixup_dc_duration(&displayed_dive.dc); + + free(stoplevels); + free(gaschanges); + free(bottom_cache); + return decodive; +} + +/* + * Get a value in tenths (so "10.2" == 102, "9" = 90) + * + * Return negative for errors. + */ +static int get_tenths(const char *begin, const char **endp) +{ + char *end; + int value = strtol(begin, &end, 10); + + if (begin == end) + return -1; + value *= 10; + + /* Fraction? We only look at the first digit */ + if (*end == '.') { + end++; + if (!isdigit(*end)) + return -1; + value += *end - '0'; + do { + end++; + } while (isdigit(*end)); + } + *endp = end; + return value; +} + +static int get_permille(const char *begin, const char **end) +{ + int value = get_tenths(begin, end); + if (value >= 0) { + /* Allow a percentage sign */ + if (**end == '%') + ++*end; + } + return value; +} + +int validate_gas(const char *text, struct gasmix *gas) +{ + int o2, he; + + if (!text) + return 0; + + while (isspace(*text)) + text++; + + if (!*text) + return 0; + + if (!strcasecmp(text, translate("gettextFromC", "air"))) { + o2 = O2_IN_AIR; + he = 0; + text += strlen(translate("gettextFromC", "air")); + } else if (!strcasecmp(text, translate("gettextFromC", "oxygen"))) { + o2 = 1000; + he = 0; + text += strlen(translate("gettextFromC", "oxygen")); + } else if (!strncasecmp(text, translate("gettextFromC", "ean"), 3)) { + o2 = get_permille(text + 3, &text); + he = 0; + } else { + o2 = get_permille(text, &text); + he = 0; + if (*text == '/') + he = get_permille(text + 1, &text); + } + + /* We don't want any extra crud */ + while (isspace(*text)) + text++; + if (*text) + return 0; + + /* Validate the gas mix */ + if (*text || o2 < 1 || o2 > 1000 || he < 0 || o2 + he > 1000) + return 0; + + /* Let it rip */ + gas->o2.permille = o2; + gas->he.permille = he; + return 1; +} + +int validate_po2(const char *text, int *mbar_po2) +{ + int po2; + + if (!text) + return 0; + + po2 = get_tenths(text, &text); + if (po2 < 0) + return 0; + + while (isspace(*text)) + text++; + + while (isspace(*text)) + text++; + if (*text) + return 0; + + *mbar_po2 = po2 * 100; + return 1; +} diff --git a/core/planner.h b/core/planner.h new file mode 100644 index 000000000..a675989e0 --- /dev/null +++ b/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/core/pluginmanager.cpp b/core/pluginmanager.cpp new file mode 100644 index 000000000..28c978280 --- /dev/null +++ b/core/pluginmanager.cpp @@ -0,0 +1,53 @@ +#include "pluginmanager.h" + +#include +#include +#include +#include + +static QList _socialNetworks; + +PluginManager& PluginManager::instance() +{ + static PluginManager self; + return self; +} + +PluginManager::PluginManager() +{ +} + +void PluginManager::loadPlugins() +{ + QDir pluginsDir(qApp->applicationDirPath()); + +#if defined(Q_OS_WIN) + if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release") + pluginsDir.cdUp(); +#elif defined(Q_OS_MAC) + if (pluginsDir.dirName() == "MacOS") { + pluginsDir.cdUp(); + pluginsDir.cdUp(); + pluginsDir.cdUp(); + } +#endif + pluginsDir.cd("plugins"); + + qDebug() << "Plugins Directory: " << pluginsDir; + foreach (const QString& fileName, pluginsDir.entryList(QDir::Files)) { + QPluginLoader loader(pluginsDir.absoluteFilePath(fileName)); + QObject *plugin = loader.instance(); + if(!plugin) + continue; + + if (ISocialNetworkIntegration *social = qobject_cast(plugin)) { + qDebug() << "Adding the plugin: " << social->socialNetworkName(); + _socialNetworks.push_back(social); + } + } +} + +QList PluginManager::socialNetworkIntegrationPlugins() const +{ + return _socialNetworks; +} diff --git a/core/pluginmanager.h b/core/pluginmanager.h new file mode 100644 index 000000000..3f43b5db1 --- /dev/null +++ b/core/pluginmanager.h @@ -0,0 +1,19 @@ +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include + +#include "isocialnetworkintegration.h" + +class PluginManager { +public: + static PluginManager& instance(); + void loadPlugins(); + QList socialNetworkIntegrationPlugins() const; +private: + PluginManager(); + PluginManager(const PluginManager&); + PluginManager& operator=(const PluginManager&); +}; + +#endif diff --git a/core/pref.h b/core/pref.h new file mode 100644 index 000000000..be684fd90 --- /dev/null +++ b/core/pref.h @@ -0,0 +1,172 @@ +#ifndef PREF_H +#define PREF_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "units.h" +#include "taxonomy.h" + +/* can't use 'bool' for the boolean values - different size in C and C++ */ +typedef struct +{ + short po2; + short pn2; + short phe; + double po2_threshold; + double pn2_threshold; + double phe_threshold; +} partial_pressure_graphs_t; + +typedef struct { + char *access_token; + char *user_id; + char *album_id; +} facebook_prefs_t; + +typedef struct { + bool enable_geocoding; + bool parse_dive_without_gps; + bool tag_existing_dives; + enum taxonomy_category category[3]; +} geocoding_prefs_t; + +typedef struct { + const char *language; + bool use_system_language; +} locale_prefs_t; + +enum deco_mode { + BUEHLMANN, + RECREATIONAL, + VPMB +}; + +struct preferences { + const char *divelist_font; + const char *default_filename; + const char *default_cylinder; + const char *cloud_base_url; + const char *cloud_git_url; + const char *time_format; + const char *date_format; + const char *date_format_short; + bool time_format_override; + bool date_format_override; + double font_size; + partial_pressure_graphs_t pp_graphs; + short mod; + double modpO2; + short ead; + short dcceiling; + short redceiling; + short calcceiling; + short calcceiling3m; + short calcalltissues; + short calcndltts; + short gflow; + short gfhigh; + int animation_speed; + bool gf_low_at_maxdepth; + bool show_ccr_setpoint; + bool show_ccr_sensors; + short display_invalid_dives; + short unit_system; + struct units units; + bool coordinates_traditional; + short show_sac; + short display_unused_tanks; + short show_average_depth; + short zoomed_plot; + short hrgraph; + short percentagegraph; + short rulergraph; + short tankbar; + short save_userid_local; + char *userid; + int ascrate75; // All rates in mm / sec + int ascrate50; + int ascratestops; + int ascratelast6m; + int descrate; + int bottompo2; + int decopo2; + int proxy_type; + char *proxy_host; + int proxy_port; + short proxy_auth; + char *proxy_user; + char *proxy_pass; + bool doo2breaks; + bool drop_stone_mode; + bool last_stop; // At 6m? + bool verbatim_plan; + bool display_runtime; + bool display_duration; + bool display_transitions; + bool safetystop; + bool switch_at_req_stop; + int reserve_gas; + int min_switch_duration; // seconds + int bottomsac; + int decosac; + int o2consumption; // ml per min + int pscr_ratio; // dump ratio times 1000 + int defaultsetpoint; // default setpoint in mbar + bool show_pictures_in_profile; + bool use_default_file; + short default_file_behavior; + facebook_prefs_t facebook; + char *cloud_storage_password; + char *cloud_storage_newpassword; + char *cloud_storage_email; + char *cloud_storage_email_encoded; + bool save_password_local; + short cloud_verification_status; + bool cloud_background_sync; + geocoding_prefs_t geocoding; + enum deco_mode deco_mode; + short conservatism_level; + int time_threshold; + int distance_threshold; + bool git_local_only; + locale_prefs_t locale; //: TODO: move the rest of locale based info here. +}; +enum unit_system_values { + METRIC, + IMPERIAL, + PERSONALIZE +}; + +enum def_file_behavior { + UNDEFINED_DEFAULT_FILE, + LOCAL_DEFAULT_FILE, + NO_DEFAULT_FILE, + CLOUD_DEFAULT_FILE +}; + +enum cloud_status { + CS_UNKNOWN, + CS_INCORRECT_USER_PASSWD, + CS_NEED_TO_VERIFY, + CS_VERIFIED +}; + +extern struct preferences prefs, default_prefs, informational_prefs; + +#define PP_GRAPHS_ENABLED (prefs.pp_graphs.po2 || prefs.pp_graphs.pn2 || prefs.pp_graphs.phe) + +extern const char *system_divelist_default_font; +extern double system_divelist_default_font_size; + +extern const char *system_default_directory(void); +extern const char *system_default_filename(); +extern bool subsurface_ignore_font(const char *font); +extern void subsurface_OS_pref_setup(); + +#ifdef __cplusplus +} +#endif + +#endif // PREF_H diff --git a/core/prefs-macros.h b/core/prefs-macros.h new file mode 100644 index 000000000..fe459d3da --- /dev/null +++ b/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/core/profile.c b/core/profile.c new file mode 100644 index 000000000..6576f6453 --- /dev/null +++ b/core/profile.c @@ -0,0 +1,1544 @@ +/* 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); + +extern bool in_planner(); +extern pressure_t first_ceiling_pressure; + +#ifdef DEBUG_PI +/* debugging tool - not normally used */ +static void dump_pi(struct plot_info *pi) +{ + int i; + + printf("pi:{nr:%d maxtime:%d meandepth:%d maxdepth:%d \n" + " maxpressure:%d mintemp:%d maxtemp:%d\n", + pi->nr, pi->maxtime, pi->meandepth, pi->maxdepth, + pi->maxpressure, pi->mintemp, pi->maxtemp); + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = &pi->entry[i]; + printf(" entry[%d]:{cylinderindex:%d sec:%d pressure:{%d,%d}\n" + " time:%d:%02d temperature:%d depth:%d stopdepth:%d stoptime:%d ndl:%d smoothed:%d po2:%lf phe:%lf pn2:%lf sum-pp %lf}\n", + i, entry->cylinderindex, entry->sec, + entry->pressure[0], entry->pressure[1], + entry->sec / 60, entry->sec % 60, + entry->temperature, entry->depth, entry->stopdepth, entry->stoptime, entry->ndl, entry->smoothed, + entry->pressures.o2, entry->pressures.he, entry->pressures.n2, + entry->pressures.o2 + entry->pressures.he + entry->pressures.n2); + } + printf(" }\n"); +} +#endif + +#define ROUND_UP(x, y) ((((x) + (y) - 1) / (y)) * (y)) +#define DIV_UP(x, y) (((x) + (y) - 1) / (y)) + +/* + * When showing dive profiles, we scale things to the + * current dive. However, we don't scale past less than + * 30 minutes or 90 ft, just so that small dives show + * up as such unless zoom is enabled. + * We also need to add 180 seconds at the end so the min/max + * plots correctly + */ +int get_maxtime(struct plot_info *pi) +{ + int seconds = pi->maxtime; + + int DURATION_THR = (pi->dive_type == FREEDIVING ? 60 : 600); + int CEILING = (pi->dive_type == FREEDIVING ? 30 : 60); + + if (prefs.zoomed_plot) { + /* Rounded up to one minute, with at least 2.5 minutes to + * spare. + * For dive times shorter than 10 minutes, we use seconds/4 to + * calculate the space dynamically. + * This is seamless since 600/4 = 150. + */ + if (seconds < DURATION_THR) + return ROUND_UP(seconds + seconds / 4, CEILING); + else + return ROUND_UP(seconds + DURATION_THR/4, CEILING); + } else { +#ifndef SUBSURFACE_MOBILE + /* min 30 minutes, rounded up to 5 minutes, with at least 2.5 minutes to spare */ + return MAX(30 * 60, ROUND_UP(seconds + DURATION_THR/4, CEILING * 5)); +#else + /* just add 2.5 minutes so we have a consistant right margin */ + return seconds + DURATION_THR / 4; +#endif + } +} + +/* get the maximum depth to which we want to plot + * take into account the additional vertical space needed to plot + * partial pressure graphs */ +int get_maxdepth(struct plot_info *pi) +{ + unsigned mm = pi->maxdepth; + int md; + + if (prefs.zoomed_plot) { + /* Rounded up to 10m, with at least 3m to spare */ + md = ROUND_UP(mm + 3000, 10000); + } else { + /* Minimum 30m, rounded up to 10m, with at least 3m to spare */ + md = MAX((unsigned)30000, ROUND_UP(mm + 3000, 10000)); + } + md += pi->maxpp * 9000; + return md; +} + +/* collect all event names and whether we display them */ +struct ev_select *ev_namelist; +int evn_allocated; +int evn_used; + +#if WE_DONT_USE_THIS /* we need to implement event filters in Qt */ +int evn_foreach (void (*callback)(const char *, bool *, void *), void *data) { + int i; + + for (i = 0; i < evn_used; i++) { + /* here we display an event name on screen - so translate */ + callback(translate("gettextFromC", ev_namelist[i].ev_name), &ev_namelist[i].plot_ev, data); + } + return i; +} +#endif /* WE_DONT_USE_THIS */ + +void clear_events(void) +{ + for (int i = 0; i < evn_used; i++) + free(ev_namelist[i].ev_name); + evn_used = 0; +} + +void remember_event(const char *eventname) +{ + int i = 0, len; + + if (!eventname || (len = strlen(eventname)) == 0) + return; + while (i < evn_used) { + if (!strncmp(eventname, ev_namelist[i].ev_name, len)) + return; + i++; + } + if (evn_used == evn_allocated) { + evn_allocated += 10; + ev_namelist = realloc(ev_namelist, evn_allocated * sizeof(struct ev_select)); + if (!ev_namelist) + /* we are screwed, but let's just bail out */ + return; + } + ev_namelist[evn_used].ev_name = strdup(eventname); + ev_namelist[evn_used].plot_ev = true; + evn_used++; +} + +/* UNUSED! */ +static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive) __attribute__((unused)); + +/* Get local sac-rate (in ml/min) between entry1 and entry2 */ +static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive) +{ + int index = entry1->cylinderindex; + cylinder_t *cyl; + int duration = entry2->sec - entry1->sec; + int depth, airuse; + pressure_t a, b; + double atm; + + if (entry2->cylinderindex != index) + return 0; + if (duration <= 0) + return 0; + a.mbar = GET_PRESSURE(entry1); + b.mbar = GET_PRESSURE(entry2); + if (!b.mbar || a.mbar <= b.mbar) + return 0; + + /* Mean pressure in ATM */ + depth = (entry1->depth + entry2->depth) / 2; + atm = depth_to_atm(depth, dive); + + cyl = dive->cylinder + index; + + airuse = gas_volume(cyl, a) - gas_volume(cyl, b); + + /* milliliters per minute */ + return airuse / atm * 60 / duration; +} + +static void analyze_plot_info_minmax_minute(struct plot_data *entry, struct plot_data *first, struct plot_data *last, int index) +{ + struct plot_data *p = entry; + int time = entry->sec; + int seconds = 90 * (index + 1); + struct plot_data *min, *max; + int avg, nr; + + /* Go back 'seconds' in time */ + while (p > first) { + if (p[-1].sec < time - seconds) + break; + p--; + } + + /* Then go forward until we hit an entry past the time */ + min = max = p; + avg = p->depth; + nr = 1; + while (++p < last) { + int depth = p->depth; + if (p->sec > time + seconds) + break; + avg += depth; + nr++; + if (depth < min->depth) + min = p; + if (depth > max->depth) + max = p; + } + entry->min[index] = min; + entry->max[index] = max; + entry->avg[index] = (avg + nr / 2) / nr; +} + +static void analyze_plot_info_minmax(struct plot_data *entry, struct plot_data *first, struct plot_data *last) +{ + analyze_plot_info_minmax_minute(entry, first, last, 0); + analyze_plot_info_minmax_minute(entry, first, last, 1); + analyze_plot_info_minmax_minute(entry, first, last, 2); +} + +static velocity_t velocity(int speed) +{ + velocity_t v; + + if (speed < -304) /* ascent faster than -60ft/min */ + v = CRAZY; + else if (speed < -152) /* above -30ft/min */ + v = FAST; + else if (speed < -76) /* -15ft/min */ + v = MODERATE; + else if (speed < -25) /* -5ft/min */ + v = SLOW; + else if (speed < 25) /* very hard to find data, but it appears that the recommendations + for descent are usually about 2x ascent rate; still, we want + stable to mean stable */ + v = STABLE; + else if (speed < 152) /* between 5 and 30ft/min is considered slow */ + v = SLOW; + else if (speed < 304) /* up to 60ft/min is moderate */ + v = MODERATE; + else if (speed < 507) /* up to 100ft/min is fast */ + v = FAST; + else /* more than that is just crazy - you'll blow your ears out */ + v = CRAZY; + + return v; +} + +struct plot_info *analyze_plot_info(struct plot_info *pi) +{ + int i; + int nr = pi->nr; + + /* Smoothing function: 5-point triangular smooth */ + for (i = 2; i < nr; i++) { + struct plot_data *entry = pi->entry + i; + int depth; + + if (i < nr - 2) { + depth = entry[-2].depth + 2 * entry[-1].depth + 3 * entry[0].depth + 2 * entry[1].depth + entry[2].depth; + entry->smoothed = (depth + 4) / 9; + } + /* vertical velocity in mm/sec */ + /* Linus wants to smooth this - let's at least look at the samples that aren't FAST or CRAZY */ + if (entry[0].sec - entry[-1].sec) { + entry->speed = (entry[0].depth - entry[-1].depth) / (entry[0].sec - entry[-1].sec); + entry->velocity = velocity(entry->speed); + /* if our samples are short and we aren't too FAST*/ + if (entry[0].sec - entry[-1].sec < 15 && entry->velocity < FAST) { + int past = -2; + while (i + past > 0 && entry[0].sec - entry[past].sec < 15) + past--; + entry->velocity = velocity((entry[0].depth - entry[past].depth) / + (entry[0].sec - entry[past].sec)); + } + } else { + entry->velocity = STABLE; + entry->speed = 0; + } + } + + /* One-, two- and three-minute minmax data */ + for (i = 0; i < nr; i++) { + struct plot_data *entry = pi->entry + i; + analyze_plot_info_minmax(entry, pi->entry, pi->entry + nr); + } + + return pi; +} + +/* + * If the event has an explicit cylinder index, + * we return that. If it doesn't, we return the best + * match based on the gasmix. + * + * Some dive computers give cylinder indexes, some + * give just the gas mix. + */ +int get_cylinder_index(struct dive *dive, struct event *ev) +{ + int i; + int best = 0, score = INT_MAX; + int target_o2, target_he; + struct gasmix *g; + + if (ev->gas.index >= 0) + return ev->gas.index; + + g = get_gasmix_from_event(ev); + target_o2 = get_o2(g); + target_he = get_he(g); + + /* + * Try to find a cylinder that best matches the target gas + * mix. + */ + for (i = 0; i < MAX_CYLINDERS; i++) { + cylinder_t *cyl = dive->cylinder + i; + int delta_o2, delta_he, distance; + + if (cylinder_nodata(cyl)) + continue; + + delta_o2 = get_o2(&cyl->gasmix) - target_o2; + delta_he = get_he(&cyl->gasmix) - target_he; + distance = delta_o2 * delta_o2; + distance += delta_he * delta_he; + + if (distance >= score) + continue; + score = distance; + best = i; + } + return best; +} + +struct event *get_next_event(struct event *event, const char *name) +{ + if (!name || !*name) + return NULL; + while (event) { + if (!strcmp(event->name, name)) + return event; + event = event->next; + } + return event; +} + +static int count_events(struct divecomputer *dc) +{ + int result = 0; + struct event *ev = dc->events; + while (ev != NULL) { + result++; + ev = ev->next; + } + return result; +} + +static int set_cylinder_index(struct plot_info *pi, int i, int cylinderindex, int end) +{ + while (i < pi->nr) { + struct plot_data *entry = pi->entry + i; + if (entry->sec > end) + break; + if (entry->cylinderindex != cylinderindex) { + entry->cylinderindex = cylinderindex; + entry->pressure[0] = 0; + } + i++; + } + return i; +} + +static int set_setpoint(struct plot_info *pi, int i, int setpoint, int end) +{ + while (i < pi->nr) { + struct plot_data *entry = pi->entry + i; + if (entry->sec > end) + break; + entry->o2pressure.mbar = setpoint; + i++; + } + return i; +} + +/* normally the first cylinder has index 0... if not, we need to fix this up here */ +static int set_first_cylinder_index(struct plot_info *pi, int i, int cylinderindex, int end) +{ + while (i < pi->nr) { + struct plot_data *entry = pi->entry + i; + if (entry->sec > end) + break; + entry->cylinderindex = cylinderindex; + i++; + } + return i; +} + +static void check_gas_change_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) +{ + int i = 0, cylinderindex = 0; + struct event *ev = get_next_event(dc->events, "gaschange"); + + // for dive computers that tell us their first gas as an event on the first sample + // we need to make sure things are setup correctly + cylinderindex = explicit_first_cylinder(dive, dc); + set_first_cylinder_index(pi, 0, cylinderindex, INT_MAX); + + if (!ev) + return; + + do { + i = set_cylinder_index(pi, i, cylinderindex, ev->time.seconds); + cylinderindex = get_cylinder_index(dive, ev); + ev = get_next_event(ev->next, "gaschange"); + } while (ev); + set_cylinder_index(pi, i, cylinderindex, INT_MAX); +} + +static void check_setpoint_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) +{ + int i = 0; + pressure_t setpoint; + (void) dive; + setpoint.mbar = 0; + struct event *ev = get_next_event(dc->events, "SP change"); + + if (!ev) + return; + + do { + i = set_setpoint(pi, i, setpoint.mbar, ev->time.seconds); + setpoint.mbar = ev->value; + if (setpoint.mbar) + dc->divemode = CCR; + ev = get_next_event(ev->next, "SP change"); + } while (ev); + set_setpoint(pi, i, setpoint.mbar, INT_MAX); +} + + +struct plot_info calculate_max_limits_new(struct dive *dive, struct divecomputer *given_dc) +{ + struct divecomputer *dc = &(dive->dc); + bool seen = false; + static struct plot_info pi; + int maxdepth = dive->maxdepth.mm; + unsigned int maxtime = 0; + int maxpressure = 0, minpressure = INT_MAX; + int maxhr = 0, minhr = INT_MAX; + int mintemp = dive->mintemp.mkelvin; + int maxtemp = dive->maxtemp.mkelvin; + int cyl; + + /* Get the per-cylinder maximum pressure if they are manual */ + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { + int mbar = dive->cylinder[cyl].start.mbar; + if (mbar > maxpressure) + maxpressure = mbar; + if (mbar < minpressure) + minpressure = mbar; + } + + /* Then do all the samples from all the dive computers */ + do { + if (dc == given_dc) + seen = true; + int i = dc->samples; + int lastdepth = 0; + struct sample *s = dc->sample; + + while (--i >= 0) { + int depth = s->depth.mm; + int pressure = s->cylinderpressure.mbar; + int temperature = s->temperature.mkelvin; + int heartbeat = s->heartbeat; + + if (!mintemp && temperature < mintemp) + mintemp = temperature; + if (temperature > maxtemp) + maxtemp = temperature; + + if (pressure && pressure < minpressure) + minpressure = pressure; + if (pressure > maxpressure) + maxpressure = pressure; + if (heartbeat > maxhr) + maxhr = heartbeat; + if (heartbeat < minhr) + minhr = heartbeat; + + if (depth > maxdepth) + maxdepth = s->depth.mm; + if ((depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) && + s->time.seconds > maxtime) + maxtime = s->time.seconds; + lastdepth = depth; + s++; + } + dc = dc->next; + if (dc == NULL && !seen) { + dc = given_dc; + seen = true; + } + } while (dc != NULL); + + if (minpressure > maxpressure) + minpressure = 0; + if (minhr > maxhr) + minhr = 0; + + memset(&pi, 0, sizeof(pi)); + pi.maxdepth = maxdepth; + pi.maxtime = maxtime; + pi.maxpressure = maxpressure; + pi.minpressure = minpressure; + pi.minhr = minhr; + pi.maxhr = maxhr; + pi.mintemp = mintemp; + pi.maxtemp = maxtemp; + return pi; +} + +/* copy the previous entry (we know this exists), update time and depth + * and zero out the sensor pressure (since this is a synthetic entry) + * increment the entry pointer and the count of synthetic entries. */ +#define INSERT_ENTRY(_time, _depth, _sac) \ + *entry = entry[-1]; \ + entry->sec = _time; \ + entry->depth = _depth; \ + entry->running_sum = (entry - 1)->running_sum + (_time - (entry - 1)->sec) * (_depth + (entry - 1)->depth) / 2; \ + SENSOR_PRESSURE(entry) = 0; \ + entry->sac = _sac; \ + entry++; \ + idx++ + +struct plot_data *populate_plot_entries(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) +{ + + int idx, maxtime, nr, i; + int lastdepth, lasttime, lasttemp = 0; + struct plot_data *plot_data; + struct event *ev = dc->events; + (void) dive; + maxtime = pi->maxtime; + + /* + * We want to have a plot_info event at least every 10s (so "maxtime/10+1"), + * but samples could be more dense than that (so add in dc->samples). We also + * need to have one for every event (so count events and add that) and + * additionally we want two surface events around the whole thing (thus the + * additional 4). There is also one extra space for a final entry + * that has time > maxtime (because there can be surface samples + * past "maxtime" in the original sample data) + */ + nr = dc->samples + 6 + maxtime / 10 + count_events(dc); + plot_data = calloc(nr, sizeof(struct plot_data)); + pi->entry = plot_data; + if (!plot_data) + return NULL; + pi->nr = nr; + idx = 2; /* the two extra events at the start */ + + lastdepth = 0; + lasttime = 0; + /* skip events at time = 0 */ + while (ev && ev->time.seconds == 0) + ev = ev->next; + for (i = 0; i < dc->samples; i++) { + struct plot_data *entry = plot_data + idx; + struct sample *sample = dc->sample + i; + int time = sample->time.seconds; + int offset, delta; + int depth = sample->depth.mm; + int sac = sample->sac.mliter; + + /* Add intermediate plot entries if required */ + delta = time - lasttime; + if (delta <= 0) { + time = lasttime; + delta = 1; // avoid divide by 0 + } + for (offset = 10; offset < delta; offset += 10) { + if (lasttime + offset > maxtime) + break; + + /* Add events if they are between plot entries */ + while (ev && (int)ev->time.seconds < lasttime + offset) { + INSERT_ENTRY(ev->time.seconds, interpolate(lastdepth, depth, ev->time.seconds - lasttime, delta), sac); + ev = ev->next; + } + + /* now insert the time interpolated entry */ + INSERT_ENTRY(lasttime + offset, interpolate(lastdepth, depth, offset, delta), sac); + + /* skip events that happened at this time */ + while (ev && (int)ev->time.seconds == lasttime + offset) + ev = ev->next; + } + + /* Add events if they are between plot entries */ + while (ev && (int)ev->time.seconds < time) { + INSERT_ENTRY(ev->time.seconds, interpolate(lastdepth, depth, ev->time.seconds - lasttime, delta), sac); + ev = ev->next; + } + + + entry->sec = time; + entry->depth = depth; + + entry->running_sum = (entry - 1)->running_sum + (time - (entry - 1)->sec) * (depth + (entry - 1)->depth) / 2; + entry->stopdepth = sample->stopdepth.mm; + entry->stoptime = sample->stoptime.seconds; + entry->ndl = sample->ndl.seconds; + entry->tts = sample->tts.seconds; + pi->has_ndl |= sample->ndl.seconds; + entry->in_deco = sample->in_deco; + entry->cns = sample->cns; + if (dc->divemode == CCR) { + entry->o2pressure.mbar = entry->o2setpoint.mbar = sample->setpoint.mbar; // for rebreathers + entry->o2sensor[0].mbar = sample->o2sensor[0].mbar; // for up to three rebreather O2 sensors + entry->o2sensor[1].mbar = sample->o2sensor[1].mbar; + entry->o2sensor[2].mbar = sample->o2sensor[2].mbar; + } else { + entry->pressures.o2 = sample->setpoint.mbar / 1000.0; + } + /* FIXME! sensor index -> cylinder index translation! */ + // entry->cylinderindex = sample->sensor; + SENSOR_PRESSURE(entry) = sample->cylinderpressure.mbar; + O2CYLINDER_PRESSURE(entry) = sample->o2cylinderpressure.mbar; + if (sample->temperature.mkelvin) + entry->temperature = lasttemp = sample->temperature.mkelvin; + else + entry->temperature = lasttemp; + entry->heartbeat = sample->heartbeat; + entry->bearing = sample->bearing.degrees; + entry->sac = sample->sac.mliter; + if (sample->rbt.seconds) + entry->rbt = sample->rbt.seconds; + /* skip events that happened at this time */ + while (ev && (int)ev->time.seconds == time) + ev = ev->next; + lasttime = time; + lastdepth = depth; + idx++; + + if (time > maxtime) + break; + } + + /* Add two final surface events */ + plot_data[idx++].sec = lasttime + 1; + plot_data[idx++].sec = lasttime + 2; + pi->nr = idx; + + return plot_data; +} + +#undef INSERT_ENTRY + +static void populate_cylinder_pressure_data(int idx, int start, int end, struct plot_info *pi, bool o2flag) +{ + int i; + + /* First: check that none of the entries has sensor pressure for this cylinder index */ + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = pi->entry + i; + if (entry->cylinderindex != idx && !o2flag) + continue; + if (CYLINDER_PRESSURE(o2flag, entry)) + return; + } + + /* Then: populate the first entry with the beginning cylinder pressure */ + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = pi->entry + i; + if (entry->cylinderindex != idx && !o2flag) + continue; + if (o2flag) + O2CYLINDER_PRESSURE(entry) = start; + else + SENSOR_PRESSURE(entry) = start; + break; + } + + /* .. and the last entry with the ending cylinder pressure */ + for (i = pi->nr; --i >= 0; /* nothing */) { + struct plot_data *entry = pi->entry + i; + if (entry->cylinderindex != idx && !o2flag) + continue; + if (o2flag) + O2CYLINDER_PRESSURE(entry) = end; + else + SENSOR_PRESSURE(entry) = end; + break; + } +} + +/* + * Calculate the sac rate between the two plot entries 'first' and 'last'. + * + * Everything in between has a cylinder pressure, and it's all the same + * cylinder. + */ +static int sac_between(struct dive *dive, struct plot_data *first, struct plot_data *last) +{ + int airuse; + double pressuretime; + pressure_t a, b; + cylinder_t *cyl; + + if (first == last) + return 0; + + /* Calculate air use - trivial */ + a.mbar = GET_PRESSURE(first); + b.mbar = GET_PRESSURE(last); + cyl = dive->cylinder + first->cylinderindex; + airuse = gas_volume(cyl, a) - gas_volume(cyl, b); + if (airuse <= 0) + return 0; + + /* Calculate depthpressure integrated over time */ + pressuretime = 0.0; + do { + int depth = (first[0].depth + first[1].depth) / 2; + int time = first[1].sec - first[0].sec; + double atm = depth_to_atm(depth, dive); + + pressuretime += atm * time; + } while (++first < last); + + /* Turn "atmseconds" into "atmminutes" */ + pressuretime /= 60; + + /* SAC = mliter per minute */ + return rint(airuse / pressuretime); +} + +/* + * Try to do the momentary sac rate for this entry, averaging over one + * minute. + */ +static void fill_sac(struct dive *dive, struct plot_info *pi, int idx) +{ + struct plot_data *entry = pi->entry + idx; + struct plot_data *first, *last; + int time; + + if (entry->sac) + return; + + if (!GET_PRESSURE(entry)) + return; + + /* + * Try to go back 30 seconds to get 'first'. + * Stop if the sensor changed, or if we went back too far. + */ + first = entry; + time = entry->sec - 30; + while (idx > 0) { + struct plot_data *prev = first-1; + if (prev->cylinderindex != first->cylinderindex) + break; + if (prev->depth < SURFACE_THRESHOLD && first->depth < SURFACE_THRESHOLD) + break; + if (prev->sec < time) + break; + if (!GET_PRESSURE(prev)) + break; + idx--; + first = prev; + } + + /* Now find an entry a minute after the first one */ + last = first; + time = first->sec + 60; + while (++idx < pi->nr) { + struct plot_data *next = last+1; + if (next->cylinderindex != last->cylinderindex) + break; + if (next->depth < SURFACE_THRESHOLD && last->depth < SURFACE_THRESHOLD) + break; + if (next->sec > time) + break; + if (!GET_PRESSURE(next)) + break; + last = next; + } + + /* Ok, now calculate the SAC between 'first' and 'last' */ + entry->sac = sac_between(dive, first, last); +} + +static void calculate_sac(struct dive *dive, struct plot_info *pi) +{ + for (int i = 0; i < pi->nr; i++) + fill_sac(dive, pi, i); +} + +static void populate_secondary_sensor_data(struct divecomputer *dc, struct plot_info *pi) +{ + (void) dc; + (void) pi; + /* We should try to see if it has interesting pressure data here */ +} + +static void setup_gas_sensor_pressure(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) +{ + int i; + struct divecomputer *secondary; + + /* First, populate the pressures with the manual cylinder data.. */ + for (i = 0; i < MAX_CYLINDERS; i++) { + cylinder_t *cyl = dive->cylinder + i; + int start = cyl->start.mbar ?: cyl->sample_start.mbar; + int end = cyl->end.mbar ?: cyl->sample_end.mbar; + + if (!start || !end) + continue; + + populate_cylinder_pressure_data(i, start, end, pi, dive->cylinder[i].cylinder_use == OXYGEN); + } + + /* + * Here, we should try to walk through all the dive computers, + * and try to see if they have sensor data different from the + * primary dive computer (dc). + */ + secondary = &dive->dc; + do { + if (secondary == dc) + continue; + populate_secondary_sensor_data(dc, pi); + } while ((secondary = secondary->next) != NULL); +} + +#ifndef SUBSURFACE_MOBILE +/* calculate DECO STOP / TTS / NDL */ +static void calculate_ndl_tts(struct plot_data *entry, struct dive *dive, double surface_pressure) +{ + /* FIXME: This should be configurable */ + /* ascent speed up to first deco stop */ + const int ascent_s_per_step = 1; + const int ascent_mm_per_step = 200; /* 12 m/min */ + /* ascent speed between deco stops */ + const int ascent_s_per_deco_step = 1; + const int ascent_mm_per_deco_step = 16; /* 1 m/min */ + /* how long time steps in deco calculations? */ + const int time_stepsize = 60; + const int deco_stepsize = 3000; + /* at what depth is the current deco-step? */ + int next_stop = ROUND_UP(deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), + surface_pressure, dive, 1), deco_stepsize); + int ascent_depth = entry->depth; + /* at what time should we give up and say that we got enuff NDL? */ + int cylinderindex = entry->cylinderindex; + /* If iterating through a dive, entry->tts_calc needs to be reset */ + entry->tts_calc = 0; + + /* If we don't have a ceiling yet, calculate ndl. Don't try to calculate + * a ndl for lower values than 3m it would take forever */ + if (next_stop == 0) { + if (entry->depth < 3000) { + entry->ndl = MAX_PROFILE_DECO; + return; + } + /* stop if the ndl is above max_ndl seconds, and call it plenty of time */ + while (entry->ndl_calc < MAX_PROFILE_DECO && deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), surface_pressure, dive, 1) <= 0) { + entry->ndl_calc += time_stepsize; + add_segment(depth_to_bar(entry->depth, dive), + &dive->cylinder[cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, prefs.bottomsac); + } + /* we don't need to calculate anything else */ + return; + } + + /* We are in deco */ + entry->in_deco_calc = true; + + /* Add segments for movement to stopdepth */ + for (; ascent_depth > next_stop; ascent_depth -= ascent_mm_per_step, entry->tts_calc += ascent_s_per_step) { + add_segment(depth_to_bar(ascent_depth, dive), + &dive->cylinder[cylinderindex].gasmix, ascent_s_per_step, entry->o2pressure.mbar, dive, prefs.decosac); + next_stop = ROUND_UP(deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(ascent_depth, dive)), surface_pressure, dive, 1), deco_stepsize); + } + ascent_depth = next_stop; + + /* And how long is the current deco-step? */ + entry->stoptime_calc = 0; + entry->stopdepth_calc = next_stop; + next_stop -= deco_stepsize; + + /* And how long is the total TTS */ + while (next_stop >= 0) { + /* save the time for the first stop to show in the graph */ + if (ascent_depth == entry->stopdepth_calc) + entry->stoptime_calc += time_stepsize; + + entry->tts_calc += time_stepsize; + if (entry->tts_calc > MAX_PROFILE_DECO) + break; + add_segment(depth_to_bar(ascent_depth, dive), + &dive->cylinder[cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, prefs.decosac); + + if (deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(ascent_depth,dive)), surface_pressure, dive, 1) <= next_stop) { + /* move to the next stop and add the travel between stops */ + for (; ascent_depth > next_stop; ascent_depth -= ascent_mm_per_deco_step, entry->tts_calc += ascent_s_per_deco_step) + add_segment(depth_to_bar(ascent_depth, dive), + &dive->cylinder[cylinderindex].gasmix, ascent_s_per_deco_step, entry->o2pressure.mbar, dive, prefs.decosac); + ascent_depth = next_stop; + next_stop -= deco_stepsize; + } + } +} + +/* Let's try to do some deco calculations. + */ +void calculate_deco_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool print_mode) +{ + int i, count_iteration = 0; + double surface_pressure = (dc->surface_pressure.mbar ? dc->surface_pressure.mbar : get_surface_pressure_in_mbar(dive, true)) / 1000.0; + int last_ndl_tts_calc_time = 0; + int first_ceiling = 0; + bool first_iteration = true; + int final_tts = 0 , time_clear_ceiling = 0, time_deep_ceiling = 0, deco_time = 0, prev_deco_time = 10000000; + char *cache_data_initial = NULL; + /* For VPM-B outside the planner, cache the initial deco state for CVA iterations */ + if (prefs.deco_mode == VPMB && !in_planner()) + cache_deco_state(&cache_data_initial); + /* For VPM-B outside the planner, iterate until deco time converges (usually one or two iterations after the initial) + * Set maximum number of iterations to 10 just in case */ + while ((abs(prev_deco_time - deco_time) >= 30) && (count_iteration < 10)) { + for (i = 1; i < pi->nr; i++) { + struct plot_data *entry = pi->entry + i; + int j, t0 = (entry - 1)->sec, t1 = entry->sec; + int time_stepsize = 20; + + entry->ambpressure = depth_to_bar(entry->depth, dive); + entry->gfline = MAX((double)prefs.gflow, (entry->ambpressure - surface_pressure) / (gf_low_pressure_this_dive - surface_pressure) * + (prefs.gflow - prefs.gfhigh) + + prefs.gfhigh) * + (100.0 - AMB_PERCENTAGE) / 100.0 + AMB_PERCENTAGE; + if (t0 > t1) { + fprintf(stderr, "non-monotonous dive stamps %d %d\n", t0, t1); + int xchg = t1; + t1 = t0; + t0 = xchg; + } + if (t0 != t1 && t1 - t0 < time_stepsize) + time_stepsize = t1 - t0; + for (j = t0 + time_stepsize; j <= t1; j += time_stepsize) { + int depth = interpolate(entry[-1].depth, entry[0].depth, j - t0, t1 - t0); + add_segment(depth_to_bar(depth, dive), + &dive->cylinder[entry->cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, entry->sac); + if ((t1 - j < time_stepsize) && (j < t1)) + time_stepsize = t1 - j; + } + if (t0 == t1) { + entry->ceiling = (entry - 1)->ceiling; + } else { + /* Keep updating the VPM-B gradients until the start of the ascent phase of the dive. */ + if (prefs.deco_mode == VPMB && !in_planner() && (entry - 1)->ceiling >= first_ceiling && first_iteration == true) { + nuclear_regeneration(t1); + vpmb_start_gradient(); + /* For CVA calculations, start by guessing deco time = dive time remaining */ + deco_time = pi->maxtime - t1; + vpmb_next_gradient(deco_time, surface_pressure / 1000.0); + } + entry->ceiling = deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), surface_pressure, dive, !prefs.calcceiling3m); + /* If using VPM-B outside the planner, take first_ceiling_pressure as the deepest ceiling */ + if (prefs.deco_mode == VPMB && !in_planner()) { + if (entry->ceiling >= first_ceiling) { + time_deep_ceiling = t1; + first_ceiling = entry->ceiling; + first_ceiling_pressure.mbar = depth_to_mbar(first_ceiling, dive); + if (first_iteration) { + nuclear_regeneration(t1); + vpmb_start_gradient(); + /* For CVA calculations, start by guessing deco time = dive time remaining */ + deco_time = pi->maxtime - t1; + vpmb_next_gradient(deco_time, surface_pressure / 1000.0); + } + } + // Use the point where the ceiling clears as the end of deco phase for CVA calculations + if (entry->ceiling > 0) + time_clear_ceiling = 0; + else if (time_clear_ceiling == 0) + time_clear_ceiling = t1; + } + } + for (j = 0; j < 16; j++) { + double m_value = buehlmann_inertgas_a[j] + entry->ambpressure / buehlmann_inertgas_b[j]; + entry->ceilings[j] = deco_allowed_depth(tolerated_by_tissue[j], surface_pressure, dive, 1); + entry->percentages[j] = tissue_inertgas_saturation[j] < entry->ambpressure ? + tissue_inertgas_saturation[j] / entry->ambpressure * AMB_PERCENTAGE : + AMB_PERCENTAGE + (tissue_inertgas_saturation[j] - entry->ambpressure) / (m_value - entry->ambpressure) * (100.0 - AMB_PERCENTAGE); + } + + /* should we do more calculations? + * We don't for print-mode because this info doesn't show up there + * If the ceiling hasn't cleared by the last data point, we need tts for VPM-B CVA calculation + * It is not necessary to do these calculation on the first VPMB iteration, except for the last data point */ + if ((prefs.calcndltts && !print_mode && (prefs.deco_mode != VPMB || in_planner() || !first_iteration)) || + (prefs.deco_mode == VPMB && !in_planner() && i == pi->nr - 1)) { + /* only calculate ndl/tts on every 30 seconds */ + if ((entry->sec - last_ndl_tts_calc_time) < 30 && i != pi->nr - 1) { + struct plot_data *prev_entry = (entry - 1); + entry->stoptime_calc = prev_entry->stoptime_calc; + entry->stopdepth_calc = prev_entry->stopdepth_calc; + entry->tts_calc = prev_entry->tts_calc; + entry->ndl_calc = prev_entry->ndl_calc; + continue; + } + last_ndl_tts_calc_time = entry->sec; + + /* We are going to mess up deco state, so store it for later restore */ + char *cache_data = NULL; + cache_deco_state(&cache_data); + calculate_ndl_tts(entry, dive, surface_pressure); + if (prefs.deco_mode == VPMB && !in_planner() && i == pi->nr - 1) + final_tts = entry->tts_calc; + /* Restore "real" deco state for next real time step */ + restore_deco_state(cache_data); + free(cache_data); + } + } + if (prefs.deco_mode == VPMB && !in_planner()) { + prev_deco_time = deco_time; + // Do we need to update deco_time? + if (final_tts > 0) + deco_time = pi->maxtime + final_tts - time_deep_ceiling; + else if (time_clear_ceiling > 0) + deco_time = time_clear_ceiling - time_deep_ceiling; + vpmb_next_gradient(deco_time, surface_pressure / 1000.0); + final_tts = 0; + last_ndl_tts_calc_time = 0; + first_ceiling = 0; + first_iteration = false; + count_iteration ++; + restore_deco_state(cache_data_initial); + } else { + // With Buhlmann, or not in planner, iterating isn't needed. This makes the while condition false. + prev_deco_time = deco_time = 0; + } + } + free(cache_data_initial); +#if DECO_CALC_DEBUG & 1 + dump_tissues(); +#endif +} +#endif + +/* Function calculate_ccr_po2: This function takes information from one plot_data structure (i.e. one point on + * the dive profile), containing the oxygen sensor values of a CCR system and, for that plot_data structure, + * calculates the po2 value from the sensor data. Several rules are applied, depending on how many o2 sensors + * there are and the differences among the readings from these sensors. + */ +static int calculate_ccr_po2(struct plot_data *entry, struct divecomputer *dc) +{ + int sump = 0, minp = 999999, maxp = -999999; + int diff_limit = 100; // The limit beyond which O2 sensor differences are considered significant (default = 100 mbar) + int i, np = 0; + + for (i = 0; i < dc->no_o2sensors; i++) + if (entry->o2sensor[i].mbar) { // Valid reading + ++np; + sump += entry->o2sensor[i].mbar; + minp = MIN(minp, entry->o2sensor[i].mbar); + maxp = MAX(maxp, entry->o2sensor[i].mbar); + } + switch (np) { + case 0: // Uhoh + return entry->o2pressure.mbar; + case 1: // Return what we have + return sump; + case 2: // Take the average + return sump / 2; + case 3: // Voting logic + if (2 * maxp - sump + minp < diff_limit) { // Upper difference acceptable... + if (2 * minp - sump + maxp) // ...and lower difference acceptable + return sump / 3; + else + return (sump - minp) / 2; + } else { + if (2 * minp - sump + maxp) // ...but lower difference acceptable + return (sump - maxp) / 2; + else + return sump / 3; + } + default: // This should not happen + assert(np <= 3); + return 0; + } +} + +static void calculate_gas_information_new(struct dive *dive, struct plot_info *pi) +{ + int i; + double amb_pressure; + + for (i = 1; i < pi->nr; i++) { + int fn2, fhe; + struct plot_data *entry = pi->entry + i; + int cylinderindex = entry->cylinderindex; + + amb_pressure = depth_to_bar(entry->depth, dive); + + fill_pressures(&entry->pressures, amb_pressure, &dive->cylinder[cylinderindex].gasmix, entry->o2pressure.mbar / 1000.0, dive->dc.divemode); + fn2 = (int)(1000.0 * entry->pressures.n2 / amb_pressure); + fhe = (int)(1000.0 * entry->pressures.he / amb_pressure); + + /* Calculate MOD, EAD, END and EADD based on partial pressures calculated before + * so there is no difference in calculating between OC and CC + * END takes O₂ + N₂ (air) into account ("Narcotic" for trimix dives) + * EAD just uses N₂ ("Air" for nitrox dives) */ + pressure_t modpO2 = { .mbar = (int)(prefs.modpO2 * 1000) }; + entry->mod = (double)gas_mod(&dive->cylinder[cylinderindex].gasmix, modpO2, dive, 1).mm; + entry->end = (entry->depth + 10000) * (1000 - fhe) / 1000.0 - 10000; + entry->ead = (entry->depth + 10000) * fn2 / (double)N2_IN_AIR - 10000; + entry->eadd = (entry->depth + 10000) * + (entry->pressures.o2 / amb_pressure * O2_DENSITY + + entry->pressures.n2 / amb_pressure * N2_DENSITY + + entry->pressures.he / amb_pressure * HE_DENSITY) / + (O2_IN_AIR * O2_DENSITY + N2_IN_AIR * N2_DENSITY) * 1000 - 10000; + if (entry->mod < 0) + entry->mod = 0; + if (entry->ead < 0) + entry->ead = 0; + if (entry->end < 0) + entry->end = 0; + if (entry->eadd < 0) + entry->eadd = 0; + } +} + +void fill_o2_values(struct divecomputer *dc, struct plot_info *pi, struct dive *dive) +/* In the samples from each dive computer, there may be uninitialised oxygen + * sensor or setpoint values, e.g. when events were inserted into the dive log + * or if the dive computer does not report o2 values with every sample. But + * for drawing the profile a complete series of valid o2 pressure values is + * required. This function takes the oxygen sensor data and setpoint values + * from the structures of plotinfo and replaces the zero values with their + * last known values so that the oxygen sensor data are complete and ready + * for plotting. This function called by: create_plot_info_new() */ +{ + int i, j; + pressure_t last_sensor[3], o2pressure; + pressure_t amb_pressure; + + for (i = 0; i < pi->nr; i++) { + struct plot_data *entry = pi->entry + i; + + if (dc->divemode == CCR) { + if (i == 0) { // For 1st iteration, initialise the last_sensor values + for (j = 0; j < dc->no_o2sensors; j++) + last_sensor[j].mbar = pi->entry->o2sensor[j].mbar; + } else { // Now re-insert the missing oxygen pressure values + for (j = 0; j < dc->no_o2sensors; j++) + if (entry->o2sensor[j].mbar) + last_sensor[j].mbar = entry->o2sensor[j].mbar; + else + entry->o2sensor[j].mbar = last_sensor[j].mbar; + } // having initialised the empty o2 sensor values for this point on the profile, + amb_pressure.mbar = depth_to_mbar(entry->depth, dive); + o2pressure.mbar = calculate_ccr_po2(entry, dc); // ...calculate the po2 based on the sensor data + entry->o2pressure.mbar = MIN(o2pressure.mbar, amb_pressure.mbar); + } else { + entry->o2pressure.mbar = 0; // initialise po2 to zero for dctype = OC + } + } +} + +#ifdef DEBUG_GAS +/* A CCR debug function that writes the cylinder pressure and the oxygen values to the file debug_print_profiledata.dat: + * Called in create_plot_info_new() + */ +static void debug_print_profiledata(struct plot_info *pi) +{ + FILE *f1; + struct plot_data *entry; + int i; + if (!(f1 = fopen("debug_print_profiledata.dat", "w"))) { + printf("File open error for: debug_print_profiledata.dat\n"); + } else { + fprintf(f1, "id t1 gas gasint t2 t3 dil dilint t4 t5 setpoint sensor1 sensor2 sensor3 t6 po2 fo2\n"); + for (i = 0; i < pi->nr; i++) { + entry = pi->entry + i; + fprintf(f1, "%d gas=%8d %8d ; dil=%8d %8d ; o2_sp= %d %d %d %d PO2= %f\n", i, SENSOR_PRESSURE(entry), + INTERPOLATED_PRESSURE(entry), O2CYLINDER_PRESSURE(entry), INTERPOLATED_O2CYLINDER_PRESSURE(entry), + entry->o2pressure.mbar, entry->o2sensor[0].mbar, entry->o2sensor[1].mbar, entry->o2sensor[2].mbar, entry->pressures.o2); + } + fclose(f1); + } +} +#endif + +/* + * Create a plot-info with smoothing and ranged min/max + * + * This also makes sure that we have extra empty events on both + * sides, so that you can do end-points without having to worry + * about it. + */ +void create_plot_info_new(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool fast) +{ + int o2, he, o2max; +#ifndef SUBSURFACE_MOBILE + init_decompression(dive); +#endif + /* Create the new plot data */ + free((void *)last_pi_entry_new); + + get_dive_gas(dive, &o2, &he, &o2max); + if (dc->divemode == FREEDIVE){ + pi->dive_type = FREEDIVE; + } else if (he > 0) { + pi->dive_type = TRIMIX; + } else { + if (o2) + pi->dive_type = NITROX; + else + pi->dive_type = AIR; + } + + last_pi_entry_new = populate_plot_entries(dive, dc, pi); + + check_gas_change_events(dive, dc, pi); /* Populate the gas index from the gas change events */ + check_setpoint_events(dive, dc, pi); /* Populate setpoints */ + setup_gas_sensor_pressure(dive, dc, pi); /* Try to populate our gas pressure knowledge */ + if (!fast) { + populate_pressure_information(dive, dc, pi, false); /* .. calculate missing pressure entries for all gasses except o2 */ + if (dc->divemode == CCR) /* For CCR dives.. */ + populate_pressure_information(dive, dc, pi, true); /* .. calculate missing o2 gas pressure entries */ + } + fill_o2_values(dc, pi, dive); /* .. and insert the O2 sensor data having 0 values. */ + calculate_sac(dive, pi); /* Calculate sac */ +#ifndef SUBSURFACE_MOBILE + calculate_deco_information(dive, dc, pi, false); /* and ceiling information, using gradient factor values in Preferences) */ +#endif + calculate_gas_information_new(dive, pi); /* Calculate gas partial pressures */ + +#ifdef DEBUG_GAS + debug_print_profiledata(pi); +#endif + + pi->meandepth = dive->dc.meandepth.mm; + analyze_plot_info(pi); +} + +struct divecomputer *select_dc(struct dive *dive) +{ + unsigned int max = number_of_computers(dive); + unsigned int i = dc_number; + + /* Reset 'dc_number' if we've switched dives and it is now out of range */ + if (i >= max) + dc_number = i = 0; + + return get_dive_dc(dive, i); +} + +static void plot_string(struct plot_info *pi, struct plot_data *entry, struct membuffer *b, bool has_ndl) +{ + int pressurevalue, mod, ead, end, eadd; + const char *depth_unit, *pressure_unit, *temp_unit, *vertical_speed_unit; + double depthvalue, tempvalue, speedvalue, sacvalue; + int decimals; + const char *unit; + + depthvalue = get_depth_units(entry->depth, NULL, &depth_unit); + put_format(b, translate("gettextFromC", "@: %d:%02d\nD: %.1f%s\n"), FRACTION(entry->sec, 60), depthvalue, depth_unit); + if (GET_PRESSURE(entry)) { + pressurevalue = get_pressure_units(GET_PRESSURE(entry), &pressure_unit); + put_format(b, translate("gettextFromC", "P: %d%s\n"), pressurevalue, pressure_unit); + } + if (entry->temperature) { + tempvalue = get_temp_units(entry->temperature, &temp_unit); + put_format(b, translate("gettextFromC", "T: %.1f%s\n"), tempvalue, temp_unit); + } + speedvalue = get_vertical_speed_units(abs(entry->speed), NULL, &vertical_speed_unit); + /* Ascending speeds are positive, descending are negative */ + if (entry->speed > 0) + speedvalue *= -1; + put_format(b, translate("gettextFromC", "V: %.1f%s\n"), speedvalue, vertical_speed_unit); + sacvalue = get_volume_units(entry->sac, &decimals, &unit); + if (entry->sac && prefs.show_sac) + put_format(b, translate("gettextFromC", "SAC: %.*f%s/min\n"), decimals, sacvalue, unit); + if (entry->cns) + put_format(b, translate("gettextFromC", "CNS: %u%%\n"), entry->cns); + if (prefs.pp_graphs.po2) + put_format(b, translate("gettextFromC", "pO%s: %.2fbar\n"), UTF8_SUBSCRIPT_2, entry->pressures.o2); + if (prefs.pp_graphs.pn2) + put_format(b, translate("gettextFromC", "pN%s: %.2fbar\n"), UTF8_SUBSCRIPT_2, entry->pressures.n2); + if (prefs.pp_graphs.phe) + put_format(b, translate("gettextFromC", "pHe: %.2fbar\n"), entry->pressures.he); + if (prefs.mod) { + mod = (int)get_depth_units(entry->mod, NULL, &depth_unit); + put_format(b, translate("gettextFromC", "MOD: %d%s\n"), mod, depth_unit); + } + eadd = (int)get_depth_units(entry->eadd, NULL, &depth_unit); + if (prefs.ead) { + switch (pi->dive_type) { + case NITROX: + ead = (int)get_depth_units(entry->ead, NULL, &depth_unit); + put_format(b, translate("gettextFromC", "EAD: %d%s\nEADD: %d%s\n"), ead, depth_unit, eadd, depth_unit); + break; + case TRIMIX: + end = (int)get_depth_units(entry->end, NULL, &depth_unit); + put_format(b, translate("gettextFromC", "END: %d%s\nEADD: %d%s\n"), end, depth_unit, eadd, depth_unit); + break; + case AIR: + case FREEDIVING: + /* nothing */ + break; + } + } + if (entry->stopdepth) { + depthvalue = get_depth_units(entry->stopdepth, NULL, &depth_unit); + if (entry->ndl) { + /* this is a safety stop as we still have ndl */ + if (entry->stoptime) + put_format(b, translate("gettextFromC", "Safetystop: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60), + depthvalue, depth_unit); + else + put_format(b, translate("gettextFromC", "Safetystop: unkn time @ %.0f%s\n"), + depthvalue, depth_unit); + } else { + /* actual deco stop */ + if (entry->stoptime) + put_format(b, translate("gettextFromC", "Deco: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60), + depthvalue, depth_unit); + else + put_format(b, translate("gettextFromC", "Deco: unkn time @ %.0f%s\n"), + depthvalue, depth_unit); + } + } else if (entry->in_deco) { + put_string(b, translate("gettextFromC", "In deco\n")); + } else if (has_ndl) { + put_format(b, translate("gettextFromC", "NDL: %umin\n"), DIV_UP(entry->ndl, 60)); + } + if (entry->tts) + put_format(b, translate("gettextFromC", "TTS: %umin\n"), DIV_UP(entry->tts, 60)); + if (entry->stopdepth_calc && entry->stoptime_calc) { + depthvalue = get_depth_units(entry->stopdepth_calc, NULL, &depth_unit); + put_format(b, translate("gettextFromC", "Deco: %umin @ %.0f%s (calc)\n"), DIV_UP(entry->stoptime_calc, 60), + depthvalue, depth_unit); + } else if (entry->in_deco_calc) { + /* This means that we have no NDL left, + * and we have no deco stop, + * so if we just accend to the surface slowly + * (ascent_mm_per_step / ascent_s_per_step) + * everything will be ok. */ + put_string(b, translate("gettextFromC", "In deco (calc)\n")); + } else if (prefs.calcndltts && entry->ndl_calc != 0) { + if(entry->ndl_calc < MAX_PROFILE_DECO) + put_format(b, translate("gettextFromC", "NDL: %umin (calc)\n"), DIV_UP(entry->ndl_calc, 60)); + else + put_format(b, "%s", translate("gettextFromC", "NDL: >2h (calc)\n")); + } + if (entry->tts_calc) { + if (entry->tts_calc < MAX_PROFILE_DECO) + put_format(b, translate("gettextFromC", "TTS: %umin (calc)\n"), DIV_UP(entry->tts_calc, 60)); + else + put_format(b, "%s", translate("gettextFromC", "TTS: >2h (calc)\n")); + } + if (entry->rbt) + put_format(b, translate("gettextFromC", "RBT: %umin\n"), DIV_UP(entry->rbt, 60)); + if (entry->ceiling) { + depthvalue = get_depth_units(entry->ceiling, NULL, &depth_unit); + put_format(b, translate("gettextFromC", "Calculated ceiling %.0f%s\n"), depthvalue, depth_unit); + if (prefs.calcalltissues) { + int k; + for (k = 0; k < 16; k++) { + if (entry->ceilings[k]) { + depthvalue = get_depth_units(entry->ceilings[k], NULL, &depth_unit); + put_format(b, translate("gettextFromC", "Tissue %.0fmin: %.1f%s\n"), buehlmann_N2_t_halflife[k], depthvalue, depth_unit); + } + } + } + } + if (entry->heartbeat && prefs.hrgraph) + put_format(b, translate("gettextFromC", "heartbeat: %d\n"), entry->heartbeat); + if (entry->bearing) + put_format(b, translate("gettextFromC", "bearing: %d\n"), entry->bearing); + if (entry->running_sum) { + depthvalue = get_depth_units(entry->running_sum / entry->sec, NULL, &depth_unit); + put_format(b, translate("gettextFromC", "mean depth to here %.1f%s\n"), depthvalue, depth_unit); + } + + strip_mb(b); +} + +struct plot_data *get_plot_details_new(struct plot_info *pi, int time, struct membuffer *mb) +{ + struct plot_data *entry = NULL; + int i; + + for (i = 0; i < pi->nr; i++) { + entry = pi->entry + i; + if (entry->sec >= time) + break; + } + if (entry) + plot_string(pi, entry, mb, pi->has_ndl); + return (entry); +} + +/* Compare two plot_data entries and writes the results into a string */ +void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int bufsize, int sum) +{ + struct plot_data *start, *stop, *data; + const char *depth_unit, *pressure_unit, *vertical_speed_unit; + char *buf2 = malloc(bufsize); + int avg_speed, max_asc_speed, max_desc_speed; + int delta_depth, avg_depth, max_depth, min_depth; + int bar_used, last_pressure, pressurevalue; + int count, last_sec, delta_time; + + double depthvalue, speedvalue; + + if (bufsize > 0) + buf[0] = '\0'; + if (e1 == NULL || e2 == NULL) { + free(buf2); + return; + } + + if (e1->sec < e2->sec) { + start = e1; + stop = e2; + } else if (e1->sec > e2->sec) { + start = e2; + stop = e1; + } else { + free(buf2); + return; + } + count = 0; + avg_speed = 0; + max_asc_speed = 0; + max_desc_speed = 0; + + delta_depth = abs(start->depth - stop->depth); + delta_time = abs(start->sec - stop->sec); + avg_depth = 0; + max_depth = 0; + min_depth = INT_MAX; + bar_used = 0; + + last_sec = start->sec; + last_pressure = GET_PRESSURE(start); + + data = start; + while (data != stop) { + data = start + count; + if (sum) + avg_speed += abs(data->speed) * (data->sec - last_sec); + else + avg_speed += data->speed * (data->sec - last_sec); + avg_depth += data->depth * (data->sec - last_sec); + + if (data->speed > max_desc_speed) + max_desc_speed = data->speed; + if (data->speed < max_asc_speed) + max_asc_speed = data->speed; + + if (data->depth < min_depth) + min_depth = data->depth; + if (data->depth > max_depth) + max_depth = data->depth; + /* Try to detect gas changes */ + if (GET_PRESSURE(data) < last_pressure + 2000) + bar_used += last_pressure - GET_PRESSURE(data); + + count += 1; + last_sec = data->sec; + last_pressure = GET_PRESSURE(data); + } + avg_depth /= stop->sec - start->sec; + avg_speed /= stop->sec - start->sec; + + snprintf(buf, bufsize, translate("gettextFromC", "%sT: %d:%02d min"), UTF8_DELTA, delta_time / 60, delta_time % 60); + memcpy(buf2, buf, bufsize); + + depthvalue = get_depth_units(delta_depth, NULL, &depth_unit); + snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s"), buf2, UTF8_DELTA, depthvalue, depth_unit); + memcpy(buf2, buf, bufsize); + + depthvalue = get_depth_units(min_depth, NULL, &depth_unit); + snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s"), buf2, UTF8_DOWNWARDS_ARROW, depthvalue, depth_unit); + memcpy(buf2, buf, bufsize); + + depthvalue = get_depth_units(max_depth, NULL, &depth_unit); + snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s"), buf2, UTF8_UPWARDS_ARROW, depthvalue, depth_unit); + memcpy(buf2, buf, bufsize); + + depthvalue = get_depth_units(avg_depth, NULL, &depth_unit); + snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s\n"), buf2, UTF8_AVERAGE, depthvalue, depth_unit); + memcpy(buf2, buf, bufsize); + + speedvalue = get_vertical_speed_units(abs(max_desc_speed), NULL, &vertical_speed_unit); + snprintf(buf, bufsize, translate("gettextFromC", "%s%sV:%.2f%s"), buf2, UTF8_DOWNWARDS_ARROW, speedvalue, vertical_speed_unit); + memcpy(buf2, buf, bufsize); + + speedvalue = get_vertical_speed_units(abs(max_asc_speed), NULL, &vertical_speed_unit); + snprintf(buf, bufsize, translate("gettextFromC", "%s %sV:%.2f%s"), buf2, UTF8_UPWARDS_ARROW, speedvalue, vertical_speed_unit); + memcpy(buf2, buf, bufsize); + + speedvalue = get_vertical_speed_units(abs(avg_speed), NULL, &vertical_speed_unit); + snprintf(buf, bufsize, translate("gettextFromC", "%s %sV:%.2f%s"), buf2, UTF8_AVERAGE, speedvalue, vertical_speed_unit); + memcpy(buf2, buf, bufsize); + + /* Only print if gas has been used */ + if (bar_used) { + pressurevalue = get_pressure_units(bar_used, &pressure_unit); + memcpy(buf2, buf, bufsize); + snprintf(buf, bufsize, translate("gettextFromC", "%s %sP:%d %s"), buf2, UTF8_DELTA, pressurevalue, pressure_unit); + } + + free(buf2); +} diff --git a/core/profile.h b/core/profile.h new file mode 100644 index 000000000..abac9dd49 --- /dev/null +++ b/core/profile.h @@ -0,0 +1,111 @@ +#ifndef PROFILE_H +#define PROFILE_H + +#include "dive.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + STABLE, + SLOW, + MODERATE, + FAST, + CRAZY +} velocity_t; + +struct membuffer; +struct divecomputer; +struct plot_info; +struct plot_data { + unsigned int in_deco : 1; + int cylinderindex; + int sec; + /* pressure[0] is sensor cylinder pressure [when CCR, the pressure of the diluent cylinder] + * pressure[1] is interpolated cylinder pressure */ + int pressure[2]; + /* o2pressure[0] is o2 cylinder pressure [CCR] + * o2pressure[1] is interpolated o2 cylinder pressure [CCR] */ + int o2cylinderpressure[2]; + int temperature; + /* Depth info */ + int depth; + int ceiling; + int ceilings[16]; + int percentages[16]; + int ndl; + int tts; + int rbt; + int stoptime; + int stopdepth; + int cns; + int smoothed; + int sac; + int running_sum; + struct gas_pressures pressures; + pressure_t o2pressure; // for rebreathers, this is consensus measured po2, or setpoint otherwise. 0 for OC. + pressure_t o2sensor[3]; //for rebreathers with up to 3 PO2 sensors + pressure_t o2setpoint; + double mod, ead, end, eadd; + velocity_t velocity; + int speed; + struct plot_data *min[3]; + struct plot_data *max[3]; + int avg[3]; + /* values calculated by us */ + unsigned int in_deco_calc : 1; + int ndl_calc; + int tts_calc; + int stoptime_calc; + int stopdepth_calc; + int pressure_time; + int heartbeat; + int bearing; + double ambpressure; + double gfline; +}; + +struct ev_select { + char *ev_name; + bool plot_ev; +}; + +struct plot_info calculate_max_limits_new(struct dive *dive, struct divecomputer *given_dc); +void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int bufsize, int sum); +struct plot_data *populate_plot_entries(struct dive *dive, struct divecomputer *dc, struct plot_info *pi); +struct plot_info *analyze_plot_info(struct plot_info *pi); +void create_plot_info_new(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool fast); +void calculate_deco_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool print_mode); +struct plot_data *get_plot_details_new(struct plot_info *pi, int time, struct membuffer *); + +/* + * When showing dive profiles, we scale things to the + * current dive. However, we don't scale past less than + * 30 minutes or 90 ft, just so that small dives show + * up as such unless zoom is enabled. + * We also need to add 180 seconds at the end so the min/max + * plots correctly + */ +int get_maxtime(struct plot_info *pi); + +/* get the maximum depth to which we want to plot + * take into account the additional verical space needed to plot + * partial pressure graphs */ +int get_maxdepth(struct plot_info *pi); + +#define SENSOR_PR 0 +#define INTERPOLATED_PR 1 +#define SENSOR_PRESSURE(_entry) (_entry)->pressure[SENSOR_PR] +#define O2CYLINDER_PRESSURE(_entry) (_entry)->o2cylinderpressure[SENSOR_PR] +#define CYLINDER_PRESSURE(_o2, _entry) (_o2 ? O2CYLINDER_PRESSURE(_entry) : SENSOR_PRESSURE(_entry)) +#define INTERPOLATED_PRESSURE(_entry) (_entry)->pressure[INTERPOLATED_PR] +#define INTERPOLATED_O2CYLINDER_PRESSURE(_entry) (_entry)->o2cylinderpressure[INTERPOLATED_PR] +#define GET_PRESSURE(_entry) (SENSOR_PRESSURE(_entry) ? SENSOR_PRESSURE(_entry) : INTERPOLATED_PRESSURE(_entry)) +#define GET_O2CYLINDER_PRESSURE(_entry) (O2CYLINDER_PRESSURE(_entry) ? O2CYLINDER_PRESSURE(_entry) : INTERPOLATED_O2CYLINDER_PRESSURE(_entry)) +#define SAC_WINDOW 45 /* sliding window in seconds for current SAC calculation */ + +#ifdef __cplusplus +} +#endif +#endif // PROFILE_H diff --git a/core/qt-gui.h b/core/qt-gui.h new file mode 100644 index 000000000..92532d22f --- /dev/null +++ b/core/qt-gui.h @@ -0,0 +1,15 @@ +#ifndef QT_GUI_H +#define QT_GUI_H + +void init_qt_late(); +void init_ui(); + +void run_ui(); +void exit_ui(); + +#if defined(SUBSURFACE_MOBILE) +#include +extern QObject *qqWindowObject; +#endif + +#endif // QT_GUI_H diff --git a/core/qt-init.cpp b/core/qt-init.cpp new file mode 100644 index 000000000..b52dfd970 --- /dev/null +++ b/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/core/qthelper.cpp b/core/qthelper.cpp new file mode 100644 index 000000000..1d3951088 --- /dev/null +++ b/core/qthelper.cpp @@ -0,0 +1,1615 @@ +#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 "exif.h" +#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 QLocale loc; + +#define translate(_context, arg) trGettext(arg) +static const QString DEGREE_SIGNS("dD" UTF8_DEGREE); + +QString weight_string(int weight_in_grams) +{ + QString str; + if (get_units()->weight == units::KG) { + int gr = weight_in_grams % 1000; + int kg = weight_in_grams / 1000; + if (kg >= 20.0) + str = QString("%1").arg(kg + (gr >= 500 ? 1 : 0)); + else + str = QString("%1.%2").arg(kg).arg((unsigned)(gr + 50) / 100); + } else { + double lbs = grams_to_lbs(weight_in_grams); + if (lbs >= 40.0) + lbs = rint(lbs + 0.5); + else + lbs = rint(lbs + 0.05); + str = QString("%1").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1); + } + return (str); +} + +QString distance_string(int distanceInMeters) +{ + QString str; + if(get_units()->length == units::METERS) { + if (distanceInMeters >= 1000) + str = QString(translate("gettextFromC", "%1km")).arg(distanceInMeters / 1000); + else + str = QString(translate("gettextFromC", "%1m")).arg(distanceInMeters); + } else { + double miles = m_to_mile(distanceInMeters); + if (miles >= 1.0) + str = QString(translate("gettextFromC", "%1mi")).arg((int)miles); + else + str = QString(translate("gettextFromC", "%1yd")).arg((int)(miles * 1760)); + } + return str; +} + +extern "C" const char *printGPSCoords(int lat, int lon) +{ + unsigned int latdeg, londeg; + unsigned int latmin, lonmin; + double latsec, lonsec; + QString lath, lonh, result; + + if (!lat && !lon) + return strdup(""); + + if (prefs.coordinates_traditional) { + lath = lat >= 0 ? translate("gettextFromC", "N") : translate("gettextFromC", "S"); + lonh = lon >= 0 ? translate("gettextFromC", "E") : translate("gettextFromC", "W"); + lat = abs(lat); + lon = abs(lon); + latdeg = lat / 1000000U; + londeg = lon / 1000000U; + latmin = (lat % 1000000U) * 60U; + lonmin = (lon % 1000000U) * 60U; + latsec = (latmin % 1000000) * 60; + lonsec = (lonmin % 1000000) * 60; + result.sprintf("%u%s%02d\'%06.3f\"%s %u%s%02d\'%06.3f\"%s", + latdeg, UTF8_DEGREE, latmin / 1000000, latsec / 1000000, lath.toUtf8().data(), + londeg, UTF8_DEGREE, lonmin / 1000000, lonsec / 1000000, lonh.toUtf8().data()); + } else { + result.sprintf("%f %f", (double) lat / 1000000.0, (double) lon / 1000000.0); + } + return strdup(result.toUtf8().data()); +} + +/** +* Try to parse in a generic manner a coordinate. +*/ +static bool parseCoord(const QString& txt, int& pos, const QString& positives, + const QString& negatives, const QString& others, + double& value) +{ + bool numberDefined = false, degreesDefined = false, + minutesDefined = false, secondsDefined = false; + double number = 0.0; + int posBeforeNumber = pos; + int sign = 0; + value = 0.0; + while (pos < txt.size()) { + if (txt[pos].isDigit()) { + if (numberDefined) + return false; + QRegExp numberRe("(\\d+(?:[\\.,]\\d+)?).*"); + if (!numberRe.exactMatch(txt.mid(pos))) + return false; + number = numberRe.cap(1).toDouble(); + numberDefined = true; + posBeforeNumber = pos; + pos += numberRe.cap(1).size() - 1; + } else if (positives.indexOf(txt[pos]) >= 0) { + if (sign != 0) + return false; + sign = 1; + if (degreesDefined || numberDefined) { + //sign after the degrees => + //at the end of the coordinate + ++pos; + break; + } + } else if (negatives.indexOf(txt[pos]) >= 0) { + if (sign != 0) { + if (others.indexOf(txt[pos]) >= 0) + //special case for the '-' sign => next coordinate + break; + return false; + } + sign = -1; + if (degreesDefined || numberDefined) { + //sign after the degrees => + //at the end of the coordinate + ++pos; + break; + } + } else if (others.indexOf(txt[pos]) >= 0) { + //we are at the next coordinate. + break; + } else if (DEGREE_SIGNS.indexOf(txt[pos]) >= 0 || + (txt[pos].isSpace() && !degreesDefined && numberDefined)) { + if (!numberDefined) + return false; + if (degreesDefined) { + //next coordinate => need to put back the number + pos = posBeforeNumber; + numberDefined = false; + break; + } + value += number; + numberDefined = false; + degreesDefined = true; + } else if (txt[pos] == '\'' || (txt[pos].isSpace() && !minutesDefined && numberDefined)) { + if (!numberDefined || minutesDefined) + return false; + value += number / 60.0; + numberDefined = false; + minutesDefined = true; + } else if (txt[pos] == '"' || (txt[pos].isSpace() && !secondsDefined && numberDefined)) { + if (!numberDefined || secondsDefined) + return false; + value += number / 3600.0; + numberDefined = false; + secondsDefined = true; + } else if ((numberDefined || minutesDefined || secondsDefined) && + (txt[pos] == ',' || txt[pos] == ';')) { + // next coordinate coming up + // eat the ',' and any subsequent white space + while (txt[++pos].isSpace()) + /* nothing */ ; + break; + } else { + return false; + } + ++pos; + } + if (!degreesDefined && numberDefined) { + value = number; //just a single number => degrees + } else if (!minutesDefined && numberDefined) { + value += number / 60.0; + } else if (!secondsDefined && numberDefined) { + value += number / 3600.0; + } else if (numberDefined) { + return false; + } + if (sign == -1) value *= -1.0; + return true; +} + +/** +* Parse special coordinate formats that cannot be handled by parseCoord. +*/ +static bool parseSpecialCoords(const QString& txt, double& latitude, double& longitude) { + QRegExp xmlFormat("(-?\\d+(?:\\.\\d+)?),?\\s+(-?\\d+(?:\\.\\d+)?)"); + if (xmlFormat.exactMatch(txt)) { + latitude = xmlFormat.cap(1).toDouble(); + longitude = xmlFormat.cap(2).toDouble(); + return true; + } + return false; +} + +bool parseGpsText(const QString &gps_text, double *latitude, double *longitude) +{ + static const QString POS_LAT = QString("+N") + translate("gettextFromC", "N"); + static const QString NEG_LAT = QString("-S") + translate("gettextFromC", "S"); + static const QString POS_LON = QString("+E") + translate("gettextFromC", "E"); + static const QString NEG_LON = QString("-W") + translate("gettextFromC", "W"); + + //remove the useless spaces (but keep the ones separating numbers) + static const QRegExp SPACE_CLEANER("\\s*([" + POS_LAT + NEG_LAT + POS_LON + + NEG_LON + DEGREE_SIGNS + "'\"\\s])\\s*"); + const QString normalized = gps_text.trimmed().toUpper().replace(SPACE_CLEANER, "\\1"); + + if (normalized.isEmpty()) { + *latitude = 0.0; + *longitude = 0.0; + return true; + } + if (parseSpecialCoords(normalized, *latitude, *longitude)) + return true; + int pos = 0; + return parseCoord(normalized, pos, POS_LAT, NEG_LAT, POS_LON + NEG_LON, *latitude) && + parseCoord(normalized, pos, POS_LON, NEG_LON, "", *longitude) && + pos == normalized.size(); +} + +#if 0 // we'll need something like this for the dive site management, eventually +bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out) +{ + double latitude, longitude; + int latudeg, longudeg; + bool ignore; + bool *parsed = parsed_out ?: &ignore; + *parsed = true; + + /* if we have a master and the dive's gps address is different from it, + * don't change the dive */ + if (master && (master->latitude.udeg != dive->latitude.udeg || + master->longitude.udeg != dive->longitude.udeg)) + return false; + + if (!(*parsed = parseGpsText(gps_text, &latitude, &longitude))) + return false; + + latudeg = rint(1000000 * latitude); + longudeg = rint(1000000 * longitude); + + /* if dive gps didn't change, nothing changed */ + if (dive->latitude.udeg == latudeg && dive->longitude.udeg == longudeg) + return false; + /* ok, update the dive and mark things changed */ + dive->latitude.udeg = latudeg; + dive->longitude.udeg = longudeg; + return true; +} +#endif + +QList 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 +#ifdef SUBSURFACE_MOBILE + QString userAgent = QString("Subsurface-mobile:%1(%2):").arg(subsurface_mobile_version()).arg(subsurface_canonical_version()); +#else + QString userAgent = QString("Subsurface:%1:").arg(subsurface_canonical_version()); +#endif + userAgent.append(SubsurfaceSysInfo::prettyOsName().replace(':', ' ') + ":"); + arch = SubsurfaceSysInfo::buildCpuArchitecture().replace(':', ' '); + userAgent.append(arch); + if (arch == "i386") + userAgent.append("/" + SubsurfaceSysInfo::currentCpuArchitecture()); + userAgent.append(":" + uiLanguage(NULL)); + return userAgent; + +} + +extern "C" const char *subsurface_user_agent() +{ + static QString uA = getUserAgent(); + + return strdup(qPrintable(uA)); +} + +QString uiLanguage(QLocale *callerLoc) +{ + QString shortDateFormat; + QString dateFormat; + QString timeFormat; + QSettings s; + QVariant v; + s.beginGroup("Language"); + + if (!s.value("UseSystemLanguage", true).toBool()) { + loc = QLocale(s.value("UiLanguage", QLocale().uiLanguages().first()).toString()); + } else { + loc = QLocale(QLocale().uiLanguages().first()); + } + QStringList languages = loc.uiLanguages(); + QString uiLang; + if (languages[0].contains('-')) + uiLang = languages[0]; + else if (languages.count() > 1 && languages[1].contains('-')) + uiLang = languages[1]; + else if (languages.count() > 2 && languages[2].contains('-')) + uiLang = languages[2]; + else + uiLang = languages[0]; + GET_BOOL("time_format_override", time_format_override); + GET_BOOL("date_format_override", date_format_override); + GET_TXT("time_format", time_format); + GET_TXT("date_format", date_format); + GET_TXT("date_format_short", date_format_short); + s.endGroup(); + + // there's a stupid Qt bug on MacOS where uiLanguages doesn't give us the country info + if (!uiLang.contains('-') && uiLang != loc.bcp47Name()) { + QLocale loc2(loc.bcp47Name()); + loc = loc2; + QStringList languages = loc2.uiLanguages(); + if (languages[0].contains('-')) + uiLang = languages[0]; + else if (languages.count() > 1 && languages[1].contains('-')) + uiLang = languages[1]; + else if (languages.count() > 2 && languages[2].contains('-')) + uiLang = languages[2]; + } + if (callerLoc) + *callerLoc = loc; + + if (!prefs.date_format_override || same_string(prefs.date_format_short, "") || same_string(prefs.date_format, "")) { + // derive our standard date format from what the locale gives us + // the short format is fine + // the long format uses long weekday and month names, so replace those with the short ones + // for time we don't want the time zone designator and don't want leading zeroes on the hours + shortDateFormat = loc.dateFormat(QLocale::ShortFormat); + dateFormat = loc.dateFormat(QLocale::LongFormat); + dateFormat.replace("dddd,", "ddd").replace("dddd", "ddd").replace("MMMM", "MMM"); + // special hack for Swedish as our switching from long weekday names to short weekday names + // messes things up there + dateFormat.replace("'en' 'den' d:'e'", " d"); + if (!prefs.date_format_override || same_string(prefs.date_format, "")) { + free((void*)prefs.date_format); + prefs.date_format = strdup(qPrintable(dateFormat)); + } + if (!prefs.date_format_override || same_string(prefs.date_format_short, "")) { + free((void*)prefs.date_format_short); + prefs.date_format_short = strdup(qPrintable(shortDateFormat)); + } + } + if (!prefs.time_format_override || same_string(prefs.time_format, "")) { + timeFormat = loc.timeFormat(); + timeFormat.replace("(t)", "").replace(" t", "").replace("t", "").replace("hh", "h").replace("HH", "H").replace("'kl'.", ""); + timeFormat.replace(".ss", "").replace(":ss", "").replace("ss", ""); + free((void*)prefs.time_format); + prefs.time_format = strdup(qPrintable(timeFormat)); + } + return uiLang; +} + +QLocale getLocale() +{ + return loc; +} + +void set_filename(const char *filename, bool force) +{ + if (!force && existing_filename) + return; + free((void *)existing_filename); + if (filename) + existing_filename = strdup(filename); + else + existing_filename = NULL; +} + +const QString get_dc_nickname(const char *model, uint32_t deviceid) +{ + const DiveComputerNode *existNode = dcList.getExact(model, deviceid); + + if (existNode && !existNode->nickName.isEmpty()) + return existNode->nickName; + else + return model; +} + +QString get_depth_string(int mm, bool showunit, bool showdecimal) +{ + if (prefs.units.length == units::METERS) { + double meters = mm / 1000.0; + return QString("%1%2").arg(meters, 0, 'f', (showdecimal && meters < 20.0) ? 1 : 0).arg(showunit ? translate("gettextFromC", "m") : ""); + } else { + double feet = mm_to_feet(mm); + return QString("%1%2").arg(feet, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "ft") : ""); + } +} + +QString get_depth_string(depth_t depth, bool showunit, bool showdecimal) +{ + return get_depth_string(depth.mm, showunit, showdecimal); +} + +QString get_depth_unit() +{ + if (prefs.units.length == units::METERS) + return QString("%1").arg(translate("gettextFromC", "m")); + else + return QString("%1").arg(translate("gettextFromC", "ft")); +} + +QString get_weight_string(weight_t weight, bool showunit) +{ + QString str = weight_string(weight.grams); + if (get_units()->weight == units::KG) { + str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "kg") : ""); + } else { + str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "lbs") : ""); + } + return (str); +} + +QString get_weight_unit() +{ + if (prefs.units.weight == units::KG) + return QString("%1").arg(translate("gettextFromC", "kg")); + else + return QString("%1").arg(translate("gettextFromC", "lbs")); +} + +/* these methods retrieve used gas per cylinder */ +static unsigned start_pressure(cylinder_t *cyl) +{ + return cyl->start.mbar ?: cyl->sample_start.mbar; +} + +static unsigned end_pressure(cylinder_t *cyl) +{ + return cyl->end.mbar ?: cyl->sample_end.mbar; +} + +QString get_cylinder_used_gas_string(cylinder_t *cyl, bool showunit) +{ + int decimals; + const char *unit; + double gas_usage; + /* Get the cylinder gas use in mbar */ + gas_usage = start_pressure(cyl) - end_pressure(cyl); + /* Can we turn it into a volume? */ + if (cyl->type.size.mliter) { + gas_usage = bar_to_atm(gas_usage / 1000); + gas_usage *= cyl->type.size.mliter; + gas_usage = get_volume_units(gas_usage, &decimals, &unit); + } else { + gas_usage = get_pressure_units(gas_usage, &unit); + decimals = 0; + } + // translate("gettextFromC","%.*f %s" + return QString("%1 %2").arg(gas_usage, 0, 'f', decimals).arg(showunit ? unit : ""); +} + +QString get_temperature_string(temperature_t temp, bool showunit) +{ + if (temp.mkelvin == 0) { + return ""; //temperature not defined + } else if (prefs.units.temperature == units::CELSIUS) { + double celsius = mkelvin_to_C(temp.mkelvin); + return QString("%1%2%3").arg(celsius, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "C") : ""); + } else { + double fahrenheit = mkelvin_to_F(temp.mkelvin); + return QString("%1%2%3").arg(fahrenheit, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "F") : ""); + } +} + +QString get_temp_unit() +{ + if (prefs.units.temperature == units::CELSIUS) + return QString(UTF8_DEGREE "C"); + else + return QString(UTF8_DEGREE "F"); +} + +QString get_volume_string(volume_t volume, bool showunit) +{ + const char *unit; + int decimals; + double value = get_volume_units(volume.mliter, &decimals, &unit); + return QString("%1%2").arg(value, 0, 'f', decimals).arg(showunit ? unit : ""); +} + +QString get_volume_unit() +{ + const char *unit; + (void) get_volume_units(0, NULL, &unit); + return QString(unit); +} + +QString get_pressure_string(pressure_t pressure, bool showunit) +{ + if (prefs.units.pressure == units::BAR) { + double bar = pressure.mbar / 1000.0; + return QString("%1%2").arg(bar, 0, 'f', 1).arg(showunit ? translate("gettextFromC", "bar") : ""); + } else { + double psi = mbar_to_PSI(pressure.mbar); + return QString("%1%2").arg(psi, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "psi") : ""); + } +} + +QString getSubsurfaceDataPath(QString folderToFind) +{ + QString execdir; + QDir folder; + + // first check if we are running in the build dir, so the path that we + // are looking for is just a subdirectory of the execution path; + // this also works on Windows as there we install the dirs + // under the application path + execdir = QCoreApplication::applicationDirPath(); + folder = QDir(execdir.append(QDir::separator()).append(folderToFind)); + if (folder.exists()) + return folder.absolutePath(); + + // next check for the Linux typical $(prefix)/share/subsurface + execdir = QCoreApplication::applicationDirPath(); + if (execdir.contains("bin")) { + folder = QDir(execdir.replace("bin", "share/subsurface/").append(folderToFind)); + if (folder.exists()) + return folder.absolutePath(); + } + // then look for the usual locations on a Mac + execdir = QCoreApplication::applicationDirPath(); + folder = QDir(execdir.append("/../Resources/share/").append(folderToFind)); + if (folder.exists()) + return folder.absolutePath(); + execdir = QCoreApplication::applicationDirPath(); + folder = QDir(execdir.append("/../Resources/").append(folderToFind)); + if (folder.exists()) + return folder.absolutePath(); + return QString(""); +} + +static const char *printing_templates = "printing_templates"; + +QString getPrintingTemplatePathUser() +{ + static QString path = QString(); + if (path.isEmpty()) + path = QString(system_default_directory()) + QDir::separator() + QString(printing_templates); + return path; +} + +QString getPrintingTemplatePathBundle() +{ + static QString path = QString(); + if (path.isEmpty()) + path = getSubsurfaceDataPath(printing_templates); + return path; +} + +void copyPath(QString src, QString dst) +{ + QDir dir(src); + if (!dir.exists()) + return; + foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QString dst_path = dst + QDir::separator() + d; + dir.mkpath(dst_path); + copyPath(src + QDir::separator() + d, dst_path); + } + foreach (QString f, dir.entryList(QDir::Files)) + QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f); +} + +int gettimezoneoffset(timestamp_t when) +{ + QDateTime dt1, dt2; + if (when == 0) + dt1 = QDateTime::currentDateTime(); + else + dt1 = QDateTime::fromMSecsSinceEpoch(when * 1000); + dt2 = dt1.toUTC(); + dt1.setTimeSpec(Qt::UTC); + return dt2.secsTo(dt1); +} + +int parseLengthToMm(const QString &text) +{ + int mm; + QString numOnly = text; + numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]")); + if (numOnly.isEmpty()) + return 0; + double number = numOnly.toDouble(); + if (text.contains(QObject::tr("m"), Qt::CaseInsensitive)) { + mm = number * 1000; + } else if (text.contains(QObject::tr("ft"), Qt::CaseInsensitive)) { + mm = feet_to_mm(number); + } else { + switch (prefs.units.length) { + case units::FEET: + mm = feet_to_mm(number); + break; + case units::METERS: + mm = number * 1000; + break; + default: + mm = 0; + } + } + return mm; + +} + +int parseTemperatureToMkelvin(const QString &text) +{ + int mkelvin; + QString numOnly = text; + numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]")); + if (numOnly.isEmpty()) + return 0; + double number = numOnly.toDouble(); + if (text.contains(QObject::tr("C"), Qt::CaseInsensitive)) { + mkelvin = C_to_mkelvin(number); + } else if (text.contains(QObject::tr("F"), Qt::CaseInsensitive)) { + mkelvin = F_to_mkelvin(number); + } else { + switch (prefs.units.temperature) { + case units::CELSIUS: + mkelvin = C_to_mkelvin(number); + break; + case units::FAHRENHEIT: + mkelvin = F_to_mkelvin(number); + break; + default: + mkelvin = 0; + } + } + return mkelvin; +} + +int parseWeightToGrams(const QString &text) +{ + int grams; + QString numOnly = text; + numOnly.replace(",", ".").remove(QRegExp("[^0-9.]")); + if (numOnly.isEmpty()) + return 0; + double number = numOnly.toDouble(); + if (text.contains(QObject::tr("kg"), Qt::CaseInsensitive)) { + grams = rint(number * 1000); + } else if (text.contains(QObject::tr("lbs"), Qt::CaseInsensitive)) { + grams = lbs_to_grams(number); + } else { + switch (prefs.units.weight) { + case units::KG: + grams = rint(number * 1000); + break; + case units::LBS: + grams = lbs_to_grams(number); + break; + default: + grams = 0; + } + } + return grams; +} + +int parsePressureToMbar(const QString &text) +{ + int mbar; + QString numOnly = text; + numOnly.replace(",", ".").remove(QRegExp("[^0-9.]")); + if (numOnly.isEmpty()) + return 0; + double number = numOnly.toDouble(); + if (text.contains(QObject::tr("bar"), Qt::CaseInsensitive)) { + mbar = rint(number * 1000); + } else if (text.contains(QObject::tr("psi"), Qt::CaseInsensitive)) { + mbar = psi_to_mbar(number); + } else { + switch (prefs.units.pressure) { + case units::BAR: + mbar = rint(number * 1000); + break; + case units::PSI: + mbar = psi_to_mbar(number); + break; + default: + mbar = 0; + } + } + return mbar; +} + +int parseGasMixO2(const QString &text) +{ + QString gasString = text; + int o2, number; + if (gasString.contains(QObject::tr("AIR"), Qt::CaseInsensitive)) { + o2 = O2_IN_AIR; + } else if (gasString.contains(QObject::tr("EAN"), Qt::CaseInsensitive)) { + gasString.remove(QRegExp("[^0-9]")); + number = gasString.toInt(); + o2 = number * 10; + } else if (gasString.contains("/")) { + QStringList gasSplit = gasString.split("/"); + number = gasSplit[0].toInt(); + o2 = number * 10; + } else { + number = gasString.toInt(); + o2 = number * 10; + } + return o2; +} + +int parseGasMixHE(const QString &text) +{ + QString gasString = text; + int he, number; + if (gasString.contains("/")) { + QStringList gasSplit = gasString.split("/"); + number = gasSplit[1].toInt(); + he = number * 10; + } else { + he = 0; + } + return he; +} + +QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText) +{ + int hrs, mins; + mins = (when + 59) / 60; + hrs = mins / 60; + mins -= hrs * 60; + + QString displayTime; + if (hrs) + displayTime = QString("%1%2%3%4").arg(hrs).arg(hourText).arg(mins, 2, 10, QChar('0')).arg(minutesText); + else + displayTime = QString("%1%2").arg(mins).arg(minutesText); + + return displayTime; +} + +QString get_dive_date_string(timestamp_t when) +{ + QDateTime ts; + ts.setMSecsSinceEpoch(when * 1000L); + return loc.toString(ts.toUTC(), QString(prefs.date_format) + " " + prefs.time_format); +} + +QString get_short_dive_date_string(timestamp_t when) +{ + QDateTime ts; + ts.setMSecsSinceEpoch(when * 1000L); + return loc.toString(ts.toUTC(), QString(prefs.date_format_short) + " " + prefs.time_format); +} + +const char *get_dive_date_c_string(timestamp_t when) +{ + QString text = get_dive_date_string(when); + return strdup(text.toUtf8().data()); +} + +bool is_same_day(timestamp_t trip_when, timestamp_t dive_when) +{ + static timestamp_t twhen = (timestamp_t) 0; + static struct tm tmt; + struct tm tmd; + + utc_mkdate(dive_when, &tmd); + + if (twhen != trip_when) { + twhen = trip_when; + utc_mkdate(twhen, &tmt); + } + + return ((tmd.tm_mday == tmt.tm_mday) && (tmd.tm_mon == tmt.tm_mon) && (tmd.tm_year == tmt.tm_year)); +} + +QString get_trip_date_string(timestamp_t when, int nr, bool getday) +{ + struct tm tm; + utc_mkdate(when, &tm); + QDateTime localTime = QDateTime::fromTime_t(when); + localTime.setTimeSpec(Qt::UTC); + QString ret ; + + QString suffix = " " + QObject::tr("(%n dive(s))", "", nr); + if (getday) { + ret = localTime.date().toString(prefs.date_format) + suffix; + } else { + ret = localTime.date().toString("MMM yy") + suffix; + } + return ret; + +} + +extern "C" void reverseGeoLookup(degrees_t latitude, degrees_t longitude, uint32_t uuid) +{ + QNetworkRequest request; + QNetworkAccessManager *rgl = new QNetworkAccessManager(); + request.setUrl(QString("http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&accept-language=%1&lat=%2&lon=%3") + .arg(uiLanguage(NULL)).arg(latitude.udeg / 1000000.0).arg(longitude.udeg / 1000000.0)); + request.setRawHeader("Accept", "text/json"); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + QNetworkReply *reply = rgl->get(request); + QEventLoop loop; + QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + QJsonParseError errorObject; + QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &errorObject); + if (errorObject.error != QJsonParseError::NoError) { + qDebug() << errorObject.errorString(); + } else { + QJsonObject obj = jsonDoc.object(); + QJsonObject address = obj.value("address").toObject(); + qDebug() << "found country:" << address.value("country").toString(); + struct dive_site *ds = get_dive_site_by_uuid(uuid); + ds->notes = add_to_string(ds->notes, "countrytag: %s", address.value("country").toString().toUtf8().data()); + } +} + +QHash hashOf; +QMutex hashOfMutex; +QHash localFilenameOf; +QHash thumbnailCache; + +extern "C" char * hashstring(char * filename) +{ + return hashOf[QString(filename)].toHex().data(); +} + +const QString hashfile_name() +{ + return QString(system_default_directory()).append("/hashes"); +} + +extern "C" char *hashfile_name_string() +{ + return strdup(hashfile_name().toUtf8().data()); +} + +void read_hashes() +{ + QFile hashfile(hashfile_name()); + if (hashfile.open(QIODevice::ReadOnly)) { + QDataStream stream(&hashfile); + stream >> localFilenameOf; + stream >> hashOf; + stream >> thumbnailCache; + hashfile.close(); + } +} + +void write_hashes() +{ + QSaveFile hashfile(hashfile_name()); + if (hashfile.open(QIODevice::WriteOnly)) { + QDataStream stream(&hashfile); + stream << localFilenameOf; + stream << hashOf; + stream << thumbnailCache; + hashfile.commit(); + } else { + qDebug() << "cannot open" << hashfile.fileName(); + } +} + +void add_hash(const QString filename, QByteArray hash) +{ + QMutexLocker locker(&hashOfMutex); + hashOf[filename] = hash; + localFilenameOf[hash] = filename; +} + +QByteArray hashFile(const QString filename) +{ + QCryptographicHash hash(QCryptographicHash::Sha1); + QFile imagefile(filename); + if (imagefile.exists() && imagefile.open(QIODevice::ReadOnly)) { + hash.addData(&imagefile); + add_hash(filename, hash.result()); + return hash.result(); + } else { + return QByteArray(); + } +} + +void learnHash(struct picture *picture, QByteArray hash) +{ + if (picture->hash) + free(picture->hash); + QMutexLocker locker(&hashOfMutex); + hashOf[QString(picture->filename)] = hash; + picture->hash = strdup(hash.toHex()); +} + +QString localFilePath(const QString originalFilename) +{ + if (hashOf.contains(originalFilename) && localFilenameOf.contains(hashOf[originalFilename])) + return localFilenameOf[hashOf[originalFilename]]; + else + return originalFilename; +} + +QString fileFromHash(char *hash) +{ + return localFilenameOf[QByteArray::fromHex(hash)]; +} + +// This needs to operate on a copy of picture as it frees it after finishing! +void updateHash(struct picture *picture) { + QByteArray hash = hashFile(fileFromHash(picture->hash)); + learnHash(picture, hash); + picture_free(picture); +} + +// This needs to operate on a copy of picture as it frees it after finishing! +void hashPicture(struct picture *picture) +{ + char *oldHash = copy_string(picture->hash); + learnHash(picture, hashFile(QString(picture->filename))); + if (!same_string(picture->hash, "") && !same_string(picture->hash, oldHash)) + mark_divelist_changed((true)); + free(oldHash); + picture_free(picture); +} + +extern "C" void cache_picture(struct picture *picture) +{ + QString filename = picture->filename; + if (!hashOf.contains(filename)) + QtConcurrent::run(hashPicture, clone_picture(picture)); +} + +void learnImages(const QDir dir, int max_recursions) +{ + QStringList filters, files; + + if (max_recursions) { + foreach (QString dirname, dir.entryList(QStringList(), QDir::NoDotAndDotDot | QDir::Dirs)) { + learnImages(QDir(dir.filePath(dirname)), max_recursions - 1); + } + } + + foreach (QString format, QImageReader::supportedImageFormats()) { + filters.append(QString("*.").append(format)); + } + + foreach (QString file, dir.entryList(filters, QDir::Files)) { + files.append(dir.absoluteFilePath(file)); + } + + QtConcurrent::blockingMap(files, hashFile); +} + +extern "C" const char *local_file_path(struct picture *picture) +{ + QString hashString = picture->hash; + if (hashString.isEmpty()) { + QByteArray hash = hashFile(picture->filename); + free(picture->hash); + picture->hash = strdup(hash.toHex().data()); + } + QString localFileName = fileFromHash(picture->hash); + if (localFileName.isEmpty()) + localFileName = picture->filename; + return strdup(qPrintable(localFileName)); +} + +extern "C" bool picture_exists(struct picture *picture) +{ + QString localFilename = fileFromHash(picture->hash); + QByteArray hash = hashFile(localFilename); + return same_string(hash.toHex().data(), picture->hash); +} + +const QString picturedir() +{ + return QString(system_default_directory()).append("/picturedata/"); +} + +extern "C" char *picturedir_string() +{ + return strdup(picturedir().toUtf8().data()); +} + +/* when we get a picture from git storage (local or remote) and can't find the picture + * based on its hash, we create a local copy with the hash as filename and the appropriate + * suffix */ +extern "C" void savePictureLocal(struct picture *picture, const char *data, int len) +{ + QString dirname = picturedir(); + QDir localPictureDir(dirname); + localPictureDir.mkpath(dirname); + QString suffix(picture->filename); + suffix.replace(QRegularExpression(".*\\."), ""); + QString filename(dirname + picture->hash + "." + suffix); + QSaveFile out(filename); + if (out.open(QIODevice::WriteOnly)) { + out.write(data, len); + out.commit(); + add_hash(filename, QByteArray::fromHex(picture->hash)); + } +} + +extern "C" void picture_load_exif_data(struct picture *p) +{ + EXIFInfo exif; + memblock mem; + + if (readfile(localFilePath(QString(p->filename)).toUtf8().data(), &mem) <= 0) + goto picture_load_exit; + if (exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size) != PARSE_EXIF_SUCCESS) + goto picture_load_exit; + p->longitude.udeg= lrint(1000000.0 * exif.GeoLocation.Longitude); + p->latitude.udeg = lrint(1000000.0 * exif.GeoLocation.Latitude); + +picture_load_exit: + free(mem.buffer); + return; +} + +QString get_gas_string(struct gasmix gas) +{ + uint o2 = (get_o2(&gas) + 5) / 10, he = (get_he(&gas) + 5) / 10; + QString result = gasmix_is_air(&gas) ? QObject::tr("AIR") : he == 0 ? (o2 == 100 ? QObject::tr("OXYGEN") : QString("EAN%1").arg(o2, 2, 10, QChar('0'))) : QString("%1/%2").arg(o2).arg(he); + return result; +} + +QString get_divepoint_gas_string(const divedatapoint &p) +{ + return get_gas_string(p.gasmix); +} + +weight_t string_to_weight(const char *str) +{ + const char *end; + double value = strtod_flags(str, &end, 0); + QString rest = QString(end).trimmed(); + QString local_kg = QObject::tr("kg"); + QString local_lbs = QObject::tr("lbs"); + weight_t weight; + + if (rest.startsWith("kg") || rest.startsWith(local_kg)) + goto kg; + // using just "lb" instead of "lbs" is intentional - some people might enter the singular + if (rest.startsWith("lb") || rest.startsWith(local_lbs)) + goto lbs; + if (prefs.units.weight == prefs.units.LBS) + goto lbs; +kg: + weight.grams = rint(value * 1000); + return weight; +lbs: + weight.grams = lbs_to_grams(value); + return weight; +} + +depth_t string_to_depth(const char *str) +{ + const char *end; + double value = strtod_flags(str, &end, 0); + QString rest = QString(end).trimmed(); + QString local_ft = QObject::tr("ft"); + QString local_m = QObject::tr("m"); + depth_t depth; + + if (rest.startsWith("m") || rest.startsWith(local_m)) + goto m; + if (rest.startsWith("ft") || rest.startsWith(local_ft)) + goto ft; + if (prefs.units.length == prefs.units.FEET) + goto ft; +m: + depth.mm = rint(value * 1000); + return depth; +ft: + depth.mm = feet_to_mm(value); + return depth; +} + +pressure_t string_to_pressure(const char *str) +{ + const char *end; + double value = strtod_flags(str, &end, 0); + QString rest = QString(end).trimmed(); + QString local_psi = QObject::tr("psi"); + QString local_bar = QObject::tr("bar"); + pressure_t pressure; + + if (rest.startsWith("bar") || rest.startsWith(local_bar)) + goto bar; + if (rest.startsWith("psi") || rest.startsWith(local_psi)) + goto psi; + if (prefs.units.pressure == prefs.units.PSI) + goto psi; +bar: + pressure.mbar = rint(value * 1000); + return pressure; +psi: + pressure.mbar = psi_to_mbar(value); + return pressure; +} + +volume_t string_to_volume(const char *str, pressure_t workp) +{ + const char *end; + double value = strtod_flags(str, &end, 0); + QString rest = QString(end).trimmed(); + QString local_l = QObject::tr("l"); + QString local_cuft = QObject::tr("cuft"); + volume_t volume; + + if (rest.startsWith("l") || rest.startsWith("ℓ") || rest.startsWith(local_l)) + goto l; + if (rest.startsWith("cuft") || rest.startsWith(local_cuft)) + goto cuft; + /* + * If we don't have explicit units, and there is no working + * pressure, we're going to assume "liter" even in imperial + * measurements. + */ + if (!workp.mbar) + goto l; + if (prefs.units.volume == prefs.units.LITER) + goto l; +cuft: + if (workp.mbar) + value /= bar_to_atm(workp.mbar / 1000.0); + value = cuft_to_l(value); +l: + volume.mliter = rint(value * 1000); + return volume; +} + +fraction_t string_to_fraction(const char *str) +{ + const char *end; + double value = strtod_flags(str, &end, 0); + fraction_t fraction; + + fraction.permille = rint(value * 10); + return fraction; +} + +int getCloudURL(QString &filename) +{ + QString email = QString(prefs.cloud_storage_email); + email.replace(QRegularExpression("[^a-zA-Z0-9@._+-]"), ""); + if (email.isEmpty() || same_string(prefs.cloud_storage_password, "")) + return report_error("Please configure Cloud storage email and password in the preferences"); + if (email != prefs.cloud_storage_email_encoded) { + free(prefs.cloud_storage_email_encoded); + prefs.cloud_storage_email_encoded = strdup(qPrintable(email)); + } + filename = QString(QString(prefs.cloud_git_url) + "/%1[%1]").arg(email); + if (verbose) + qDebug() << "cloud URL set as" << filename; + return 0; +} + +extern "C" char *cloud_url() +{ + QString filename; + getCloudURL(filename); + return strdup(filename.toUtf8().data()); +} + +void loadPreferences() +{ + QSettings s; + QVariant v; + + uiLanguage(NULL); + s.beginGroup("Units"); + if (s.value("unit_system").toString() == "metric") { + prefs.unit_system = METRIC; + prefs.units = SI_units; + } else if (s.value("unit_system").toString() == "imperial") { + prefs.unit_system = IMPERIAL; + prefs.units = IMPERIAL_units; + } else { + prefs.unit_system = PERSONALIZE; + GET_UNIT("length", length, units::FEET, units::METERS); + GET_UNIT("pressure", pressure, units::PSI, units::BAR); + GET_UNIT("volume", volume, units::CUFT, units::LITER); + GET_UNIT("temperature", temperature, units::FAHRENHEIT, units::CELSIUS); + GET_UNIT("weight", weight, units::LBS, units::KG); + } + GET_UNIT("vertical_speed_time", vertical_speed_time, units::MINUTES, units::SECONDS); + GET_BOOL("coordinates", coordinates_traditional); + s.endGroup(); + s.beginGroup("TecDetails"); + GET_BOOL("po2graph", pp_graphs.po2); + GET_BOOL("pn2graph", pp_graphs.pn2); + GET_BOOL("phegraph", pp_graphs.phe); + GET_DOUBLE("po2threshold", pp_graphs.po2_threshold); + GET_DOUBLE("pn2threshold", pp_graphs.pn2_threshold); + GET_DOUBLE("phethreshold", pp_graphs.phe_threshold); + GET_BOOL("mod", mod); + GET_DOUBLE("modpO2", modpO2); + GET_BOOL("ead", ead); + GET_BOOL("redceiling", redceiling); + GET_BOOL("dcceiling", dcceiling); + GET_BOOL("calcceiling", calcceiling); + GET_BOOL("calcceiling3m", calcceiling3m); + GET_BOOL("calcndltts", calcndltts); + GET_BOOL("calcalltissues", calcalltissues); + GET_BOOL("hrgraph", hrgraph); + GET_BOOL("tankbar", tankbar); + GET_BOOL("RulerBar", rulergraph); + GET_BOOL("percentagegraph", percentagegraph); + GET_INT("gflow", gflow); + GET_INT("gfhigh", gfhigh); + GET_BOOL("gf_low_at_maxdepth", gf_low_at_maxdepth); + GET_BOOL("show_ccr_setpoint",show_ccr_setpoint); + GET_BOOL("show_ccr_sensors",show_ccr_sensors); + GET_BOOL("zoomed_plot", zoomed_plot); + set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); + GET_BOOL("show_sac", show_sac); + GET_BOOL("display_unused_tanks", display_unused_tanks); + GET_BOOL("show_average_depth", show_average_depth); + s.endGroup(); + + s.beginGroup("GeneralSettings"); + GET_TXT("default_filename", default_filename); + GET_INT("default_file_behavior", default_file_behavior); + if (prefs.default_file_behavior == UNDEFINED_DEFAULT_FILE) { + // undefined, so check if there's a filename set and + // use that, otherwise go with no default file + if (QString(prefs.default_filename).isEmpty()) + prefs.default_file_behavior = NO_DEFAULT_FILE; + else + prefs.default_file_behavior = LOCAL_DEFAULT_FILE; + } + GET_TXT("default_cylinder", default_cylinder); + GET_BOOL("use_default_file", use_default_file); + GET_INT("defaultsetpoint", defaultsetpoint); + GET_INT("o2consumption", o2consumption); + GET_INT("pscr_ratio", pscr_ratio); + s.endGroup(); + + s.beginGroup("Display"); + // get the font from the settings or our defaults + // respect the system default font size if none is explicitly set + QFont defaultFont = s.value("divelist_font", prefs.divelist_font).value(); + if (IS_FP_SAME(system_divelist_default_font_size, -1.0)) { + prefs.font_size = qApp->font().pointSizeF(); + system_divelist_default_font_size = prefs.font_size; // this way we don't save it on exit + } + prefs.font_size = s.value("font_size", prefs.font_size).toFloat(); + // painful effort to ignore previous default fonts on Windows - ridiculous + QString fontName = defaultFont.toString(); + if (fontName.contains(",")) + fontName = fontName.left(fontName.indexOf(",")); + if (subsurface_ignore_font(fontName.toUtf8().constData())) { + defaultFont = QFont(prefs.divelist_font); + } else { + free((void *)prefs.divelist_font); + prefs.divelist_font = strdup(fontName.toUtf8().constData()); + } + defaultFont.setPointSizeF(prefs.font_size); + qApp->setFont(defaultFont); + GET_INT("displayinvalid", display_invalid_dives); + s.endGroup(); + + s.beginGroup("Animations"); + GET_INT("animation_speed", animation_speed); + s.endGroup(); + + s.beginGroup("Network"); + GET_INT_DEF("proxy_type", proxy_type, QNetworkProxy::DefaultProxy); + GET_TXT("proxy_host", proxy_host); + GET_INT("proxy_port", proxy_port); + GET_BOOL("proxy_auth", proxy_auth); + GET_TXT("proxy_user", proxy_user); + GET_TXT("proxy_pass", proxy_pass); + s.endGroup(); + + s.beginGroup("CloudStorage"); + GET_TXT("email", cloud_storage_email); +#ifndef SUBSURFACE_MOBILE + GET_BOOL("save_password_local", save_password_local); +#else + // always save the password in Subsurface-mobile + prefs.save_password_local = true; +#endif + if (prefs.save_password_local) { // GET_TEXT macro is not a single statement + GET_TXT("password", cloud_storage_password); + } + GET_INT("cloud_verification_status", cloud_verification_status); + GET_BOOL("cloud_background_sync", cloud_background_sync); + GET_BOOL("git_local_only", git_local_only); + + // creating the git url here is simply a convenience when C code wants + // to compare against that git URL - it's always derived from the base URL + GET_TXT("cloud_base_url", cloud_base_url); + prefs.cloud_git_url = strdup(qPrintable(QString(prefs.cloud_base_url) + "/git")); + s.endGroup(); + + // Subsurface webservice id is stored outside of the groups + GET_TXT("subsurface_webservice_uid", userid); + + // but the related time / distance threshold (only used in the mobile app) + // are in their own group + s.beginGroup("locationService"); + GET_INT("distance_threshold", distance_threshold); + GET_INT("time_threshold", time_threshold); + s.endGroup(); + + // GeoManagement + s.beginGroup("geocoding"); +#ifdef DISABLED + GET_BOOL("enable_geocoding", geocoding.enable_geocoding); + GET_BOOL("parse_dive_without_gps", geocoding.parse_dive_without_gps); + GET_BOOL("tag_existing_dives", geocoding.tag_existing_dives); +#else + prefs.geocoding.enable_geocoding = true; +#endif + GET_ENUM("cat0", taxonomy_category, geocoding.category[0]); + GET_ENUM("cat1", taxonomy_category, geocoding.category[1]); + GET_ENUM("cat2", taxonomy_category, geocoding.category[2]); + s.endGroup(); + + // GPS service time and distance thresholds + s.beginGroup("LocationService"); + GET_INT("time_threshold", time_threshold); + GET_INT("distance_threshold", distance_threshold); + s.endGroup(); +} + +extern "C" bool isCloudUrl(const char *filename) +{ + QString email = QString(prefs.cloud_storage_email); + email.replace(QRegularExpression("[^a-zA-Z0-9@._+-]"), ""); + if (!email.isEmpty() && + QString(QString(prefs.cloud_git_url) + "/%1[%1]").arg(email) == filename) + return true; + return false; +} + +extern "C" bool getProxyString(char **buffer) +{ + if (prefs.proxy_type == QNetworkProxy::HttpProxy) { + QString proxy; + if (prefs.proxy_auth) + proxy = QString("http://%1:%2@%3:%4").arg(prefs.proxy_user).arg(prefs.proxy_pass) + .arg(prefs.proxy_host).arg(prefs.proxy_port); + else + proxy = QString("http://%1:%2").arg(prefs.proxy_host).arg(prefs.proxy_port); + if (buffer) + *buffer = strdup(qPrintable(proxy)); + return true; + } + return false; +} + +extern "C" void subsurface_mkdir(const char *dir) +{ + QDir directory; + if (!directory.mkpath(QString(dir))) + qDebug() << "failed to create path" << dir; +} + +extern "C" void parse_display_units(char *line) +{ + qDebug() << line; +} + +static QByteArray currentApplicationState; + +QByteArray getCurrentAppState() +{ + return currentApplicationState; +} + +void setCurrentAppState(QByteArray state) +{ + currentApplicationState = state; +} + +extern "C" bool in_planner() +{ + return (currentApplicationState == "PlanDive" || currentApplicationState == "EditPlannedDive"); +} + +void init_proxy() +{ + QNetworkProxy proxy; + proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type)); + proxy.setHostName(prefs.proxy_host); + proxy.setPort(prefs.proxy_port); + if (prefs.proxy_auth) { + proxy.setUser(prefs.proxy_user); + proxy.setPassword(prefs.proxy_pass); + } + QNetworkProxy::setApplicationProxy(proxy); +} + +QString getUUID() +{ + QString uuidString; + QSettings settings; + settings.beginGroup("UpdateManager"); + if (settings.contains("UUID")) { + uuidString = settings.value("UUID").toString(); + } else { + QUuid uuid = QUuid::createUuid(); + uuidString = uuid.toString(); + settings.setValue("UUID", uuidString); + } + uuidString.replace("{", "").replace("}", ""); + return uuidString; +} diff --git a/core/qthelper.h b/core/qthelper.h new file mode 100644 index 000000000..3a5ef60e4 --- /dev/null +++ b/core/qthelper.h @@ -0,0 +1,48 @@ +#ifndef QTHELPER_H +#define QTHELPER_H + +#include +#include +#include +#include "dive.h" +#include "divelist.h" +#include +#include + +// 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); +void add_hash(const QString filename, QByteArray hash); +void hashPicture(struct picture *picture); +QString localFilePath(const QString originalFilename); +QString fileFromHash(char *hash); +void learnHash(struct picture *picture, QByteArray hash); +extern "C" void cache_picture(struct picture *picture); +weight_t string_to_weight(const char *str); +depth_t string_to_depth(const char *str); +pressure_t string_to_pressure(const char *str); +volume_t string_to_volume(const char *str, pressure_t workp); +fraction_t string_to_fraction(const char *str); +int getCloudURL(QString &filename); +void loadPreferences(); +bool parseGpsText(const QString &gps_text, double *latitude, double *longitude); +QByteArray getCurrentAppState(); +void setCurrentAppState(QByteArray state); +extern "C" bool in_planner(); +extern "C" void subsurface_mkdir(const char *dir); +void init_proxy(); +QString getUUID(); + +#endif // QTHELPER_H diff --git a/core/qthelperfromc.h b/core/qthelperfromc.h new file mode 100644 index 000000000..32aed8949 --- /dev/null +++ b/core/qthelperfromc.h @@ -0,0 +1,22 @@ +#ifndef QTHELPERFROMC_H +#define QTHELPERFROMC_H + +bool getProxyString(char **buffer); +bool canReachCloudServer(); +void updateWindowTitle(); +bool isCloudUrl(const char *filename); +void subsurface_mkdir(const char *dir); +char *get_file_name(const char *fileName); +void copy_image_and_overwrite(const char *cfileName, const char *path, const char *cnewName); +char *hashstring(char *filename); +bool picture_exists(struct picture *picture); +char *move_away(const char *path); +const char *local_file_path(struct picture *picture); +void savePictureLocal(struct picture *picture, const char *data, int len); +void cache_picture(struct picture *picture); +char *cloud_url(); +char *hashfile_name_string(); +char *picturedir_string(); +const char *subsurface_user_agent(); + +#endif // QTHELPERFROMC_H diff --git a/core/qtserialbluetooth.cpp b/core/qtserialbluetooth.cpp new file mode 100644 index 000000000..6b104157a --- /dev/null +++ b/core/qtserialbluetooth.cpp @@ -0,0 +1,416 @@ +#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) +{ + (void)queue; + if (device == NULL) + return DC_STATUS_INVALIDARGS; +#if !defined(Q_OS_WIN) + if (device->socket == NULL) + return DC_STATUS_INVALIDARGS; +#endif + // TODO: add implementation + + return DC_STATUS_SUCCESS; +} + +static int qt_serial_get_received(serial_t *device) +{ +#if defined(Q_OS_WIN) + if (device == NULL) + return DC_STATUS_INVALIDARGS; + + // TODO use WSAIoctl to get the information + + return 0; +#else + if (device == NULL || device->socket == NULL) + return DC_STATUS_INVALIDARGS; + + return device->socket->bytesAvailable(); +#endif +} + +static int qt_serial_get_transmitted(serial_t *device) +{ +#if defined(Q_OS_WIN) + if (device == NULL) + return DC_STATUS_INVALIDARGS; + + // TODO add implementation + + return 0; +#else + if (device == NULL || device->socket == NULL) + return DC_STATUS_INVALIDARGS; + + return device->socket->bytesToWrite(); +#endif +} + +static int qt_serial_set_timeout(serial_t *device, long timeout) +{ + if (device == NULL) + return DC_STATUS_INVALIDARGS; + + device->timeout = timeout; + + return DC_STATUS_SUCCESS; +} + + +const dc_serial_operations_t qt_serial_ops = { + .open = qt_serial_open, + .close = qt_serial_close, + .read = qt_serial_read, + .write = qt_serial_write, + .flush = qt_serial_flush, + .get_received = qt_serial_get_received, + .get_transmitted = qt_serial_get_transmitted, + .set_timeout = qt_serial_set_timeout +}; + +extern void dc_serial_init (dc_serial_t *serial, void *data, const dc_serial_operations_t *ops); + +dc_status_t dc_serial_qt_open(dc_serial_t **out, dc_context_t *context, const char *devaddr) +{ + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + dc_serial_t *serial_device = (dc_serial_t *) malloc (sizeof (dc_serial_t)); + + if (serial_device == NULL) { + return DC_STATUS_NOMEMORY; + } + + // Initialize data and function pointers + dc_serial_init(serial_device, NULL, &qt_serial_ops); + + // Open the serial device. + dc_status_t rc = (dc_status_t)qt_serial_open (&serial_device->port, context, devaddr); + if (rc != DC_STATUS_SUCCESS) { + free (serial_device); + return rc; + } + + // Set the type of the device + serial_device->type = DC_TRANSPORT_BLUETOOTH; + + *out = serial_device; + + return DC_STATUS_SUCCESS; +} +} +#endif diff --git a/core/save-git.c b/core/save-git.c new file mode 100644 index 000000000..e22019ab0 --- /dev/null +++ b/core/save-git.c @@ -0,0 +1,1249 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#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" + +#define VA_BUF(b, fmt) do { va_list args; va_start(args, fmt); put_vformat(b, fmt, args); va_end(args); } while (0) + +static void cond_put_format(int cond, struct membuffer *b, const char *fmt, ...) +{ + if (cond) { + VA_BUF(b, fmt); + } +} + +#define SAVE(str, x) cond_put_format(dive->x, b, str " %d\n", dive->x) + +static void show_gps(struct membuffer *b, degrees_t latitude, degrees_t longitude) +{ + if (latitude.udeg || longitude.udeg) { + put_degrees(b, latitude, "gps ", " "); + put_degrees(b, longitude, "", "\n"); + } +} + +static void quote(struct membuffer *b, const char *text) +{ + const char *p = text; + + for (;;) { + const char *escape; + + switch (*p++) { + default: + continue; + case 0: + escape = NULL; + break; + case 1 ... 8: + case 11: + case 12: + case 14 ... 31: + escape = "?"; + break; + case '\\': + escape = "\\\\"; + break; + case '"': + escape = "\\\""; + break; + case '\n': + escape = "\n\t"; + if (*p == '\n') + escape = "\n"; + break; + } + put_bytes(b, text, (p - text - 1)); + if (!escape) + break; + put_string(b, escape); + text = p; + } +} + +static void show_utf8(struct membuffer *b, const char *prefix, const char *value, const char *postfix) +{ + if (value) { + put_format(b, "%s\"", prefix); + quote(b, value); + put_format(b, "\"%s", postfix); + } +} + +static void save_overview(struct membuffer *b, struct dive *dive) +{ + show_utf8(b, "divemaster ", dive->divemaster, "\n"); + show_utf8(b, "buddy ", dive->buddy, "\n"); + show_utf8(b, "suit ", dive->suit, "\n"); + show_utf8(b, "notes ", dive->notes, "\n"); +} + +static void save_tags(struct membuffer *b, struct tag_entry *tags) +{ + const char *sep = " "; + + if (!tags) + return; + put_string(b, "tags"); + while (tags) { + show_utf8(b, sep, tags->tag->source ? : tags->tag->name, ""); + sep = ", "; + tags = tags->next; + } + put_string(b, "\n"); +} + +static void save_extra_data(struct membuffer *b, struct extra_data *ed) +{ + while (ed) { + if (ed->key && ed->value) + put_format(b, "keyvalue \"%s\" \"%s\"\n", ed->key ? : "", ed->value ? : ""); + ed = ed->next; + } +} + +static void put_gasmix(struct membuffer *b, struct gasmix *mix) +{ + int o2 = mix->o2.permille; + int he = mix->he.permille; + + if (o2) { + put_format(b, " o2=%u.%u%%", FRACTION(o2, 10)); + if (he) + put_format(b, " he=%u.%u%%", FRACTION(he, 10)); + } +} + +static void save_cylinder_info(struct membuffer *b, struct dive *dive) +{ + int i, nr; + + nr = nr_cylinders(dive); + for (i = 0; i < nr; i++) { + cylinder_t *cylinder = dive->cylinder + i; + int volume = cylinder->type.size.mliter; + const char *description = cylinder->type.description; + + put_string(b, "cylinder"); + if (volume) + put_milli(b, " vol=", volume, "l"); + put_pressure(b, cylinder->type.workingpressure, " workpressure=", "bar"); + show_utf8(b, " description=", description, ""); + strip_mb(b); + put_gasmix(b, &cylinder->gasmix); + put_pressure(b, cylinder->start, " start=", "bar"); + put_pressure(b, cylinder->end, " end=", "bar"); + if (cylinder->cylinder_use != OC_GAS) + put_format(b, " use=%s", cylinderuse_text[cylinder->cylinder_use]); + + put_string(b, "\n"); + } +} + +static void save_weightsystem_info(struct membuffer *b, struct dive *dive) +{ + int i, nr; + + nr = nr_weightsystems(dive); + for (i = 0; i < nr; i++) { + weightsystem_t *ws = dive->weightsystem + i; + int grams = ws->weight.grams; + const char *description = ws->description; + + put_string(b, "weightsystem"); + put_milli(b, " weight=", grams, "kg"); + show_utf8(b, " description=", description, ""); + put_string(b, "\n"); + } +} + +static void save_dive_temperature(struct membuffer *b, struct dive *dive) +{ + if (dive->airtemp.mkelvin != dc_airtemp(&dive->dc)) + put_temperature(b, dive->airtemp, "airtemp ", "°C\n"); + if (dive->watertemp.mkelvin != dc_watertemp(&dive->dc)) + put_temperature(b, dive->watertemp, "watertemp ", "°C\n"); +} + +static void save_depths(struct membuffer *b, struct divecomputer *dc) +{ + put_depth(b, dc->maxdepth, "maxdepth ", "m\n"); + put_depth(b, dc->meandepth, "meandepth ", "m\n"); +} + +static void save_temperatures(struct membuffer *b, struct divecomputer *dc) +{ + put_temperature(b, dc->airtemp, "airtemp ", "°C\n"); + put_temperature(b, dc->watertemp, "watertemp ", "°C\n"); +} + +static void save_airpressure(struct membuffer *b, struct divecomputer *dc) +{ + put_pressure(b, dc->surface_pressure, "surfacepressure ", "bar\n"); +} + +static void save_salinity(struct membuffer *b, struct divecomputer *dc) +{ + /* only save if we have a value that isn't the default of sea water */ + if (!dc->salinity || dc->salinity == SEAWATER_SALINITY) + return; + put_salinity(b, dc->salinity, "salinity ", "g/l\n"); +} + +static void show_date(struct membuffer *b, timestamp_t when) +{ + struct tm tm; + + utc_mkdate(when, &tm); + + put_format(b, "date %04u-%02u-%02u\n", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + put_format(b, "time %02u:%02u:%02u\n", + tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +static void show_index(struct membuffer *b, int value, const char *pre, const char *post) +{ + if (value) + put_format(b, " %s%d%s", pre, value, post); +} + +/* + * Samples are saved as densely as possible while still being readable, + * since they are the bulk of the data. + * + * For parsing, look at the units to figure out what the numbers are. + */ +static void save_sample(struct membuffer *b, struct sample *sample, struct sample *old) +{ + put_format(b, "%3u:%02u", FRACTION(sample->time.seconds, 60)); + put_milli(b, " ", sample->depth.mm, "m"); + put_temperature(b, sample->temperature, " ", "°C"); + put_pressure(b, sample->cylinderpressure, " ", "bar"); + put_pressure(b, sample->o2cylinderpressure," o2pressure=","bar"); + + /* + * We only show sensor information for samples with pressure, and only if it + * changed from the previous sensor we showed. + */ + if (sample->cylinderpressure.mbar && sample->sensor != old->sensor) { + put_format(b, " sensor=%d", sample->sensor); + old->sensor = sample->sensor; + } + + /* the deco/ndl values are stored whenever they change */ + if (sample->ndl.seconds != old->ndl.seconds) { + put_format(b, " ndl=%u:%02u", FRACTION(sample->ndl.seconds, 60)); + old->ndl = sample->ndl; + } + if (sample->tts.seconds != old->tts.seconds) { + put_format(b, " tts=%u:%02u", FRACTION(sample->tts.seconds, 60)); + old->tts = sample->tts; + } + if (sample->in_deco != old->in_deco) { + put_format(b, " in_deco=%d", sample->in_deco ? 1 : 0); + old->in_deco = sample->in_deco; + } + if (sample->stoptime.seconds != old->stoptime.seconds) { + put_format(b, " stoptime=%u:%02u", FRACTION(sample->stoptime.seconds, 60)); + old->stoptime = sample->stoptime; + } + + if (sample->stopdepth.mm != old->stopdepth.mm) { + put_milli(b, " stopdepth=", sample->stopdepth.mm, "m"); + old->stopdepth = sample->stopdepth; + } + + if (sample->cns != old->cns) { + put_format(b, " cns=%u%%", sample->cns); + old->cns = sample->cns; + } + + if (sample->rbt.seconds) + put_format(b, " rbt=%u:%02u", FRACTION(sample->rbt.seconds, 60)); + + if (sample->o2sensor[0].mbar != old->o2sensor[0].mbar) { + put_milli(b, " sensor1=", sample->o2sensor[0].mbar, "bar"); + old->o2sensor[0] = sample->o2sensor[0]; + } + + if ((sample->o2sensor[1].mbar) && (sample->o2sensor[1].mbar != old->o2sensor[1].mbar)) { + put_milli(b, " sensor2=", sample->o2sensor[1].mbar, "bar"); + old->o2sensor[1] = sample->o2sensor[1]; + } + + if ((sample->o2sensor[2].mbar) && (sample->o2sensor[2].mbar != old->o2sensor[2].mbar)) { + put_milli(b, " sensor3=", sample->o2sensor[2].mbar, "bar"); + old->o2sensor[2] = sample->o2sensor[2]; + } + + if (sample->setpoint.mbar != old->setpoint.mbar) { + put_milli(b, " po2=", sample->setpoint.mbar, "bar"); + old->setpoint = sample->setpoint; + } + show_index(b, sample->heartbeat, "heartbeat=", ""); + show_index(b, sample->bearing.degrees, "bearing=", "°"); + put_format(b, "\n"); +} + +static void save_samples(struct membuffer *b, int nr, struct sample *s) +{ + struct sample dummy = {}; + + while (--nr >= 0) { + save_sample(b, s, &dummy); + s++; + } +} + +static void save_one_event(struct membuffer *b, struct event *ev) +{ + put_format(b, "event %d:%02d", FRACTION(ev->time.seconds, 60)); + show_index(b, ev->type, "type=", ""); + show_index(b, ev->flags, "flags=", ""); + show_index(b, ev->value, "value=", ""); + show_utf8(b, " name=", ev->name, ""); + if (event_is_gaschange(ev)) { + if (ev->gas.index >= 0) { + show_index(b, ev->gas.index, "cylinder=", ""); + put_gasmix(b, &ev->gas.mix); + } else if (!event_gasmix_redundant(ev)) + put_gasmix(b, &ev->gas.mix); + } + put_string(b, "\n"); +} + +static void save_events(struct membuffer *b, struct event *ev) +{ + while (ev) { + save_one_event(b, ev); + ev = ev->next; + } +} + +static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer *dc) +{ + show_utf8(b, "model ", dc->model, "\n"); + if (dc->deviceid) + put_format(b, "deviceid %08x\n", dc->deviceid); + if (dc->diveid) + put_format(b, "diveid %08x\n", dc->diveid); + if (dc->when && dc->when != dive->when) + show_date(b, dc->when); + if (dc->duration.seconds && dc->duration.seconds != dive->dc.duration.seconds) + put_duration(b, dc->duration, "duration ", "min\n"); + if (dc->divemode != OC) { + put_format(b, "dctype %s\n", divemode_text[dc->divemode]); + put_format(b, "numberofoxygensensors %d\n",dc->no_o2sensors); + } + + save_depths(b, dc); + save_temperatures(b, dc); + save_airpressure(b, dc); + save_salinity(b, dc); + put_duration(b, dc->surfacetime, "surfacetime ", "min\n"); + + save_extra_data(b, dc->extra_data); + save_events(b, dc->events); + save_samples(b, dc->samples, dc->sample); +} + +/* + * Note that we don't save the date and time or dive + * number: they are encoded in the filename. + */ +static void create_dive_buffer(struct dive *dive, struct membuffer *b) +{ + put_format(b, "duration %u:%02u min\n", FRACTION(dive->dc.duration.seconds, 60)); + SAVE("rating", rating); + SAVE("visibility", visibility); + cond_put_format(dive->tripflag == NO_TRIP, b, "notrip\n"); + save_tags(b, dive->tag_list); + cond_put_format(dive->dive_site_uuid && get_dive_site_by_uuid(dive->dive_site_uuid), + b, "divesiteid %08x\n", dive->dive_site_uuid); + if (verbose && dive->dive_site_uuid && !get_dive_site_by_uuid(dive->dive_site_uuid)) + fprintf(stderr, "removed reference to non-existant dive site with uuid %08x\n", dive->dive_site_uuid); + save_overview(b, dive); + save_cylinder_info(b, dive); + save_weightsystem_info(b, dive); + save_dive_temperature(b, dive); +} + +static struct membuffer error_string_buffer = { 0 }; + +/* + * Note that the act of "getting" the error string + * buffer doesn't de-allocate the buffer, but it does + * set the buffer length to zero, so that any future + * error reports will overwrite the string rather than + * append to it. + */ +const char *get_error_string(void) +{ + const char *str; + + if (!error_string_buffer.len) + return ""; + str = mb_cstring(&error_string_buffer); + error_string_buffer.len = 0; + return str; +} + +int report_error(const char *fmt, ...) +{ + struct membuffer *buf = &error_string_buffer; + + /* Previous unprinted errors? Add a newline in between */ + if (buf->len) + put_bytes(buf, "\n", 1); + VA_BUF(buf, fmt); + mb_cstring(buf); + return -1; +} + +void report_message(const char *msg) +{ + (void)report_error("%s", msg); +} + +/* + * libgit2 has a "git_treebuilder" concept, but it's broken, and can not + * be used to do a flat tree (like the git "index") nor a recursive tree. + * Stupid. + * + * So we have to do that "keep track of recursive treebuilder entries" + * ourselves. We use 'git_treebuilder' for any regular files, and our own + * data structures for recursive trees. + * + * When finally writing it out, we traverse the subdirectories depth- + * first, writing them out, and then adding the written-out trees to + * the git_treebuilder they existed in. + */ +struct dir { + git_treebuilder *files; + struct dir *subdirs, *sibling; + char unique, name[1]; +}; + +static int tree_insert(git_treebuilder *dir, const char *name, int mkunique, git_oid *id, unsigned mode) +{ + int ret; + struct membuffer uniquename = { 0 }; + + if (mkunique && git_treebuilder_get(dir, name)) { + char hex[8]; + git_oid_nfmt(hex, 7, id); + hex[7] = 0; + put_format(&uniquename, "%s~%s", name, hex); + name = mb_cstring(&uniquename); + } + ret = git_treebuilder_insert(NULL, dir, name, id, mode); + free_buffer(&uniquename); + return ret; +} + +/* + * This does *not* make sure the new subdirectory doesn't + * alias some existing name. That is actually useful: you + * can create multiple directories with the same name, and + * set the "unique" flag, which will then append the SHA1 + * of the directory to the name when it is written. + */ +static struct dir *new_directory(git_repository *repo, struct dir *parent, struct membuffer *namebuf) +{ + struct dir *subdir; + const char *name = mb_cstring(namebuf); + int len = namebuf->len; + + subdir = malloc(sizeof(*subdir)+len); + + /* + * It starts out empty: no subdirectories of its own, + * and an empty treebuilder list of files. + */ + subdir->subdirs = NULL; + git_treebuilder_new(&subdir->files, repo, NULL); + memcpy(subdir->name, name, len); + subdir->unique = 0; + subdir->name[len] = 0; + + /* Add it to the list of subdirs of the parent */ + subdir->sibling = parent->subdirs; + parent->subdirs = subdir; + + return subdir; +} + +static struct dir *mktree(git_repository *repo, struct dir *dir, const char *fmt, ...) +{ + struct membuffer buf = { 0 }; + struct dir *subdir; + + VA_BUF(&buf, fmt); + for (subdir = dir->subdirs; subdir; subdir = subdir->sibling) { + if (subdir->unique) + continue; + if (strncmp(subdir->name, buf.buffer, buf.len)) + continue; + if (!subdir->name[buf.len]) + break; + } + if (!subdir) + subdir = new_directory(repo, dir, &buf); + free_buffer(&buf); + return subdir; +} + +/* + * The name of a dive is the date and the dive number (and possibly + * the uniqueness suffix). + * + * Note that the time of the dive may not be the same as the + * time of the directory structure it is created in: the dive + * might be part of a trip that straddles a month (or even a + * year). + * + * We do *not* want to use localized weekdays and cause peoples save + * formats to depend on their locale. + */ +static void create_dive_name(struct dive *dive, struct membuffer *name, struct tm *dirtm) +{ + struct tm tm; + static const char weekday[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; + + utc_mkdate(dive->when, &tm); + if (tm.tm_year != dirtm->tm_year) + put_format(name, "%04u-", tm.tm_year + 1900); + if (tm.tm_mon != dirtm->tm_mon) + put_format(name, "%02u-", tm.tm_mon+1); + + /* a colon is an illegal char in a file name on Windows - use an '=' instead */ + put_format(name, "%02u-%s-%02u=%02u=%02u", + tm.tm_mday, weekday[tm.tm_wday], + tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +/* + * Write a membuffer to the git repo, and free it + */ +static int blob_insert(git_repository *repo, struct dir *tree, struct membuffer *b, const char *fmt, ...) +{ + int ret; + git_oid blob_id; + struct membuffer name = { 0 }; + + ret = git_blob_create_frombuffer(&blob_id, repo, b->buffer, b->len); + free_buffer(b); + if (ret) + return ret; + + VA_BUF(&name, fmt); + ret = tree_insert(tree->files, mb_cstring(&name), 1, &blob_id, GIT_FILEMODE_BLOB); + free_buffer(&name); + return ret; +} + +static int save_one_divecomputer(git_repository *repo, struct dir *tree, struct dive *dive, struct divecomputer *dc, int idx) +{ + int ret; + struct membuffer buf = { 0 }; + + save_dc(&buf, dive, dc); + ret = blob_insert(repo, tree, &buf, "Divecomputer%c%03u", idx ? '-' : 0, idx); + if (ret) + report_error("divecomputer tree insert failed"); + return ret; +} + +static int save_one_picture(git_repository *repo, struct dir *dir, struct picture *pic) +{ + int offset = pic->offset.seconds; + struct membuffer buf = { 0 }; + char sign = '+'; + unsigned h; + int error; + + show_utf8(&buf, "filename ", pic->filename, "\n"); + show_gps(&buf, pic->latitude, pic->longitude); + show_utf8(&buf, "hash ", pic->hash, "\n"); + + /* Picture loading will load even negative offsets.. */ + if (offset < 0) { + offset = -offset; + sign = '-'; + } + + /* Use full hh:mm:ss format to make it all sort nicely */ + h = offset / 3600; + offset -= h *3600; + error = blob_insert(repo, dir, &buf, "%c%02u=%02u=%02u", + sign, h, FRACTION(offset, 60)); +#if 0 + /* storing pictures into git was a mistake. This makes for HUGE git repositories */ + if (!error) { + /* next store the actual picture; we prefix all picture names + * with "PIC-" to make things easier on the parsing side */ + struct membuffer namebuf = { 0 }; + const char *localfn = local_file_path(pic); + put_format(&namebuf, "PIC-%s", pic->hash); + error = blob_insert_fromdisk(repo, dir, localfn, mb_cstring(&namebuf)); + free((void *)localfn); + } +#endif + return error; +} + +static int save_pictures(git_repository *repo, struct dir *dir, struct dive *dive) +{ + if (dive->picture_list) { + dir = mktree(repo, dir, "Pictures"); + FOR_EACH_PICTURE(dive) { + save_one_picture(repo, dir, picture); + } + } + return 0; +} + +static int save_one_dive(git_repository *repo, struct dir *tree, struct dive *dive, struct tm *tm, bool cached_ok) +{ + struct divecomputer *dc; + struct membuffer buf = { 0 }, name = { 0 }; + struct dir *subdir; + int ret, nr; + + /* Create dive directory */ + create_dive_name(dive, &name, tm); + + /* + * If the dive git ID is valid, we just create the whole directory + * with that ID + */ + if (cached_ok && dive_cache_is_valid(dive)) { + git_oid oid; + git_oid_fromraw(&oid, dive->git_id); + ret = tree_insert(tree->files, mb_cstring(&name), 1, + &oid, GIT_FILEMODE_TREE); + free_buffer(&name); + if (ret) + return report_error("cached dive tree insert failed"); + return 0; + } + + subdir = new_directory(repo, tree, &name); + subdir->unique = 1; + free_buffer(&name); + + create_dive_buffer(dive, &buf); + nr = dive->number; + ret = blob_insert(repo, subdir, &buf, + "Dive%c%d", nr ? '-' : 0, nr); + if (ret) + return report_error("dive save-file tree insert failed"); + + /* + * Save the dive computer data. If there is only one dive + * computer, use index 0 for that (which disables the index + * generation when naming it). + */ + dc = &dive->dc; + nr = dc->next ? 1 : 0; + do { + save_one_divecomputer(repo, subdir, dive, dc, nr++); + dc = dc->next; + } while (dc); + + /* Save the picture data, if any */ + save_pictures(repo, subdir, dive); + return 0; +} + +/* + * We'll mark the trip directories unique, so this does not + * need to be unique per se. It could be just "trip". But + * to make things a bit more user-friendly, we try to take + * the trip location into account. + * + * But no special characters, and no numbers (numbers in the + * name could be construed as a date). + * + * So we might end up with "02-Maui", and then the unique + * flag will make us write it out as "02-Maui~54b4" or + * similar. + */ +#define MAXTRIPNAME 15 +static void create_trip_name(dive_trip_t *trip, struct membuffer *name, struct tm *tm) +{ + put_format(name, "%02u-", tm->tm_mday); + if (trip->location) { + char ascii_loc[MAXTRIPNAME+1], *p = trip->location; + int i; + + for (i = 0; i < MAXTRIPNAME; ) { + char c = *p++; + switch (c) { + case 0: + case ',': + case '.': + break; + + case 'a' ... 'z': + case 'A' ... 'Z': + ascii_loc[i++] = c; + continue; + default: + continue; + } + break; + } + if (i > 1) { + put_bytes(name, ascii_loc, i); + return; + } + } + + /* No useful name? */ + put_string(name, "trip"); +} + +static int save_trip_description(git_repository *repo, struct dir *dir, dive_trip_t *trip, struct tm *tm) +{ + int ret; + git_oid blob_id; + struct membuffer desc = { 0 }; + + put_format(&desc, "date %04u-%02u-%02u\n", + tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + put_format(&desc, "time %02u:%02u:%02u\n", + tm->tm_hour, tm->tm_min, tm->tm_sec); + + show_utf8(&desc, "location ", trip->location, "\n"); + show_utf8(&desc, "notes ", trip->notes, "\n"); + + ret = git_blob_create_frombuffer(&blob_id, repo, desc.buffer, desc.len); + free_buffer(&desc); + if (ret) + return report_error("trip blob creation failed"); + ret = tree_insert(dir->files, "00-Trip", 0, &blob_id, GIT_FILEMODE_BLOB); + if (ret) + return report_error("trip description tree insert failed"); + return 0; +} + +static void verify_shared_date(timestamp_t when, struct tm *tm) +{ + struct tm tmp_tm; + + utc_mkdate(when, &tmp_tm); + if (tmp_tm.tm_year != tm->tm_year) { + tm->tm_year = -1; + tm->tm_mon = -1; + } + if (tmp_tm.tm_mon != tm->tm_mon) + tm->tm_mon = -1; +} + +#define MIN_TIMESTAMP (0) +#define MAX_TIMESTAMP (0x7fffffffffffffff) + +static int save_one_trip(git_repository *repo, struct dir *tree, dive_trip_t *trip, struct tm *tm, bool cached_ok) +{ + int i; + struct dive *dive; + struct dir *subdir; + struct membuffer name = { 0 }; + timestamp_t first, last; + + /* Create trip directory */ + create_trip_name(trip, &name, tm); + subdir = new_directory(repo, tree, &name); + subdir->unique = 1; + free_buffer(&name); + + /* Trip description file */ + save_trip_description(repo, subdir, trip, tm); + + /* Make sure we write out the dates to the dives consistently */ + first = MAX_TIMESTAMP; + last = MIN_TIMESTAMP; + for_each_dive(i, dive) { + if (dive->divetrip != trip) + continue; + if (dive->when < first) + first = dive->when; + if (dive->when > last) + last = dive->when; + } + verify_shared_date(first, tm); + verify_shared_date(last, tm); + + /* Save each dive in the directory */ + for_each_dive(i, dive) { + if (dive->divetrip == trip) + save_one_dive(repo, subdir, dive, tm, cached_ok); + } + + return 0; +} + +static void save_units(void *_b) +{ + struct membuffer *b =_b; + if (prefs.unit_system == METRIC) + put_string(b, "units METRIC\n"); + else if (prefs.unit_system == IMPERIAL) + put_string(b, "units IMPERIAL\n"); + else + put_format(b, "units PERSONALIZE %s %s %s %s %s %s", + prefs.units.length == METERS ? "METERS" : "FEET", + prefs.units.volume == LITER ? "LITER" : "CUFT", + prefs.units.pressure == BAR ? "BAR" : prefs.units.pressure == PSI ? "PSI" : "PASCAL", + prefs.units.temperature == CELSIUS ? "CELSIUS" : prefs.units.temperature == FAHRENHEIT ? "FAHRENHEIT" : "KELVIN", + prefs.units.weight == KG ? "KG" : "LBS", + prefs.units.vertical_speed_time == SECONDS ? "SECONDS" : "MINUTES"); +} + +static void save_userid(void *_b) +{ + struct membuffer *b = _b; + if (prefs.save_userid_local) + put_format(b, "userid %30s\n", prefs.userid); +} + +static void save_one_device(void *_b, const char *model, uint32_t deviceid, + const char *nickname, const char *serial, const char *firmware) +{ + struct membuffer *b = _b; + + if (nickname && !strcmp(model, nickname)) + nickname = NULL; + if (serial && !*serial) serial = NULL; + if (firmware && !*firmware) firmware = NULL; + if (nickname && !*nickname) nickname = NULL; + if (!nickname && !serial && !firmware) + return; + + show_utf8(b, "divecomputerid ", model, ""); + put_format(b, " deviceid=%08x", deviceid); + show_utf8(b, " serial=", serial, ""); + show_utf8(b, " firmware=", firmware, ""); + show_utf8(b, " nickname=", nickname, ""); + put_string(b, "\n"); +} + +static void save_settings(git_repository *repo, struct dir *tree) +{ + struct membuffer b = { 0 }; + + put_format(&b, "version %d\n", DATAFORMAT_VERSION); + save_userid(&b); + call_for_each_dc(&b, save_one_device, false); + cond_put_format(autogroup, &b, "autogroup\n"); + save_units(&b); + + blob_insert(repo, tree, &b, "00-Subsurface"); +} + +static void save_divesites(git_repository *repo, struct dir *tree) +{ + struct dir *subdir; + struct membuffer dirname = { 0 }; + put_format(&dirname, "01-Divesites"); + subdir = new_directory(repo, tree, &dirname); + + for (int i = 0; i < dive_site_table.nr; i++) { + struct membuffer b = { 0 }; + struct dive_site *ds = get_dive_site(i); + if (dive_site_is_empty(ds)) { + int j; + struct dive *d; + for_each_dive(j, d) { + if (d->dive_site_uuid == ds->uuid) + d->dive_site_uuid = 0; + } + delete_dive_site(ds->uuid); + i--; // since we just deleted that one + continue; + } else if (ds->name && + (strncmp(ds->name, "Auto-created dive", 17) == 0 || + strncmp(ds->name, "New Dive", 8) == 0)) { + fprintf(stderr, "found an auto divesite %s\n", ds->name); + // these are the two default names for sites from + // the web service; if the site isn't used in any + // dive (really? you didn't rename it?), delete it + if (!is_dive_site_used(ds->uuid, false)) { + if (verbose) + fprintf(stderr, "Deleted unused auto-created dive site %s\n", ds->name); + delete_dive_site(ds->uuid); + i--; // since we just deleted that one + continue; + } + } + struct membuffer site_file_name = { 0 }; + put_format(&site_file_name, "Site-%08x", ds->uuid); + show_utf8(&b, "name ", ds->name, "\n"); + show_utf8(&b, "description ", ds->description, "\n"); + show_utf8(&b, "notes ", ds->notes, "\n"); + show_gps(&b, ds->latitude, ds->longitude); + for (int j = 0; j < ds->taxonomy.nr; j++) { + struct taxonomy *t = &ds->taxonomy.category[j]; + if (t->category != TC_NONE && t->value) { + put_format(&b, "geo cat %d origin %d ", t->category, t->origin); + show_utf8(&b, "", t->value, "\n" ); + } + } + blob_insert(repo, subdir, &b, mb_cstring(&site_file_name)); + } +} + +static int create_git_tree(git_repository *repo, struct dir *root, bool select_only, bool cached_ok) +{ + int i; + struct dive *dive; + dive_trip_t *trip; + + save_settings(repo, root); + + save_divesites(repo, root); + + for (trip = dive_trip_list; trip != NULL; trip = trip->next) + trip->index = 0; + + /* save the dives */ + int notify_increment = dive_table.nr > 10 ? dive_table.nr / 10 : 1; + int last_threshold = 0; + for_each_dive(i, dive) { + struct tm tm; + struct dir *tree; + char buf[] = "save dives x0%"; + + if (i / notify_increment > last_threshold) { + // notify of progress - we cover the range of 20..50 + last_threshold = i / notify_increment; + buf[11] = last_threshold + '0'; + git_storage_update_progress(20 + 3 * last_threshold, buf); + } + + trip = dive->divetrip; + + if (select_only) { + if (!dive->selected) + continue; + /* We don't save trips when doing selected dive saves */ + trip = NULL; + } + + /* Create the date-based hierarchy */ + utc_mkdate(trip ? trip->when : dive->when, &tm); + tree = mktree(repo, root, "%04d", tm.tm_year + 1900); + tree = mktree(repo, tree, "%02d", tm.tm_mon + 1); + + if (trip) { + /* Did we already save this trip? */ + if (trip->index) + continue; + trip->index = 1; + + /* Pass that new subdirectory in for save-trip */ + save_one_trip(repo, tree, trip, &tm, cached_ok); + continue; + } + + save_one_dive(repo, tree, dive, &tm, cached_ok); + } + return 0; +} + +/* + * See if we can find the parent ID that the git data came from + */ +static git_object *try_to_find_parent(const char *hex_id, git_repository *repo) +{ + git_oid object_id; + git_commit *commit; + + if (!hex_id) + return NULL; + if (git_oid_fromstr(&object_id, hex_id)) + return NULL; + if (git_commit_lookup(&commit, repo, &object_id)) + return NULL; + return (git_object *)commit; +} + +static int notify_cb(git_checkout_notify_t why, + const char *path, + const git_diff_file *baseline, + const git_diff_file *target, + const git_diff_file *workdir, + void *payload) +{ + (void) baseline; + (void) target; + (void) workdir; + (void) payload; + (void) why; + report_error("File '%s' does not match in working tree", path); + return 0; /* Continue with checkout */ +} + +static git_tree *get_git_tree(git_repository *repo, git_object *parent) +{ + git_tree *tree; + if (!parent) + return NULL; + if (git_tree_lookup(&tree, repo, git_commit_tree_id((const git_commit *) parent))) + return NULL; + return tree; +} + +int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_DIRTY; + opts.notify_cb = notify_cb; + opts.baseline = get_git_tree(repo, parent); + return git_checkout_tree(repo, (git_object *) tree, &opts); +} + +static int get_authorship(git_repository *repo, git_signature **authorp) +{ +#if LIBGIT2_VER_MAJOR || LIBGIT2_VER_MINOR >= 20 + if (git_signature_default(authorp, repo) == 0) + return 0; +#endif + /* Default name information, with potential OS overrides */ + struct user_info user = { + .name = "Subsurface", + .email = "subsurace@subsurface-divelog.org" + }; + + subsurface_user_info(&user); + + /* git_signature_default() is too recent */ + return git_signature_now(authorp, user.name, user.email); +} + +static void create_commit_message(struct membuffer *msg) +{ + int nr = dive_table.nr; + struct dive *dive = get_dive(nr-1); + + if (dive) { + dive_trip_t *trip = dive->divetrip; + const char *location = get_dive_location(dive) ? : "no location"; + struct divecomputer *dc = &dive->dc; + const char *sep = "\n"; + + if (dive->number) + nr = dive->number; + + put_format(msg, "dive %d: %s", nr, location); + if (trip && trip->location && *trip->location && strcmp(trip->location, location)) + put_format(msg, " (%s)", trip->location); + put_format(msg, "\n"); + do { + if (dc->model && *dc->model) { + put_format(msg, "%s%s", sep, dc->model); + sep = ", "; + } + } while ((dc = dc->next) != NULL); + put_format(msg, "\n"); + } + put_format(msg, "Created by subsurface %s\n", subsurface_user_agent()); +} + +static int create_new_commit(git_repository *repo, const char *remote, const char *branch, git_oid *tree_id) +{ + int ret; + git_reference *ref; + git_object *parent; + git_oid commit_id; + git_signature *author; + git_commit *commit; + git_tree *tree; + + ret = git_branch_lookup(&ref, repo, branch, GIT_BRANCH_LOCAL); + switch (ret) { + default: + return report_error("Bad branch '%s' (%s)", branch, strerror(errno)); + case GIT_EINVALIDSPEC: + return report_error("Invalid branch name '%s'", branch); + case GIT_ENOTFOUND: /* We'll happily create it */ + ref = NULL; + parent = try_to_find_parent(saved_git_id, repo); + break; + case 0: + if (git_reference_peel(&parent, ref, GIT_OBJ_COMMIT)) + return report_error("Unable to look up parent in branch '%s'", branch); + + if (saved_git_id) { + if (existing_filename) + fprintf(stderr, "existing filename %s\n", existing_filename); + const git_oid *id = git_commit_id((const git_commit *) parent); + /* if we are saving to the same git tree we got this from, let's make + * sure there is no confusion */ + if (same_string(existing_filename, remote) && git_oid_strcmp(id, saved_git_id)) + return report_error("The git branch does not match the git parent of the source"); + } + + /* all good */ + break; + } + + if (git_tree_lookup(&tree, repo, tree_id)) + return report_error("Could not look up newly created tree"); + + if (get_authorship(repo, &author)) + return report_error("No user name configuration in git repo"); + + /* If the parent commit has the same tree ID, do not create a new commit */ + if (parent && git_oid_equal(tree_id, git_commit_tree_id((const git_commit *) parent))) { + /* If the parent already came from the ref, the commit is already there */ + if (ref) + return 0; + /* Else we do want to create the new branch, but with the old commit */ + commit = (git_commit *) parent; + } else { + struct membuffer commit_msg = { 0 }; + + create_commit_message(&commit_msg); + if (git_commit_create_v(&commit_id, repo, NULL, author, author, NULL, mb_cstring(&commit_msg), tree, parent != NULL, parent)) + return report_error("Git commit create failed (%s)", strerror(errno)); + free_buffer(&commit_msg); + + if (git_commit_lookup(&commit, repo, &commit_id)) + return report_error("Could not look up newly created commit"); + } + + if (!ref) { + if (git_branch_create(&ref, repo, branch, commit, 0)) + return report_error("Failed to create branch '%s'", branch); + } + /* + * If it's a checked-out branch, try to also update the working + * tree and index. If that fails (dirty working tree or whatever), + * this is not technically a save error (we did save things in + * the object database), but it can cause extreme confusion, so + * warn about it. + */ + if (git_branch_is_head(ref) && !git_repository_is_bare(repo)) { + if (update_git_checkout(repo, parent, tree)) { + const git_error *err = giterr_last(); + const char *errstr = err ? err->message : strerror(errno); + report_error("Git branch '%s' is checked out, but worktree is dirty (%s)", + branch, errstr); + } + } + + if (git_reference_set_target(&ref, ref, &commit_id, "Subsurface save event")) + return report_error("Failed to update branch '%s'", branch); + set_git_id(&commit_id); + + git_signature_free(author); + + return 0; +} + +static int write_git_tree(git_repository *repo, struct dir *tree, git_oid *result) +{ + int ret; + struct dir *subdir; + + /* Write out our subdirectories, add them to the treebuilder, and free them */ + while ((subdir = tree->subdirs) != NULL) { + git_oid id; + + if (!write_git_tree(repo, subdir, &id)) + tree_insert(tree->files, subdir->name, subdir->unique, &id, GIT_FILEMODE_TREE); + tree->subdirs = subdir->sibling; + free(subdir); + }; + + /* .. write out the resulting treebuilder */ + ret = git_treebuilder_write(result, tree->files); + + /* .. and free the now useless treebuilder */ + git_treebuilder_free(tree->files); + + return ret; +} + +int do_git_save(git_repository *repo, const char *branch, const char *remote, bool select_only, bool create_empty) +{ + struct dir tree; + git_oid id; + bool cached_ok; + + if (verbose) + fprintf(stderr, "git storage: do git save\n"); + + if (!create_empty) // so we are actually saving the dives + git_storage_update_progress(19, "start git save"); + + /* + * Check if we can do the cached writes - we need to + * have the original git commit we loaded in the repo + */ + cached_ok = try_to_find_parent(saved_git_id, repo); + + /* Start with an empty tree: no subdirectories, no files */ + tree.name[0] = 0; + tree.subdirs = NULL; + if (git_treebuilder_new(&tree.files, repo, NULL)) + return report_error("git treebuilder failed"); + + if (!create_empty) + /* Populate our tree data structure */ + if (create_git_tree(repo, &tree, select_only, cached_ok)) + return -1; + + if (verbose) + fprintf(stderr, "git storage, write git tree\n"); + + if (write_git_tree(repo, &tree, &id)) + return report_error("git tree write failed"); + + /* And save the tree! */ + if (create_new_commit(repo, remote, branch, &id)) + return report_error("creating commit failed"); + + if (remote && prefs.cloud_background_sync) { + /* now sync the tree with the cloud server */ + if (strstr(remote, prefs.cloud_git_url)) { + return sync_with_remote(repo, remote, branch, RT_HTTPS); + } + } + return 0; +} + +int git_save_dives(struct git_repository *repo, const char *branch, const char *remote, bool select_only) +{ + int ret; + + if (repo == dummy_git_repository) + return report_error("Unable to open git repository '%s'", branch); + ret = do_git_save(repo, branch, remote, select_only, false); + git_repository_free(repo); + free((void *)branch); + return ret; +} diff --git a/core/save-html.c b/core/save-html.c new file mode 100644 index 000000000..2d0ea9cf3 --- /dev/null +++ b/core/save-html.c @@ -0,0 +1,559 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#include "save-html.h" +#include "qthelperfromc.h" +#include "gettext.h" +#include "stdio.h" + +void write_attribute(struct membuffer *b, const char *att_name, const char *value, const char *separator) +{ + if (!value) + value = "--"; + put_format(b, "\"%s\":\"", att_name); + put_HTML_quoted(b, value); + put_format(b, "\"%s", separator); +} + +void save_photos(struct membuffer *b, const char *photos_dir, struct dive *dive) +{ + struct picture *pic = dive->picture_list; + + if (!pic) + return; + + char *separator = "\"photos\":["; + do { + put_string(b, separator); + separator = ", "; + char *fname = get_file_name(local_file_path(pic)); + put_format(b, "{\"filename\":\"%s\"}", fname); + copy_image_and_overwrite(local_file_path(pic), photos_dir, fname); + free(fname); + pic = pic->next; + } while (pic); + put_string(b, "],"); +} + +void write_divecomputers(struct membuffer *b, struct dive *dive) +{ + put_string(b, "\"divecomputers\":["); + struct divecomputer *dc; + char *separator = ""; + for_each_dc (dive, dc) { + put_string(b, separator); + separator = ", "; + put_format(b, "{"); + write_attribute(b, "model", dc->model, ", "); + if (dc->deviceid) + put_format(b, "\"deviceid\":\"%08x\", ", dc->deviceid); + else + put_string(b, "\"deviceid\":\"--\", "); + if (dc->diveid) + put_format(b, "\"diveid\":\"%08x\" ", dc->diveid); + else + put_string(b, "\"diveid\":\"--\" "); + put_format(b, "}"); + } + put_string(b, "],"); +} + +void write_dive_status(struct membuffer *b, struct dive *dive) +{ + put_format(b, "\"sac\":\"%d\",", dive->sac); + put_format(b, "\"otu\":\"%d\",", dive->otu); + put_format(b, "\"cns\":\"%d\",", dive->cns); +} + +void put_HTML_bookmarks(struct membuffer *b, struct dive *dive) +{ + struct event *ev = dive->dc.events; + + if (!ev) + return; + + char *separator = "\"events\":["; + do { + put_string(b, separator); + separator = ", "; + put_format(b, "{\"name\":\"%s\",", ev->name); + put_format(b, "\"value\":\"%d\",", ev->value); + put_format(b, "\"type\":\"%d\",", ev->type); + put_format(b, "\"time\":\"%d\"}", ev->time.seconds); + ev = ev->next; + } while (ev); + put_string(b, "],"); +} + +static void put_weightsystem_HTML(struct membuffer *b, struct dive *dive) +{ + int i, nr; + + nr = nr_weightsystems(dive); + + put_string(b, "\"Weights\":["); + + char *separator = ""; + + for (i = 0; i < nr; i++) { + weightsystem_t *ws = dive->weightsystem + i; + int grams = ws->weight.grams; + const char *description = ws->description; + + put_string(b, separator); + separator = ", "; + put_string(b, "{"); + put_HTML_weight_units(b, grams, "\"weight\":\"", "\","); + write_attribute(b, "description", description, " "); + put_string(b, "}"); + } + put_string(b, "],"); +} + +static void put_cylinder_HTML(struct membuffer *b, struct dive *dive) +{ + int i, nr; + char *separator = "\"Cylinders\":["; + nr = nr_cylinders(dive); + + if (!nr) + put_string(b, separator); + + for (i = 0; i < nr; i++) { + cylinder_t *cylinder = dive->cylinder + i; + put_format(b, "%s{", separator); + separator = ", "; + write_attribute(b, "Type", cylinder->type.description, ", "); + if (cylinder->type.size.mliter) { + int volume = cylinder->type.size.mliter; + if (prefs.units.volume == CUFT && cylinder->type.workingpressure.mbar) + volume *= bar_to_atm(cylinder->type.workingpressure.mbar / 1000.0); + put_HTML_volume_units(b, volume, "\"Size\":\"", " \", "); + } else { + write_attribute(b, "Size", "--", ", "); + } + put_HTML_pressure_units(b, cylinder->type.workingpressure, "\"WPressure\":\"", " \", "); + + if (cylinder->start.mbar) { + put_HTML_pressure_units(b, cylinder->start, "\"SPressure\":\"", " \", "); + } else { + write_attribute(b, "SPressure", "--", ", "); + } + + if (cylinder->end.mbar) { + put_HTML_pressure_units(b, cylinder->end, "\"EPressure\":\"", " \", "); + } else { + write_attribute(b, "EPressure", "--", ", "); + } + + if (cylinder->gasmix.o2.permille) { + put_format(b, "\"O2\":\"%u.%u%%\",", FRACTION(cylinder->gasmix.o2.permille, 10)); + put_format(b, "\"He\":\"%u.%u%%\"", FRACTION(cylinder->gasmix.he.permille, 10)); + } else { + write_attribute(b, "O2", "Air", ""); + } + + put_string(b, "}"); + } + + put_string(b, "],"); +} + + +void put_HTML_samples(struct membuffer *b, struct dive *dive) +{ + int i; + put_format(b, "\"maxdepth\":%d,", dive->dc.maxdepth.mm); + put_format(b, "\"duration\":%d,", dive->dc.duration.seconds); + struct sample *s = dive->dc.sample; + + if (!dive->dc.samples) + return; + + char *separator = "\"samples\":["; + for (i = 0; i < dive->dc.samples; i++) { + put_format(b, "%s[%d,%d,%d,%d]", separator, s->time.seconds, s->depth.mm, s->cylinderpressure.mbar, s->temperature.mkelvin); + separator = ", "; + s++; + } + put_string(b, "],"); +} + +void put_HTML_coordinates(struct membuffer *b, struct dive *dive) +{ + struct dive_site *ds = get_dive_site_for_dive(dive); + if (!ds) + return; + degrees_t latitude = ds->latitude; + degrees_t longitude = ds->longitude; + + //don't put coordinates if in (0,0) + if (!latitude.udeg && !longitude.udeg) + return; + + put_string(b, "\"coordinates\":{"); + put_degrees(b, latitude, "\"lat\":\"", "\","); + put_degrees(b, longitude, "\"lon\":\"", "\""); + put_string(b, "},"); +} + +void put_HTML_date(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +{ + struct tm tm; + utc_mkdate(dive->when, &tm); + put_format(b, "%s%04u-%02u-%02u%s", pre, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, post); +} + +void put_HTML_quoted(struct membuffer *b, const char *text) +{ + int is_html = 1, is_attribute = 1; + put_quoted(b, text, is_attribute, is_html); +} + +void put_HTML_notes(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +{ + put_string(b, pre); + if (dive->notes) { + put_HTML_quoted(b, dive->notes); + } else { + put_string(b, "--"); + } + put_string(b, post); +} + +void put_HTML_pressure_units(struct membuffer *b, pressure_t pressure, const char *pre, const char *post) +{ + const char *unit; + double value; + + if (!pressure.mbar) { + put_format(b, "%s%s", pre, post); + return; + } + + value = get_pressure_units(pressure.mbar, &unit); + put_format(b, "%s%.1f %s%s", pre, value, unit, post); +} + +void put_HTML_volume_units(struct membuffer *b, unsigned int ml, const char *pre, const char *post) +{ + const char *unit; + double value; + int frac; + + value = get_volume_units(ml, &frac, &unit); + put_format(b, "%s%.1f %s%s", pre, value, unit, post); +} + +void put_HTML_weight_units(struct membuffer *b, unsigned int grams, const char *pre, const char *post) +{ + const char *unit; + double value; + int frac; + + value = get_weight_units(grams, &frac, &unit); + put_format(b, "%s%.1f %s%s", pre, value, unit, post); +} + +void put_HTML_time(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +{ + struct tm tm; + utc_mkdate(dive->when, &tm); + put_format(b, "%s%02u:%02u:%02u%s", pre, tm.tm_hour, tm.tm_min, tm.tm_sec, post); +} + +void put_HTML_depth(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +{ + const char *unit; + double value; + struct units *units_p = get_units(); + + if (!dive->maxdepth.mm) { + put_format(b, "%s--%s", pre, post); + return; + } + value = get_depth_units(dive->maxdepth.mm, NULL, &unit); + + switch (units_p->length) { + case METERS: + default: + put_format(b, "%s%.1f %s%s", pre, value, unit, post); + break; + case FEET: + put_format(b, "%s%.0f %s%s", pre, value, unit, post); + break; + } +} + +void put_HTML_airtemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +{ + const char *unit; + double value; + + if (!dive->airtemp.mkelvin) { + put_format(b, "%s--%s", pre, post); + return; + } + value = get_temp_units(dive->airtemp.mkelvin, &unit); + put_format(b, "%s%.1f %s%s", pre, value, unit, post); +} + +void put_HTML_watertemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +{ + const char *unit; + double value; + + if (!dive->watertemp.mkelvin) { + put_format(b, "%s--%s", pre, post); + return; + } + value = get_temp_units(dive->watertemp.mkelvin, &unit); + put_format(b, "%s%.1f %s%s", pre, value, unit, post); +} + +void put_HTML_tags(struct membuffer *b, struct dive *dive, const char *pre, const char *post) +{ + put_string(b, pre); + struct tag_entry *tag = dive->tag_list; + + if (!tag) + put_string(b, "[\"--\""); + + char *separator = "["; + while (tag) { + put_format(b, "%s\"", separator); + separator = ", "; + put_HTML_quoted(b, tag->tag->name); + put_string(b, "\""); + tag = tag->next; + } + put_string(b, "]"); + put_string(b, post); +} + +/* if exporting list_only mode, we neglect exporting the samples, bookmarks and cylinders */ +void write_one_dive(struct membuffer *b, struct dive *dive, const char *photos_dir, int *dive_no, const bool list_only) +{ + put_string(b, "{"); + put_format(b, "\"number\":%d,", *dive_no); + put_format(b, "\"subsurface_number\":%d,", dive->number); + put_HTML_date(b, dive, "\"date\":\"", "\","); + put_HTML_time(b, dive, "\"time\":\"", "\","); + write_attribute(b, "location", get_dive_location(dive), ", "); + put_HTML_coordinates(b, dive); + put_format(b, "\"rating\":%d,", dive->rating); + put_format(b, "\"visibility\":%d,", dive->visibility); + put_format(b, "\"dive_duration\":\"%u:%02u min\",", + FRACTION(dive->duration.seconds, 60)); + put_string(b, "\"temperature\":{"); + put_HTML_airtemp(b, dive, "\"air\":\"", "\","); + put_HTML_watertemp(b, dive, "\"water\":\"", "\""); + put_string(b, " },"); + write_attribute(b, "buddy", dive->buddy, ", "); + write_attribute(b, "divemaster", dive->divemaster, ", "); + write_attribute(b, "suit", dive->suit, ", "); + put_HTML_tags(b, dive, "\"tags\":", ","); + if (!list_only) { + put_cylinder_HTML(b, dive); + put_weightsystem_HTML(b, dive); + put_HTML_samples(b, dive); + put_HTML_bookmarks(b, dive); + write_dive_status(b, dive); + if (photos_dir && strcmp(photos_dir, "")) + save_photos(b, photos_dir, dive); + write_divecomputers(b, dive); + } + put_HTML_notes(b, dive, "\"notes\":\"", "\""); + put_string(b, "}\n"); + (*dive_no)++; +} + +void write_no_trip(struct membuffer *b, int *dive_no, bool selected_only, const char *photos_dir, const bool list_only, char *sep) +{ + int i; + struct dive *dive; + char *separator = ""; + bool found_sel_dive = 0; + + for_each_dive (i, dive) { + // write dive if it doesn't belong to any trip and the dive is selected + // or we are in exporting all dives mode. + if (!dive->divetrip && (dive->selected || !selected_only)) { + if (!found_sel_dive) { + put_format(b, "%c{", *sep); + (*sep) = ','; + put_format(b, "\"name\":\"Other\","); + put_format(b, "\"dives\":["); + found_sel_dive = 1; + } + put_string(b, separator); + separator = ", "; + write_one_dive(b, dive, photos_dir, dive_no, list_only); + } + } + if (found_sel_dive) + put_format(b, "]}\n\n"); +} + +void write_trip(struct membuffer *b, dive_trip_t *trip, int *dive_no, bool selected_only, const char *photos_dir, const bool list_only, char *sep) +{ + struct dive *dive; + char *separator = ""; + bool found_sel_dive = 0; + + for (dive = trip->dives; dive != NULL; dive = dive->next) { + if (!dive->selected && selected_only) + continue; + + // save trip if found at least one selected dive. + if (!found_sel_dive) { + found_sel_dive = 1; + put_format(b, "%c {", *sep); + (*sep) = ','; + put_format(b, "\"name\":\"%s\",", trip->location); + put_format(b, "\"dives\":["); + } + put_string(b, separator); + separator = ", "; + write_one_dive(b, dive, photos_dir, dive_no, list_only); + } + + // close the trip object if contain dives. + if (found_sel_dive) + put_format(b, "]}\n\n"); +} + +void write_trips(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only) +{ + int i, dive_no = 0; + struct dive *dive; + dive_trip_t *trip; + char sep_ = ' '; + char *sep = &sep_; + + for (trip = dive_trip_list; trip != NULL; trip = trip->next) + trip->index = 0; + + for_each_dive (i, dive) { + trip = dive->divetrip; + + /*Continue if the dive have no trips or we have seen this trip before*/ + if (!trip || trip->index) + continue; + + /* We haven't seen this trip before - save it and all dives */ + trip->index = 1; + write_trip(b, trip, &dive_no, selected_only, photos_dir, list_only, sep); + } + + /*Save all remaining trips into Others*/ + write_no_trip(b, &dive_no, selected_only, photos_dir, list_only, sep); +} + +void export_list(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only) +{ + put_string(b, "trips=["); + write_trips(b, photos_dir, selected_only, list_only); + put_string(b, "]"); +} + +void export_HTML(const char *file_name, const char *photos_dir, const bool selected_only, const bool list_only) +{ + FILE *f; + + struct membuffer buf = { 0 }; + export_list(&buf, photos_dir, selected_only, list_only); + + f = subsurface_fopen(file_name, "w+"); + if (!f) { + report_error(translate("gettextFromC", "Can't open file %s"), file_name); + } else { + flush_buffer(&buf, f); /*check for writing errors? */ + fclose(f); + } + free_buffer(&buf); +} + +void export_translation(const char *file_name) +{ + FILE *f; + + struct membuffer buf = { 0 }; + struct membuffer *b = &buf; + + //export translated words here + put_format(b, "translate={"); + + //Dive list view + write_attribute(b, "Number", translate("gettextFromC", "Number"), ", "); + write_attribute(b, "Date", translate("gettextFromC", "Date"), ", "); + write_attribute(b, "Time", translate("gettextFromC", "Time"), ", "); + write_attribute(b, "Location", translate("gettextFromC", "Location"), ", "); + write_attribute(b, "Air_Temp", translate("gettextFromC", "Air temp."), ", "); + write_attribute(b, "Water_Temp", translate("gettextFromC", "Water temp."), ", "); + write_attribute(b, "dives", translate("gettextFromC", "Dives"), ", "); + write_attribute(b, "Expand_All", translate("gettextFromC", "Expand all"), ", "); + write_attribute(b, "Collapse_All", translate("gettextFromC", "Collapse all"), ", "); + write_attribute(b, "trips", translate("gettextFromC", "Trips"), ", "); + write_attribute(b, "Statistics", translate("gettextFromC", "Statistics"), ", "); + write_attribute(b, "Advanced_Search", translate("gettextFromC", "Advanced search"), ", "); + + //Dive expanded view + write_attribute(b, "Rating", translate("gettextFromC", "Rating"), ", "); + write_attribute(b, "Visibility", translate("gettextFromC", "Visibility"), ", "); + write_attribute(b, "Duration", translate("gettextFromC", "Duration"), ", "); + write_attribute(b, "DiveMaster", translate("gettextFromC", "Divemaster"), ", "); + write_attribute(b, "Buddy", translate("gettextFromC", "Buddy"), ", "); + write_attribute(b, "Suit", translate("gettextFromC", "Suit"), ", "); + write_attribute(b, "Tags", translate("gettextFromC", "Tags"), ", "); + write_attribute(b, "Notes", translate("gettextFromC", "Notes"), ", "); + write_attribute(b, "Show_more_details", translate("gettextFromC", "Show more details"), ", "); + + //Yearly statistics view + write_attribute(b, "Yearly_statistics", translate("gettextFromC", "Yearly statistics"), ", "); + write_attribute(b, "Year", translate("gettextFromC", "Year"), ", "); + write_attribute(b, "Total_Time", translate("gettextFromC", "Total time"), ", "); + write_attribute(b, "Average_Time", translate("gettextFromC", "Average time"), ", "); + write_attribute(b, "Shortest_Time", translate("gettextFromC", "Shortest time"), ", "); + write_attribute(b, "Longest_Time", translate("gettextFromC", "Longest time"), ", "); + write_attribute(b, "Average_Depth", translate("gettextFromC", "Average depth"), ", "); + write_attribute(b, "Min_Depth", translate("gettextFromC", "Min. depth"), ", "); + write_attribute(b, "Max_Depth", translate("gettextFromC", "Max. depth"), ", "); + write_attribute(b, "Average_SAC", translate("gettextFromC", "Average SAC"), ", "); + write_attribute(b, "Min_SAC", translate("gettextFromC", "Min. SAC"), ", "); + write_attribute(b, "Max_SAC", translate("gettextFromC", "Max. SAC"), ", "); + write_attribute(b, "Average_Temp", translate("gettextFromC", "Average temp."), ", "); + write_attribute(b, "Min_Temp", translate("gettextFromC", "Min. temp."), ", "); + write_attribute(b, "Max_Temp", translate("gettextFromC", "Max. temp."), ", "); + write_attribute(b, "Back_to_List", translate("gettextFromC", "Back to list"), ", "); + + //dive detailed view + write_attribute(b, "Dive_No", translate("gettextFromC", "Dive No."), ", "); + write_attribute(b, "Dive_profile", translate("gettextFromC", "Dive profile"), ", "); + write_attribute(b, "Dive_information", translate("gettextFromC", "Dive information"), ", "); + write_attribute(b, "Dive_equipment", translate("gettextFromC", "Dive equipment"), ", "); + write_attribute(b, "Type", translate("gettextFromC", "Type"), ", "); + write_attribute(b, "Size", translate("gettextFromC", "Size"), ", "); + write_attribute(b, "Work_Pressure", translate("gettextFromC", "Work pressure"), ", "); + write_attribute(b, "Start_Pressure", translate("gettextFromC", "Start pressure"), ", "); + write_attribute(b, "End_Pressure", translate("gettextFromC", "End pressure"), ", "); + write_attribute(b, "Gas", translate("gettextFromC", "Gas"), ", "); + write_attribute(b, "Weight", translate("gettextFromC", "Weight"), ", "); + write_attribute(b, "Type", translate("gettextFromC", "Type"), ", "); + write_attribute(b, "Events", translate("gettextFromC", "Events"), ", "); + write_attribute(b, "Name", translate("gettextFromC", "Name"), ", "); + write_attribute(b, "Value", translate("gettextFromC", "Value"), ", "); + write_attribute(b, "Coordinates", translate("gettextFromC", "Coordinates"), ", "); + write_attribute(b, "Dive_Status", translate("gettextFromC", "Dive status"), " "); + + put_format(b, "}"); + + f = subsurface_fopen(file_name, "w+"); + if (!f) { + report_error(translate("gettextFromC", "Can't open file %s"), file_name); + } else { + flush_buffer(&buf, f); /*check for writing errors? */ + fclose(f); + } + free_buffer(&buf); +} diff --git a/core/save-html.h b/core/save-html.h new file mode 100644 index 000000000..13bb102b1 --- /dev/null +++ b/core/save-html.h @@ -0,0 +1,31 @@ +#ifndef HTML_SAVE_H +#define HTML_SAVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "dive.h" +#include "membuffer.h" + +void put_HTML_date(struct membuffer *b, struct dive *dive, const char *pre, const char *post); +void put_HTML_depth(struct membuffer *b, struct dive *dive, const char *pre, const char *post); +void put_HTML_airtemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post); +void put_HTML_watertemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post); +void put_HTML_time(struct membuffer *b, struct dive *dive, const char *pre, const char *post); +void put_HTML_notes(struct membuffer *b, struct dive *dive, const char *pre, const char *post); +void put_HTML_quoted(struct membuffer *b, const char *text); +void put_HTML_pressure_units(struct membuffer *b, pressure_t pressure, const char *pre, const char *post); +void put_HTML_weight_units(struct membuffer *b, unsigned int grams, const char *pre, const char *post); +void put_HTML_volume_units(struct membuffer *b, unsigned int ml, const char *pre, const char *post); + +void export_HTML(const char *file_name, const char *photos_dir, const bool selected_only, const bool list_only); +void export_list(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only); + +void export_translation(const char *file_name); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/save-xml.c b/core/save-xml.c new file mode 100644 index 000000000..2335637e8 --- /dev/null +++ b/core/save-xml.c @@ -0,0 +1,749 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#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); + + save_dives_buffer(&buf, select_only); + + if (same_string(filename, "-")) { + f = stdout; + } else { + try_to_backup(filename); + error = -1; + f = subsurface_fopen(filename, "w"); + } + if (f) { + flush_buffer(&buf, f); + error = fclose(f); + } + if (error) + report_error("Save failed (%s)", strerror(errno)); + + free_buffer(&buf); + return error; +} + +int export_dives_xslt(const char *filename, const bool selected, const int units, const char *export_xslt) +{ + FILE *f; + struct membuffer buf = { 0 }; + xmlDoc *doc; + xsltStylesheetPtr xslt = NULL; + xmlDoc *transformed; + int res = 0; + char *params[3]; + int pnr = 0; + char unitstr[3]; + + if (verbose) + fprintf(stderr, "export_dives_xslt with stylesheet %s\n", export_xslt); + + if (!filename) + return report_error("No filename for export"); + + /* Save XML to file and convert it into a memory buffer */ + save_dives_buffer(&buf, selected); + + /* + * Parse the memory buffer into XML document and + * transform it to selected export format, finally dumping + * the XML into a character buffer. + */ + doc = xmlReadMemory(buf.buffer, buf.len, "divelog", NULL, 0); + free_buffer(&buf); + if (!doc) + return report_error("Failed to read XML memory"); + + /* Convert to export format */ + xslt = get_stylesheet(export_xslt); + if (!xslt) + return report_error("Failed to open export conversion stylesheet"); + + snprintf(unitstr, 3, "%d", units); + params[pnr++] = "units"; + params[pnr++] = unitstr; + params[pnr++] = NULL; + + transformed = xsltApplyStylesheet(xslt, doc, (const char **)params); + xmlFreeDoc(doc); + + /* Write the transformed export to file */ + f = subsurface_fopen(filename, "w"); + if (f) { + xsltSaveResultToFile(f, transformed, xslt); + fclose(f); + /* Check write errors? */ + } else { + res = report_error("Failed to open %s for writing (%s)", filename, strerror(errno)); + } + xsltFreeStylesheet(xslt); + xmlFreeDoc(transformed); + + return res; +} diff --git a/core/serial_ftdi.c b/core/serial_ftdi.c new file mode 100644 index 000000000..ff1335171 --- /dev/null +++ b/core/serial_ftdi.c @@ -0,0 +1,665 @@ +/* + * 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)) { + free(device); + ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); + return DC_STATUS_IO; + } + + if (ftdi_usb_purge_buffers(ftdi_ctx)) { + free(device); + ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); + return DC_STATUS_IO; + } + + device->ftdi_ctx = ftdi_ctx; + + //FIXME: remove this when custom-serial have support for configure calls + serial_ftdi_configure (device, 115200, 8, 0, 1, 0); + + *out = device; + + return DC_STATUS_SUCCESS; +} + +// +// Close the serial port. +// +static int serial_ftdi_close (serial_t *device) +{ + if (device == NULL) + return 0; + + // Restore the initial terminal attributes. + // See if it is possible using libusb or libftdi + + int ret = ftdi_usb_close(device->ftdi_ctx); + if (ret < 0) { + ERROR (device->context, "Unable to close the ftdi device : %d (%s)\n", + ret, ftdi_get_error_string(device->ftdi_ctx)); + return ret; + } + + ftdi_free(device->ftdi_ctx); + + // Free memory. + free (device); + + return 0; +} + +// +// Configure the serial port (baudrate, databits, parity, stopbits and flowcontrol). +// +static dc_status_t serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + INFO (device->context, "Configure: baudrate=%i, databits=%i, parity=%i, stopbits=%i, flowcontrol=%i", + baudrate, databits, parity, stopbits, flowcontrol); + + enum ftdi_bits_type ft_bits; + enum ftdi_stopbits_type ft_stopbits; + enum ftdi_parity_type ft_parity; + + if (ftdi_set_baudrate(device->ftdi_ctx, baudrate) < 0) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return -1; + } + + // Set the character size. + switch (databits) { + case 7: + ft_bits = BITS_7; + break; + case 8: + ft_bits = BITS_8; + break; + default: + return DC_STATUS_INVALIDARGS; + } + + // Set the parity type. + switch (parity) { + case SERIAL_PARITY_NONE: // No parity + ft_parity = NONE; + break; + case SERIAL_PARITY_EVEN: // Even parity + ft_parity = EVEN; + break; + case SERIAL_PARITY_ODD: // Odd parity + ft_parity = ODD; + break; + default: + return DC_STATUS_INVALIDARGS; + } + + // Set the number of stop bits. + switch (stopbits) { + case 1: // One stopbit + ft_stopbits = STOP_BIT_1; + break; + case 2: // Two stopbits + ft_stopbits = STOP_BIT_2; + break; + default: + return DC_STATUS_INVALIDARGS; + } + + // Set the attributes + if (ftdi_set_line_property(device->ftdi_ctx, ft_bits, ft_stopbits, ft_parity)) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return DC_STATUS_IO; + } + + // Set the flow control. + switch (flowcontrol) { + case SERIAL_FLOWCONTROL_NONE: // No flow control. + if (ftdi_setflowctrl(device->ftdi_ctx, SIO_DISABLE_FLOW_CTRL) < 0) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return DC_STATUS_IO; + } + break; + case SERIAL_FLOWCONTROL_HARDWARE: // Hardware (RTS/CTS) flow control. + if (ftdi_setflowctrl(device->ftdi_ctx, SIO_RTS_CTS_HS) < 0) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return DC_STATUS_IO; + } + break; + case SERIAL_FLOWCONTROL_SOFTWARE: // Software (XON/XOFF) flow control. + if (ftdi_setflowctrl(device->ftdi_ctx, SIO_XON_XOFF_HS) < 0) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return DC_STATUS_IO; + } + break; + default: + return DC_STATUS_INVALIDARGS; + } + + device->baudrate = baudrate; + device->nbits = 1 + databits + stopbits + (parity ? 1 : 0); + + return DC_STATUS_SUCCESS; +} + +// +// Configure the serial port (timeouts). +// +static int serial_ftdi_set_timeout (serial_t *device, long timeout) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + INFO (device->context, "Timeout: value=%li", timeout); + + device->timeout = timeout; + + return 0; +} + +static int serial_ftdi_set_halfduplex (serial_t *device, int value) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + // Most ftdi chips support full duplex operation. ft232rl does. + // Crosscheck other chips. + + device->halfduplex = value; + + return 0; +} + +static int serial_ftdi_read (serial_t *device, void *data, unsigned int size) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + // The total timeout. + long timeout = device->timeout; + + // The absolute target time. + struct timeval tve; + + static int backoff = 1; + int init = 1; + unsigned int nbytes = 0; + while (nbytes < size) { + struct timeval tvt; + if (timeout > 0) { + struct timeval now; + if (gettimeofday (&now, NULL) != 0) { + SYSERROR (device->context, errno); + return -1; + } + + if (init) { + // Calculate the initial timeout. + tvt.tv_sec = (timeout / 1000); + tvt.tv_usec = (timeout % 1000) * 1000; + // Calculate the target time. + timeradd (&now, &tvt, &tve); + } else { + // Calculate the remaining timeout. + if (timercmp (&now, &tve, <)) + timersub (&tve, &now, &tvt); + else + timerclear (&tvt); + } + init = 0; + } else if (timeout == 0) { + timerclear (&tvt); + } + + int n = ftdi_read_data (device->ftdi_ctx, (char *) data + nbytes, size - nbytes); + if (n < 0) { + if (n == LIBUSB_ERROR_INTERRUPTED) + continue; //Retry. + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return -1; //Error during read call. + } else if (n == 0) { + // Exponential backoff. + if (backoff > MAX_BACKOFF) { + ERROR(device->context, "%s", "FTDI read timed out."); + return -1; + } + serial_ftdi_sleep (device, backoff); + backoff *= 2; + } else { + // Reset backoff to 1 on success. + backoff = 1; + } + + nbytes += n; + } + + INFO (device->context, "Read %d bytes", nbytes); + + return nbytes; +} + +static int serial_ftdi_write (serial_t *device, const void *data, unsigned int size) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + struct timeval tve, tvb; + if (device->halfduplex) { + // Get the current time. + if (gettimeofday (&tvb, NULL) != 0) { + SYSERROR (device->context, errno); + return -1; + } + } + + unsigned int nbytes = 0; + while (nbytes < size) { + + int n = ftdi_write_data (device->ftdi_ctx, (char *) data + nbytes, size - nbytes); + if (n < 0) { + if (n == LIBUSB_ERROR_INTERRUPTED) + continue; // Retry. + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return -1; // Error during write call. + } else if (n == 0) { + break; // EOF. + } + + nbytes += n; + } + + if (device->halfduplex) { + // Get the current time. + if (gettimeofday (&tve, NULL) != 0) { + SYSERROR (device->context, errno); + return -1; + } + + // Calculate the elapsed time (microseconds). + struct timeval tvt; + timersub (&tve, &tvb, &tvt); + unsigned long elapsed = tvt.tv_sec * 1000000 + tvt.tv_usec; + + // Calculate the expected duration (microseconds). A 2 millisecond fudge + // factor is added because it improves the success rate significantly. + unsigned long expected = 1000000.0 * device->nbits / device->baudrate * size + 0.5 + 2000; + + // Wait for the remaining time. + if (elapsed < expected) { + unsigned long remaining = expected - elapsed; + + // The remaining time is rounded up to the nearest millisecond to + // match the Windows implementation. The higher resolution is + // pointless anyway, since we already added a fudge factor above. + serial_ftdi_sleep (device, (remaining + 999) / 1000); + } + } + + INFO (device->context, "Wrote %d bytes", nbytes); + + return nbytes; +} + +static int serial_ftdi_flush (serial_t *device, int queue) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + INFO (device->context, "Flush: queue=%u, input=%i, output=%i", queue, + serial_ftdi_get_received (device), + serial_ftdi_get_transmitted (device)); + + switch (queue) { + case SERIAL_QUEUE_INPUT: + if (ftdi_usb_purge_tx_buffer(device->ftdi_ctx)) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return -1; + } + break; + case SERIAL_QUEUE_OUTPUT: + if (ftdi_usb_purge_rx_buffer(device->ftdi_ctx)) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return -1; + } + break; + default: + if (ftdi_usb_purge_buffers(device->ftdi_ctx)) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return -1; + } + break; + } + + return 0; +} + +static int serial_ftdi_send_break (serial_t *device) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument)a + + INFO (device->context, "Break : One time period."); + + // no direct functions for sending break signals in libftdi. + // there is a suggestion to lower the baudrate and sending NUL + // and resetting the baudrate up again. But it has flaws. + // Not implementing it before researching more. + + return -1; +} + +static int serial_ftdi_set_break (serial_t *device, int level) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + INFO (device->context, "Break: value=%i", level); + + // Not implemented in libftdi yet. Research it further. + + return -1; +} + +static int serial_ftdi_set_dtr (serial_t *device, int level) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + INFO (device->context, "DTR: value=%i", level); + + if (ftdi_setdtr(device->ftdi_ctx, level)) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return -1; + } + + return 0; +} + +static int serial_ftdi_set_rts (serial_t *device, int level) +{ + if (device == NULL) + return -1; // EINVAL (Invalid argument) + + INFO (device->context, "RTS: value=%i", level); + + if (ftdi_setrts(device->ftdi_ctx, level)) { + ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); + return -1; + } + + return 0; +} + +const dc_serial_operations_t serial_ftdi_ops = { + .open = serial_ftdi_open, + .close = serial_ftdi_close, + .read = serial_ftdi_read, + .write = serial_ftdi_write, + .flush = serial_ftdi_flush, + .get_received = serial_ftdi_get_received, + .get_transmitted = NULL, /*NOT USED ANYWHERE! serial_ftdi_get_transmitted */ + .set_timeout = serial_ftdi_set_timeout +#ifdef FIXED_SSRF_CUSTOM_SERIAL + , + .configure = serial_ftdi_configure, +//static int serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) + .set_halfduplex = serial_ftdi_set_halfduplex, +//static int serial_ftdi_set_halfduplex (serial_t *device, int value) + .send_break = serial_ftdi_send_break, +//static int serial_ftdi_send_break (serial_t *device) + .set_break = serial_ftdi_set_break, +//static int serial_ftdi_set_break (serial_t *device, int level) + .set_dtr = serial_ftdi_set_dtr, +//static int serial_ftdi_set_dtr (serial_t *device, int level) + .set_rts = serial_ftdi_set_rts +//static int serial_ftdi_set_rts (serial_t *device, int level) +#endif +}; + +dc_status_t dc_serial_ftdi_open(dc_serial_t **out, dc_context_t *context) +{ + if (out == NULL) + return DC_STATUS_INVALIDARGS; + + // Allocate memory. + dc_serial_t *serial_device = (dc_serial_t *) malloc (sizeof (dc_serial_t)); + + if (serial_device == NULL) { + return DC_STATUS_NOMEMORY; + } + + // Initialize data and function pointers + dc_serial_init(serial_device, NULL, &serial_ftdi_ops); + + // Open the serial device. + dc_status_t rc = (dc_status_t) serial_ftdi_open (&serial_device->port, context, NULL); + if (rc != DC_STATUS_SUCCESS) { + free (serial_device); + return rc; + } + + // Set the type of the device + serial_device->type = DC_TRANSPORT_USB;; + + *out = serial_device; + + return DC_STATUS_SUCCESS; +} diff --git a/core/sha1.c b/core/sha1.c new file mode 100644 index 000000000..acf8c5d9f --- /dev/null +++ b/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/core/sha1.h b/core/sha1.h new file mode 100644 index 000000000..cab6ff77d --- /dev/null +++ b/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/core/statistics.c b/core/statistics.c new file mode 100644 index 000000000..6a05cffc1 --- /dev/null +++ b/core/statistics.c @@ -0,0 +1,404 @@ +/* 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; +stats_t *stats_by_type = NULL; + +static void process_temperatures(struct dive *dp, stats_t *stats) +{ + int min_temp, mean_temp, max_temp = 0; + + max_temp = dp->maxtemp.mkelvin; + if (max_temp && (!stats->max_temp || max_temp > stats->max_temp)) + stats->max_temp = max_temp; + + min_temp = dp->mintemp.mkelvin; + if (min_temp && (!stats->min_temp || min_temp < stats->min_temp)) + stats->min_temp = min_temp; + + if (min_temp || max_temp) { + mean_temp = min_temp; + if (mean_temp) + mean_temp = (mean_temp + max_temp) / 2; + else + mean_temp = max_temp; + stats->combined_temp += get_temp_units(mean_temp, NULL); + stats->combined_count++; + } +} + +static void process_dive(struct dive *dp, stats_t *stats) +{ + int old_tt, sac_time = 0; + uint32_t duration = dp->duration.seconds; + + old_tt = stats->total_time.seconds; + stats->total_time.seconds += duration; + if (duration > stats->longest_time.seconds) + stats->longest_time.seconds = duration; + if (stats->shortest_time.seconds == 0 || duration < stats->shortest_time.seconds) + stats->shortest_time.seconds = duration; + if (dp->maxdepth.mm > stats->max_depth.mm) + stats->max_depth.mm = dp->maxdepth.mm; + if (stats->min_depth.mm == 0 || dp->maxdepth.mm < stats->min_depth.mm) + stats->min_depth.mm = dp->maxdepth.mm; + + process_temperatures(dp, stats); + + /* Maybe we should drop zero-duration dives */ + if (!duration) + return; + stats->avg_depth.mm = (1.0 * old_tt * stats->avg_depth.mm + + duration * dp->meandepth.mm) / + stats->total_time.seconds; + if (dp->sac > 100) { /* less than .1 l/min is bogus, even with a pSCR */ + sac_time = stats->total_sac_time + duration; + stats->avg_sac.mliter = (1.0 * stats->total_sac_time * stats->avg_sac.mliter + + duration * dp->sac) / + sac_time; + if (dp->sac > stats->max_sac.mliter) + stats->max_sac.mliter = dp->sac; + if (stats->min_sac.mliter == 0 || dp->sac < stats->min_sac.mliter) + stats->min_sac.mliter = dp->sac; + stats->total_sac_time = sac_time; + } +} + +char *get_minutes(int seconds) +{ + static char buf[80]; + snprintf(buf, sizeof(buf), "%d:%.2d", FRACTION(seconds, 60)); + return buf; +} + +void process_all_dives(struct dive *dive, struct dive **prev_dive) +{ + int idx; + struct dive *dp; + struct tm tm; + int current_year = 0; + int current_month = 0; + int year_iter = 0; + int month_iter = 0; + int prev_month = 0, prev_year = 0; + int trip_iter = 0; + dive_trip_t *trip_ptr = 0; + unsigned int size, tsize; + + *prev_dive = NULL; + memset(&stats, 0, sizeof(stats)); + if (dive_table.nr > 0) { + stats.shortest_time.seconds = dive_table.dives[0]->duration.seconds; + stats.min_depth.mm = dive_table.dives[0]->maxdepth.mm; + stats.selection_size = dive_table.nr; + } + + /* allocate sufficient space to hold the worst + * case (one dive per year or all dives during + * one month) for yearly and monthly statistics*/ + + free(stats_yearly); + free(stats_monthly); + free(stats_by_trip); + free(stats_by_type); + + size = sizeof(stats_t) * (dive_table.nr + 1); + tsize = sizeof(stats_t) * (NUM_DC_TYPE + 1); + stats_yearly = malloc(size); + stats_monthly = malloc(size); + stats_by_trip = malloc(size); + stats_by_type = malloc(tsize); + if (!stats_yearly || !stats_monthly || !stats_by_trip || !stats_by_type) + return; + memset(stats_yearly, 0, size); + memset(stats_monthly, 0, size); + memset(stats_by_trip, 0, size); + memset(stats_by_type, 0, tsize); + stats_yearly[0].is_year = true; + + /* Setting the is_trip to true to show the location as first + * field in the statistics window */ + stats_by_type[0].location = strdup("All (by type stats)"); + stats_by_type[0].is_trip = true; + stats_by_type[1].location = strdup("OC"); + stats_by_type[1].is_trip = true; + stats_by_type[2].location = strdup("CCR"); + stats_by_type[2].is_trip = true; + stats_by_type[3].location = strdup("pSCR"); + stats_by_type[3].is_trip = true; + stats_by_type[4].location = strdup("Freedive"); + stats_by_type[4].is_trip = true; + + /* this relies on the fact that the dives in the dive_table + * are in chronological order */ + for_each_dive (idx, dp) { + if (dive && dp->when == dive->when) { + /* that's the one we are showing */ + if (idx > 0) + *prev_dive = dive_table.dives[idx - 1]; + } + process_dive(dp, &stats); + + /* yearly statistics */ + utc_mkdate(dp->when, &tm); + if (current_year == 0) + current_year = tm.tm_year + 1900; + + if (current_year != tm.tm_year + 1900) { + current_year = tm.tm_year + 1900; + process_dive(dp, &(stats_yearly[++year_iter])); + stats_yearly[year_iter].is_year = true; + } else { + process_dive(dp, &(stats_yearly[year_iter])); + } + stats_yearly[year_iter].selection_size++; + stats_yearly[year_iter].period = current_year; + + /* stats_by_type[0] is all the dives combined */ + stats_by_type[0].selection_size++; + process_dive(dp, &(stats_by_type[0])); + + process_dive(dp, &(stats_by_type[dp->dc.divemode + 1])); + stats_by_type[dp->dc.divemode + 1].selection_size++; + + if (dp->divetrip != NULL) { + if (trip_ptr != dp->divetrip) { + trip_ptr = dp->divetrip; + trip_iter++; + } + + /* stats_by_trip[0] is all the dives combined */ + stats_by_trip[0].selection_size++; + process_dive(dp, &(stats_by_trip[0])); + stats_by_trip[0].is_trip = true; + stats_by_trip[0].location = strdup("All (by trip stats)"); + + process_dive(dp, &(stats_by_trip[trip_iter])); + stats_by_trip[trip_iter].selection_size++; + stats_by_trip[trip_iter].is_trip = true; + stats_by_trip[trip_iter].location = dp->divetrip->location; + } + + /* monthly statistics */ + if (current_month == 0) { + current_month = tm.tm_mon + 1; + } else { + if (current_month != tm.tm_mon + 1) + current_month = tm.tm_mon + 1; + if (prev_month != current_month || prev_year != current_year) + month_iter++; + } + process_dive(dp, &(stats_monthly[month_iter])); + stats_monthly[month_iter].selection_size++; + stats_monthly[month_iter].period = current_month; + prev_month = current_month; + prev_year = current_year; + } +} + +/* make sure we skip the selected summary entries */ +void process_selected_dives(void) +{ + struct dive *dive; + unsigned int i, nr; + + memset(&stats_selection, 0, sizeof(stats_selection)); + + nr = 0; + for_each_dive(i, dive) { + if (dive->selected) { + process_dive(dive, &stats_selection); + nr++; + } + } + stats_selection.selection_size = nr; +} + +char *get_time_string_s(int seconds, int maxdays, bool freediving) +{ + static char buf[80]; + if (maxdays && seconds > 3600 * 24 * maxdays) { + snprintf(buf, sizeof(buf), translate("gettextFromC", "more than %d days"), maxdays); + } else { + int days = seconds / 3600 / 24; + int hours = (seconds - days * 3600 * 24) / 3600; + int minutes = (seconds - days * 3600 * 24 - hours * 3600) / 60; + int secs = (seconds - days * 3600 * 24 - hours * 3600 - minutes*60); + if (days > 0) + snprintf(buf, sizeof(buf), translate("gettextFromC", "%dd %dh %dmin"), days, hours, minutes); + else + if (freediving && seconds < 3600) + snprintf(buf, sizeof(buf), translate("gettextFromC", "%dmin %dsecs"), minutes, secs); + else + snprintf(buf, sizeof(buf), translate("gettextFromC", "%dh %dmin"), hours, minutes); + } + return buf; +} + +/* this gets called when at least two but not all dives are selected */ +static void get_ranges(char *buffer, int size) +{ + int i, len; + int first = -1, last = -1; + struct dive *dive; + + snprintf(buffer, size, "%s", translate("gettextFromC", "for dives #")); + for_each_dive (i, dive) { + if (!dive->selected) + continue; + if (dive->number < 1) { + /* uhh - weird numbers - bail */ + snprintf(buffer, size, "%s", translate("gettextFromC", "for selected dives")); + return; + } + len = strlen(buffer); + if (last == -1) { + snprintf(buffer + len, size - len, "%d", dive->number); + first = last = dive->number; + } else { + if (dive->number == last + 1) { + last++; + continue; + } else { + if (first == last) + snprintf(buffer + len, size - len, ", %d", dive->number); + else if (first + 1 == last) + snprintf(buffer + len, size - len, ", %d, %d", last, dive->number); + else + snprintf(buffer + len, size - len, "-%d, %d", last, dive->number); + first = last = dive->number; + } + } + } + len = strlen(buffer); + if (first != last) { + if (first + 1 == last) + snprintf(buffer + len, size - len, ", %d", last); + else + snprintf(buffer + len, size - len, "-%d", last); + } +} + +void get_selected_dives_text(char *buffer, size_t size) +{ + if (amount_selected == 1) { + if (current_dive) + snprintf(buffer, size, translate("gettextFromC", "for dive #%d"), current_dive->number); + else + snprintf(buffer, size, "%s", translate("gettextFromC", "for selected dive")); + } else if (amount_selected == (unsigned int)dive_table.nr) { + snprintf(buffer, size, "%s", translate("gettextFromC", "for all dives")); + } else if (amount_selected == 0) { + snprintf(buffer, size, "%s", translate("gettextFromC", "(no dives)")); + } else { + get_ranges(buffer, size); + if (strlen(buffer) == size - 1) { + /* add our own ellipse... the way Pango does this is ugly + * as it will leave partial numbers there which I don't like */ + size_t offset = 4; + while (offset < size && isdigit(buffer[size - offset])) + offset++; + strcpy(buffer + size - offset, "..."); + } + } +} + +#define SOME_GAS 5000 // 5bar drop in cylinder pressure makes cylinder used + +bool is_cylinder_used(struct dive *dive, int idx) +{ + struct divecomputer *dc; + bool firstGasExplicit = false; + if (cylinder_none(&dive->cylinder[idx])) + return false; + + if ((dive->cylinder[idx].start.mbar - dive->cylinder[idx].end.mbar) > SOME_GAS) + return true; + for_each_dc(dive, dc) { + struct event *event = get_next_event(dc->events, "gaschange"); + while (event) { + if (dc->sample && (event->time.seconds == 0 || + (dc->samples && dc->sample[0].time.seconds == event->time.seconds))) + firstGasExplicit = true; + if (get_cylinder_index(dive, event) == idx) + return true; + event = get_next_event(event->next, "gaschange"); + } + if (dc->divemode == CCR && (idx == dive->diluent_cylinder_index || idx == dive->oxygen_cylinder_index)) + return true; + } + if (idx == 0 && !firstGasExplicit) + return true; + return false; +} + +void get_gas_used(struct dive *dive, volume_t gases[MAX_CYLINDERS]) +{ + int idx; + for (idx = 0; idx < MAX_CYLINDERS; idx++) { + cylinder_t *cyl = &dive->cylinder[idx]; + pressure_t start, end; + + if (!is_cylinder_used(dive, idx)) + continue; + + start = cyl->start.mbar ? cyl->start : cyl->sample_start; + end = cyl->end.mbar ? cyl->end : cyl->sample_end; + if (end.mbar && start.mbar > end.mbar) + gases[idx].mliter = gas_volume(cyl, start) - gas_volume(cyl, end); + } +} + +/* Quite crude reverse-blender-function, but it produces a approx result */ +static void get_gas_parts(struct gasmix mix, volume_t vol, int o2_in_topup, volume_t *o2, volume_t *he) +{ + volume_t air = {}; + + if (gasmix_is_air(&mix)) { + o2->mliter = 0; + he->mliter = 0; + return; + } + + air.mliter = rint(((double)vol.mliter * (1000 - get_he(&mix) - get_o2(&mix))) / (1000 - o2_in_topup)); + he->mliter = rint(((double)vol.mliter * get_he(&mix)) / 1000.0); + o2->mliter += vol.mliter - he->mliter - air.mliter; +} + +void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot) +{ + int i, j; + struct dive *d; + for_each_dive (i, d) { + if (!d->selected) + continue; + volume_t diveGases[MAX_CYLINDERS] = {}; + get_gas_used(d, diveGases); + for (j = 0; j < MAX_CYLINDERS; j++) { + if (diveGases[j].mliter) { + volume_t o2 = {}, he = {}; + get_gas_parts(d->cylinder[j].gasmix, diveGases[j], O2_IN_AIR, &o2, &he); + o2_tot->mliter += o2.mliter; + he_tot->mliter += he.mliter; + } + } + } +} diff --git a/core/statistics.h b/core/statistics.h new file mode 100644 index 000000000..015c3481e --- /dev/null +++ b/core/statistics.h @@ -0,0 +1,59 @@ +/* + * statistics.h + * + * core logic functions called from statistics UI + * common types and variables + */ + +#ifndef STATISTICS_H +#define STATISTICS_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct +{ + int period; + duration_t total_time; + /* avg_time is simply total_time / nr -- let's not keep this */ + duration_t shortest_time; + duration_t longest_time; + depth_t max_depth; + depth_t min_depth; + depth_t avg_depth; + volume_t max_sac; + volume_t min_sac; + volume_t avg_sac; + int max_temp; + int min_temp; + double combined_temp; + unsigned int combined_count; + unsigned int selection_size; + unsigned int total_sac_time; + bool is_year; + bool is_trip; + char *location; +} stats_t; +extern stats_t stats_selection; +extern stats_t *stats_yearly; +extern stats_t *stats_monthly; +extern stats_t *stats_by_trip; +extern stats_t *stats_by_type; + +extern char *get_time_string_s(int seconds, int maxdays, bool freediving); +extern char *get_minutes(int seconds); +extern void process_all_dives(struct dive *dive, struct dive **prev_dive); +extern void get_selected_dives_text(char *buffer, size_t size); +extern void get_gas_used(struct dive *dive, volume_t gases[MAX_CYLINDERS]); +extern void process_selected_dives(void); +void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot); + +inline char *get_time_string(int seconds, int maxdays) { + return get_time_string_s( seconds, maxdays, false); +} +#ifdef __cplusplus +} +#endif + +#endif // STATISTICS_H diff --git a/core/strndup.h b/core/strndup.h new file mode 100644 index 000000000..84e18b60f --- /dev/null +++ b/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/core/strtod.c b/core/strtod.c new file mode 100644 index 000000000..81e5d42d1 --- /dev/null +++ b/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/core/subsurface-qt/DiveObjectHelper.cpp b/core/subsurface-qt/DiveObjectHelper.cpp new file mode 100644 index 000000000..ab88d2f3c --- /dev/null +++ b/core/subsurface-qt/DiveObjectHelper.cpp @@ -0,0 +1,338 @@ +#include "DiveObjectHelper.h" + +#include +#include + +#include "../qthelper.h" +#include "../helpers.h" + +static QString EMPTY_DIVE_STRING = QStringLiteral("--"); +enum returnPressureSelector {START_PRESSURE, END_PRESSURE}; + +static QString getFormattedWeight(struct dive *dive, unsigned int idx) +{ + weightsystem_t *weight = &dive->weightsystem[idx]; + if (!weight->description) + return QString(EMPTY_DIVE_STRING); + QString fmt = QString(weight->description); + fmt += ", " + get_weight_string(weight->weight, true); + return fmt; +} + +static QString getFormattedCylinder(struct dive *dive, unsigned int idx) +{ + cylinder_t *cyl = &dive->cylinder[idx]; + const char *desc = cyl->type.description; + if (!desc && idx > 0) + return QString(EMPTY_DIVE_STRING); + QString fmt = desc ? QString(desc) : QObject::tr("unknown"); + fmt += ", " + get_volume_string(cyl->type.size, true); + fmt += ", " + get_pressure_string(cyl->type.workingpressure, true); + fmt += ", " + get_pressure_string(cyl->start, false) + " - " + get_pressure_string(cyl->end, true); + fmt += ", " + get_gas_string(cyl->gasmix); + return fmt; +} + +static QString getPressures(struct dive *dive, enum returnPressureSelector ret) +{ + cylinder_t *cyl = &dive->cylinder[0]; + QString fmt; + if (ret == START_PRESSURE) { + if (cyl->start.mbar) + fmt = get_pressure_string(cyl->start, true); + else if (cyl->sample_start.mbar) + fmt = get_pressure_string(cyl->sample_start, true); + } + if (ret == END_PRESSURE) { + if (cyl->end.mbar) + fmt = get_pressure_string(cyl->end, true); + else if(cyl->sample_end.mbar) + fmt = get_pressure_string(cyl->sample_end, true); + } + return fmt; +} + +DiveObjectHelper::DiveObjectHelper(struct dive *d) : + m_dive(d) +{ +} + +DiveObjectHelper::~DiveObjectHelper() +{ +} + +int DiveObjectHelper::number() const +{ + return m_dive->number; +} + +int DiveObjectHelper::id() const +{ + return m_dive->id; +} + +QString DiveObjectHelper::date() const +{ + QDateTime localTime = QDateTime::fromTime_t(m_dive->when - gettimezoneoffset(m_dive->when)); + localTime.setTimeSpec(Qt::UTC); + return localTime.date().toString(prefs.date_format); +} + +timestamp_t DiveObjectHelper::timestamp() const +{ + return m_dive->when; +} + +QString DiveObjectHelper::time() const +{ + QDateTime localTime = QDateTime::fromTime_t(m_dive->when - gettimezoneoffset(m_dive->when)); + localTime.setTimeSpec(Qt::UTC); + return localTime.time().toString(prefs.time_format); +} + +QString DiveObjectHelper::location() const +{ + return get_dive_location(m_dive) ? QString::fromUtf8(get_dive_location(m_dive)) : EMPTY_DIVE_STRING; +} + +QString DiveObjectHelper::gps() const +{ + struct dive_site *ds = get_dive_site_by_uuid(m_dive->dive_site_uuid); + return ds ? QString(printGPSCoords(ds->latitude.udeg, ds->longitude.udeg)) : QString(); +} +QString DiveObjectHelper::duration() const +{ + return get_dive_duration_string(m_dive->duration.seconds, QObject::tr("h:"), QObject::tr("min")); +} + +bool DiveObjectHelper::noDive() const +{ + return m_dive->duration.seconds == 0 && m_dive->dc.duration.seconds == 0; +} + +QString DiveObjectHelper::depth() const +{ + return get_depth_string(m_dive->dc.maxdepth.mm, true, true); +} + +QString DiveObjectHelper::divemaster() const +{ + return m_dive->divemaster ? m_dive->divemaster : EMPTY_DIVE_STRING; +} + +QString DiveObjectHelper::buddy() const +{ + return m_dive->buddy ? m_dive->buddy : EMPTY_DIVE_STRING; +} + +QString DiveObjectHelper::airTemp() const +{ + QString temp = get_temperature_string(m_dive->airtemp, true); + if (temp.isEmpty()) { + temp = EMPTY_DIVE_STRING; + } + return temp; +} + +QString DiveObjectHelper::waterTemp() const +{ + QString temp = get_temperature_string(m_dive->watertemp, true); + if (temp.isEmpty()) { + temp = EMPTY_DIVE_STRING; + } + return temp; +} + +QString DiveObjectHelper::notes() const +{ + QString tmp = m_dive->notes ? QString::fromUtf8(m_dive->notes) : EMPTY_DIVE_STRING; + if (same_string(m_dive->dc.model, "planned dive")) { + QTextDocument notes; + #define _NOTES_BR "\n" + tmp.replace("", "" _NOTES_BR) + .replace("
", "
" _NOTES_BR) + .replace("", "" _NOTES_BR) + .replace("", "" _NOTES_BR); + notes.setHtml(tmp); + tmp = notes.toPlainText(); + tmp.replace(_NOTES_BR, "
"); + #undef _NOTES_BR + } else { + tmp.replace("\n", "
"); + } + return tmp; +} + +QString DiveObjectHelper::tags() const +{ + static char buffer[256]; + taglist_get_tagstring(m_dive->tag_list, buffer, 256); + return QString(buffer); +} + +QString DiveObjectHelper::gas() const +{ + /*WARNING: here should be the gastlist, returned + * from the get_gas_string function or this is correct? + */ + QString gas, gases; + for (int i = 0; i < MAX_CYLINDERS; i++) { + if (!is_cylinder_used(m_dive, i)) + continue; + gas = m_dive->cylinder[i].type.description; + if (!gas.isEmpty()) + gas += QChar(' '); + gas += gasname(&m_dive->cylinder[i].gasmix); + // if has a description and if such gas is not already present + if (!gas.isEmpty() && gases.indexOf(gas) == -1) { + if (!gases.isEmpty()) + gases += QString(" / "); + gases += gas; + } + } + return gases; +} + +QString DiveObjectHelper::sac() const +{ + if (!m_dive->sac) + return QString(); + const char *unit; + int decimal; + double value = get_volume_units(m_dive->sac, &decimal, &unit); + return QString::number(value, 'f', decimal).append(unit); +} + +QString DiveObjectHelper::weightList() const +{ + QString weights; + for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) { + QString w = getFormattedWeight(m_dive, i); + if (w == EMPTY_DIVE_STRING) + continue; + weights += w + "; "; + } + return weights; +} + +QStringList DiveObjectHelper::weights() const +{ + QStringList weights; + for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) + weights << getFormattedWeight(m_dive, i); + return weights; +} + +bool DiveObjectHelper::singleWeight() const +{ + return weightsystem_none(&m_dive->weightsystem[1]); +} + +QString DiveObjectHelper::weight(int idx) const +{ + if ( (idx < 0) || idx > MAX_WEIGHTSYSTEMS ) + return QString(EMPTY_DIVE_STRING); + return getFormattedWeight(m_dive, idx); +} + +QString DiveObjectHelper::suit() const +{ + return m_dive->suit ? m_dive->suit : EMPTY_DIVE_STRING; +} + +QString DiveObjectHelper::cylinderList() const +{ + QString cylinders; + for (int i = 0; i < MAX_CYLINDERS; i++) { + QString cyl = getFormattedCylinder(m_dive, i); + if (cyl == EMPTY_DIVE_STRING) + continue; + cylinders += cyl + "; "; + } + return cylinders; +} + +QStringList DiveObjectHelper::cylinders() const +{ + QStringList cylinders; + for (int i = 0; i < MAX_CYLINDERS; i++) + cylinders << getFormattedCylinder(m_dive, i); + return cylinders; +} + +QString DiveObjectHelper::cylinder(int idx) const +{ + if ( (idx < 0) || idx > MAX_CYLINDERS) + return QString(EMPTY_DIVE_STRING); + return getFormattedCylinder(m_dive, idx); +} + +QString DiveObjectHelper::trip() const +{ + return m_dive->divetrip ? m_dive->divetrip->location : EMPTY_DIVE_STRING; +} + +// combine the pointer address with the trip location so that +// we detect multiple, destinct trips to the same location +QString DiveObjectHelper::tripMeta() const +{ + QString ret = EMPTY_DIVE_STRING; + if (m_dive->divetrip) + ret = QString::number((quint64)m_dive->divetrip, 16) + QLatin1Literal("::") + m_dive->divetrip->location; + return ret; +} + +QString DiveObjectHelper::maxcns() const +{ + return QString(m_dive->maxcns); +} + +QString DiveObjectHelper::otu() const +{ + return QString(m_dive->otu); +} + +int DiveObjectHelper::rating() const +{ + return m_dive->rating; +} + +QString DiveObjectHelper::sumWeight() const +{ + weight_t sum = { 0 }; + for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++){ + sum.grams += m_dive->weightsystem[i].weight.grams; + } + return get_weight_string(sum, true); +} + +QString DiveObjectHelper::getCylinder() const +{ + QString getCylinder; + if (is_cylinder_used(m_dive, 1)){ + getCylinder = QObject::tr("Multiple"); + } + else { + getCylinder = m_dive->cylinder[0].type.description; + } + return getCylinder; +} + +QString DiveObjectHelper::startPressure() const +{ + QString startPressure = getPressures(m_dive, START_PRESSURE); + return startPressure; +} + +QString DiveObjectHelper::endPressure() const +{ + QString endPressure = getPressures(m_dive, END_PRESSURE); + return endPressure; +} + +QString DiveObjectHelper::firstGas() const +{ + QString gas; + gas = get_gas_string(m_dive->cylinder[0].gasmix); + return gas; +} diff --git a/core/subsurface-qt/DiveObjectHelper.h b/core/subsurface-qt/DiveObjectHelper.h new file mode 100644 index 000000000..602775ef8 --- /dev/null +++ b/core/subsurface-qt/DiveObjectHelper.h @@ -0,0 +1,89 @@ +#ifndef DIVE_QOBJECT_H +#define DIVE_QOBJECT_H + +#include "../dive.h" +#include +#include +#include + +class DiveObjectHelper : public QObject { + Q_OBJECT + Q_PROPERTY(int number READ number CONSTANT) + Q_PROPERTY(int id READ id CONSTANT) + Q_PROPERTY(int rating READ rating CONSTANT) + Q_PROPERTY(QString date READ date CONSTANT) + Q_PROPERTY(QString time READ time CONSTANT) + Q_PROPERTY(QString location READ location CONSTANT) + Q_PROPERTY(QString gps READ gps CONSTANT) + Q_PROPERTY(QString duration READ duration CONSTANT) + Q_PROPERTY(bool noDive READ noDive CONSTANT) + Q_PROPERTY(QString depth READ depth CONSTANT) + Q_PROPERTY(QString divemaster READ divemaster CONSTANT) + Q_PROPERTY(QString buddy READ buddy CONSTANT) + Q_PROPERTY(QString airTemp READ airTemp CONSTANT) + Q_PROPERTY(QString waterTemp READ waterTemp CONSTANT) + Q_PROPERTY(QString notes READ notes CONSTANT) + Q_PROPERTY(QString tags READ tags CONSTANT) + Q_PROPERTY(QString gas READ gas CONSTANT) + Q_PROPERTY(QString sac READ sac CONSTANT) + Q_PROPERTY(QString weightList READ weightList CONSTANT) + Q_PROPERTY(QStringList weights READ weights CONSTANT) + Q_PROPERTY(bool singleWeight READ singleWeight CONSTANT) + Q_PROPERTY(QString suit READ suit CONSTANT) + Q_PROPERTY(QString cylinderList READ cylinderList CONSTANT) + Q_PROPERTY(QStringList cylinders READ cylinders CONSTANT) + Q_PROPERTY(QString trip READ trip CONSTANT) + Q_PROPERTY(QString tripMeta READ tripMeta CONSTANT) + Q_PROPERTY(QString maxcns READ maxcns CONSTANT) + Q_PROPERTY(QString otu READ otu CONSTANT) + Q_PROPERTY(QString sumWeight READ sumWeight CONSTANT) + Q_PROPERTY(QString getCylinder READ getCylinder CONSTANT) + Q_PROPERTY(QString startPressure READ startPressure CONSTANT) + Q_PROPERTY(QString endPressure READ endPressure CONSTANT) + Q_PROPERTY(QString firstGas READ firstGas CONSTANT) +public: + DiveObjectHelper(struct dive *dive = NULL); + ~DiveObjectHelper(); + int number() const; + int id() const; + int rating() const; + QString date() const; + timestamp_t timestamp() const; + QString time() const; + QString location() const; + QString gps() const; + QString duration() const; + bool noDive() const; + QString depth() const; + QString divemaster() const; + QString buddy() const; + QString airTemp() const; + QString waterTemp() const; + QString notes() const; + QString tags() const; + QString gas() const; + QString sac() const; + QString weightList() const; + QStringList weights() const; + QString weight(int idx) const; + bool singleWeight() const; + QString suit() const; + QString cylinderList() const; + QStringList cylinders() const; + QString cylinder(int idx) const; + QString trip() const; + QString tripMeta() const; + QString maxcns() const; + QString otu() const; + QString sumWeight() const; + QString getCylinder() const; + QString startPressure() const; + QString endPressure() const; + QString firstGas() const; + +private: + struct dive *m_dive; +}; + Q_DECLARE_METATYPE(DiveObjectHelper *) + +#endif diff --git a/core/subsurface-qt/SettingsObjectWrapper.cpp b/core/subsurface-qt/SettingsObjectWrapper.cpp new file mode 100644 index 000000000..e43be1a9b --- /dev/null +++ b/core/subsurface-qt/SettingsObjectWrapper.cpp @@ -0,0 +1,1617 @@ +#include "SettingsObjectWrapper.h" +#include +#include +#include + +#include "../dive.h" // TODO: remove copy_string from dive.h + + +static QString tecDetails = QStringLiteral("TecDetails"); + +PartialPressureGasSettings::PartialPressureGasSettings(QObject* parent): + QObject(parent), + group("TecDetails") +{ + +} + +short PartialPressureGasSettings::showPo2() const +{ + return prefs.pp_graphs.po2; +} + +short PartialPressureGasSettings::showPn2() const +{ + return prefs.pp_graphs.pn2; +} + +short PartialPressureGasSettings::showPhe() const +{ + return prefs.pp_graphs.phe; +} + +double PartialPressureGasSettings::po2Threshold() const +{ + return prefs.pp_graphs.po2_threshold; +} + +double PartialPressureGasSettings::pn2Threshold() const +{ + return prefs.pp_graphs.pn2_threshold; +} + +double PartialPressureGasSettings::pheThreshold() const +{ + return prefs.pp_graphs.phe_threshold; +} + +void PartialPressureGasSettings::setShowPo2(short value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("po2graph", value); + prefs.pp_graphs.po2 = value; + emit showPo2Changed(value); +} + +void PartialPressureGasSettings::setShowPn2(short value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("pn2graph", value); + prefs.pp_graphs.pn2 = value; + emit showPn2Changed(value); +} + +void PartialPressureGasSettings::setShowPhe(short value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("phegraph", value); + prefs.pp_graphs.phe = value; + emit showPheChanged(value); +} + +void PartialPressureGasSettings::setPo2Threshold(double value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("po2threshold", value); + prefs.pp_graphs.po2_threshold = value; + emit po2ThresholdChanged(value); +} + +void PartialPressureGasSettings::setPn2Threshold(double value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("pn2threshold", value); + prefs.pp_graphs.pn2_threshold = value; + emit pn2ThresholdChanged(value); +} + +void PartialPressureGasSettings::setPheThreshold(double value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("phethreshold", value); + prefs.pp_graphs.phe_threshold = value; + emit pheThresholdChanged(value); +} + + +TechnicalDetailsSettings::TechnicalDetailsSettings(QObject* parent): QObject(parent) +{ + +} + +double TechnicalDetailsSettings:: modp02() const +{ + return prefs.modpO2; +} + +bool TechnicalDetailsSettings::ead() const +{ + return prefs.ead; +} + +bool TechnicalDetailsSettings::dcceiling() const +{ + return prefs.dcceiling; +} + +bool TechnicalDetailsSettings::redceiling() const +{ + return prefs.redceiling; +} + +bool TechnicalDetailsSettings::calcceiling() const +{ + return prefs.calcceiling; +} + +bool TechnicalDetailsSettings::calcceiling3m() const +{ + return prefs.calcceiling3m; +} + +bool TechnicalDetailsSettings::calcalltissues() const +{ + return prefs.calcalltissues; +} + +bool TechnicalDetailsSettings::calcndltts() const +{ + return prefs.calcndltts; +} + +bool TechnicalDetailsSettings::gflow() const +{ + return prefs.gflow; +} + +bool TechnicalDetailsSettings::gfhigh() const +{ + return prefs.gfhigh; +} + +bool TechnicalDetailsSettings::hrgraph() const +{ + return prefs.hrgraph; +} + +bool TechnicalDetailsSettings::tankBar() const +{ + return prefs.tankbar; +} + +bool TechnicalDetailsSettings::percentageGraph() const +{ + return prefs.percentagegraph; +} + +bool TechnicalDetailsSettings::rulerGraph() const +{ + return prefs.rulergraph; +} + +bool TechnicalDetailsSettings::showCCRSetpoint() const +{ + return prefs.show_ccr_setpoint; +} + +bool TechnicalDetailsSettings::showCCRSensors() const +{ + return prefs.show_ccr_sensors; +} + +bool TechnicalDetailsSettings::zoomedPlot() const +{ + return prefs.zoomed_plot; +} + +bool TechnicalDetailsSettings::showSac() const +{ + return prefs.show_sac; +} + +bool TechnicalDetailsSettings::gfLowAtMaxDepth() const +{ + return prefs.gf_low_at_maxdepth; +} + +bool TechnicalDetailsSettings::displayUnusedTanks() const +{ + return prefs.display_unused_tanks; +} + +bool TechnicalDetailsSettings::showAverageDepth() const +{ + return prefs.show_average_depth; +} + +bool TechnicalDetailsSettings::mod() const +{ + return prefs.mod; +} + +bool TechnicalDetailsSettings::showPicturesInProfile() const +{ + return prefs.show_pictures_in_profile; +} + +void TechnicalDetailsSettings::setModp02(double value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("modpO2", value); + prefs.modpO2 = value; + emit modpO2Changed(value); +} + +void TechnicalDetailsSettings::setShowPicturesInProfile(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("show_pictures_in_profile", value); + prefs.show_pictures_in_profile = value; + emit showPicturesInProfileChanged(value); +} + +void TechnicalDetailsSettings::setEad(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("ead", value); + prefs.ead = value; + emit eadChanged(value); +} + +void TechnicalDetailsSettings::setMod(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("mod", value); + prefs.mod = value; + emit modChanged(value); +} + +void TechnicalDetailsSettings::setDCceiling(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("dcceiling", value); + prefs.dcceiling = value; + emit dcceilingChanged(value); +} + +void TechnicalDetailsSettings::setRedceiling(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("redceiling", value); + prefs.redceiling = value; + emit redceilingChanged(value); +} + +void TechnicalDetailsSettings::setCalcceiling(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("calcceiling", value); + prefs.calcceiling = value; + emit calcceilingChanged(value); +} + +void TechnicalDetailsSettings::setCalcceiling3m(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("calcceiling3m", value); + prefs.calcceiling3m = value; + emit calcceiling3mChanged(value); +} + +void TechnicalDetailsSettings::setCalcalltissues(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("calcalltissues", value); + prefs.calcalltissues = value; + emit calcalltissuesChanged(value); +} + +void TechnicalDetailsSettings::setCalcndltts(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("calcndltts", value); + prefs.calcndltts = value; + emit calcndlttsChanged(value); +} + +void TechnicalDetailsSettings::setGflow(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("gflow", value); + prefs.gflow = value; + set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); + emit gflowChanged(value); +} + +void TechnicalDetailsSettings::setGfhigh(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("gfhigh", value); + prefs.gfhigh = value; + set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); + emit gfhighChanged(value); +} + +void TechnicalDetailsSettings::setHRgraph(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("hrgraph", value); + prefs.hrgraph = value; + emit hrgraphChanged(value); +} + +void TechnicalDetailsSettings::setTankBar(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("tankbar", value); + prefs.tankbar = value; + emit tankBarChanged(value); +} + +void TechnicalDetailsSettings::setPercentageGraph(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("percentagegraph", value); + prefs.percentagegraph = value; + emit percentageGraphChanged(value); +} + +void TechnicalDetailsSettings::setRulerGraph(bool value) +{ + /* TODO: search for the QSettings of the RulerBar */ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("RulerBar", value); + prefs.rulergraph = value; + emit rulerGraphChanged(value); +} + +void TechnicalDetailsSettings::setShowCCRSetpoint(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("show_ccr_setpoint", value); + prefs.show_ccr_setpoint = value; + emit showCCRSetpointChanged(value); +} + +void TechnicalDetailsSettings::setShowCCRSensors(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("show_ccr_sensors", value); + prefs.show_ccr_sensors = value; + emit showCCRSensorsChanged(value); +} + +void TechnicalDetailsSettings::setZoomedPlot(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("zoomed_plot", value); + prefs.zoomed_plot = value; + emit zoomedPlotChanged(value); +} + +void TechnicalDetailsSettings::setShowSac(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("show_sac", value); + prefs.show_sac = value; + emit showSacChanged(value); +} + +void TechnicalDetailsSettings::setGfLowAtMaxDepth(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("gf_low_at_maxdepth", value); + prefs.gf_low_at_maxdepth = value; + set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); + emit gfLowAtMaxDepthChanged(value); +} + +void TechnicalDetailsSettings::setDisplayUnusedTanks(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("display_unused_tanks", value); + prefs.display_unused_tanks = value; + emit displayUnusedTanksChanged(value); +} + +void TechnicalDetailsSettings::setShowAverageDepth(bool value) +{ + QSettings s; + s.beginGroup(tecDetails); + s.setValue("show_average_depth", value); + prefs.show_average_depth = value; + emit showAverageDepthChanged(value); +} + + + +FacebookSettings::FacebookSettings(QObject *parent) : + QObject(parent), + group(QStringLiteral("WebApps")), + subgroup(QStringLiteral("Facebook")) +{ +} + +QString FacebookSettings::accessToken() const +{ + return QString(prefs.facebook.access_token); +} + +QString FacebookSettings::userId() const +{ + return QString(prefs.facebook.user_id); +} + +QString FacebookSettings::albumId() const +{ + return QString(prefs.facebook.album_id); +} + +void FacebookSettings::setAccessToken (const QString& value) +{ +#if SAVE_FB_CREDENTIALS + QSettings s; + s.beginGroup(group); + s.beginGroup(subgroup); + s.setValue("ConnectToken", value); +#endif + prefs.facebook.access_token = copy_string(qPrintable(value)); + emit accessTokenChanged(value); +} + +void FacebookSettings::setUserId(const QString& value) +{ +#if SAVE_FB_CREDENTIALS + QSettings s; + s.beginGroup(group); + s.beginGroup(subgroup); + s.setValue("UserId", value); +#endif + prefs.facebook.user_id = copy_string(qPrintable(value)); + emit userIdChanged(value); +} + +void FacebookSettings::setAlbumId(const QString& value) +{ +#if SAVE_FB_CREDENTIALS + QSettings s; + s.beginGroup(group); + s.beginGroup(subgroup); + s.setValue("AlbumId", value); +#endif + prefs.facebook.album_id = copy_string(qPrintable(value)); + emit albumIdChanged(value); +} + + +GeocodingPreferences::GeocodingPreferences(QObject *parent) : + QObject(parent), + group(QStringLiteral("geocoding")) +{ + +} + +bool GeocodingPreferences::enableGeocoding() const +{ + return prefs.geocoding.enable_geocoding; +} + +bool GeocodingPreferences::parseDiveWithoutGps() const +{ + return prefs.geocoding.parse_dive_without_gps; +} + +bool GeocodingPreferences::tagExistingDives() const +{ + return prefs.geocoding.tag_existing_dives; +} + +taxonomy_category GeocodingPreferences::firstTaxonomyCategory() const +{ + return prefs.geocoding.category[0]; +} + +taxonomy_category GeocodingPreferences::secondTaxonomyCategory() const +{ + return prefs.geocoding.category[1]; +} + +taxonomy_category GeocodingPreferences::thirdTaxonomyCategory() const +{ + return prefs.geocoding.category[2]; +} + +void GeocodingPreferences::setEnableGeocoding(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("enable_geocoding", value); + prefs.geocoding.enable_geocoding = value; + emit enableGeocodingChanged(value); +} + +void GeocodingPreferences::setParseDiveWithoutGps(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("parse_dives_without_gps", value); + prefs.geocoding.parse_dive_without_gps = value; + emit parseDiveWithoutGpsChanged(value); +} + +void GeocodingPreferences::setTagExistingDives(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("tag_existing_dives", value); + prefs.geocoding.tag_existing_dives = value; + emit tagExistingDivesChanged(value); +} + +void GeocodingPreferences::setFirstTaxonomyCategory(taxonomy_category value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("cat0", value); + prefs.show_average_depth = value; + emit firstTaxonomyCategoryChanged(value); +} + +void GeocodingPreferences::setSecondTaxonomyCategory(taxonomy_category value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("cat1", value); + prefs.show_average_depth = value; + emit secondTaxonomyCategoryChanged(value); +} + +void GeocodingPreferences::setThirdTaxonomyCategory(taxonomy_category value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("cat2", value); + prefs.show_average_depth = value; + emit thirdTaxonomyCategoryChanged(value); +} + +ProxySettings::ProxySettings(QObject *parent) : + QObject(parent), + group(QStringLiteral("Network")) +{ +} + +int ProxySettings::type() const +{ + return prefs.proxy_type; +} + +QString ProxySettings::host() const +{ + return prefs.proxy_host; +} + +int ProxySettings::port() const +{ + return prefs.proxy_port; +} + +short ProxySettings::auth() const +{ + return prefs.proxy_auth; +} + +QString ProxySettings::user() const +{ + return prefs.proxy_user; +} + +QString ProxySettings::pass() const +{ + return prefs.proxy_pass; +} + +void ProxySettings::setType(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("proxy_type", value); + prefs.proxy_type = value; + emit typeChanged(value); +} + +void ProxySettings::setHost(const QString& value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("proxy_host", value); + free(prefs.proxy_host); + prefs.proxy_host = copy_string(qPrintable(value));; + emit hostChanged(value); +} + +void ProxySettings::setPort(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("proxy_port", value); + prefs.proxy_port = value; + emit portChanged(value); +} + +void ProxySettings::setAuth(short value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("proxy_auth", value); + prefs.proxy_auth = value; + emit authChanged(value); +} + +void ProxySettings::setUser(const QString& value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("proxy_user", value); + free(prefs.proxy_user); + prefs.proxy_user = copy_string(qPrintable(value)); + emit userChanged(value); +} + +void ProxySettings::setPass(const QString& value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("proxy_pass", value); + free(prefs.proxy_pass); + prefs.proxy_pass = copy_string(qPrintable(value)); + emit passChanged(value); +} + +CloudStorageSettings::CloudStorageSettings(QObject *parent) : + QObject(parent), + group(QStringLiteral("CloudStorage")) +{ + +} + +bool CloudStorageSettings::gitLocalOnly() const +{ + return prefs.git_local_only; +} + +QString CloudStorageSettings::password() const +{ + return QString(prefs.cloud_storage_password); +} + +QString CloudStorageSettings::newPassword() const +{ + return QString(prefs.cloud_storage_newpassword); +} + +QString CloudStorageSettings::email() const +{ + return QString(prefs.cloud_storage_email); +} + +QString CloudStorageSettings::emailEncoded() const +{ + return QString(prefs.cloud_storage_email_encoded); +} + +bool CloudStorageSettings::savePasswordLocal() const +{ + return prefs.save_password_local; +} + +short CloudStorageSettings::verificationStatus() const +{ + return prefs.cloud_verification_status; +} + +bool CloudStorageSettings::backgroundSync() const +{ + return prefs.cloud_background_sync; +} + +QString CloudStorageSettings::userId() const +{ + return QString(prefs.userid); +} + +QString CloudStorageSettings::baseUrl() const +{ + return QString(prefs.cloud_base_url); +} + +QString CloudStorageSettings::gitUrl() const +{ + return QString(prefs.cloud_git_url); +} + +void CloudStorageSettings::setPassword(const QString& value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("password", value); + free(prefs.proxy_pass); + prefs.proxy_pass = copy_string(qPrintable(value)); + emit passwordChanged(value); +} + +void CloudStorageSettings::setNewPassword(const QString& value) +{ + /*TODO: This looks like wrong, but 'new password' is not saved on disk, why it's on prefs? */ + free(prefs.cloud_storage_newpassword); + prefs.cloud_storage_newpassword = copy_string(qPrintable(value)); + emit newPasswordChanged(value); +} + +void CloudStorageSettings::setEmail(const QString& value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("email", value); + free(prefs.cloud_storage_email); + prefs.cloud_storage_email = copy_string(qPrintable(value)); + emit emailChanged(value); +} + +void CloudStorageSettings::setUserId(const QString& value) +{ + //WARNING: UserId is stored outside of any group, but it belongs to Cloud Storage. + QSettings s; + s.setValue("subsurface_webservice_uid", value); + free(prefs.userid); + prefs.userid = copy_string(qPrintable(value)); + emit userIdChanged(value); +} + +void CloudStorageSettings::setEmailEncoded(const QString& value) +{ + /*TODO: This looks like wrong, but 'email encoded' is not saved on disk, why it's on prefs? */ + free(prefs.cloud_storage_email_encoded); + prefs.cloud_storage_email_encoded = copy_string(qPrintable(value)); + emit emailEncodedChanged(value); +} + +void CloudStorageSettings::setSavePasswordLocal(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("save_password_local", value); + prefs.save_password_local = value; + emit savePasswordLocalChanged(value); +} + +void CloudStorageSettings::setVerificationStatus(short value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("cloud_verification_status", value); + prefs.cloud_verification_status = value; + emit verificationStatusChanged(value); +} + +void CloudStorageSettings::setBackgroundSync(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("cloud_background_sync", value); + prefs.cloud_background_sync = value; + emit backgroundSyncChanged(value); +} + +void CloudStorageSettings::setBaseUrl(const QString& value) +{ + free((void*)prefs.cloud_base_url); + free((void*)prefs.cloud_git_url); + prefs.cloud_base_url = copy_string(qPrintable(value)); + prefs.cloud_git_url = copy_string(qPrintable(QString(prefs.cloud_base_url) + "/git")); +} + +void CloudStorageSettings::setGitUrl(const QString& value) +{ + Q_UNUSED(value); /* no op */ +} + +void CloudStorageSettings::setGitLocalOnly(bool value) +{ + prefs.git_local_only = value; +} + +DivePlannerSettings::DivePlannerSettings(QObject *parent) : + QObject(parent), + group(QStringLiteral("Planner")) +{ +} + +bool DivePlannerSettings::lastStop() const +{ + return prefs.last_stop; +} + +bool DivePlannerSettings::verbatimPlan() const +{ + return prefs.verbatim_plan; +} + +bool DivePlannerSettings::displayRuntime() const +{ + return prefs.display_runtime; +} + +bool DivePlannerSettings::displayDuration() const +{ + return prefs.display_duration; +} + +bool DivePlannerSettings::displayTransitions() const +{ + return prefs.display_transitions; +} + +bool DivePlannerSettings::doo2breaks() const +{ + return prefs.doo2breaks; +} + +bool DivePlannerSettings::dropStoneMode() const +{ + return prefs.drop_stone_mode; +} + +bool DivePlannerSettings::safetyStop() const +{ + return prefs.safetystop; +} + +bool DivePlannerSettings::switchAtRequiredStop() const +{ + return prefs.switch_at_req_stop; +} + +int DivePlannerSettings::ascrate75() const +{ + return prefs.ascrate75; +} + +int DivePlannerSettings::ascrate50() const +{ + return prefs.ascrate50; +} + +int DivePlannerSettings::ascratestops() const +{ + return prefs.ascratestops; +} + +int DivePlannerSettings::ascratelast6m() const +{ + return prefs.ascratelast6m; +} + +int DivePlannerSettings::descrate() const +{ + return prefs.descrate; +} + +int DivePlannerSettings::bottompo2() const +{ + return prefs.bottompo2; +} + +int DivePlannerSettings::decopo2() const +{ + return prefs.decopo2; +} + +int DivePlannerSettings::reserveGas() const +{ + return prefs.reserve_gas; +} + +int DivePlannerSettings::minSwitchDuration() const +{ + return prefs.min_switch_duration; +} + +int DivePlannerSettings::bottomSac() const +{ + return prefs.bottomsac; +} + +int DivePlannerSettings::decoSac() const +{ + return prefs.decosac; +} + +short DivePlannerSettings::conservatismLevel() const +{ + return prefs.conservatism_level; +} + +deco_mode DivePlannerSettings::decoMode() const +{ + return prefs.deco_mode; +} + +void DivePlannerSettings::setLastStop(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("last_stop", value); + prefs.last_stop = value; + emit lastStopChanged(value); +} + +void DivePlannerSettings::setVerbatimPlan(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("verbatim_plan", value); + prefs.verbatim_plan = value; + emit verbatimPlanChanged(value); +} + +void DivePlannerSettings::setDisplayRuntime(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("display_runtime", value); + prefs.display_runtime = value; + emit displayRuntimeChanged(value); +} + +void DivePlannerSettings::setDisplayDuration(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("display_duration", value); + prefs.display_duration = value; + emit displayDurationChanged(value); +} + +void DivePlannerSettings::setDisplayTransitions(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("display_transitions", value); + prefs.display_transitions = value; + emit displayTransitionsChanged(value); +} + +void DivePlannerSettings::setDoo2breaks(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("doo2breaks", value); + prefs.doo2breaks = value; + emit doo2breaksChanged(value); +} + +void DivePlannerSettings::setDropStoneMode(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("drop_stone_mode", value); + prefs.drop_stone_mode = value; + emit dropStoneModeChanged(value); +} + +void DivePlannerSettings::setSafetyStop(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("safetystop", value); + prefs.safetystop = value; + emit safetyStopChanged(value); +} + +void DivePlannerSettings::setSwitchAtRequiredStop(bool value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("switch_at_req_stop", value); + prefs.switch_at_req_stop = value; + emit switchAtRequiredStopChanged(value); +} + +void DivePlannerSettings::setAscrate75(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("ascrate75", value); + prefs.ascrate75 = value; + emit ascrate75Changed(value); +} + +void DivePlannerSettings::setAscrate50(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("ascrate50", value); + prefs.ascrate50 = value; + emit ascrate50Changed(value); +} + +void DivePlannerSettings::setAscratestops(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("ascratestops", value); + prefs.ascratestops = value; + emit ascratestopsChanged(value); +} + +void DivePlannerSettings::setAscratelast6m(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("ascratelast6m", value); + prefs.ascratelast6m = value; + emit ascratelast6mChanged(value); +} + +void DivePlannerSettings::setDescrate(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("descrate", value); + prefs.descrate = value; + emit descrateChanged(value); +} + +void DivePlannerSettings::setBottompo2(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("bottompo2", value); + prefs.bottompo2 = value; + emit bottompo2Changed(value); +} + +void DivePlannerSettings::setDecopo2(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("decopo2", value); + prefs.decopo2 = value; + emit decopo2Changed(value); +} + +void DivePlannerSettings::setReserveGas(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("reserve_gas", value); + prefs.reserve_gas = value; + emit reserveGasChanged(value); +} + +void DivePlannerSettings::setMinSwitchDuration(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("min_switch_duration", value); + prefs.min_switch_duration = value; + emit minSwitchDurationChanged(value); +} + +void DivePlannerSettings::setBottomSac(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("bottomsac", value); + prefs.bottomsac = value; + emit bottomSacChanged(value); +} + +void DivePlannerSettings::setSecoSac(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("decosac", value); + prefs.decosac = value; + emit decoSacChanged(value); +} + +void DivePlannerSettings::setConservatismLevel(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("conservatism", value); + prefs.conservatism_level = value; + emit conservatismLevelChanged(value); +} + +void DivePlannerSettings::setDecoMode(deco_mode value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("deco_mode", value); + prefs.deco_mode = value; + emit decoModeChanged(value); +} + +UnitsSettings::UnitsSettings(QObject *parent) : + QObject(parent), + group(QStringLiteral("Units")) +{ + +} + +int UnitsSettings::length() const +{ + return prefs.units.length; +} + +int UnitsSettings::pressure() const +{ + return prefs.units.pressure; +} + +int UnitsSettings::volume() const +{ + return prefs.units.volume; +} + +int UnitsSettings::temperature() const +{ + return prefs.units.temperature; +} + +int UnitsSettings::weight() const +{ + return prefs.units.weight; +} + +int UnitsSettings::verticalSpeedTime() const +{ + return prefs.units.vertical_speed_time; +} + +QString UnitsSettings::unitSystem() const +{ + return QString(); /*FIXME: there's no char * units on the prefs. */ +} + +bool UnitsSettings::coordinatesTraditional() const +{ + return prefs.coordinates_traditional; +} + +void UnitsSettings::setLength(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("length", value); + prefs.units.length = (units::LENGHT) value; + emit lengthChanged(value); +} + +void UnitsSettings::setPressure(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("pressure", value); + prefs.units.pressure = (units::PRESSURE) value; + emit pressureChanged(value); +} + +void UnitsSettings::setVolume(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("volume", value); + prefs.units.volume = (units::VOLUME) value; + emit volumeChanged(value); +} + +void UnitsSettings::setTemperature(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("temperature", value); + prefs.units.temperature = (units::TEMPERATURE) value; + emit temperatureChanged(value); +} + +void UnitsSettings::setWeight(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("weight", value); + prefs.units.weight = (units::WEIGHT) value; + emit weightChanged(value); +} + +void UnitsSettings::setVerticalSpeedTime(int value) +{ + QSettings s; + s.beginGroup(group); + s.setValue("vertical_speed_time", value); + prefs.units.vertical_speed_time = (units::TIME) value; + emit verticalSpeedTimeChanged(value); +} + +void UnitsSettings::setCoordinatesTraditional(bool value) +{ + QSettings s; + s.setValue("coordinates", value); + prefs.coordinates_traditional = value; + emit coordinatesTraditionalChanged(value); +} + +void UnitsSettings::setUnitSystem(const QString& value) +{ + QSettings s; + s.setValue("unit_system", value); + + if (value == QStringLiteral("metric")) { + prefs.unit_system = METRIC; + prefs.units = SI_units; + } else if (value == QStringLiteral("imperial")) { + prefs.unit_system = IMPERIAL; + prefs.units = IMPERIAL_units; + } else { + prefs.unit_system = PERSONALIZE; + } + + emit unitSystemChanged(value); + // TODO: emit the other values here? +} + +GeneralSettingsObjectWrapper::GeneralSettingsObjectWrapper(QObject *parent) : + QObject(parent), + group(QStringLiteral("GeneralSettings")) +{ +} + +QString GeneralSettingsObjectWrapper::defaultFilename() const +{ + return prefs.default_filename; +} + +QString GeneralSettingsObjectWrapper::defaultCylinder() const +{ + return prefs.default_cylinder; +} + +short GeneralSettingsObjectWrapper::defaultFileBehavior() const +{ + return prefs.default_file_behavior; +} + +bool GeneralSettingsObjectWrapper::useDefaultFile() const +{ + return prefs.use_default_file; +} + +int GeneralSettingsObjectWrapper::defaultSetPoint() const +{ + return prefs.defaultsetpoint; +} + +int GeneralSettingsObjectWrapper::o2Consumption() const +{ + return prefs.o2consumption; +} + +int GeneralSettingsObjectWrapper::pscrRatio() const +{ + return prefs.pscr_ratio; +} + +void GeneralSettingsObjectWrapper::setDefaultFilename(const QString& value) +{ + QSettings s; + s.setValue("default_filename", value); + prefs.default_filename = copy_string(qPrintable(value)); + emit defaultFilenameChanged(value); +} + +void GeneralSettingsObjectWrapper::setDefaultCylinder(const QString& value) +{ + QSettings s; + s.setValue("default_cylinder", value); + prefs.default_cylinder = copy_string(qPrintable(value)); + emit defaultCylinderChanged(value); +} + +void GeneralSettingsObjectWrapper::setDefaultFileBehavior(short value) +{ + QSettings s; + s.setValue("default_file_behavior", value); + prefs.default_file_behavior = value; + if (prefs.default_file_behavior == UNDEFINED_DEFAULT_FILE) { + // undefined, so check if there's a filename set and + // use that, otherwise go with no default file + if (QString(prefs.default_filename).isEmpty()) + prefs.default_file_behavior = NO_DEFAULT_FILE; + else + prefs.default_file_behavior = LOCAL_DEFAULT_FILE; + } + emit defaultFileBehaviorChanged(value); +} + +void GeneralSettingsObjectWrapper::setUseDefaultFile(bool value) +{ + QSettings s; + s.setValue("use_default_file", value); + prefs.use_default_file = value; + emit useDefaultFileChanged(value); +} + +void GeneralSettingsObjectWrapper::setDefaultSetPoint(int value) +{ + QSettings s; + s.setValue("defaultsetpoint", value); + prefs.defaultsetpoint = value; + emit defaultSetPointChanged(value); +} + +void GeneralSettingsObjectWrapper::setO2Consumption(int value) +{ + QSettings s; + s.setValue("o2consumption", value); + prefs.o2consumption = value; + emit o2ConsumptionChanged(value); +} + +void GeneralSettingsObjectWrapper::setPscrRatio(int value) +{ + QSettings s; + s.setValue("pscr_ratio", value); + prefs.pscr_ratio = value; + emit pscrRatioChanged(value); +} + +DisplaySettingsObjectWrapper::DisplaySettingsObjectWrapper(QObject *parent) : + QObject(parent), + group(QStringLiteral("Display")) +{ +} + +QString DisplaySettingsObjectWrapper::divelistFont() const +{ + return prefs.divelist_font; +} + +double DisplaySettingsObjectWrapper::fontSize() const +{ + return prefs.font_size; +} + +short DisplaySettingsObjectWrapper::displayInvalidDives() const +{ + return prefs.display_invalid_dives; +} + +void DisplaySettingsObjectWrapper::setDivelistFont(const QString& value) +{ + QSettings s; + s.setValue("divelist_font", value); + QString newValue = value; + if (value.contains(",")) + newValue = value.left(value.indexOf(",")); + + if (!subsurface_ignore_font(newValue.toUtf8().constData())) { + free((void *)prefs.divelist_font); + prefs.divelist_font = strdup(newValue.toUtf8().constData()); + qApp->setFont(QFont(newValue)); + } + emit divelistFontChanged(newValue); +} + +void DisplaySettingsObjectWrapper::setFontSize(double value) +{ + QSettings s; + s.setValue("font_size", value); + prefs.font_size = value; + QFont defaultFont = qApp->font(); + defaultFont.setPointSizeF(prefs.font_size); + qApp->setFont(defaultFont); + emit fontSizeChanged(value); +} + +void DisplaySettingsObjectWrapper::setDisplayInvalidDives(short value) +{ + QSettings s; + s.setValue("displayinvalid", value); + prefs.display_invalid_dives = value; + emit displayInvalidDivesChanged(value); +} + +LanguageSettingsObjectWrapper::LanguageSettingsObjectWrapper(QObject *parent) : + QObject(parent), + group("Language") +{ +} + +QString LanguageSettingsObjectWrapper::language() const +{ + return prefs.locale.language; +} + +QString LanguageSettingsObjectWrapper::timeFormat() const +{ + return prefs.time_format; +} + +QString LanguageSettingsObjectWrapper::dateFormat() const +{ + return prefs.date_format; +} + +QString LanguageSettingsObjectWrapper::dateFormatShort() const +{ + return prefs.date_format_short; +} + +bool LanguageSettingsObjectWrapper::timeFormatOverride() const +{ + return prefs.time_format_override; +} + +bool LanguageSettingsObjectWrapper::dateFormatOverride() const +{ + return prefs.date_format_override; +} + +bool LanguageSettingsObjectWrapper::useSystemLanguage() const +{ + return prefs.locale.use_system_language; +} + +void LanguageSettingsObjectWrapper::setUseSystemLanguage(bool value) +{ + QSettings s; + s.setValue("UseSystemLanguage", value); + prefs.locale.use_system_language = copy_string(qPrintable(value)); + emit useSystemLanguageChanged(value); +} + +void LanguageSettingsObjectWrapper::setLanguage(const QString& value) +{ + QSettings s; + s.setValue("UiLanguage", value); + prefs.locale.language = copy_string(qPrintable(value)); + emit languageChanged(value); +} + +void LanguageSettingsObjectWrapper::setTimeFormat(const QString& value) +{ + QSettings s; + s.setValue("time_format", value); + prefs.time_format = copy_string(qPrintable(value));; + emit timeFormatChanged(value); +} + +void LanguageSettingsObjectWrapper::setDateFormat(const QString& value) +{ + QSettings s; + s.setValue("date_format", value); + prefs.date_format = copy_string(qPrintable(value));; + emit dateFormatChanged(value); +} + +void LanguageSettingsObjectWrapper::setDateFormatShort(const QString& value) +{ + QSettings s; + s.setValue("date_format_short", value); + prefs.date_format_short = copy_string(qPrintable(value));; + emit dateFormatShortChanged(value); +} + +void LanguageSettingsObjectWrapper::setTimeFormatOverride(bool value) +{ + QSettings s; + s.setValue("time_format_override", value); + prefs.time_format_override = value; + emit timeFormatOverrideChanged(value); +} + +void LanguageSettingsObjectWrapper::setDateFormatOverride(bool value) +{ + QSettings s; + s.setValue("date_format_override", value); + prefs.date_format_override = value; + emit dateFormatOverrideChanged(value); +} + +AnimationsSettingsObjectWrapper::AnimationsSettingsObjectWrapper(QObject* parent): + QObject(parent), + group("Animations") + +{ +} + +int AnimationsSettingsObjectWrapper::animationSpeed() const +{ + return prefs.animation_speed; +} + +void AnimationsSettingsObjectWrapper::setAnimationSpeed(int value) +{ + QSettings s; + s.setValue("animation_speed", value); + prefs.animation_speed = value; + emit animationSpeedChanged(value); +} + +LocationServiceSettingsObjectWrapper::LocationServiceSettingsObjectWrapper(QObject* parent): + QObject(parent), + group("locationService") +{ +} + +int LocationServiceSettingsObjectWrapper::distanceThreshold() const +{ + return prefs.distance_threshold; +} + +int LocationServiceSettingsObjectWrapper::timeThreshold() const +{ + return prefs.time_threshold; +} + +void LocationServiceSettingsObjectWrapper::setDistanceThreshold(int value) +{ + QSettings s; + s.setValue("distance_threshold", value); + prefs.distance_threshold = value; + emit distanceThresholdChanged(value); +} + +void LocationServiceSettingsObjectWrapper::setTimeThreshold(int value) +{ + QSettings s; + s.setValue("time_threshold", value); + prefs.time_threshold = value; + emit timeThresholdChanged( value); +} + +SettingsObjectWrapper::SettingsObjectWrapper(QObject* parent): +QObject(parent), + techDetails(new TechnicalDetailsSettings(this)), + pp_gas(new PartialPressureGasSettings(this)), + facebook(new FacebookSettings(this)), + geocoding(new GeocodingPreferences(this)), + proxy(new ProxySettings(this)), + cloud_storage(new CloudStorageSettings(this)), + planner_settings(new DivePlannerSettings(this)), + unit_settings(new UnitsSettings(this)), + general_settings(new GeneralSettingsObjectWrapper(this)), + display_settings(new DisplaySettingsObjectWrapper(this)), + language_settings(new LanguageSettingsObjectWrapper(this)), + animation_settings(new AnimationsSettingsObjectWrapper(this)), + location_settings(new LocationServiceSettingsObjectWrapper(this)) +{ +} + +void SettingsObjectWrapper::setSaveUserIdLocal(short int value) +{ + Q_UNUSED(value); + //TODO: Find where this is stored on the preferences. +} + +short int SettingsObjectWrapper::saveUserIdLocal() const +{ + return prefs.save_userid_local; +} + +SettingsObjectWrapper* SettingsObjectWrapper::instance() +{ + static SettingsObjectWrapper settings; + return &settings; +} diff --git a/core/subsurface-qt/SettingsObjectWrapper.h b/core/subsurface-qt/SettingsObjectWrapper.h new file mode 100644 index 000000000..f115e2d86 --- /dev/null +++ b/core/subsurface-qt/SettingsObjectWrapper.h @@ -0,0 +1,642 @@ +#ifndef SETTINGSOBJECTWRAPPER_H +#define SETTINGSOBJECTWRAPPER_H + +#include + +#include "../pref.h" +#include "../prefs-macros.h" + +/* Wrapper class for the Settings. This will allow + * seamlessy integration of the settings with the QML + * and QWidget frontends. This class will be huge, since + * I need tons of properties, one for each option. */ + +/* Control the state of the Partial Pressure Graphs preferences */ +class PartialPressureGasSettings : public QObject { + Q_OBJECT + Q_PROPERTY(short show_po2 READ showPo2 WRITE setShowPo2 NOTIFY showPo2Changed) + Q_PROPERTY(short show_pn2 READ showPn2 WRITE setShowPn2 NOTIFY showPn2Changed) + Q_PROPERTY(short show_phe READ showPhe WRITE setShowPhe NOTIFY showPheChanged) + Q_PROPERTY(double po2_threshold READ po2Threshold WRITE setPo2Threshold NOTIFY po2ThresholdChanged) + Q_PROPERTY(double pn2_threshold READ pn2Threshold WRITE setPn2Threshold NOTIFY pn2ThresholdChanged) + Q_PROPERTY(double phe_threshold READ pheThreshold WRITE setPheThreshold NOTIFY pheThresholdChanged) + +public: + PartialPressureGasSettings(QObject *parent); + short showPo2() const; + short showPn2() const; + short showPhe() const; + double po2Threshold() const; + double pn2Threshold() const; + double pheThreshold() const; + +public slots: + void setShowPo2(short value); + void setShowPn2(short value); + void setShowPhe(short value); + void setPo2Threshold(double value); + void setPn2Threshold(double value); + void setPheThreshold(double value); + +signals: + void showPo2Changed(short value); + void showPn2Changed(short value); + void showPheChanged(short value); + void po2ThresholdChanged(double value); + void pn2ThresholdChanged(double value); + void pheThresholdChanged(double value); +private: + QString group; +}; + +class TechnicalDetailsSettings : public QObject { + Q_OBJECT + Q_PROPERTY(double modpO2 READ modp02 WRITE setModp02 NOTIFY modpO2Changed) + Q_PROPERTY(bool ead READ ead WRITE setEad NOTIFY eadChanged) + Q_PROPERTY(bool mod READ mod WRITE setMod NOTIFY modChanged); + Q_PROPERTY(bool dcceiling READ dcceiling WRITE setDCceiling NOTIFY dcceilingChanged) + Q_PROPERTY(bool redceiling READ redceiling WRITE setRedceiling NOTIFY redceilingChanged) + Q_PROPERTY(bool calcceiling READ calcceiling WRITE setCalcceiling NOTIFY calcceilingChanged) + Q_PROPERTY(bool calcceiling3m READ calcceiling3m WRITE setCalcceiling3m NOTIFY calcceiling3mChanged) + Q_PROPERTY(bool calcalltissues READ calcalltissues WRITE setCalcalltissues NOTIFY calcalltissuesChanged) + Q_PROPERTY(bool calcndltts READ calcndltts WRITE setCalcndltts NOTIFY calcndlttsChanged) + Q_PROPERTY(bool gflow READ gflow WRITE setGflow NOTIFY gflowChanged) + Q_PROPERTY(bool gfhigh READ gfhigh WRITE setGfhigh NOTIFY gfhighChanged) + Q_PROPERTY(bool hrgraph READ hrgraph WRITE setHRgraph NOTIFY hrgraphChanged) + Q_PROPERTY(bool tankbar READ tankBar WRITE setTankBar NOTIFY tankBarChanged) + Q_PROPERTY(bool percentagegraph READ percentageGraph WRITE setPercentageGraph NOTIFY percentageGraphChanged) + Q_PROPERTY(bool rulergraph READ rulerGraph WRITE setRulerGraph NOTIFY rulerGraphChanged) + Q_PROPERTY(bool show_ccr_setpoint READ showCCRSetpoint WRITE setShowCCRSetpoint NOTIFY showCCRSetpointChanged) + Q_PROPERTY(bool show_ccr_sensors READ showCCRSensors WRITE setShowCCRSensors NOTIFY showCCRSensorsChanged) + Q_PROPERTY(bool zoomed_plot READ zoomedPlot WRITE setZoomedPlot NOTIFY zoomedPlotChanged) + Q_PROPERTY(bool show_sac READ showSac WRITE setShowSac NOTIFY showSacChanged) + Q_PROPERTY(bool gf_low_at_maxdepth READ gfLowAtMaxDepth WRITE setGfLowAtMaxDepth NOTIFY gfLowAtMaxDepthChanged) + Q_PROPERTY(bool display_unused_tanks READ displayUnusedTanks WRITE setDisplayUnusedTanks NOTIFY displayUnusedTanksChanged) + Q_PROPERTY(bool show_average_depth READ showAverageDepth WRITE setShowAverageDepth NOTIFY showAverageDepthChanged) + Q_PROPERTY(bool show_pictures_in_profile READ showPicturesInProfile WRITE setShowPicturesInProfile NOTIFY showPicturesInProfileChanged) +public: + TechnicalDetailsSettings(QObject *parent); + + double modp02() const; + bool ead() const; + bool mod() const; + bool dcceiling() const; + bool redceiling() const; + bool calcceiling() const; + bool calcceiling3m() const; + bool calcalltissues() const; + bool calcndltts() const; + bool gflow() const; + bool gfhigh() const; + bool hrgraph() const; + bool tankBar() const; + bool percentageGraph() const; + bool rulerGraph() const; + bool showCCRSetpoint() const; + bool showCCRSensors() const; + bool zoomedPlot() const; + bool showSac() const; + bool gfLowAtMaxDepth() const; + bool displayUnusedTanks() const; + bool showAverageDepth() const; + bool showPicturesInProfile() const; + +public slots: + void setMod(bool value); + void setModp02(double value); + void setEad(bool value); + void setDCceiling(bool value); + void setRedceiling(bool value); + void setCalcceiling(bool value); + void setCalcceiling3m(bool value); + void setCalcalltissues(bool value); + void setCalcndltts(bool value); + void setGflow(bool value); + void setGfhigh(bool value); + void setHRgraph(bool value); + void setTankBar(bool value); + void setPercentageGraph(bool value); + void setRulerGraph(bool value); + void setShowCCRSetpoint(bool value); + void setShowCCRSensors(bool value); + void setZoomedPlot(bool value); + void setShowSac(bool value); + void setGfLowAtMaxDepth(bool value); + void setDisplayUnusedTanks(bool value); + void setShowAverageDepth(bool value); + void setShowPicturesInProfile(bool value); + +signals: + void modpO2Changed(double value); + void eadChanged(bool value); + void modChanged(bool value); + void dcceilingChanged(bool value); + void redceilingChanged(bool value); + void calcceilingChanged(bool value); + void calcceiling3mChanged(bool value); + void calcalltissuesChanged(bool value); + void calcndlttsChanged(bool value); + void gflowChanged(bool value); + void gfhighChanged(bool value); + void hrgraphChanged(bool value); + void tankBarChanged(bool value); + void percentageGraphChanged(bool value); + void rulerGraphChanged(bool value); + void showCCRSetpointChanged(bool value); + void showCCRSensorsChanged(bool value); + void zoomedPlotChanged(bool value); + void showSacChanged(bool value); + void gfLowAtMaxDepthChanged(bool value); + void displayUnusedTanksChanged(bool value); + void showAverageDepthChanged(bool value); + void showPicturesInProfileChanged(bool value); +}; + +/* Control the state of the Facebook preferences */ +class FacebookSettings : public QObject { + Q_OBJECT + Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged) + Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged) + Q_PROPERTY(QString albumId READ albumId WRITE setAlbumId NOTIFY albumIdChanged) + +public: + FacebookSettings(QObject *parent); + QString accessToken() const; + QString userId() const; + QString albumId() const; + +public slots: + void setAccessToken (const QString& value); + void setUserId(const QString& value); + void setAlbumId(const QString& value); + +signals: + void accessTokenChanged(const QString& value); + void userIdChanged(const QString& value); + void albumIdChanged(const QString& value); +private: + QString group; + QString subgroup; +}; + +/* Control the state of the Geocoding preferences */ +class GeocodingPreferences : public QObject { + Q_OBJECT + Q_PROPERTY(bool enable_geocoding READ enableGeocoding WRITE setEnableGeocoding NOTIFY enableGeocodingChanged) + Q_PROPERTY(bool parse_dive_without_gps READ parseDiveWithoutGps WRITE setParseDiveWithoutGps NOTIFY parseDiveWithoutGpsChanged) + Q_PROPERTY(bool tag_existing_dives READ tagExistingDives WRITE setTagExistingDives NOTIFY tagExistingDivesChanged) + Q_PROPERTY(taxonomy_category first_category READ firstTaxonomyCategory WRITE setFirstTaxonomyCategory NOTIFY firstTaxonomyCategoryChanged) + Q_PROPERTY(taxonomy_category second_category READ secondTaxonomyCategory WRITE setSecondTaxonomyCategory NOTIFY secondTaxonomyCategoryChanged) + Q_PROPERTY(taxonomy_category third_category READ thirdTaxonomyCategory WRITE setThirdTaxonomyCategory NOTIFY thirdTaxonomyCategoryChanged) +public: + GeocodingPreferences(QObject *parent); + bool enableGeocoding() const; + bool parseDiveWithoutGps() const; + bool tagExistingDives() const; + taxonomy_category firstTaxonomyCategory() const; + taxonomy_category secondTaxonomyCategory() const; + taxonomy_category thirdTaxonomyCategory() const; + +public slots: + void setEnableGeocoding(bool value); + void setParseDiveWithoutGps(bool value); + void setTagExistingDives(bool value); + void setFirstTaxonomyCategory(taxonomy_category value); + void setSecondTaxonomyCategory(taxonomy_category value); + void setThirdTaxonomyCategory(taxonomy_category value); + +signals: + void enableGeocodingChanged(bool value); + void parseDiveWithoutGpsChanged(bool value); + void tagExistingDivesChanged(bool value); + void firstTaxonomyCategoryChanged(taxonomy_category value); + void secondTaxonomyCategoryChanged(taxonomy_category value); + void thirdTaxonomyCategoryChanged(taxonomy_category value); +private: + QString group; +}; + +class ProxySettings : public QObject { + Q_OBJECT + Q_PROPERTY(int type READ type WRITE setType NOTIFY typeChanged) + Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged) + Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged) + Q_PROPERTY(short auth READ auth WRITE setAuth NOTIFY authChanged) + Q_PROPERTY(QString user READ user WRITE setUser NOTIFY userChanged) + Q_PROPERTY(QString pass READ pass WRITE setPass NOTIFY passChanged) + +public: + ProxySettings(QObject *parent); + int type() const; + QString host() const; + int port() const; + short auth() const; + QString user() const; + QString pass() const; + +public slots: + void setType(int value); + void setHost(const QString& value); + void setPort(int value); + void setAuth(short value); + void setUser(const QString& value); + void setPass(const QString& value); + +signals: + void typeChanged(int value); + void hostChanged(const QString& value); + void portChanged(int value); + void authChanged(short value); + void userChanged(const QString& value); + void passChanged(const QString& value); +private: + QString group; +}; + +class CloudStorageSettings : public QObject { + Q_OBJECT + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) + Q_PROPERTY(QString newpassword READ newPassword WRITE setNewPassword NOTIFY newPasswordChanged) + Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged) + Q_PROPERTY(QString email_encoded READ emailEncoded WRITE setEmailEncoded NOTIFY emailEncodedChanged) + Q_PROPERTY(QString userid READ userId WRITE setUserId NOTIFY userIdChanged) + Q_PROPERTY(QString base_url READ baseUrl WRITE setBaseUrl NOTIFY baseUrlChanged) + Q_PROPERTY(QString git_url READ gitUrl WRITE setGitUrl NOTIFY gitUrlChanged) + Q_PROPERTY(bool git_local_only READ gitLocalOnly WRITE setGitLocalOnly NOTIFY gitLocalOnlyChanged) + Q_PROPERTY(bool save_password_local READ savePasswordLocal WRITE setSavePasswordLocal NOTIFY savePasswordLocalChanged) + Q_PROPERTY(short verification_status READ verificationStatus WRITE setVerificationStatus NOTIFY verificationStatusChanged) + Q_PROPERTY(bool background_sync READ backgroundSync WRITE setBackgroundSync NOTIFY backgroundSyncChanged) +public: + CloudStorageSettings(QObject *parent); + QString password() const; + QString newPassword() const; + QString email() const; + QString emailEncoded() const; + QString userId() const; + QString baseUrl() const; + QString gitUrl() const; + bool savePasswordLocal() const; + short verificationStatus() const; + bool backgroundSync() const; + bool gitLocalOnly() const; + +public slots: + void setPassword(const QString& value); + void setNewPassword(const QString& value); + void setEmail(const QString& value); + void setEmailEncoded(const QString& value); + void setUserId(const QString& value); + void setBaseUrl(const QString& value); + void setGitUrl(const QString& value); + void setSavePasswordLocal(bool value); + void setVerificationStatus(short value); + void setBackgroundSync(bool value); + void setGitLocalOnly(bool value); + +signals: + void passwordChanged(const QString& value); + void newPasswordChanged(const QString& value); + void emailChanged(const QString& value); + void emailEncodedChanged(const QString& value); + void userIdChanged(const QString& value); + void baseUrlChanged(const QString& value); + void gitUrlChanged(const QString& value); + void savePasswordLocalChanged(bool value); + void verificationStatusChanged(short value); + void backgroundSyncChanged(bool value); + void gitLocalOnlyChanged(bool value); +private: + QString group; +}; + +class DivePlannerSettings : public QObject { + Q_OBJECT + Q_PROPERTY(bool last_stop READ lastStop WRITE setLastStop NOTIFY lastStopChanged) + Q_PROPERTY(bool verbatim_plan READ verbatimPlan WRITE setVerbatimPlan NOTIFY verbatimPlanChanged) + Q_PROPERTY(bool display_runtime READ displayRuntime WRITE setDisplayRuntime NOTIFY displayRuntimeChanged) + Q_PROPERTY(bool display_duration READ displayDuration WRITE setDisplayDuration NOTIFY displayDurationChanged) + Q_PROPERTY(bool display_transitions READ displayTransitions WRITE setDisplayTransitions NOTIFY displayTransitionsChanged) + Q_PROPERTY(bool doo2breaks READ doo2breaks WRITE setDoo2breaks NOTIFY doo2breaksChanged) + Q_PROPERTY(bool drop_stone_mode READ dropStoneMode WRITE setDropStoneMode NOTIFY dropStoneModeChanged) + Q_PROPERTY(bool safetystop READ safetyStop WRITE setSafetyStop NOTIFY safetyStopChanged) + Q_PROPERTY(bool switch_at_req_stop READ switchAtRequiredStop WRITE setSwitchAtRequiredStop NOTIFY switchAtRequiredStopChanged) + Q_PROPERTY(int ascrate75 READ ascrate75 WRITE setAscrate75 NOTIFY ascrate75Changed) + Q_PROPERTY(int ascrate50 READ ascrate50 WRITE setAscrate50 NOTIFY ascrate50Changed) + Q_PROPERTY(int ascratestops READ ascratestops WRITE setAscratestops NOTIFY ascratestopsChanged) + Q_PROPERTY(int ascratelast6m READ ascratelast6m WRITE setAscratelast6m NOTIFY ascratelast6mChanged) + Q_PROPERTY(int descrate READ descrate WRITE setDescrate NOTIFY descrateChanged) + Q_PROPERTY(int bottompo2 READ bottompo2 WRITE setBottompo2 NOTIFY bottompo2Changed) + Q_PROPERTY(int decopo2 READ decopo2 WRITE setDecopo2 NOTIFY decopo2Changed) + Q_PROPERTY(int reserve_gas READ reserveGas WRITE setReserveGas NOTIFY reserveGasChanged) + Q_PROPERTY(int min_switch_duration READ minSwitchDuration WRITE setMinSwitchDuration NOTIFY minSwitchDurationChanged) + Q_PROPERTY(int bottomsac READ bottomSac WRITE setBottomSac NOTIFY bottomSacChanged) + Q_PROPERTY(int decosac READ decoSac WRITE setSecoSac NOTIFY decoSacChanged) + Q_PROPERTY(short conservatism_level READ conservatismLevel WRITE setConservatismLevel NOTIFY conservatismLevelChanged) + Q_PROPERTY(deco_mode decoMode READ decoMode WRITE setDecoMode NOTIFY decoModeChanged) + +public: + DivePlannerSettings(QObject *parent = 0); + bool lastStop() const; + bool verbatimPlan() const; + bool displayRuntime() const; + bool displayDuration() const; + bool displayTransitions() const; + bool doo2breaks() const; + bool dropStoneMode() const; + bool safetyStop() const; + bool switchAtRequiredStop() const; + int ascrate75() const; + int ascrate50() const; + int ascratestops() const; + int ascratelast6m() const; + int descrate() const; + int bottompo2() const; + int decopo2() const; + int reserveGas() const; + int minSwitchDuration() const; + int bottomSac() const; + int decoSac() const; + short conservatismLevel() const; + deco_mode decoMode() const; + +public slots: + void setLastStop(bool value); + void setVerbatimPlan(bool value); + void setDisplayRuntime(bool value); + void setDisplayDuration(bool value); + void setDisplayTransitions(bool value); + void setDoo2breaks(bool value); + void setDropStoneMode(bool value); + void setSafetyStop(bool value); + void setSwitchAtRequiredStop(bool value); + void setAscrate75(int value); + void setAscrate50(int value); + void setAscratestops(int value); + void setAscratelast6m(int value); + void setDescrate(int value); + void setBottompo2(int value); + void setDecopo2(int value); + void setReserveGas(int value); + void setMinSwitchDuration(int value); + void setBottomSac(int value); + void setSecoSac(int value); + void setConservatismLevel(int value); + void setDecoMode(deco_mode value); + +signals: + void lastStopChanged(bool value); + void verbatimPlanChanged(bool value); + void displayRuntimeChanged(bool value); + void displayDurationChanged(bool value); + void displayTransitionsChanged(bool value); + void doo2breaksChanged(bool value); + void dropStoneModeChanged(bool value); + void safetyStopChanged(bool value); + void switchAtRequiredStopChanged(bool value); + void ascrate75Changed(int value); + void ascrate50Changed(int value); + void ascratestopsChanged(int value); + void ascratelast6mChanged(int value); + void descrateChanged(int value); + void bottompo2Changed(int value); + void decopo2Changed(int value); + void reserveGasChanged(int value); + void minSwitchDurationChanged(int value); + void bottomSacChanged(int value); + void decoSacChanged(int value); + void conservatismLevelChanged(int value); + void decoModeChanged(deco_mode value); + +private: + QString group; +}; + +class UnitsSettings : public QObject { + Q_OBJECT + Q_PROPERTY(int length READ length WRITE setLength NOTIFY lengthChanged) + Q_PROPERTY(int pressure READ pressure WRITE setPressure NOTIFY pressureChanged) + Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged) + Q_PROPERTY(int temperature READ temperature WRITE setTemperature NOTIFY temperatureChanged) + Q_PROPERTY(int weight READ weight WRITE setWeight NOTIFY weightChanged) + Q_PROPERTY(QString unit_system READ unitSystem WRITE setUnitSystem NOTIFY unitSystemChanged) + Q_PROPERTY(bool coordinates_traditional READ coordinatesTraditional WRITE setCoordinatesTraditional NOTIFY coordinatesTraditionalChanged) + Q_PROPERTY(int vertical_speed_time READ verticalSpeedTime WRITE setVerticalSpeedTime NOTIFY verticalSpeedTimeChanged) + +public: + UnitsSettings(QObject *parent = 0); + int length() const; + int pressure() const; + int volume() const; + int temperature() const; + int weight() const; + int verticalSpeedTime() const; + QString unitSystem() const; + bool coordinatesTraditional() const; + +public slots: + void setLength(int value); + void setPressure(int value); + void setVolume(int value); + void setTemperature(int value); + void setWeight(int value); + void setVerticalSpeedTime(int value); + void setUnitSystem(const QString& value); + void setCoordinatesTraditional(bool value); + +signals: + void lengthChanged(int value); + void pressureChanged(int value); + void volumeChanged(int value); + void temperatureChanged(int value); + void weightChanged(int value); + void verticalSpeedTimeChanged(int value); + void unitSystemChanged(const QString& value); + void coordinatesTraditionalChanged(bool value); +private: + QString group; +}; + +class GeneralSettingsObjectWrapper : public QObject { + Q_OBJECT + Q_PROPERTY(QString default_filename READ defaultFilename WRITE setDefaultFilename NOTIFY defaultFilenameChanged) + Q_PROPERTY(QString default_cylinder READ defaultCylinder WRITE setDefaultCylinder NOTIFY defaultCylinderChanged) + Q_PROPERTY(short default_file_behavior READ defaultFileBehavior WRITE setDefaultFileBehavior NOTIFY defaultFileBehaviorChanged) + Q_PROPERTY(bool use_default_file READ useDefaultFile WRITE setUseDefaultFile NOTIFY useDefaultFileChanged) + Q_PROPERTY(int defaultsetpoint READ defaultSetPoint WRITE setDefaultSetPoint NOTIFY defaultSetPointChanged) + Q_PROPERTY(int o2consumption READ o2Consumption WRITE setO2Consumption NOTIFY o2ConsumptionChanged) + Q_PROPERTY(int pscr_ratio READ pscrRatio WRITE setPscrRatio NOTIFY pscrRatioChanged) + +public: + GeneralSettingsObjectWrapper(QObject *parent); + QString defaultFilename() const; + QString defaultCylinder() const; + short defaultFileBehavior() const; + bool useDefaultFile() const; + int defaultSetPoint() const; + int o2Consumption() const; + int pscrRatio() const; + +public slots: + void setDefaultFilename (const QString& value); + void setDefaultCylinder (const QString& value); + void setDefaultFileBehavior (short value); + void setUseDefaultFile (bool value); + void setDefaultSetPoint (int value); + void setO2Consumption (int value); + void setPscrRatio (int value); + +signals: + void defaultFilenameChanged(const QString& value); + void defaultCylinderChanged(const QString& value); + void defaultFileBehaviorChanged(short value); + void useDefaultFileChanged(bool value); + void defaultSetPointChanged(int value); + void o2ConsumptionChanged(int value); + void pscrRatioChanged(int value); +private: + QString group; +}; + +class DisplaySettingsObjectWrapper : public QObject { + Q_OBJECT + Q_PROPERTY(QString divelist_font READ divelistFont WRITE setDivelistFont NOTIFY divelistFontChanged) + Q_PROPERTY(double font_size READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) + Q_PROPERTY(short display_invalid_dives READ displayInvalidDives WRITE setDisplayInvalidDives NOTIFY displayInvalidDivesChanged) +public: + DisplaySettingsObjectWrapper(QObject *parent); + QString divelistFont() const; + double fontSize() const; + short displayInvalidDives() const; +public slots: + void setDivelistFont(const QString& value); + void setFontSize(double value); + void setDisplayInvalidDives(short value); +signals: + void divelistFontChanged(const QString& value); + void fontSizeChanged(double value); + void displayInvalidDivesChanged(short value); +private: + QString group; +}; + +class LanguageSettingsObjectWrapper : public QObject { + Q_OBJECT + Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged) + Q_PROPERTY(QString time_format READ timeFormat WRITE setTimeFormat NOTIFY timeFormatChanged) + Q_PROPERTY(QString date_format READ dateFormat WRITE setDateFormat NOTIFY dateFormatChanged) + Q_PROPERTY(QString date_format_short READ dateFormatShort WRITE setDateFormatShort NOTIFY dateFormatShortChanged) + Q_PROPERTY(bool time_format_override READ timeFormatOverride WRITE setTimeFormatOverride NOTIFY timeFormatOverrideChanged) + Q_PROPERTY(bool date_format_override READ dateFormatOverride WRITE setDateFormatOverride NOTIFY dateFormatOverrideChanged) + Q_PROPERTY(bool use_system_language READ useSystemLanguage WRITE setUseSystemLanguage NOTIFY useSystemLanguageChanged) + +public: + LanguageSettingsObjectWrapper(QObject *parent); + QString language() const; + QString timeFormat() const; + QString dateFormat() const; + QString dateFormatShort() const; + bool timeFormatOverride() const; + bool dateFormatOverride() const; + bool useSystemLanguage() const; + +public slots: + void setLanguage (const QString& value); + void setTimeFormat (const QString& value); + void setDateFormat (const QString& value); + void setDateFormatShort (const QString& value); + void setTimeFormatOverride (bool value); + void setDateFormatOverride (bool value); + void setUseSystemLanguage (bool value); +signals: + void languageChanged(const QString& value); + void timeFormatChanged(const QString& value); + void dateFormatChanged(const QString& value); + void dateFormatShortChanged(const QString& value); + void timeFormatOverrideChanged(bool value); + void dateFormatOverrideChanged(bool value); + void useSystemLanguageChanged(bool value); + +private: + QString group; +}; + +class AnimationsSettingsObjectWrapper : public QObject { + Q_OBJECT + Q_PROPERTY(int animation_speed READ animationSpeed WRITE setAnimationSpeed NOTIFY animationSpeedChanged) +public: + AnimationsSettingsObjectWrapper(QObject *parent); + int animationSpeed() const; + +public slots: + void setAnimationSpeed(int value); + +signals: + void animationSpeedChanged(int value); + +private: + QString group; +}; + +class LocationServiceSettingsObjectWrapper : public QObject { + Q_OBJECT + Q_PROPERTY(int time_threshold READ timeThreshold WRITE setTimeThreshold NOTIFY timeThresholdChanged) + Q_PROPERTY(int distance_threshold READ distanceThreshold WRITE setDistanceThreshold NOTIFY distanceThresholdChanged) +public: + LocationServiceSettingsObjectWrapper(QObject *parent); + int timeThreshold() const; + int distanceThreshold() const; +public slots: + void setTimeThreshold(int value); + void setDistanceThreshold(int value); +signals: + void timeThresholdChanged(int value); + void distanceThresholdChanged(int value); +private: + QString group; +}; + +class SettingsObjectWrapper : public QObject { + Q_OBJECT + Q_PROPERTY(short save_userid_local READ saveUserIdLocal WRITE setSaveUserIdLocal NOTIFY saveUserIdLocalChanged) + + Q_PROPERTY(TechnicalDetailsSettings* techical_details MEMBER techDetails CONSTANT) + Q_PROPERTY(PartialPressureGasSettings* pp_gas MEMBER pp_gas CONSTANT) + Q_PROPERTY(FacebookSettings* facebook MEMBER facebook CONSTANT) + Q_PROPERTY(GeocodingPreferences* geocoding MEMBER geocoding CONSTANT) + Q_PROPERTY(ProxySettings* proxy MEMBER proxy CONSTANT) + Q_PROPERTY(CloudStorageSettings* cloud_storage MEMBER cloud_storage CONSTANT) + Q_PROPERTY(DivePlannerSettings* planner MEMBER planner_settings CONSTANT) + Q_PROPERTY(UnitsSettings* units MEMBER unit_settings CONSTANT) + + Q_PROPERTY(GeneralSettingsObjectWrapper* general MEMBER general_settings CONSTANT) + Q_PROPERTY(DisplaySettingsObjectWrapper* display MEMBER display_settings CONSTANT) + Q_PROPERTY(LanguageSettingsObjectWrapper* language MEMBER language_settings CONSTANT) + Q_PROPERTY(AnimationsSettingsObjectWrapper* animation MEMBER animation_settings CONSTANT) + Q_PROPERTY(LocationServiceSettingsObjectWrapper* Location MEMBER location_settings CONSTANT) +public: + static SettingsObjectWrapper *instance(); + short saveUserIdLocal() const; + + TechnicalDetailsSettings *techDetails; + PartialPressureGasSettings *pp_gas; + FacebookSettings *facebook; + GeocodingPreferences *geocoding; + ProxySettings *proxy; + CloudStorageSettings *cloud_storage; + DivePlannerSettings *planner_settings; + UnitsSettings *unit_settings; + GeneralSettingsObjectWrapper *general_settings; + DisplaySettingsObjectWrapper *display_settings; + LanguageSettingsObjectWrapper *language_settings; + AnimationsSettingsObjectWrapper *animation_settings; + LocationServiceSettingsObjectWrapper *location_settings; + +public slots: + void setSaveUserIdLocal(short value); +private: + SettingsObjectWrapper(QObject *parent = NULL); +signals: + void saveUserIdLocalChanged(short value); +}; + +#endif diff --git a/core/subsurfacestartup.c b/core/subsurfacestartup.c new file mode 100644 index 000000000..6e0dede1c --- /dev/null +++ b/core/subsurfacestartup.c @@ -0,0 +1,310 @@ +#include "subsurfacestartup.h" +#include "version.h" +#include +#include +#include "gettext.h" +#include "qthelperfromc.h" +#include "git-access.h" +#include "libdivecomputer/version.h" + +struct preferences prefs, informational_prefs; +struct preferences default_prefs = { + .cloud_base_url = "https://cloud.subsurface-divelog.org/", + .units = SI_UNITS, + .unit_system = METRIC, + .coordinates_traditional = true, + .pp_graphs = { + .po2 = false, + .pn2 = false, + .phe = false, + .po2_threshold = 1.6, + .pn2_threshold = 4.0, + .phe_threshold = 13.0, + }, + .mod = false, + .modpO2 = 1.6, + .ead = false, + .hrgraph = false, + .percentagegraph = false, + .dcceiling = true, + .redceiling = false, + .calcceiling = false, + .calcceiling3m = false, + .calcndltts = false, + .gflow = 30, + .gfhigh = 75, + .animation_speed = 500, + .gf_low_at_maxdepth = false, + .show_ccr_setpoint = false, + .show_ccr_sensors = false, + .font_size = -1, + .display_invalid_dives = false, + .show_sac = false, + .display_unused_tanks = false, + .show_average_depth = true, + .ascrate75 = 9000 / 60, + .ascrate50 = 6000 / 60, + .ascratestops = 6000 / 60, + .ascratelast6m = 1000 / 60, + .descrate = 18000 / 60, + .bottompo2 = 1400, + .decopo2 = 1600, + .doo2breaks = false, + .drop_stone_mode = false, + .switch_at_req_stop = false, + .min_switch_duration = 60, + .last_stop = false, + .verbatim_plan = false, + .display_runtime = true, + .display_duration = true, + .display_transitions = true, + .safetystop = true, + .bottomsac = 20000, + .decosac = 17000, + .reserve_gas=40000, + .o2consumption = 720, + .pscr_ratio = 100, + .show_pictures_in_profile = true, + .tankbar = false, + .facebook = { + .user_id = NULL, + .album_id = NULL, + .access_token = NULL + }, + .defaultsetpoint = 1100, + .cloud_background_sync = true, + .geocoding = { + .enable_geocoding = true, + .parse_dive_without_gps = false, + .tag_existing_dives = false, + .category = { 0 } + }, + .deco_mode = BUEHLMANN, + .conservatism_level = 3, + .distance_threshold = 1000, + .time_threshold = 600 +}; + +int run_survey; + +struct units *get_units() +{ + return &prefs.units; +} + +/* random helper functions, used here or elsewhere */ +static int sortfn(const void *_a, const void *_b) +{ + const struct dive *a = (const struct dive *)*(void **)_a; + const struct dive *b = (const struct dive *)*(void **)_b; + + if (a->when < b->when) + return -1; + if (a->when > b->when) + return 1; + return 0; +} + +void sort_table(struct dive_table *table) +{ + qsort(table->dives, table->nr, sizeof(struct dive *), sortfn); +} + +const char *weekday(int wday) +{ + static const char wday_array[7][7] = { + /*++GETTEXT: these are three letter days - we allow up to six code bytes */ + QT_TRANSLATE_NOOP("gettextFromC", "Sun"), QT_TRANSLATE_NOOP("gettextFromC", "Mon"), QT_TRANSLATE_NOOP("gettextFromC", "Tue"), QT_TRANSLATE_NOOP("gettextFromC", "Wed"), QT_TRANSLATE_NOOP("gettextFromC", "Thu"), QT_TRANSLATE_NOOP("gettextFromC", "Fri"), QT_TRANSLATE_NOOP("gettextFromC", "Sat") + }; + return translate("gettextFromC", wday_array[wday]); +} + +const char *monthname(int mon) +{ + static const char month_array[12][7] = { + /*++GETTEXT: these are three letter months - we allow up to six code bytes*/ + QT_TRANSLATE_NOOP("gettextFromC", "Jan"), QT_TRANSLATE_NOOP("gettextFromC", "Feb"), QT_TRANSLATE_NOOP("gettextFromC", "Mar"), QT_TRANSLATE_NOOP("gettextFromC", "Apr"), QT_TRANSLATE_NOOP("gettextFromC", "May"), QT_TRANSLATE_NOOP("gettextFromC", "Jun"), + QT_TRANSLATE_NOOP("gettextFromC", "Jul"), QT_TRANSLATE_NOOP("gettextFromC", "Aug"), QT_TRANSLATE_NOOP("gettextFromC", "Sep"), QT_TRANSLATE_NOOP("gettextFromC", "Oct"), QT_TRANSLATE_NOOP("gettextFromC", "Nov"), QT_TRANSLATE_NOOP("gettextFromC", "Dec"), + }; + return translate("gettextFromC", month_array[mon]); +} + +/* + * track whether we switched to importing dives + */ +bool imported = false; + +static void print_version() +{ + printf("Subsurface v%s, ", subsurface_git_version()); + printf("built with libdivecomputer v%s\n", dc_version(NULL)); +} + +void print_files() +{ + const char *branch = 0; + const char *remote = 0; + const char *filename, *local_git; + + filename = cloud_url(); + + is_git_repository(filename, &branch, &remote, true); + printf("\nFile locations:\n\n"); + if (branch && remote) { + local_git = get_local_dir(remote, branch); + printf("Local git storage: %s\n", local_git); + } else { + printf("Unable to get local git directory\n"); + } + char *tmp = cloud_url(); + printf("Cloud URL: %s\n", tmp); + free(tmp); + tmp = hashfile_name_string(); + printf("Image hashes: %s\n", tmp); + free(tmp); + tmp = picturedir_string(); + printf("Local picture directory: %s\n\n", tmp); + free(tmp); +} + +static void print_help() +{ + print_version(); + printf("\nUsage: subsurface [options] [logfile ...] [--import logfile ...]"); + printf("\n\noptions include:"); + printf("\n --help|-h This help text"); + printf("\n --import logfile ... Logs before this option is treated as base, everything after is imported"); + printf("\n --verbose|-v Verbose debug (repeat to increase verbosity)"); + printf("\n --version Prints current version"); + printf("\n --survey Offer to submit a user survey"); + printf("\n --win32console Create a dedicated console if needed (Windows only). Add option before everything else\n\n"); +} + +void parse_argument(const char *arg) +{ + const char *p = arg + 1; + + do { + switch (*p) { + case 'h': + print_help(); + exit(0); + case 'v': + verbose++; + continue; + case 'q': + quit++; + continue; + case '-': + /* long options with -- */ + if (strcmp(arg, "--help") == 0) { + print_help(); + exit(0); + } + if (strcmp(arg, "--import") == 0) { + imported = true; /* mark the dives so far as the base, * everything after is imported */ + return; + } + if (strcmp(arg, "--verbose") == 0) { + verbose++; + return; + } + if (strcmp(arg, "--version") == 0) { + print_version(); + exit(0); + } + if (strcmp(arg, "--survey") == 0) { + run_survey = true; + return; + } + if (strcmp(arg, "--allow_run_as_root") == 0) { + ++force_root; + return; + } + if (strcmp(arg, "--win32console") == 0) + return; + /* fallthrough */ + case 'p': + /* ignore process serial number argument when run as native macosx app */ + if (strncmp(arg, "-psQT_TR_NOOP(", 5) == 0) { + return; + } + /* fallthrough */ + default: + fprintf(stderr, "Bad argument '%s'\n", arg); + exit(1); + } + } while (*++p); +} + +/* + * Under a POSIX setup, the locale string should have a format + * like [language[_territory][.codeset][@modifier]]. + * + * So search for the underscore, and see if the "territory" is + * US, and turn on imperial units by default. + * + * I guess Burma and Liberia should trigger this too. I'm too + * lazy to look up the territory names, though. + */ +void setup_system_prefs(void) +{ + const char *env; + + subsurface_OS_pref_setup(); + default_prefs.divelist_font = strdup(system_divelist_default_font); + default_prefs.font_size = system_divelist_default_font_size; + default_prefs.default_filename = system_default_filename(); + + env = getenv("LC_MEASUREMENT"); + if (!env) + env = getenv("LC_ALL"); + if (!env) + env = getenv("LANG"); + if (!env) + return; + env = strchr(env, '_'); + if (!env) + return; + env++; + if (strncmp(env, "US", 2)) + return; + + default_prefs.units = IMPERIAL_units; +} + +/* copy a preferences block, including making copies of all included strings */ +void copy_prefs(struct preferences *src, struct preferences *dest) +{ + *dest = *src; + dest->divelist_font = copy_string(src->divelist_font); + dest->default_filename = copy_string(src->default_filename); + dest->default_cylinder = copy_string(src->default_cylinder); + dest->cloud_base_url = copy_string(src->cloud_base_url); + dest->cloud_git_url = copy_string(src->cloud_git_url); + dest->userid = copy_string(src->userid); + dest->proxy_host = copy_string(src->proxy_host); + dest->proxy_user = copy_string(src->proxy_user); + dest->proxy_pass = copy_string(src->proxy_pass); + dest->time_format = copy_string(src->time_format); + dest->date_format = copy_string(src->date_format); + dest->date_format_short = copy_string(src->date_format_short); + dest->cloud_storage_password = copy_string(src->cloud_storage_password); + dest->cloud_storage_newpassword = copy_string(src->cloud_storage_newpassword); + dest->cloud_storage_email = copy_string(src->cloud_storage_email); + dest->cloud_storage_email_encoded = copy_string(src->cloud_storage_email_encoded); + dest->facebook.access_token = copy_string(src->facebook.access_token); + dest->facebook.user_id = copy_string(src->facebook.user_id); + dest->facebook.album_id = copy_string(src->facebook.album_id); +} + +/* + * Free strduped prefs before exit. + * + * These are not real leaks but they plug the holes found by eg. + * valgrind so you can find the real leaks. + */ +void free_prefs(void) +{ + // nop +} diff --git a/core/subsurfacestartup.h b/core/subsurfacestartup.h new file mode 100644 index 000000000..3ccc24aa4 --- /dev/null +++ b/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/core/subsurfacesysinfo.cpp b/core/subsurfacesysinfo.cpp new file mode 100644 index 000000000..a7173b169 --- /dev/null +++ b/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/core/subsurfacesysinfo.h b/core/subsurfacesysinfo.h new file mode 100644 index 000000000..b2c267b83 --- /dev/null +++ b/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/core/taxonomy.c b/core/taxonomy.c new file mode 100644 index 000000000..670d85ad0 --- /dev/null +++ b/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/core/taxonomy.h b/core/taxonomy.h new file mode 100644 index 000000000..51245d562 --- /dev/null +++ b/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/core/time.c b/core/time.c new file mode 100644 index 000000000..0893f19d8 --- /dev/null +++ b/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 unsigned int mdays[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + }; + static const unsigned int mdays_leap[] = { + 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + }; + unsigned long val; + unsigned int leapyears; + int m; + const unsigned int *mp; + + memset(tm, 0, sizeof(*tm)); + + /* seconds since 1970 -> minutes since 1970 */ + tm->tm_sec = timestamp % 60; + val = timestamp /= 60; + + /* Do the simple stuff */ + tm->tm_min = val % 60; + val /= 60; + tm->tm_hour = val % 24; + val /= 24; + + /* Jan 1, 1970 was a Thursday (tm_wday=4) */ + tm->tm_wday = (val + 4) % 7; + + /* + * Now we're in "days since Jan 1, 1970". To make things easier, + * let's make it "days since Jan 1, 1968", since that's a leap-year + */ + val += 365 + 366; + + /* This only works up until 2099 (2100 isn't a leap-year) */ + leapyears = val / (365 * 4 + 1); + val %= (365 * 4 + 1); + tm->tm_year = 68 + leapyears * 4; + + /* Handle the leap-year itself */ + mp = mdays_leap; + if (val > 365) { + tm->tm_year++; + val -= 366; + tm->tm_year += val / 365; + val %= 365; + mp = mdays; + } + + for (m = 0; m < 12; m++) { + if (val < *mp) + break; + val -= *mp++; + } + tm->tm_mday = val + 1; + tm->tm_mon = m; +} + +timestamp_t utc_mktime(struct tm *tm) +{ + static const int mdays[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + int year = tm->tm_year; + int month = tm->tm_mon; + int day = tm->tm_mday; + + /* First normalize relative to 1900 */ + if (year < 70) + year += 100; + else if (year > 1900) + year -= 1900; + + /* Normalized to Jan 1, 1970: unix time */ + year -= 70; + + if (year < 0 || year > 129) /* algo only works for 1970-2099 */ + return -1; + if (month < 0 || month > 11) /* array bounds */ + return -1; + if (month < 2 || (year + 2) % 4) + day--; + if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) + return -1; + return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24 * 60 * 60UL + + tm->tm_hour * 60 * 60 + tm->tm_min * 60 + tm->tm_sec; +} diff --git a/core/uemis-downloader.c b/core/uemis-downloader.c new file mode 100644 index 000000000..b9b532303 --- /dev/null +++ b/core/uemis-downloader.c @@ -0,0 +1,1403 @@ +/* + * 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; + +static int max_deleted_seen = -1; + +/* helper function to parse the Uemis data structures */ +static void uemis_ts(char *buffer, void *_when) +{ + struct tm tm; + timestamp_t *when = _when; + + memset(&tm, 0, sizeof(tm)); + sscanf(buffer, "%d-%d-%dT%d:%d:%d", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec); + tm.tm_mon -= 1; + tm.tm_year -= 1900; + *when = utc_mktime(&tm); +} + +/* float minutes */ +static void uemis_duration(char *buffer, duration_t *duration) +{ + duration->seconds = rint(ascii_strtod(buffer, NULL) * 60); +} + +/* int cm */ +static void uemis_depth(char *buffer, depth_t *depth) +{ + depth->mm = atoi(buffer) * 10; +} + +static void uemis_get_index(char *buffer, int *idx) +{ + *idx = atoi(buffer); +} + +/* space separated */ +static void uemis_add_string(const char *buffer, char **text, const char *delimit) +{ + /* do nothing if this is an empty buffer (Uemis sometimes returns a single + * space for empty buffers) */ + if (!buffer || !*buffer || (*buffer == ' ' && *(buffer + 1) == '\0')) + return; + if (!*text) { + *text = strdup(buffer); + } else { + char *buf = malloc(strlen(buffer) + strlen(*text) + 2); + strcpy(buf, *text); + strcat(buf, delimit); + strcat(buf, buffer); + free(*text); + *text = buf; + } +} + +/* still unclear if it ever reports lbs */ +static void uemis_get_weight(char *buffer, weightsystem_t *weight, int diveid) +{ + weight->weight.grams = uemis_get_weight_unit(diveid) ? + lbs_to_grams(ascii_strtod(buffer, NULL)) : + ascii_strtod(buffer, NULL) * 1000; + weight->description = strdup(translate("gettextFromC", "unknown")); +} + +static struct dive *uemis_start_dive(uint32_t deviceid) +{ + struct dive *dive = alloc_dive(); + dive->downloaded = true; + dive->dc.model = strdup("Uemis Zurich"); + dive->dc.deviceid = deviceid; + return dive; +} + +static struct dive *get_dive_by_uemis_diveid(device_data_t *devdata, uint32_t object_id) +{ + for (int i = 0; i < devdata->download_table->nr; i++) { + if (object_id == devdata->download_table->dives[i]->dc.diveid) + return devdata->download_table->dives[i]; + } + return NULL; +} + +static void record_uemis_dive(device_data_t *devdata, struct dive *dive) +{ + if (devdata->create_new_trip) { + if (!devdata->trip) + devdata->trip = create_and_hookup_trip_from_dive(dive); + else + add_dive_to_trip(dive, devdata->trip); + } + record_dive_to_table(dive, devdata->download_table); +} + +/* send text to the importer progress bar */ +static void uemis_info(const char *fmt, ...) +{ + static char buffer[256]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + progress_bar_text = buffer; + if (verbose) + fprintf(stderr, "Uemis downloader: %s\n", buffer); +} + +static long bytes_available(int file) +{ + long result; + long now = lseek(file, 0, SEEK_CUR); + if (now == -1) + return 0; + result = lseek(file, 0, SEEK_END); + lseek(file, now, SEEK_SET); + if (now == -1 || result == -1) + return 0; + return result; +} + +static int number_of_file(char *path) +{ + int count = 0; +#ifdef WIN32 + struct _wdirent *entry; + _WDIR *dirp = (_WDIR *)subsurface_opendir(path); +#else + struct dirent *entry; + DIR *dirp = (DIR *)subsurface_opendir(path); +#endif + + while (dirp) { +#ifdef WIN32 + entry = _wreaddir(dirp); + if (!entry) + break; +#else + entry = readdir(dirp); + if (!entry) + break; + if (strstr(entry->d_name, ".TXT") || strstr(entry->d_name, ".txt")) /* If the entry is a regular file */ +#endif + count++; + } +#ifdef WIN32 + _wclosedir(dirp); +#else + closedir(dirp); +#endif + return count; +} + +static char *build_filename(const char *path, const char *name) +{ + int len = strlen(path) + strlen(name) + 2; + char *buf = malloc(len); +#if WIN32 + snprintf(buf, len, "%s\\%s", path, name); +#else + snprintf(buf, len, "%s/%s", path, name); +#endif + return buf; +} + +/* Check if there's a req.txt file and get the starting filenr from it. + * Test for the maximum number of ANS files (I believe this is always + * 4000 but in case there are differences depending on firmware, this + * code is easy enough */ +static bool uemis_init(const char *path) +{ + char *ans_path; + int i; + + if (!path) + return false; + /* let's check if this is indeed a Uemis DC */ + reqtxt_path = build_filename(path, "req.txt"); + reqtxt_file = subsurface_open(reqtxt_path, O_RDONLY | O_CREAT, 0666); + if (reqtxt_file < 0) { +#if UEMIS_DEBUG & 1 + fprintf(debugfile, ":EE req.txt can't be opened\n"); +#endif + return false; + } + if (bytes_available(reqtxt_file) > 5) { + char tmp[6]; + read(reqtxt_file, tmp, 5); + tmp[5] = '\0'; +#if UEMIS_DEBUG & 2 + fprintf(debugfile, "::r req.txt \"%s\"\n", tmp); +#endif + if (sscanf(tmp + 1, "%d", &filenr) != 1) + return false; + } else { + filenr = 0; +#if UEMIS_DEBUG & 2 + fprintf(debugfile, "::r req.txt skipped as there were fewer than 5 bytes\n"); +#endif + } + close(reqtxt_file); + + /* It would be nice if we could simply go back to the first set of + * ANS files. But with a FAT filesystem that isn't possible */ + ans_path = build_filename(path, "ANS"); + number_of_files = number_of_file(ans_path); + free(ans_path); + /* initialize the array in which we collect the answers */ + for (i = 0; i < NUM_PARAM_BUFS; i++) + param_buff[i] = ""; + return true; +} + +static void str_append_with_delim(char *s, char *t) +{ + int len = strlen(s); + snprintf(s + len, BUFLEN - len, "%s{", t); +} + +/* The communication protocol with the DC is truly funky. + * After you write your request to the req.txt file you call this function. + * It writes the number of the next ANS file at the beginning of the req.txt + * file (prefixed by 'n' or 'r') and then again at the very end of it, after + * the full request (this time without the prefix). + * Then it syncs (not needed on Windows) and closes the file. */ +static void trigger_response(int file, char *command, int nr, long tailpos) +{ + char fl[10]; + + snprintf(fl, 8, "%s%04d", command, nr); +#if UEMIS_DEBUG & 4 + fprintf(debugfile, ":tr %s (after seeks)\n", fl); +#endif + if (lseek(file, 0, SEEK_SET) == -1) + goto fs_error; + if (write(file, fl, strlen(fl)) == -1) + goto fs_error; + if (lseek(file, tailpos, SEEK_SET) == -1) + goto fs_error; + if (write(file, fl + 1, strlen(fl + 1)) == -1) + goto fs_error; +#ifndef WIN32 + fsync(file); +#endif +fs_error: + close(file); +} + +static char *next_token(char **buf) +{ + char *q, *p = strchr(*buf, '{'); + if (p) + *p = '\0'; + else + p = *buf + strlen(*buf) - 1; + q = *buf; + *buf = p + 1; + return q; +} + +/* poor man's tokenizer that understands a quoted delimiter ('{') */ +static char *next_segment(char *buf, int *offset, int size) +{ + int i = *offset; + int seg_size; + bool done = false; + char *segment; + + while (!done) { + if (i < size) { + if (i < size - 1 && buf[i] == '\\' && + (buf[i + 1] == '\\' || buf[i + 1] == '{')) + memcpy(buf + i, buf + i + 1, size - i - 1); + else if (buf[i] == '{') + done = true; + i++; + } else { + done = true; + } + } + seg_size = i - *offset - 1; + if (seg_size < 0) + seg_size = 0; + segment = malloc(seg_size + 1); + memcpy(segment, buf + *offset, seg_size); + segment[seg_size] = '\0'; + *offset = i; + return segment; +} + +/* a dynamically growing buffer to store the potentially massive responses. + * The binary data block can be more than 100k in size (base64 encoded) */ +static void buffer_add(char **buffer, int *buffer_size, char *buf) +{ + if (!buf) + return; + if (!*buffer) { + *buffer = strdup(buf); + *buffer_size = strlen(*buffer) + 1; + } else { + *buffer_size += strlen(buf); + *buffer = realloc(*buffer, *buffer_size); + strcat(*buffer, buf); + } +#if UEMIS_DEBUG & 8 + fprintf(debugfile, "added \"%s\" to buffer - new length %d\n", buf, *buffer_size); +#endif +} + +/* are there more ANS files we can check? */ +static bool next_file(int max) +{ + if (filenr >= max) + return false; + filenr++; + return true; +} + +/* try and do a quick decode - without trying to get to fancy in case the data + * straddles a block boundary... + * we are parsing something that looks like this: + * object_id{int{2{date{ts{2011-04-05T12:38:04{duration{float{12.000... + */ +static char *first_object_id_val(char *buf) +{ + char *object, *bufend; + if (!buf) + return NULL; + bufend = buf + strlen(buf); + object = strstr(buf, "object_id"); + if (object && object + 14 < bufend) { + /* get the value */ + char tmp[36]; + char *p = object + 14; + char *t = tmp; + +#if UEMIS_DEBUG & 4 + char debugbuf[50]; + strncpy(debugbuf, object, 49); + debugbuf[49] = '\0'; + fprintf(debugfile, "buf |%s|\n", debugbuf); +#endif + while (p < bufend && *p != '{' && t < tmp + 9) + *t++ = *p++; + if (*p == '{') { + /* found the object_id - let's quickly look for the date */ + if (strncmp(p, "{date{ts{", 9) == 0 && strstr(p, "{duration{") != NULL) { + /* cool - that was easy */ + *t++ = ','; + *t++ = ' '; + /* skip the 9 characters we just found and take the date, ignoring the seconds + * and replace the silly 'T' in the middle with a space */ + strncpy(t, p + 9, 16); + if (*(t + 10) == 'T') + *(t + 10) = ' '; + t += 16; + } + *t = '\0'; + return strdup(tmp); + } + } + return NULL; +} + +/* ultra-simplistic; it doesn't deal with the case when the object_id is + * split across two chunks. It also doesn't deal with the discrepancy between + * object_id and dive number as understood by the dive computer */ +static void show_progress(char *buf, const char *what) +{ + char *val = first_object_id_val(buf); + if (val) { +/* let the user know what we are working on */ +#if UEMIS_DEBUG & 8 + fprintf(debugfile, "reading %s\n %s\n %s\n", what, val, buf); +#endif + uemis_info(translate("gettextFromC", "%s %s"), what, val); + free(val); + } +} + +static void uemis_increased_timeout(int *timeout) +{ + if (*timeout < UEMIS_MAX_TIMEOUT) + *timeout += UEMIS_LONG_TIMEOUT; + usleep(*timeout); +} + +/* send a request to the dive computer and collect the answer */ +static bool uemis_get_answer(const char *path, char *request, int n_param_in, + int n_param_out, const char **error_text) +{ + int i = 0, file_length; + char sb[BUFLEN]; + char fl[13]; + char tmp[101]; + const char *what = translate("gettextFromC", "data"); + bool searching = true; + bool assembling_mbuf = false; + bool ismulti = false; + bool found_answer = false; + bool more_files = true; + bool answer_in_mbuf = false; + char *ans_path; + int ans_file; + int timeout = UEMIS_LONG_TIMEOUT; + + reqtxt_file = subsurface_open(reqtxt_path, O_RDWR | O_CREAT, 0666); + if (reqtxt_file == -1) { + *error_text = "can't open req.txt"; +#ifdef UEMIS_DEBUG + fprintf(debugfile, "open %s failed with errno %d\n", reqtxt_path, errno); +#endif + return false; + } + snprintf(sb, BUFLEN, "n%04d12345678", filenr); + str_append_with_delim(sb, request); + for (i = 0; i < n_param_in; i++) + str_append_with_delim(sb, param_buff[i]); + if (!strcmp(request, "getDivelogs") || !strcmp(request, "getDeviceData") || !strcmp(request, "getDirectory") || + !strcmp(request, "getDivespot") || !strcmp(request, "getDive")) { + answer_in_mbuf = true; + str_append_with_delim(sb, ""); + if (!strcmp(request, "getDivelogs")) + what = translate("gettextFromC", "divelog #"); + else if (!strcmp(request, "getDivespot")) + what = translate("gettextFromC", "divespot #"); + else if (!strcmp(request, "getDive")) + what = translate("gettextFromC", "details for #"); + } + str_append_with_delim(sb, ""); + file_length = strlen(sb); + snprintf(fl, 10, "%08d", file_length - 13); + memcpy(sb + 5, fl, strlen(fl)); +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "::w req.txt \"%s\"\n", sb); +#endif + int written = write(reqtxt_file, sb, strlen(sb)); + if (written == -1 || (size_t)written != strlen(sb)) { + *error_text = translate("gettextFromC", ERR_FS_SHORT_WRITE); + return false; + } + if (!next_file(number_of_files)) { + *error_text = translate("gettextFromC", ERR_FS_FULL); + more_files = false; + } + trigger_response(reqtxt_file, "n", filenr, file_length); + usleep(timeout); + free(mbuf); + mbuf = NULL; + mbuf_size = 0; + while (searching || assembling_mbuf) { + if (import_thread_cancelled) + return false; + progress_bar_fraction = filenr / (double)UEMIS_MAX_FILES; + snprintf(fl, 13, "ANS%d.TXT", filenr - 1); + ans_path = build_filename(build_filename(path, "ANS"), fl); + ans_file = subsurface_open(ans_path, O_RDONLY, 0666); + read(ans_file, tmp, 100); + close(ans_file); +#if UEMIS_DEBUG & 8 + tmp[100] = '\0'; + fprintf(debugfile, "::t %s \"%s\"\n", ans_path, tmp); +#elif UEMIS_DEBUG & 4 + char pbuf[4]; + pbuf[0] = tmp[0]; + pbuf[1] = tmp[1]; + pbuf[2] = tmp[2]; + pbuf[3] = 0; + fprintf(debugfile, "::t %s \"%s...\"\n", ans_path, pbuf); +#endif + free(ans_path); + if (tmp[0] == '1') { + searching = false; + if (tmp[1] == 'm') { + assembling_mbuf = true; + ismulti = true; + } + if (tmp[2] == 'e') + assembling_mbuf = false; + if (assembling_mbuf) { + if (!next_file(number_of_files)) { + *error_text = translate("gettextFromC", ERR_FS_FULL); + more_files = false; + assembling_mbuf = false; + } + reqtxt_file = subsurface_open(reqtxt_path, O_RDWR | O_CREAT, 0666); + if (reqtxt_file == -1) { + *error_text = "can't open req.txt"; + fprintf(stderr, "open %s failed with errno %d\n", reqtxt_path, errno); + return false; + } + trigger_response(reqtxt_file, "n", filenr, file_length); + } + } else { + if (!next_file(number_of_files - 1)) { + *error_text = translate("gettextFromC", ERR_FS_FULL); + more_files = false; + assembling_mbuf = false; + searching = false; + } + reqtxt_file = subsurface_open(reqtxt_path, O_RDWR | O_CREAT, 0666); + if (reqtxt_file == -1) { + *error_text = "can't open req.txt"; + fprintf(stderr, "open %s failed with errno %d\n", reqtxt_path, errno); + return false; + } + trigger_response(reqtxt_file, "r", filenr, file_length); + uemis_increased_timeout(&timeout); + } + if (ismulti && more_files && tmp[0] == '1') { + int size; + snprintf(fl, 13, "ANS%d.TXT", assembling_mbuf ? filenr - 2 : filenr - 1); + ans_path = build_filename(build_filename(path, "ANS"), fl); + ans_file = subsurface_open(ans_path, O_RDONLY, 0666); + free(ans_path); + size = bytes_available(ans_file); + if (size > 3) { + char *buf; + int r; + if (lseek(ans_file, 3, SEEK_CUR) == -1) + goto fs_error; + buf = malloc(size - 2); + if ((r = read(ans_file, buf, size - 3)) != size - 3) { + free(buf); + goto fs_error; + } + buf[r] = '\0'; + buffer_add(&mbuf, &mbuf_size, buf); + show_progress(buf, what); + free(buf); + param_buff[3]++; + } + close(ans_file); + timeout = UEMIS_TIMEOUT; + usleep(UEMIS_TIMEOUT); + } + } + if (more_files) { + int size = 0, j = 0; + char *buf = NULL; + + if (!ismulti) { + snprintf(fl, 13, "ANS%d.TXT", filenr - 1); + ans_path = build_filename(build_filename(path, "ANS"), fl); + ans_file = subsurface_open(ans_path, O_RDONLY, 0666); + free(ans_path); + size = bytes_available(ans_file); + if (size > 3) { + int r; + if (lseek(ans_file, 3, SEEK_CUR) == -1) + goto fs_error; + buf = malloc(size - 2); + if ((r = read(ans_file, buf, size - 3)) != size - 3) { + free(buf); + goto fs_error; + } + buf[r] = '\0'; + buffer_add(&mbuf, &mbuf_size, buf); + show_progress(buf, what); +#if UEMIS_DEBUG & 8 + fprintf(debugfile, "::r %s \"%s\"\n", fl, buf); +#endif + } + size -= 3; + close(ans_file); + } else { + ismulti = false; + } +#if UEMIS_DEBUG & 8 + fprintf(debugfile, ":r: %s\n", buf); +#endif + if (!answer_in_mbuf) + for (i = 0; i < n_param_out && j < size; i++) + param_buff[i] = next_segment(buf, &j, size); + found_answer = true; + free(buf); + } +#if UEMIS_DEBUG & 1 + for (i = 0; i < n_param_out; i++) + fprintf(debugfile, "::: %d: %s\n", i, param_buff[i]); +#endif + return found_answer; +fs_error: + close(ans_file); + return false; +} + +static bool parse_divespot(char *buf) +{ + char *bp = buf + 1; + char *tp = next_token(&bp); + char *tag, *type, *val; + char locationstring[1024] = ""; + int divespot, len; + double latitude = 0.0, longitude = 0.0; + + // dive spot got deleted, so fail here + if (strstr(bp, "deleted{bool{true")) + return false; + // not a dive spot, fail here + if (strcmp(tp, "divespot")) + return false; + do + tag = next_token(&bp); + while (*tag && strcmp(tag, "object_id")); + if (!*tag) + return false; + next_token(&bp); + val = next_token(&bp); + divespot = atoi(val); + do { + tag = next_token(&bp); + type = next_token(&bp); + val = next_token(&bp); + if (!strcmp(type, "string") && *val && strcmp(val, " ")) { + len = strlen(locationstring); + snprintf(locationstring + len, sizeof(locationstring) - len, + "%s%s", len ? ", " : "", val); + } else if (!strcmp(type, "float")) { + if (!strcmp(tag, "longitude")) + longitude = ascii_strtod(val, NULL); + else if (!strcmp(tag, "latitude")) + latitude = ascii_strtod(val, NULL); + } + } while (tag && *tag); + + uemis_set_divelocation(divespot, locationstring, longitude, latitude); + return true; +} + +static char *suit[] = {"", QT_TRANSLATE_NOOP("gettextFromC", "wetsuit"), QT_TRANSLATE_NOOP("gettextFromC", "semidry"), QT_TRANSLATE_NOOP("gettextFromC", "drysuit")}; +static char *suit_type[] = {"", QT_TRANSLATE_NOOP("gettextFromC", "shorty"), QT_TRANSLATE_NOOP("gettextFromC", "vest"), QT_TRANSLATE_NOOP("gettextFromC", "long john"), QT_TRANSLATE_NOOP("gettextFromC", "jacket"), QT_TRANSLATE_NOOP("gettextFromC", "full suit"), QT_TRANSLATE_NOOP("gettextFromC", "2 pcs full suit")}; +static char *suit_thickness[] = {"", "0.5-2mm", "2-3mm", "3-5mm", "5-7mm", "8mm+", QT_TRANSLATE_NOOP("gettextFromC", "membrane")}; + +static void parse_tag(struct dive *dive, char *tag, char *val) +{ + /* we can ignore computer_id, water and gas as those are redundant + * with the binary data and would just get overwritten */ +#if UEMIS_DEBUG & 4 + if (strcmp(tag, "file_content")) + fprintf(debugfile, "Adding to dive %d : %s = %s\n", dive->dc.diveid, tag, val); +#endif + if (!strcmp(tag, "date")) { + uemis_ts(val, &dive->when); + } else if (!strcmp(tag, "duration")) { + uemis_duration(val, &dive->dc.duration); + } else if (!strcmp(tag, "depth")) { + uemis_depth(val, &dive->dc.maxdepth); + } else if (!strcmp(tag, "file_content")) { + uemis_parse_divelog_binary(val, dive); + } else if (!strcmp(tag, "altitude")) { + uemis_get_index(val, &dive->dc.surface_pressure.mbar); + } else if (!strcmp(tag, "f32Weight")) { + uemis_get_weight(val, &dive->weightsystem[0], dive->dc.diveid); + } else if (!strcmp(tag, "notes")) { + uemis_add_string(val, &dive->notes, " "); + } else if (!strcmp(tag, "u8DiveSuit")) { + if (*suit[atoi(val)]) + uemis_add_string(translate("gettextFromC", suit[atoi(val)]), &dive->suit, " "); + } else if (!strcmp(tag, "u8DiveSuitType")) { + if (*suit_type[atoi(val)]) + uemis_add_string(translate("gettextFromC", suit_type[atoi(val)]), &dive->suit, " "); + } else if (!strcmp(tag, "u8SuitThickness")) { + if (*suit_thickness[atoi(val)]) + uemis_add_string(translate("gettextFromC", suit_thickness[atoi(val)]), &dive->suit, " "); + } else if (!strcmp(tag, "nickname")) { + uemis_add_string(val, &dive->buddy, ","); + } +} + +static bool uemis_delete_dive(device_data_t *devdata, uint32_t diveid) +{ + struct dive *dive = NULL; + + if (devdata->download_table->dives[devdata->download_table->nr - 1]->dc.diveid == diveid) { + /* we hit the last one in the array */ + dive = devdata->download_table->dives[devdata->download_table->nr - 1]; + } else { + for (int i = 0; i < devdata->download_table->nr - 1; i++) { + if (devdata->download_table->dives[i]->dc.diveid == diveid) { + dive = devdata->download_table->dives[i]; + for (int x = i; x < devdata->download_table->nr - 1; x++) + devdata->download_table->dives[i] = devdata->download_table->dives[x + 1]; + } + } + } + if (dive) { + devdata->download_table->dives[--devdata->download_table->nr] = NULL; + if (dive->tripflag != TF_NONE) + remove_dive_from_trip(dive, false); + + free(dive->dc.sample); + free((void *)dive->notes); + free((void *)dive->divemaster); + free((void *)dive->buddy); + free((void *)dive->suit); + taglist_free(dive->tag_list); + free(dive); + + return true; + } + return false; +} + +/* This function is called for both divelog and dive information that we get + * from the SDA (what an insane design, btw). The object_id in the divelog + * matches the logfilenr in the dive information (which has its own, often + * different object_id) - we use this as the diveid. + * We create the dive when parsing the divelog and then later, when we parse + * the dive information we locate the already created dive via its diveid. + * Most things just get parsed and converted into our internal data structures, + * but the dive location API is even more crazy. We just get an id that is an + * index into yet another data store that we read out later. In order to + * correctly populate the location and gps data from that we need to remember + * the addresses of those fields for every dive that references the divespot. */ +static bool process_raw_buffer(device_data_t *devdata, uint32_t deviceid, char *inbuf, char **max_divenr, int *for_dive) +{ + char *buf = strdup(inbuf); + char *tp, *bp, *tag, *type, *val; + bool done = false; + int inbuflen = strlen(inbuf); + char *endptr = buf + inbuflen; + bool is_log = false, is_dive = false; + char *sections[10]; + size_t s, nr_sections = 0; + struct dive *dive = NULL; + char dive_no[10]; + +#if UEMIS_DEBUG & 8 + fprintf(debugfile, "p_r_b %s\n", inbuf); +#endif + if (for_dive) + *for_dive = -1; + bp = buf + 1; + tp = next_token(&bp); + if (strcmp(tp, "divelog") == 0) { + /* this is a divelog */ + is_log = true; + tp = next_token(&bp); + /* is it a valid entry or nothing ? */ + if (strcmp(tp, "1.0") != 0 || strstr(inbuf, "divelog{1.0{{{{")) { + free(buf); + return false; + } + } else if (strcmp(tp, "dive") == 0) { + /* this is dive detail */ + is_dive = true; + tp = next_token(&bp); + if (strcmp(tp, "1.0") != 0) { + free(buf); + return false; + } + } else { + /* don't understand the buffer */ + free(buf); + return false; + } + if (is_log) { + dive = uemis_start_dive(deviceid); + } else { + /* remember, we don't know if this is the right entry, + * so first test if this is even a valid entry */ + if (strstr(inbuf, "deleted{bool{true")) { +#if UEMIS_DEBUG & 2 + fprintf(debugfile, "p_r_b entry deleted\n"); +#endif + /* oops, this one isn't valid, suggest to try the previous one */ + free(buf); + return false; + } + /* quickhack and workaround to capture the original dive_no + * I am doing this so I don't have to change the original design + * but when parsing a dive we never parse the dive number because + * at the time it's being read the *dive variable is not set because + * the dive_no tag comes before the object_id in the uemis ans file + */ + dive_no[0] = '\0'; + char *dive_no_buf = strdup(inbuf); + char *dive_no_ptr = strstr(dive_no_buf, "dive_no{int{") + 12; + if (dive_no_ptr) { + char *dive_no_end = strstr(dive_no_ptr, "{"); + if (dive_no_end) { + *dive_no_end = '\0'; + strncpy(dive_no, dive_no_ptr, 9); + dive_no[9] = '\0'; + } + } + free(dive_no_buf); + } + while (!done) { + /* the valid buffer ends with a series of delimiters */ + if (bp >= endptr - 2 || !strcmp(bp, "{{")) + break; + tag = next_token(&bp); + /* we also end if we get an empty tag */ + if (*tag == '\0') + break; + for (s = 0; s < nr_sections; s++) + if (!strcmp(tag, sections[s])) { + tag = next_token(&bp); + break; + } + type = next_token(&bp); + if (!strcmp(type, "1.0")) { + /* this tells us the sections that will follow; the tag here + * is of the format dive-
*/ + sections[nr_sections] = strchr(tag, '-') + 1; +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "Expect to find section %s\n", sections[nr_sections]); +#endif + if (nr_sections < sizeof(sections) - 1) + nr_sections++; + continue; + } + val = next_token(&bp); +#if UEMIS_DEBUG & 8 + if (strlen(val) < 20) + fprintf(debugfile, "Parsed %s, %s, %s\n*************************\n", tag, type, val); +#endif + if (is_log && strcmp(tag, "object_id") == 0) { + free(*max_divenr); + *max_divenr = strdup(val); + dive->dc.diveid = atoi(val); +#if UEMIS_DEBUG % 2 + fprintf(debugfile, "Adding new dive from log with object_id %d.\n", atoi(val)); +#endif + } else if (is_dive && strcmp(tag, "logfilenr") == 0) { + /* this one tells us which dive we are adding data to */ + dive = get_dive_by_uemis_diveid(devdata, atoi(val)); + if (strcmp(dive_no, "0")) + dive->number = atoi(dive_no); + if (for_dive) + *for_dive = atoi(val); + } else if (!is_log && dive && !strcmp(tag, "divespot_id")) { + int divespot_id = atoi(val); + if (divespot_id != -1) { + dive->dive_site_uuid = create_dive_site("from Uemis", dive->when); + uemis_mark_divelocation(dive->dc.diveid, divespot_id, dive->dive_site_uuid); + } +#if UEMIS_DEBUG & 2 + fprintf(debugfile, "Created divesite %d for diveid : %d\n", dive->dive_site_uuid, dive->dc.diveid); +#endif + } else if (dive) { + parse_tag(dive, tag, val); + } + if (is_log && !strcmp(tag, "file_content")) + done = true; + /* done with one dive (got the file_content tag), but there could be more: + * a '{' indicates the end of the record - but we need to see another "{{" + * later in the buffer to know that the next record is complete (it could + * be a short read because of some error */ + if (done && ++bp < endptr && *bp != '{' && strstr(bp, "{{")) { + done = false; + record_uemis_dive(devdata, dive); + mark_divelist_changed(true); + dive = uemis_start_dive(deviceid); + } + } + if (is_log) { + if (dive->dc.diveid) { + record_uemis_dive(devdata, dive); + mark_divelist_changed(true); + } else { /* partial dive */ + free(dive); + free(buf); + return false; + } + } + free(buf); + return true; +} + +static char *uemis_get_divenr(char *deviceidstr, int force) +{ + uint32_t deviceid, maxdiveid = 0; + int i; + char divenr[10]; + struct dive_table *table; + deviceid = atoi(deviceidstr); + + /* + * If we are are retrying after a disconnect/reconnect, we + * will look up the highest dive number in the dives we + * already have. + * + * Also, if "force_download" is true, do this even if we + * don't have any dives (maxdiveid will remain zero) + */ + if (force || downloadTable.nr) + table = &downloadTable; + else + table = &dive_table; + + for (i = 0; i < table->nr; i++) { + struct dive *d = table->dives[i]; + struct divecomputer *dc; + if (!d) + continue; + for_each_dc (d, dc) { + if (dc->model && !strcmp(dc->model, "Uemis Zurich") && + (dc->deviceid == 0 || dc->deviceid == 0x7fffffff || dc->deviceid == deviceid) && + dc->diveid > maxdiveid) + maxdiveid = dc->diveid; + } + } + if (max_deleted_seen >= 0 && maxdiveid < (uint32_t)max_deleted_seen) { + maxdiveid = max_deleted_seen; +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "overriding max seen with max deleted seen %d\n", max_deleted_seen); +#endif + } + snprintf(divenr, 10, "%d", maxdiveid); + return strdup(divenr); +} + +#if UEMIS_DEBUG +static int bufCnt = 0; +static bool do_dump_buffer_to_file(char *buf, char *prefix) +{ + char path[100]; + char date[40]; + char obid[40]; + if (!buf) + return false; + + if (strstr(buf, "date{ts{")) + strncpy(date, strstr(buf, "date{ts{"), sizeof(date)); + else + strncpy(date, "date{ts{no-date{", sizeof(date)); + + if (!strstr(buf, "object_id{int{")) + return false; + + strncpy(obid, strstr(buf, "object_id{int{"), sizeof(obid)); + char *ptr1 = strstr(date, "date{ts{"); + char *ptr2 = strstr(obid, "object_id{int{"); + char *pdate = next_token(&ptr1); + pdate = next_token(&ptr1); + pdate = next_token(&ptr1); + char *pobid = next_token(&ptr2); + pobid = next_token(&ptr2); + pobid = next_token(&ptr2); + snprintf(path, sizeof(path), "/%s/%s/UEMIS Dump/%s_%s_Uemis_dump_%s_in_round_%d_%d.txt", home, user, prefix, pdate, pobid, debug_round, bufCnt); + int dumpFile = subsurface_open(path, O_RDWR | O_CREAT, 0666); + if (dumpFile == -1) + return false; + write(dumpFile, buf, strlen(buf)); + close(dumpFile); + bufCnt++; + return true; +} +#endif + +/* do some more sophisticated calculations here to try and predict if the next round of + * divelog/divedetail reads will fit into the UEMIS buffer, + * filenr holds now the uemis filenr after having read several logs including the dive details, + * fCapacity will five us the average number of files needed for all currently loaded data + * remember the maximum file usage per dive + * return : UEMIS_MEM_OK if there is enough memory for a full round + * UEMIS_MEM_CRITICAL if the memory is good for reading the dive logs + * UEMIS_MEM_FULL if the memory is exhausted + */ +static int get_memory(struct dive_table *td, int checkpoint) +{ + if (td->nr <= 0) + return UEMIS_MEM_OK; + + switch (checkpoint) { + case UEMIS_CHECK_LOG: + if (filenr / td->nr > max_mem_used) + max_mem_used = filenr / td->nr; + + /* check if a full block of dive logs + dive details and dive spot fit into the UEMIS buffer */ +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "max_mem_used %d (from td->nr %d) * block_size %d > max_files %d - filenr %d?\n", max_mem_used, td->nr, UEMIS_LOG_BLOCK_SIZE, UEMIS_MAX_FILES, filenr); +#endif + if (max_mem_used * UEMIS_LOG_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) + return UEMIS_MEM_FULL; + break; + case UEMIS_CHECK_DETAILS: + /* check if the next set of dive details and dive spot fit into the UEMIS buffer */ + if ((UEMIS_DIVE_DETAILS_SIZE + UEMIS_SPOT_BLOCK_SIZE) * UEMIS_LOG_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) + return UEMIS_MEM_FULL; + break; + case UEMIS_CHECK_SINGLE_DIVE: + if (UEMIS_DIVE_DETAILS_SIZE + UEMIS_SPOT_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) + return UEMIS_MEM_FULL; + break; + } + return UEMIS_MEM_OK; +} + +/* mark a dive as deleted by setting download to false + * this will be picked up by some cleaning statement later */ +static void do_delete_dives(struct dive_table *td, int idx) +{ + for (int x = idx; x < td->nr; x++) + td->dives[x]->downloaded = false; +} + +static bool load_uemis_divespot(const char *mountpath, int divespot_id) +{ + char divespotnr[10]; + snprintf(divespotnr, sizeof(divespotnr), "%d", divespot_id); + param_buff[2] = divespotnr; +#if UEMIS_DEBUG & 2 + fprintf(debugfile, "getDivespot %d\n", divespot_id); +#endif + bool success = uemis_get_answer(mountpath, "getDivespot", 3, 0, NULL); + if (mbuf && success) { +#if UEMIS_DEBUG & 16 + do_dump_buffer_to_file(mbuf, "Spot"); +#endif + return parse_divespot(mbuf); + } + return false; +} + +static void get_uemis_divespot(const char *mountpath, int divespot_id, struct dive *dive) +{ + struct dive_site *nds = get_dive_site_by_uuid(dive->dive_site_uuid); + if (nds && nds->name && strstr(nds->name,"from Uemis")) { + if (load_uemis_divespot(mountpath, divespot_id)) { + /* get the divesite based on the diveid, this should give us + * the newly created site + */ + struct dive_site *ods = NULL; + /* with the divesite name we got from parse_dive, that is called on load_uemis_divespot + * we search all existing divesites if we have one with the same name already. The function + * returns the first found which is luckily not the newly created. + */ + (void)get_dive_site_uuid_by_name(nds->name, &ods); + if (ods) { + /* if the uuid's are the same, the new site is a duplicate and can be deleted */ + if (nds->uuid != ods->uuid) { + delete_dive_site(nds->uuid); + dive->dive_site_uuid = ods->uuid; + } + } + } else { + /* if we can't load the dive site details, delete the site we + * created in process_raw_buffer + */ + delete_dive_site(dive->dive_site_uuid); + } + } +} + +static bool get_matching_dive(int idx, char *newmax, int *uemis_mem_status, struct device_data_t *data, const char *mountpath, const char deviceidnr) +{ + struct dive *dive = data->download_table->dives[idx]; + char log_file_no_to_find[20]; + char dive_to_read_buf[10]; + bool found = false; + bool found_below = false; + bool found_above = false; + int deleted_files = 0; + + snprintf(log_file_no_to_find, sizeof(log_file_no_to_find), "logfilenr{int{%d", dive->dc.diveid); +#if UEMIS_DEBUG & 2 + fprintf(debugfile, "Looking for dive details to go with divelog id %d\n", dive->dc.diveid); +#endif + while (!found) { + if (import_thread_cancelled) + break; + snprintf(dive_to_read_buf, sizeof(dive_to_read_buf), "%d", dive_to_read); + param_buff[2] = dive_to_read_buf; + (void)uemis_get_answer(mountpath, "getDive", 3, 0, NULL); +#if UEMIS_DEBUG & 16 + do_dump_buffer_to_file(mbuf, "Dive"); +#endif + *uemis_mem_status = get_memory(data->download_table, UEMIS_CHECK_SINGLE_DIVE); + if (*uemis_mem_status == UEMIS_MEM_OK) { + /* if the memory isn's completely full we can try to read more divelog vs. dive details + * UEMIS_MEM_CRITICAL means not enough space for a full round but the dive details + * and the divespots should fit into the UEMIS memory + * The match we do here is to map the object_id to the logfilenr, we do this + * by iterating through the last set of loaded divelogs and then find the corresponding + * dive with the matching logfilenr */ + if (mbuf) { + if (strstr(mbuf, log_file_no_to_find)) { + /* we found the logfilenr that matches our object_id from the divelog we were looking for + * we mark the search successful even if the dive has been deleted. */ + found = true; + if (strstr(mbuf, "deleted{bool{true") == NULL) { + process_raw_buffer(data, deviceidnr, mbuf, &newmax, NULL); + /* remember the last log file number as it is very likely that subsequent dives + * have the same or higher logfile number. + * UEMIS unfortunately deletes dives by deleting the dive details and not the logs. */ +#if UEMIS_DEBUG & 2 + d_time = get_dive_date_c_string(dive->when); + fprintf(debugfile, "Matching divelog id %d from %s with dive details %d\n", dive->dc.diveid, d_time, dive_to_read); +#endif + int divespot_id = uemis_get_divespot_id_by_diveid(dive->dc.diveid); + if (divespot_id >= 0) + get_uemis_divespot(mountpath, divespot_id, dive); + + } else { + /* in this case we found a deleted file, so let's increment */ +#if UEMIS_DEBUG & 2 + d_time = get_dive_date_c_string(dive->when); + fprintf(debugfile, "TRY matching divelog id %d from %s with dive details %d but details are deleted\n", dive->dc.diveid, d_time, dive_to_read); +#endif + deleted_files++; + max_deleted_seen = dive_to_read; + /* mark this log entry as deleted and cleanup later, otherwise we mess up our array */ + dive->downloaded = false; +#if UEMIS_DEBUG & 2 + fprintf(debugfile, "Deleted dive from %s, with id %d from table -- newmax is %s\n", d_time, dive->dc.diveid, newmax); +#endif + } + } else { + uint32_t nr_found = 0; + char *logfilenr = strstr(mbuf, "logfilenr"); + if (logfilenr) { + sscanf(logfilenr, "logfilenr{int{%u", &nr_found); + if (nr_found >= dive->dc.diveid || nr_found == 0) { + found_above = true; + dive_to_read = dive_to_read - 2; + } else { + found_below = true; + } + if (dive_to_read < -1) + dive_to_read = -1; + } + } + } + if (found_above && found_below) + break; + dive_to_read++; + } else { + /* At this point the memory of the UEMIS is full, let's cleanup all divelog files were + * we could not match the details to. */ + do_delete_dives(data->download_table, idx); + return false; + } + } + /* decrement iDiveToRead by the amount of deleted entries found to assure + * we are not missing any valid matches when processing subsequent logs */ + dive_to_read = (dive_to_read - deleted_files > 0 ? dive_to_read - deleted_files : 0); + deleted_files = 0; + return true; +} + +const char *do_uemis_import(device_data_t *data) +{ + const char *mountpath = data->devname; + short force_download = data->force_download; + char *newmax = NULL; + int first, start, end = -2; + uint32_t deviceidnr; + char *deviceid = NULL; + const char *result = NULL; + char *endptr; + bool success, once = true; + int match_dive_and_log = 0; + int uemis_mem_status = UEMIS_MEM_OK; + +#if UEMIS_DEBUG + home = getenv("HOME"); + user = getenv("LOGNAME"); +#endif + uemis_info(translate("gettextFromC", "Initialise communication")); + if (!uemis_init(mountpath)) { + free(reqtxt_path); + return translate("gettextFromC", "Uemis init failed"); + } + + if (!uemis_get_answer(mountpath, "getDeviceId", 0, 1, &result)) + goto bail; + deviceid = strdup(param_buff[0]); + deviceidnr = atoi(deviceid); + + /* param_buff[0] is still valid */ + if (!uemis_get_answer(mountpath, "initSession", 1, 6, &result)) + goto bail; + + uemis_info(translate("gettextFromC", "Start download")); + if (!uemis_get_answer(mountpath, "processSync", 0, 2, &result)) + goto bail; + + /* before starting the long download, check if user pressed cancel */ + if (import_thread_cancelled) + goto bail; + + param_buff[1] = "notempty"; + newmax = uemis_get_divenr(deviceid, force_download); + if (verbose) + fprintf(stderr, "Uemis downloader: start looking at dive nr %s", newmax); + + first = start = atoi(newmax); + dive_to_read = first; + for (;;) { +#if UEMIS_DEBUG & 2 + debug_round++; +#endif +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "d_u_i inner loop start %d end %d newmax %s\n", start, end, newmax); +#endif + /* start at the last filled download table index */ + match_dive_and_log = data->download_table->nr; + sprintf(newmax, "%d", start); + param_buff[2] = newmax; + param_buff[3] = 0; + success = uemis_get_answer(mountpath, "getDivelogs", 3, 0, &result); + uemis_mem_status = get_memory(data->download_table, UEMIS_CHECK_DETAILS); + /* first, remove any leading garbage... this needs to start with a '{' */ + char *realmbuf = mbuf; + if (mbuf) + realmbuf = strchr(mbuf, '{'); + if (success && realmbuf && uemis_mem_status != UEMIS_MEM_FULL) { +#if UEMIS_DEBUG & 16 + do_dump_buffer_to_file(realmbuf, "Divelogs"); +#endif + /* process the buffer we have assembled */ + if (!process_raw_buffer(data, deviceidnr, realmbuf, &newmax, NULL)) { + /* if no dives were downloaded, mark end appropriately */ + if (end == -2) + end = start - 1; + success = false; + } + if (once) { + char *t = first_object_id_val(realmbuf); + if (t && atoi(t) > start) + start = atoi(t); + free(t); + once = false; + } + /* clean up mbuf */ + endptr = strstr(realmbuf, "{{{"); + if (endptr) + *(endptr + 2) = '\0'; + /* last object_id we parsed */ + sscanf(newmax, "%d", &end); +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "d_u_i after download and parse start %d end %d newmax %s progress %4.2f\n", start, end, newmax, progress_bar_fraction); +#endif + /* The way this works is that I am reading the current dive from what has been loaded during the getDiveLogs call to the UEMIS. + * As the object_id of the divelog entry and the object_id of the dive details are not necessarily the same, the match needs + * to happen based on the logfilenr. + * What the following part does is to optimize the mapping by using + * dive_to_read = the dive details entry that need to be read using the object_id + * logFileNoToFind = map the logfilenr of the dive details with the object_id = diveid from the get dive logs */ + for (int i = match_dive_and_log; i < data->download_table->nr; i++) { + bool success = get_matching_dive(i, newmax, &uemis_mem_status, data, mountpath, deviceidnr); + if (!success) + break; + if (import_thread_cancelled) + break; + } + + start = end; + + /* Do some memory checking here */ + uemis_mem_status = get_memory(data->download_table, UEMIS_CHECK_LOG); + if (uemis_mem_status != UEMIS_MEM_OK) { +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "d_u_i out of memory, bailing\n"); +#endif + break; + } + /* if the user clicked cancel, exit gracefully */ + if (import_thread_cancelled) { +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "d_u_i thread canceled, bailing\n"); +#endif + break; + } + /* if we got an error or got nothing back, stop trying */ + if (!success || !param_buff[3]) { +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "d_u_i after download nothing found, giving up\n"); +#endif + break; + } +#if UEMIS_DEBUG & 2 + if (debug_round != -1) + if (debug_round-- == 0) { + fprintf(debugfile, "d_u_i debug_round is now 0, bailing\n"); + goto bail; + } +#endif + } else { + /* some of the loading from the UEMIS failed at the divelog level + * if the memory status = full, we can't even load the divespots and/or buddies. + * The loaded block of divelogs is useless and all new loaded divelogs need to + * be deleted from the download_table. + */ + if (uemis_mem_status == UEMIS_MEM_FULL) + do_delete_dives(data->download_table, match_dive_and_log); +#if UEMIS_DEBUG & 4 + fprintf(debugfile, "d_u_i out of memory, bailing instead of processing\n"); +#endif + break; + } + } + + if (end == -2 && sscanf(newmax, "%d", &end) != 1) + end = start; + +#if UEMIS_DEBUG & 2 + fprintf(debugfile, "Done: read from object_id %d to %d\n", first, end); +#endif + + /* Regardless on where we are with the memory situation, it's time now + * to see if we have to clean some dead bodies from our download table */ + next_table_index = 0; + while (next_table_index < data->download_table->nr) { + if (!data->download_table->dives[next_table_index]->downloaded) + uemis_delete_dive(data, data->download_table->dives[next_table_index]->dc.diveid); + else + next_table_index++; + } + + if (uemis_mem_status != UEMIS_MEM_OK) + result = translate("gettextFromC", ERR_FS_ALMOST_FULL); + +bail: + (void)uemis_get_answer(mountpath, "terminateSync", 0, 3, &result); + if (!strcmp(param_buff[0], "error")) { + if (!strcmp(param_buff[2], "Out of Memory")) + result = translate("gettextFromC", ERR_FS_FULL); + else + result = param_buff[2]; + } + free(deviceid); + free(reqtxt_path); + if (!data->download_table->nr) + result = translate("gettextFromC", ERR_NO_FILES); + return result; +} diff --git a/core/uemis.c b/core/uemis.c new file mode 100644 index 000000000..5635d5630 --- /dev/null +++ b/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 { + uint32_t diveid; + int lbs; + int divespot; + int dive_site_uuid; + struct uemis_helper *next; +}; +static struct uemis_helper *uemis_helper = NULL; + +static struct uemis_helper *uemis_get_helper(uint32_t diveid) +{ + struct uemis_helper **php = &uemis_helper; + struct uemis_helper *hp = *php; + + while (hp) { + if (hp->diveid == diveid) + return hp; + if (hp->next) { + hp = hp->next; + continue; + } + php = &hp->next; + break; + } + hp = *php = calloc(1, sizeof(struct uemis_helper)); + hp->diveid = diveid; + hp->next = NULL; + return hp; +} + +static void uemis_weight_unit(int diveid, int lbs) +{ + struct uemis_helper *hp = uemis_get_helper(diveid); + if (hp) + hp->lbs = lbs; +} + +int uemis_get_weight_unit(uint32_t diveid) +{ + struct uemis_helper *hp = uemis_helper; + while (hp) { + if (hp->diveid == diveid) + return hp->lbs; + hp = hp->next; + } + /* odd - we should have found this; default to kg */ + return 0; +} + +void uemis_mark_divelocation(int diveid, int divespot, uint32_t dive_site_uuid) +{ + struct uemis_helper *hp = uemis_get_helper(diveid); + hp->divespot = divespot; + hp->dive_site_uuid = dive_site_uuid; +} + +/* support finding a dive spot based on the diveid */ +int uemis_get_divespot_id_by_diveid(uint32_t diveid) +{ + struct uemis_helper *hp = uemis_helper; + while (hp) { + if (hp->diveid == diveid) + return hp->divespot; + hp = hp->next; + } + return -1; +} + +void uemis_set_divelocation(int divespot, char *text, double longitude, double latitude) +{ + struct uemis_helper *hp = uemis_helper; + while (hp) { + if (hp->divespot == divespot) { + struct dive_site *ds = get_dive_site_by_uuid(hp->dive_site_uuid); + if (ds) { + ds->name = strdup(text); + ds->longitude.udeg = round(longitude * 1000000); + ds->latitude.udeg = round(latitude * 1000000); + } + } + hp = hp->next; + } +} + +/* Create events from the flag bits and other data in the sample; + * These bits basically represent what is displayed on screen at sample time. + * Many of these 'warnings' are way hyper-active and seriously clutter the + * profile plot - so these are disabled by default + * + * we mark all the strings for translation, but we store the untranslated + * strings and only convert them when displaying them on screen - this way + * when we write them to the XML file we'll always have the English strings, + * regardless of locale + */ +static void uemis_event(struct dive *dive, struct divecomputer *dc, struct sample *sample, uemis_sample_t *u_sample) +{ + uint8_t *flags = u_sample->flags; + int stopdepth; + static int lastndl; + + if (flags[1] & 0x01) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Safety stop violation")); + if (flags[1] & 0x08) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Speed alarm")); +#if WANT_CRAZY_WARNINGS + if (flags[1] & 0x06) /* both bits 1 and 2 are a warning */ + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Speed warning")); + if (flags[1] & 0x10) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂ green warning")); +#endif + if (flags[1] & 0x20) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂ ascend warning")); + if (flags[1] & 0x40) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂ ascend alarm")); + /* flags[2] reflects the deco / time bar + * flags[3] reflects more display details on deco and pO2 */ + if (flags[4] & 0x01) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Tank pressure info")); + if (flags[4] & 0x04) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "RGT warning")); + if (flags[4] & 0x08) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "RGT alert")); + if (flags[4] & 0x40) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Tank change suggested")); + if (flags[4] & 0x80) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Depth limit exceeded")); + if (flags[5] & 0x01) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Max deco time warning")); + if (flags[5] & 0x04) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Dive time info")); + if (flags[5] & 0x08) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Dive time alert")); + if (flags[5] & 0x10) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Marker")); + if (flags[6] & 0x02) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "No tank data")); + if (flags[6] & 0x04) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Low battery warning")); + if (flags[6] & 0x08) + add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Low battery alert")); +/* flags[7] reflects the little on screen icons that remind of previous + * warnings / alerts - not useful for events */ + +#if UEMIS_DEBUG & 32 + int i, j; + for (i = 0; i < 8; i++) { + printf(" %d: ", 29 + i); + for (j = 7; j >= 0; j--) + printf("%c", flags[i] & 1 << j ? '1' : '0'); + } + printf("\n"); +#endif + /* now add deco / NDL + * we don't use events but store this in the sample - that makes much more sense + * for the way we display this information + * What we know about the encoding so far: + * flags[3].bit0 | flags[5].bit1 != 0 ==> in deco + * flags[0].bit7 == 1 ==> Safety Stop + * otherwise NDL */ + stopdepth = rel_mbar_to_depth(u_sample->hold_depth, dive); + if ((flags[3] & 1) | (flags[5] & 2)) { + /* deco */ + sample->in_deco = true; + sample->stopdepth.mm = stopdepth; + sample->stoptime.seconds = u_sample->hold_time * 60; + sample->ndl.seconds = 0; + } else if (flags[0] & 128) { + /* safety stop - distinguished from deco stop by having + * both ndl and stop information */ + sample->in_deco = false; + sample->stopdepth.mm = stopdepth; + sample->stoptime.seconds = u_sample->hold_time * 60; + sample->ndl.seconds = lastndl; + } else { + /* NDL */ + sample->in_deco = false; + lastndl = sample->ndl.seconds = u_sample->hold_time * 60; + sample->stopdepth.mm = 0; + sample->stoptime.seconds = 0; + } +#if UEMIS_DEBUG & 32 + printf("%dm:%ds: p_amb_tol:%d surface:%d holdtime:%d holddepth:%d/%d ---> stopdepth:%d stoptime:%d ndl:%d\n", + sample->time.seconds / 60, sample->time.seconds % 60, u_sample->p_amb_tol, dive->dc.surface_pressure.mbar, + u_sample->hold_time, u_sample->hold_depth, stopdepth, sample->stopdepth.mm, sample->stoptime.seconds, sample->ndl.seconds); +#endif +} + +/* + * parse uemis base64 data blob into struct dive + */ +void uemis_parse_divelog_binary(char *base64, void *datap) +{ + int datalen; + int i; + uint8_t *data; + struct sample *sample = NULL; + uemis_sample_t *u_sample; + struct dive *dive = datap; + struct divecomputer *dc = &dive->dc; + int template, gasoffset; + uint8_t active = 0; + char version[5]; + + datalen = uemis_convert_base64(base64, &data); + dive->dc.airtemp.mkelvin = C_to_mkelvin((*(uint16_t *)(data + 45)) / 10.0); + dive->dc.surface_pressure.mbar = *(uint16_t *)(data + 43); + if (*(uint8_t *)(data + 19)) + dive->dc.salinity = SEAWATER_SALINITY; /* avg grams per 10l sea water */ + else + dive->dc.salinity = FRESHWATER_SALINITY; /* grams per 10l fresh water */ + + /* this will allow us to find the last dive read so far from this computer */ + dc->model = strdup("Uemis Zurich"); + dc->deviceid = *(uint32_t *)(data + 9); + dc->diveid = *(uint16_t *)(data + 7); + /* remember the weight units used in this dive - we may need this later when + * parsing the weight */ + uemis_weight_unit(dc->diveid, *(uint8_t *)(data + 24)); + /* dive template in use: + 0 = air + 1 = nitrox (B) + 2 = nitrox (B+D) + 3 = nitrox (B+T+D) + uemis cylinder data is insane - it stores seven tank settings in a block + and the template tells us which of the four groups of tanks we need to look at + */ + gasoffset = template = *(uint8_t *)(data + 115); + if (template == 3) + gasoffset = 4; + if (template == 0) + template = 1; + for (i = 0; i < template; i++) { + float volume = *(float *)(data + 116 + 25 * (gasoffset + i)) * 1000.0; + /* uemis always assumes a working pressure of 202.6bar (!?!?) - I first thought + * it was 3000psi, but testing against all my dives gets me that strange number. + * Still, that's of course completely bogus and shows they don't get how + * cylinders are named in non-metric parts of the world... + * we store the incorrect working pressure to get the SAC calculations "close" + * but the user will have to correct this manually + */ + dive->cylinder[i].type.size.mliter = rint(volume); + dive->cylinder[i].type.workingpressure.mbar = 202600; + dive->cylinder[i].gasmix.o2.permille = *(uint8_t *)(data + 120 + 25 * (gasoffset + i)) * 10; + dive->cylinder[i].gasmix.he.permille = 0; + } + /* first byte of divelog data is at offset 0x123 */ + i = 0x123; + u_sample = (uemis_sample_t *)(data + i); + while ((i <= datalen) && (data[i] != 0 || data[i + 1] != 0)) { + if (u_sample->active_tank != active) { + if (u_sample->active_tank >= MAX_CYLINDERS) { + fprintf(stderr, "got invalid sensor #%d was #%d\n", u_sample->active_tank, active); + } else { + active = u_sample->active_tank; + add_gas_switch_event(dive, dc, u_sample->dive_time, active); + } + } + sample = prepare_sample(dc); + sample->time.seconds = u_sample->dive_time; + sample->depth.mm = rel_mbar_to_depth(u_sample->water_pressure, dive); + sample->temperature.mkelvin = C_to_mkelvin(u_sample->dive_temperature / 10.0); + sample->sensor = active; + sample->cylinderpressure.mbar = + (u_sample->tank_pressure_high * 256 + u_sample->tank_pressure_low) * 10; + sample->cns = u_sample->cns; + uemis_event(dive, dc, sample, u_sample); + finish_sample(dc); + i += 0x25; + u_sample++; + } + if (sample) + dive->dc.duration.seconds = sample->time.seconds - 1; + + /* get data from the footer */ + char buffer[24]; + + snprintf(version, sizeof(version), "%1u.%02u", data[18], data[17]); + add_extra_data(dc, "FW Version", version); + snprintf(buffer, sizeof(buffer), "%08x", *(uint32_t *)(data + 9)); + add_extra_data(dc, "Serial", buffer); + snprintf(buffer, sizeof(buffer), "%d", *(uint16_t *)(data + i + 35)); + add_extra_data(dc, "main battery after dive", buffer); + snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 24), 60)); + add_extra_data(dc, "no fly time", buffer); + snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 26), 60)); + add_extra_data(dc, "no dive time", buffer); + snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 28), 60)); + add_extra_data(dc, "desat time", buffer); + snprintf(buffer, sizeof(buffer), "%u", *(uint16_t *)(data + i + 30)); + add_extra_data(dc, "allowed altitude", buffer); + + return; +} diff --git a/core/uemis.h b/core/uemis.h new file mode 100644 index 000000000..1758b4b32 --- /dev/null +++ b/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(uint32_t diveid); +void uemis_mark_divelocation(int diveid, int divespot, uint32_t dive_site_uuid); +void uemis_set_divelocation(int divespot, char *text, double longitude, double latitude); +int uemis_get_divespot_id_by_diveid(uint32_t diveid); + +typedef struct +{ + uint16_t dive_time; + uint16_t water_pressure; // (in cbar) + uint16_t dive_temperature; // (in dC) + uint8_t ascent_speed; // (units unclear) + uint8_t work_fact; + uint8_t cold_fact; + uint8_t bubble_fact; + uint16_t ascent_time; + uint16_t ascent_time_opt; + uint16_t p_amb_tol; + uint16_t satt; + uint16_t hold_depth; + uint16_t hold_time; + uint8_t active_tank; + // bloody glib, when compiled for Windows, forces the whole program to use + // the Windows packing rules. So to avoid problems on Windows (and since + // only tank_pressure is currently used and that exactly once) I give in and + // make this silly low byte / high byte 8bit entries + uint8_t tank_pressure_low; // (in cbar) + uint8_t tank_pressure_high; + uint8_t consumption_low; // (units unclear) + uint8_t consumption_high; + uint8_t rgt; // (remaining gas time in minutes) + uint8_t cns; + uint8_t flags[8]; +} __attribute((packed)) uemis_sample_t; + +#ifdef __cplusplus +} +#endif + +#endif // UEMIS_H diff --git a/core/units.h b/core/units.h new file mode 100644 index 000000000..029bb64fa --- /dev/null +++ b/core/units.h @@ -0,0 +1,280 @@ +#ifndef UNITS_H +#define UNITS_H + +#include +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define O2_IN_AIR 209 // permille +#define N2_IN_AIR 781 +#define O2_DENSITY 1429 // mg/Liter +#define N2_DENSITY 1251 +#define HE_DENSITY 179 +#define SURFACE_PRESSURE 1013 // mbar +#define SURFACE_PRESSURE_STRING "1013" +#define ZERO_C_IN_MKELVIN 273150 // mKelvin + +#ifdef __cplusplus +#define M_OR_FT(_m, _f) ((prefs.units.length == units::METERS) ? ((_m) * 1000) : (feet_to_mm(_f))) +#else +#define M_OR_FT(_m, _f) ((prefs.units.length == METERS) ? ((_m) * 1000) : (feet_to_mm(_f))) +#endif + +/* Salinity is expressed in weight in grams per 10l */ +#define SEAWATER_SALINITY 10300 +#define FRESHWATER_SALINITY 10000 + +#include +/* + * Some silly typedefs to make our units very explicit. + * + * Also, the units are chosen so that values can be expressible as + * integers, so that we never have FP rounding issues. And they + * are small enough that converting to/from imperial units doesn't + * really matter. + * + * We also strive to make '0' a meaningless number saying "not + * initialized", since many values are things that may not have + * been reported (eg cylinder pressure or temperature from dive + * computers that don't support them). But sometimes -1 is an even + * more explicit way of saying "not there". + * + * Thus "millibar" for pressure, for example, or "millikelvin" for + * temperatures. Doing temperatures in celsius or fahrenheit would + * make for loss of precision when converting from one to the other, + * and using millikelvin is SI-like but also means that a temperature + * of '0' is clearly just a missing temperature or cylinder pressure. + * + * Also strive to use units that can not possibly be mistaken for a + * valid value in a "normal" system without conversion. If the max + * depth of a dive is '20000', you probably didn't convert from mm on + * output, or if the max depth gets reported as "0.2ft" it was either + * a really boring dive, or there was some missing input conversion, + * and a 60-ft dive got recorded as 60mm. + * + * Doing these as "structs containing value" means that we always + * have to explicitly write out those units in order to get at the + * actual value. So there is hopefully little fear of using a value + * in millikelvin as Fahrenheit by mistake. + * + * We don't actually use these all yet, so maybe they'll change, but + * I made a number of types as guidelines. + */ +typedef int64_t timestamp_t; + +typedef struct +{ + uint32_t seconds; // durations up to 68 yrs +} duration_t; + +typedef struct +{ + int32_t seconds; // offsets up to +/- 34 yrs +} offset_t; + +typedef struct +{ + int32_t mm; +} depth_t; // depth to 2000 km + +typedef struct +{ + int32_t mbar; // pressure up to 2000 bar +} pressure_t; + +typedef struct +{ + uint16_t mbar; +} o2pressure_t; // pressure up to 65 bar + +typedef struct +{ + int16_t degrees; +} bearing_t; // compass bearing + +typedef struct +{ + uint32_t mkelvin; // up to 1750 degrees K (temperatures in K are always positive) +} temperature_t; + +typedef struct +{ + int mliter; +} volume_t; + +typedef struct +{ + int permille; +} fraction_t; + +typedef struct +{ + int grams; +} weight_t; + +typedef struct +{ + int udeg; +} degrees_t; + +static inline double udeg_to_radians(int udeg) +{ + return (udeg * M_PI) / (1000000.0 * 180.0); +} + +static inline double grams_to_lbs(int grams) +{ + return grams / 453.6; +} + +static inline int lbs_to_grams(double lbs) +{ + return rint(lbs * 453.6); +} + +static inline double ml_to_cuft(int ml) +{ + return ml / 28316.8466; +} + +static inline double cuft_to_l(double cuft) +{ + return cuft * 28.3168466; +} + +static inline double mm_to_feet(int mm) +{ + return mm * 0.00328084; +} + +static inline double m_to_mile(int m) +{ + return m / 1609.344; +} + +static inline unsigned long feet_to_mm(double feet) +{ + return rint(feet * 304.8); +} + +static inline int to_feet(depth_t depth) +{ + return rint(mm_to_feet(depth.mm)); +} + +static inline double mkelvin_to_C(int mkelvin) +{ + return (mkelvin - ZERO_C_IN_MKELVIN) / 1000.0; +} + +static inline double mkelvin_to_F(int mkelvin) +{ + return mkelvin * 9 / 5000.0 - 459.670; +} + +static inline unsigned long F_to_mkelvin(double f) +{ + return rint((f - 32) * 1000 / 1.8 + ZERO_C_IN_MKELVIN); +} + +static inline unsigned long C_to_mkelvin(double c) +{ + return rint(c * 1000 + ZERO_C_IN_MKELVIN); +} + +static inline double psi_to_bar(double psi) +{ + return psi / 14.5037738; +} + +static inline long psi_to_mbar(double psi) +{ + return rint(psi_to_bar(psi) * 1000); +} + +static inline int to_PSI(pressure_t pressure) +{ + return rint(pressure.mbar * 0.0145037738); +} + +static inline double bar_to_atm(double bar) +{ + return bar / SURFACE_PRESSURE * 1000; +} + +static inline double mbar_to_atm(int mbar) +{ + return (double)mbar / SURFACE_PRESSURE; +} + +static inline int mbar_to_PSI(int mbar) +{ + pressure_t p = { mbar }; + return to_PSI(p); +} + +/* + * We keep our internal data in well-specified units, but + * the input and output may come in some random format. This + * keeps track of those units. + */ +/* turns out in Win32 PASCAL is defined as a calling convention */ +#ifdef WIN32 +#undef PASCAL +#endif +struct units { + enum LENGHT { + METERS, + FEET + } length; + enum VOLUME { + LITER, + CUFT + } volume; + enum PRESSURE { + BAR, + PSI, + PASCAL + } pressure; + enum TEMPERATURE { + CELSIUS, + FAHRENHEIT, + KELVIN + } temperature; + enum WEIGHT { + KG, + LBS + } weight; + enum TIME { + SECONDS, + MINUTES + } vertical_speed_time; +}; + +/* + * We're going to default to SI units for input. Yes, + * technically the SI unit for pressure is Pascal, but + * we default to bar (10^5 pascal), which people + * actually use. Similarly, C instead of Kelvin. + * And kg instead of g. + */ +#define SI_UNITS \ + { \ + .length = METERS, .volume = LITER, .pressure = BAR, .temperature = CELSIUS, .weight = KG, .vertical_speed_time = MINUTES \ + } + +#define IMPERIAL_UNITS \ + { \ + .length = FEET, .volume = CUFT, .pressure = PSI, .temperature = FAHRENHEIT, .weight = LBS, .vertical_speed_time = MINUTES \ + } + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/version.c b/core/version.c new file mode 100644 index 000000000..764e4d2db --- /dev/null +++ b/core/version.c @@ -0,0 +1,18 @@ +#include "ssrf-version.h" + +const char *subsurface_git_version(void) +{ + return GIT_VERSION_STRING; +} + +const char *subsurface_canonical_version(void) +{ + return CANONICAL_VERSION_STRING; +} + +#ifdef SUBSURFACE_MOBILE +const char *subsurface_mobile_version(void) +{ + return MOBILE_VERSION_STRING; +} +#endif diff --git a/core/version.h b/core/version.h new file mode 100644 index 000000000..0a3204bd9 --- /dev/null +++ b/core/version.h @@ -0,0 +1,19 @@ +#ifndef VERSION_H +#define VERSION_H + +#ifdef __cplusplus +extern "C" { +#endif + +const char *subsurface_git_version(void); +const char *subsurface_canonical_version(void); + +#ifdef SUBSURFACE_MOBILE +const char *subsurface_mobile_version(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/webservice.h b/core/webservice.h new file mode 100644 index 000000000..052b8aae7 --- /dev/null +++ b/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/core/windows.c b/core/windows.c new file mode 100644 index 000000000..58d3beaad --- /dev/null +++ b/core/windows.c @@ -0,0 +1,454 @@ +/* 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 +} + +bool subsurface_user_is_root() +{ + /* FIXME: Detect admin rights */ + return (false); +} diff --git a/core/windowtitleupdate.cpp b/core/windowtitleupdate.cpp new file mode 100644 index 000000000..963455f1d --- /dev/null +++ b/core/windowtitleupdate.cpp @@ -0,0 +1,32 @@ +#include "windowtitleupdate.h" + +WindowTitleUpdate *WindowTitleUpdate::m_instance = NULL; + +WindowTitleUpdate::WindowTitleUpdate(QObject *parent) : QObject(parent) +{ + Q_ASSERT_X(m_instance == NULL, "WindowTitleUpdate", "WindowTitleUpdate recreated!"); + + m_instance = this; +} + +WindowTitleUpdate *WindowTitleUpdate::instance() +{ + return m_instance; +} + +WindowTitleUpdate::~WindowTitleUpdate() +{ + m_instance = NULL; +} + +void WindowTitleUpdate::emitSignal() +{ + emit updateTitle(); +} + +extern "C" void updateWindowTitle() +{ + WindowTitleUpdate *wt = WindowTitleUpdate::instance(); + if (wt) + wt->emitSignal(); +} diff --git a/core/windowtitleupdate.h b/core/windowtitleupdate.h new file mode 100644 index 000000000..8650e5868 --- /dev/null +++ b/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/core/worldmap-options.h b/core/worldmap-options.h new file mode 100644 index 000000000..177443563 --- /dev/null +++ b/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/core/worldmap-save.c b/core/worldmap-save.c new file mode 100644 index 000000000..e7e8bcc30 --- /dev/null +++ b/core/worldmap-save.c @@ -0,0 +1,117 @@ +// Clang has a bug on zero-initialization of C structs. +#pragma clang diagnostic ignored "-Wmissing-field-initializers" + +#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); + put_string(b, "

"); + put_HTML_quoted(b, translate("gettextFromC", "Max. depth:")); + put_HTML_depth(b, dive, " ", "

"); + 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/core/worldmap-save.h b/core/worldmap-save.h new file mode 100644 index 000000000..102ea40e5 --- /dev/null +++ b/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/desktop-widgets/about.cpp b/desktop-widgets/about.cpp index 02bb9b72b..6bd2503f6 100644 --- a/desktop-widgets/about.cpp +++ b/desktop-widgets/about.cpp @@ -1,5 +1,5 @@ -#include "about.h" -#include "version.h" +#include "desktop-widgets/about.h" +#include "core/version.h" #include #include #include diff --git a/desktop-widgets/configuredivecomputerdialog.cpp b/desktop-widgets/configuredivecomputerdialog.cpp index 428b326e4..471db9a65 100644 --- a/desktop-widgets/configuredivecomputerdialog.cpp +++ b/desktop-widgets/configuredivecomputerdialog.cpp @@ -1,8 +1,8 @@ -#include "configuredivecomputerdialog.h" +#include "desktop-widgets/configuredivecomputerdialog.h" -#include "helpers.h" -#include "mainwindow.h" -#include "display.h" +#include "core/helpers.h" +#include "desktop-widgets/mainwindow.h" +#include "core/display.h" #include #include diff --git a/desktop-widgets/configuredivecomputerdialog.h b/desktop-widgets/configuredivecomputerdialog.h index 9ad30ac67..f3ea97de5 100644 --- a/desktop-widgets/configuredivecomputerdialog.h +++ b/desktop-widgets/configuredivecomputerdialog.h @@ -4,12 +4,12 @@ #include #include #include "ui_configuredivecomputerdialog.h" -#include "subsurface-core/libdivecomputer.h" -#include "configuredivecomputer.h" +#include "core/libdivecomputer.h" +#include "core/configuredivecomputer.h" #include #include #ifdef BT_SUPPORT -#include "btdeviceselectiondialog.h" +#include "desktop-widgets/btdeviceselectiondialog.h" #endif class GasSpinBoxItemDelegate : public QStyledItemDelegate { diff --git a/desktop-widgets/divecomputermanagementdialog.cpp b/desktop-widgets/divecomputermanagementdialog.cpp index fd9273ffb..41572355c 100644 --- a/desktop-widgets/divecomputermanagementdialog.cpp +++ b/desktop-widgets/divecomputermanagementdialog.cpp @@ -1,7 +1,7 @@ -#include "divecomputermanagementdialog.h" -#include "mainwindow.h" -#include "helpers.h" -#include "divecomputermodel.h" +#include "desktop-widgets/divecomputermanagementdialog.h" +#include "desktop-widgets/mainwindow.h" +#include "core/helpers.h" +#include "qt-models/divecomputermodel.h" #include #include diff --git a/desktop-widgets/divelistview.cpp b/desktop-widgets/divelistview.cpp index f3204a111..0aea54f2a 100644 --- a/desktop-widgets/divelistview.cpp +++ b/desktop-widgets/divelistview.cpp @@ -4,11 +4,11 @@ * classes for the divelist of Subsurface * */ -#include "filtermodels.h" -#include "modeldelegates.h" -#include "mainwindow.h" -#include "divepicturewidget.h" -#include "display.h" +#include "qt-models/filtermodels.h" +#include "desktop-widgets/modeldelegates.h" +#include "desktop-widgets/mainwindow.h" +#include "desktop-widgets/divepicturewidget.h" +#include "core/display.h" #include #include #include @@ -17,12 +17,12 @@ #include #include #include -#include "qthelper.h" -#include "undocommands.h" -#include "divelistview.h" -#include "divepicturemodel.h" -#include "metrics.h" -#include "helpers.h" +#include "core/qthelper.h" +#include "desktop-widgets/undocommands.h" +#include "desktop-widgets/divelistview.h" +#include "qt-models/divepicturemodel.h" +#include "core/metrics.h" +#include "core/helpers.h" // # Date Rtg Dpth Dur Tmp Wght Suit Cyl Gas SAC OTU CNS Loc static int defaultWidth[] = { 70, 140, 90, 50, 50, 50, 50, 70, 50, 50, 70, 50, 50, 500}; diff --git a/desktop-widgets/divelistview.h b/desktop-widgets/divelistview.h index aaec37af5..2554725b7 100644 --- a/desktop-widgets/divelistview.h +++ b/desktop-widgets/divelistview.h @@ -14,7 +14,7 @@ #include #include #include -#include "divetripmodel.h" +#include "qt-models/divetripmodel.h" class DiveListView : public QTreeView { Q_OBJECT diff --git a/desktop-widgets/divelogexportdialog.cpp b/desktop-widgets/divelogexportdialog.cpp index e7c53274c..5b58d0c1d 100644 --- a/desktop-widgets/divelogexportdialog.cpp +++ b/desktop-widgets/divelogexportdialog.cpp @@ -3,14 +3,14 @@ #include #include -#include "divelogexportdialog.h" -#include "divelogexportlogic.h" -#include "diveshareexportdialog.h" +#include "desktop-widgets/divelogexportdialog.h" +#include "core/divelogexportlogic.h" +#include "desktop-widgets/diveshareexportdialog.h" #include "ui_divelogexportdialog.h" -#include "subsurfacewebservices.h" -#include "worldmap-save.h" -#include "save-html.h" -#include "mainwindow.h" +#include "desktop-widgets/subsurfacewebservices.h" +#include "core/worldmap-save.h" +#include "core/save-html.h" +#include "desktop-widgets/mainwindow.h" #define GET_UNIT(name, field, f, t) \ v = settings.value(QString(name)); \ diff --git a/desktop-widgets/divelogexportdialog.h b/desktop-widgets/divelogexportdialog.h index b72d96c50..e7cde318d 100644 --- a/desktop-widgets/divelogexportdialog.h +++ b/desktop-widgets/divelogexportdialog.h @@ -4,8 +4,8 @@ #include #include #include -#include "helpers.h" -#include "statistics.h" +#include "core/helpers.h" +#include "core/statistics.h" class QAbstractButton; diff --git a/desktop-widgets/divelogimportdialog.cpp b/desktop-widgets/divelogimportdialog.cpp index 4e5607ed3..cd9026f87 100644 --- a/desktop-widgets/divelogimportdialog.cpp +++ b/desktop-widgets/divelogimportdialog.cpp @@ -1,6 +1,6 @@ -#include "divelogimportdialog.h" -#include "mainwindow.h" -#include "color.h" +#include "desktop-widgets/divelogimportdialog.h" +#include "desktop-widgets/mainwindow.h" +#include "core/color.h" #include "ui_divelogimportdialog.h" #include #include diff --git a/desktop-widgets/divelogimportdialog.h b/desktop-widgets/divelogimportdialog.h index 2d12c7cac..811775379 100644 --- a/desktop-widgets/divelogimportdialog.h +++ b/desktop-widgets/divelogimportdialog.h @@ -9,8 +9,8 @@ #include #include -#include "subsurface-core/dive.h" -#include "subsurface-core/divelist.h" +#include "core/dive.h" +#include "core/divelist.h" namespace Ui { class DiveLogImportDialog; diff --git a/desktop-widgets/divelogimportdialog.ui b/desktop-widgets/divelogimportdialog.ui index 6d154b7c6..36746ffe9 100644 --- a/desktop-widgets/divelogimportdialog.ui +++ b/desktop-widgets/divelogimportdialog.ui @@ -200,12 +200,12 @@ ColumnNameView QListView -
divelogimportdialog.h
+
desktop-widgets/divelogimportdialog.h
ColumnDropCSVView QTableView -
divelogimportdialog.h
+
desktop-widgets/divelogimportdialog.h
diff --git a/desktop-widgets/divepicturewidget.cpp b/desktop-widgets/divepicturewidget.cpp index d52fbb9d7..04ae76e23 100644 --- a/desktop-widgets/divepicturewidget.cpp +++ b/desktop-widgets/divepicturewidget.cpp @@ -1,8 +1,8 @@ -#include "divepicturewidget.h" -#include "divepicturemodel.h" -#include "metrics.h" -#include "dive.h" -#include "divelist.h" +#include "desktop-widgets/divepicturewidget.h" +#include "qt-models/divepicturemodel.h" +#include "core/metrics.h" +#include "core/dive.h" +#include "core/divelist.h" #include #include #include @@ -11,8 +11,8 @@ #include #include #include -#include -#include +#include "desktop-widgets/mainwindow.h" +#include "core/qthelper.h" #include #include diff --git a/desktop-widgets/diveplanner.cpp b/desktop-widgets/diveplanner.cpp index f84781e96..3a2436c6c 100644 --- a/desktop-widgets/diveplanner.cpp +++ b/desktop-widgets/diveplanner.cpp @@ -1,12 +1,12 @@ -#include "diveplanner.h" -#include "modeldelegates.h" -#include "mainwindow.h" -#include "planner.h" -#include "helpers.h" -#include "cylindermodel.h" -#include "models.h" +#include "desktop-widgets/diveplanner.h" +#include "desktop-widgets/modeldelegates.h" +#include "desktop-widgets/mainwindow.h" +#include "core/planner.h" +#include "core/helpers.h" +#include "qt-models/cylindermodel.h" +#include "qt-models/models.h" #include "profile-widget/profilewidget2.h" -#include "diveplannermodel.h" +#include "qt-models/diveplannermodel.h" #include #include diff --git a/desktop-widgets/diveplanner.h b/desktop-widgets/diveplanner.h index 1cc96adc3..03014cb9c 100644 --- a/desktop-widgets/diveplanner.h +++ b/desktop-widgets/diveplanner.h @@ -7,7 +7,7 @@ #include #include -#include "dive.h" +#include "core/dive.h" class QListView; class QModelIndex; diff --git a/desktop-widgets/diveplanner.ui b/desktop-widgets/diveplanner.ui index adb44fad9..b7d1e74bf 100644 --- a/desktop-widgets/diveplanner.ui +++ b/desktop-widgets/diveplanner.ui @@ -243,7 +243,7 @@ TableView QWidget -
tableview.h
+
desktop-widgets/tableview.h
1
diff --git a/desktop-widgets/diveshareexportdialog.cpp b/desktop-widgets/diveshareexportdialog.cpp index ed6e2829a..4460a44df 100644 --- a/desktop-widgets/diveshareexportdialog.cpp +++ b/desktop-widgets/diveshareexportdialog.cpp @@ -1,10 +1,10 @@ -#include "diveshareexportdialog.h" +#include "desktop-widgets/diveshareexportdialog.h" #include "ui_diveshareexportdialog.h" -#include "mainwindow.h" -#include "save-html.h" -#include "subsurfacewebservices.h" -#include "helpers.h" -#include "subsurface-core/cloudstorage.h" +#include "desktop-widgets/mainwindow.h" +#include "core/save-html.h" +#include "desktop-widgets/subsurfacewebservices.h" +#include "core/helpers.h" +#include "core/cloudstorage.h" #include #include diff --git a/desktop-widgets/downloadfromdivecomputer.cpp b/desktop-widgets/downloadfromdivecomputer.cpp index be6018b36..7ef4e1e28 100644 --- a/desktop-widgets/downloadfromdivecomputer.cpp +++ b/desktop-widgets/downloadfromdivecomputer.cpp @@ -1,10 +1,10 @@ -#include "downloadfromdivecomputer.h" -#include "helpers.h" -#include "mainwindow.h" -#include "divelistview.h" -#include "display.h" -#include "uemis.h" -#include "models.h" +#include "desktop-widgets/downloadfromdivecomputer.h" +#include "core/helpers.h" +#include "desktop-widgets/mainwindow.h" +#include "desktop-widgets/divelistview.h" +#include "core/display.h" +#include "core/uemis.h" +#include "qt-models/models.h" #include #include diff --git a/desktop-widgets/downloadfromdivecomputer.h b/desktop-widgets/downloadfromdivecomputer.h index 7acd49e95..33c3ef0a8 100644 --- a/desktop-widgets/downloadfromdivecomputer.h +++ b/desktop-widgets/downloadfromdivecomputer.h @@ -7,8 +7,8 @@ #include #include -#include "libdivecomputer.h" -#include "configuredivecomputerdialog.h" +#include "core/libdivecomputer.h" +#include "desktop-widgets/configuredivecomputerdialog.h" #include "ui_downloadfromdivecomputer.h" #if defined(BT_SUPPORT) diff --git a/desktop-widgets/globe.cpp b/desktop-widgets/globe.cpp index 135f195a1..39f030b74 100644 --- a/desktop-widgets/globe.cpp +++ b/desktop-widgets/globe.cpp @@ -1,10 +1,10 @@ -#include "globe.h" +#include "desktop-widgets/globe.h" #ifndef NO_MARBLE -#include "mainwindow.h" -#include "helpers.h" -#include "divelistview.h" -#include "maintab.h" -#include "display.h" +#include "desktop-widgets/mainwindow.h" +#include "core/helpers.h" +#include "desktop-widgets/divelistview.h" +#include "desktop-widgets/maintab.h" +#include "core/display.h" #include #include diff --git a/desktop-widgets/locationInformation.ui b/desktop-widgets/locationInformation.ui index 58d065648..923e17f24 100644 --- a/desktop-widgets/locationInformation.ui +++ b/desktop-widgets/locationInformation.ui @@ -145,7 +145,7 @@ KMessageWidget QFrame -
kmessagewidget.h
+
desktop-widgets/kmessagewidget.h
1
diff --git a/desktop-widgets/locationinformation.cpp b/desktop-widgets/locationinformation.cpp index 5ca858a6b..20cc6fe1d 100644 --- a/desktop-widgets/locationinformation.cpp +++ b/desktop-widgets/locationinformation.cpp @@ -1,13 +1,13 @@ -#include "locationinformation.h" -#include "dive.h" -#include "mainwindow.h" -#include "divelistview.h" -#include "qthelper.h" -#include "globe.h" -#include "filtermodels.h" -#include "divelocationmodel.h" -#include "divesitehelpers.h" -#include "modeldelegates.h" +#include "desktop-widgets/locationinformation.h" +#include "core/dive.h" +#include "desktop-widgets/mainwindow.h" +#include "desktop-widgets/divelistview.h" +#include "core/qthelper.h" +#include "desktop-widgets/globe.h" +#include "qt-models/filtermodels.h" +#include "qt-models/divelocationmodel.h" +#include "core/divesitehelpers.h" +#include "desktop-widgets/modeldelegates.h" #include #include diff --git a/desktop-widgets/maintab.cpp b/desktop-widgets/maintab.cpp index eddf90b98..180814c9c 100644 --- a/desktop-widgets/maintab.cpp +++ b/desktop-widgets/maintab.cpp @@ -4,25 +4,25 @@ * classes for the "notebook" area of the main window of Subsurface * */ -#include "maintab.h" -#include "mainwindow.h" -#include "globe.h" -#include "helpers.h" -#include "statistics.h" -#include "modeldelegates.h" -#include "diveplannermodel.h" -#include "divelistview.h" -#include "display.h" +#include "desktop-widgets/maintab.h" +#include "desktop-widgets/mainwindow.h" +#include "desktop-widgets/globe.h" +#include "core/helpers.h" +#include "core/statistics.h" +#include "desktop-widgets/modeldelegates.h" +#include "qt-models/diveplannermodel.h" +#include "desktop-widgets/divelistview.h" +#include "core/display.h" #include "profile-widget/profilewidget2.h" -#include "diveplanner.h" -#include "divesitehelpers.h" -#include "cylindermodel.h" -#include "weightmodel.h" -#include "divepicturemodel.h" -#include "divecomputerextradatamodel.h" -#include "divelocationmodel.h" -#include "divesite.h" -#include "locationinformation.h" +#include "desktop-widgets/diveplanner.h" +#include "core/divesitehelpers.h" +#include "qt-models/cylindermodel.h" +#include "qt-models/weightmodel.h" +#include "qt-models/divepicturemodel.h" +#include "qt-models/divecomputerextradatamodel.h" +#include "qt-models/divelocationmodel.h" +#include "core/divesite.h" +#include "desktop-widgets/locationinformation.h" #include #include diff --git a/desktop-widgets/maintab.h b/desktop-widgets/maintab.h index d4f7aaaa9..50a7d6d21 100644 --- a/desktop-widgets/maintab.h +++ b/desktop-widgets/maintab.h @@ -13,9 +13,9 @@ #include #include "ui_maintab.h" -#include "completionmodels.h" -#include "divelocationmodel.h" -#include "dive.h" +#include "qt-models/completionmodels.h" +#include "qt-models/divelocationmodel.h" +#include "core/dive.h" class WeightModel; class CylindersModel; diff --git a/desktop-widgets/maintab.ui b/desktop-widgets/maintab.ui index 7bc516b1a..964593b38 100644 --- a/desktop-widgets/maintab.ui +++ b/desktop-widgets/maintab.ui @@ -1205,47 +1205,47 @@ KMessageWidget QFrame -
kmessagewidget.h
+
desktop-widgets/kmessagewidget.h
1
StarWidget QWidget -
starwidget.h
+
desktop-widgets/starwidget.h
1
MinMaxAvgWidget QWidget -
simplewidgets.h
+
desktop-widgets/simplewidgets.h
1
TableView QWidget -
tableview.h
+
desktop-widgets/desktop-widget/tableview.h
1
TagWidget QPlainTextEdit -
tagwidget.h
+
desktop-widgets/tagwidget.h
DivePictureWidget QListView -
divepicturewidget.h
+
desktop-widgets/divepicturewidget.h
QtWaitingSpinner QWidget -
qtwaitingspinner.h
+
desktop-widgets/qtwaitingspinner.h
1
DiveLocationLineEdit QLineEdit -
locationinformation.h
+
desktop-widgets/locationinformation.h
diff --git a/desktop-widgets/mainwindow.cpp b/desktop-widgets/mainwindow.cpp index ca78b82af..c6f487486 100644 --- a/desktop-widgets/mainwindow.cpp +++ b/desktop-widgets/mainwindow.cpp @@ -12,50 +12,50 @@ #include #include -#include "version.h" -#include "divelistview.h" -#include "downloadfromdivecomputer.h" -#include "subsurfacewebservices.h" -#include "divecomputermanagementdialog.h" -#include "about.h" -#include "updatemanager.h" -#include "planner.h" -#include "filtermodels.h" +#include "core/version.h" +#include "desktop-widgets/divelistview.h" +#include "desktop-widgets/downloadfromdivecomputer.h" +#include "desktop-widgets/subsurfacewebservices.h" +#include "desktop-widgets/divecomputermanagementdialog.h" +#include "desktop-widgets/about.h" +#include "desktop-widgets/updatemanager.h" +#include "core/planner.h" +#include "qt-models/filtermodels.h" #include "profile-widget/profilewidget2.h" -#include "globe.h" -#include "divecomputer.h" -#include "maintab.h" -#include "diveplanner.h" +#include "desktop-widgets/globe.h" +#include "core/divecomputer.h" +#include "desktop-widgets/maintab.h" +#include "desktop-widgets/diveplanner.h" #ifndef NO_PRINTING #include #include -#include "printdialog.h" +#include "desktop-widgets/printdialog.h" #endif -#include "tankinfomodel.h" -#include "weigthsysteminfomodel.h" -#include "yearlystatisticsmodel.h" -#include "diveplannermodel.h" -#include "divelogimportdialog.h" -#include "divelogexportdialog.h" -#include "usersurvey.h" -#include "divesitehelpers.h" -#include "windowtitleupdate.h" -#include "locationinformation.h" +#include "qt-models/tankinfomodel.h" +#include "qt-models/weigthsysteminfomodel.h" +#include "qt-models/yearlystatisticsmodel.h" +#include "qt-models/diveplannermodel.h" +#include "desktop-widgets/divelogimportdialog.h" +#include "desktop-widgets/divelogexportdialog.h" +#include "desktop-widgets/usersurvey.h" +#include "core/divesitehelpers.h" +#include "core/windowtitleupdate.h" +#include "desktop-widgets/locationinformation.h" #include "preferences/preferencesdialog.h" #ifndef NO_USERMANUAL #include "usermanual.h" #endif -#include "divepicturemodel.h" -#include "git-access.h" +#include "qt-models/divepicturemodel.h" +#include "core/git-access.h" #include #include -#include +#include "core/qthelper.h" #include -#include "subsurface-core/color.h" -#include "subsurface-core/isocialnetworkintegration.h" -#include "subsurface-core/pluginmanager.h" -#include +#include "core/color.h" +#include "core/isocialnetworkintegration.h" +#include "core/pluginmanager.h" +#include "core/subsurface-qt/SettingsObjectWrapper.h" #if defined(FBSUPPORT) #include "plugins/facebook/facebook_integration.h" diff --git a/desktop-widgets/mainwindow.h b/desktop-widgets/mainwindow.h index 7e7f5db1e..0a53405e8 100644 --- a/desktop-widgets/mainwindow.h +++ b/desktop-widgets/mainwindow.h @@ -14,9 +14,9 @@ #include #include "ui_mainwindow.h" -#include "notificationwidget.h" -#include "windowtitleupdate.h" -#include "gpslocation.h" +#include "desktop-widgets/notificationwidget.h" +#include "core/windowtitleupdate.h" +#include "core/gpslocation.h" struct DiveList; class QSortFilterProxyModel; diff --git a/desktop-widgets/mainwindow.ui b/desktop-widgets/mainwindow.ui index 724c83657..85f034692 100644 --- a/desktop-widgets/mainwindow.ui +++ b/desktop-widgets/mainwindow.ui @@ -750,13 +750,13 @@ NotificationWidget QWidget -
notificationwidget.h
+
desktop-widgets/notificationwidget.h
1
MultiFilter QWidget -
simplewidgets.h
+
desktop-widgets/simplewidgets.h
1
diff --git a/desktop-widgets/modeldelegates.cpp b/desktop-widgets/modeldelegates.cpp index 377a1baed..a80137e80 100644 --- a/desktop-widgets/modeldelegates.cpp +++ b/desktop-widgets/modeldelegates.cpp @@ -1,18 +1,18 @@ -#include "modeldelegates.h" -#include "dive.h" -#include "gettextfromc.h" -#include "mainwindow.h" -#include "cylindermodel.h" -#include "models.h" -#include "starwidget.h" +#include "desktop-widgets/modeldelegates.h" +#include "core/dive.h" +#include "core/gettextfromc.h" +#include "desktop-widgets/mainwindow.h" +#include "qt-models/cylindermodel.h" +#include "qt-models/models.h" +#include "desktop-widgets/starwidget.h" #include "profile-widget/profilewidget2.h" -#include "tankinfomodel.h" -#include "weigthsysteminfomodel.h" -#include "weightmodel.h" -#include "divetripmodel.h" -#include "qthelper.h" +#include "qt-models/tankinfomodel.h" +#include "qt-models/weigthsysteminfomodel.h" +#include "qt-models/weightmodel.h" +#include "qt-models/divetripmodel.h" +#include "core/qthelper.h" #ifndef NO_MARBLE -#include "globe.h" +#include "desktop-widgets/globe.h" #endif #include diff --git a/desktop-widgets/notificationwidget.h b/desktop-widgets/notificationwidget.h index 8a551a0b3..388982191 100644 --- a/desktop-widgets/notificationwidget.h +++ b/desktop-widgets/notificationwidget.h @@ -4,7 +4,7 @@ #include #include -#include +#include "desktop-widgets/kmessagewidget.h" namespace Ui { class NotificationWidget; diff --git a/desktop-widgets/plugins/facebook/facebook_integration.h b/desktop-widgets/plugins/facebook/facebook_integration.h index 40b16917d..ce4793cad 100644 --- a/desktop-widgets/plugins/facebook/facebook_integration.h +++ b/desktop-widgets/plugins/facebook/facebook_integration.h @@ -1,7 +1,7 @@ #ifndef FACEBOOK_INTEGRATION_H #define FACEBOOK_INTEGRATION_H -#include "subsurface-core/isocialnetworkintegration.h" +#include "core/isocialnetworkintegration.h" #include class FacebookConnectWidget; @@ -23,4 +23,4 @@ private: SocialNetworkDialog *fbUploadDialog; }; -#endif \ No newline at end of file +#endif diff --git a/desktop-widgets/preferences/preferences_defaults.cpp b/desktop-widgets/preferences/preferences_defaults.cpp index 45888ebec..62bb3bcb2 100644 --- a/desktop-widgets/preferences/preferences_defaults.cpp +++ b/desktop-widgets/preferences/preferences_defaults.cpp @@ -1,7 +1,7 @@ #include "preferences_defaults.h" #include "ui_preferences_defaults.h" -#include "dive.h" -#include "subsurface-core/prefs-macros.h" +#include "core/dive.h" +#include "core/prefs-macros.h" #include #include diff --git a/desktop-widgets/preferences/preferences_defaults.h b/desktop-widgets/preferences/preferences_defaults.h index c72be31a6..eb588c097 100644 --- a/desktop-widgets/preferences/preferences_defaults.h +++ b/desktop-widgets/preferences/preferences_defaults.h @@ -2,7 +2,7 @@ #define PREFERENCES_DEFAULTS_H #include "abstractpreferenceswidget.h" -#include "subsurface-core/pref.h" +#include "core/pref.h" namespace Ui { class PreferencesDefaults; @@ -25,4 +25,4 @@ private: }; -#endif \ No newline at end of file +#endif diff --git a/desktop-widgets/preferences/preferences_georeference.cpp b/desktop-widgets/preferences/preferences_georeference.cpp index 7e8ccec9d..8446d0e9a 100644 --- a/desktop-widgets/preferences/preferences_georeference.cpp +++ b/desktop-widgets/preferences/preferences_georeference.cpp @@ -1,7 +1,7 @@ #include "preferences_georeference.h" #include "ui_prefs_georeference.h" -#include "prefs-macros.h" -#include "qthelper.h" +#include "core/prefs-macros.h" +#include "core/qthelper.h" #include "qt-models/divelocationmodel.h" #include @@ -42,4 +42,4 @@ void PreferencesGeoreference::syncSettings() s.setValue("cat1", ui->second_item->currentIndex()); s.setValue("cat2", ui->third_item->currentIndex()); s.endGroup(); -} \ No newline at end of file +} diff --git a/desktop-widgets/preferences/preferences_graph.cpp b/desktop-widgets/preferences/preferences_graph.cpp index 327557692..3d8d8f685 100644 --- a/desktop-widgets/preferences/preferences_graph.cpp +++ b/desktop-widgets/preferences/preferences_graph.cpp @@ -1,6 +1,6 @@ #include "preferences_graph.h" #include "ui_preferences_graph.h" -#include "subsurface-core/prefs-macros.h" +#include "core/prefs-macros.h" #include #include @@ -74,4 +74,4 @@ void PreferencesGraph::on_gfhigh_valueChanged(int gf) { ui->gfhigh->setStyleSheet(DANGER_GF); } -#undef DANGER_GF \ No newline at end of file +#undef DANGER_GF diff --git a/desktop-widgets/preferences/preferences_language.cpp b/desktop-widgets/preferences/preferences_language.cpp index 31bbd1c20..264e2a1a5 100644 --- a/desktop-widgets/preferences/preferences_language.cpp +++ b/desktop-widgets/preferences/preferences_language.cpp @@ -1,6 +1,6 @@ #include "preferences_language.h" #include "ui_prefs_language.h" -#include "subsurface-core/helpers.h" +#include "core/helpers.h" #include #include diff --git a/desktop-widgets/preferences/preferences_network.cpp b/desktop-widgets/preferences/preferences_network.cpp index 818a16f1b..7d89d4496 100644 --- a/desktop-widgets/preferences/preferences_network.cpp +++ b/desktop-widgets/preferences/preferences_network.cpp @@ -1,9 +1,9 @@ #include "preferences_network.h" #include "ui_preferences_network.h" -#include "dive.h" +#include "core/dive.h" #include "subsurfacewebservices.h" -#include "subsurface-core/prefs-macros.h" -#include "subsurface-core/cloudstorage.h" +#include "core/prefs-macros.h" +#include "core/cloudstorage.h" #include #include diff --git a/desktop-widgets/preferences/preferences_units.cpp b/desktop-widgets/preferences/preferences_units.cpp index cc77e51bb..8fb67a813 100644 --- a/desktop-widgets/preferences/preferences_units.cpp +++ b/desktop-widgets/preferences/preferences_units.cpp @@ -1,7 +1,7 @@ #include "preferences_units.h" #include "ui_preferences_units.h" -#include "prefs-macros.h" -#include "qthelper.h" +#include "core/prefs-macros.h" +#include "core/qthelper.h" #include diff --git a/desktop-widgets/preferences/preferencesdialog.cpp b/desktop-widgets/preferences/preferencesdialog.cpp index 34df09b27..01d0aace4 100644 --- a/desktop-widgets/preferences/preferencesdialog.cpp +++ b/desktop-widgets/preferences/preferencesdialog.cpp @@ -8,7 +8,7 @@ #include "preferences_graph.h" #include "preferences_network.h" -#include "subsurface-core/qthelper.h" +#include "core/qthelper.h" #include #include diff --git a/desktop-widgets/preferences/preferencesdialog.h b/desktop-widgets/preferences/preferencesdialog.h index 5f7f5f979..9210dddb0 100644 --- a/desktop-widgets/preferences/preferencesdialog.h +++ b/desktop-widgets/preferences/preferencesdialog.h @@ -2,7 +2,7 @@ #define PREFERENCES_WIDGET_H #include -#include "pref.h" +#include "core/pref.h" class AbstractPreferencesWidget; class QListWidget; diff --git a/desktop-widgets/simplewidgets.cpp b/desktop-widgets/simplewidgets.cpp index 6c9ec3f96..cbf31cb98 100644 --- a/desktop-widgets/simplewidgets.cpp +++ b/desktop-widgets/simplewidgets.cpp @@ -1,5 +1,5 @@ -#include "simplewidgets.h" -#include "filtermodels.h" +#include "desktop-widgets/simplewidgets.h" +#include "qt-models/filtermodels.h" #include #include @@ -10,14 +10,14 @@ #include #include -#include "file.h" -#include "mainwindow.h" -#include "helpers.h" +#include "core/file.h" +#include "desktop-widgets/mainwindow.h" +#include "core/helpers.h" #include "libdivecomputer/parser.h" -#include "divelistview.h" -#include "display.h" +#include "desktop-widgets/divelistview.h" +#include "core/display.h" #include "profile-widget/profilewidget2.h" -#include "undocommands.h" +#include "desktop-widgets/undocommands.h" class MinMaxAvgWidgetPrivate { public: diff --git a/desktop-widgets/simplewidgets.h b/desktop-widgets/simplewidgets.h index 8a7a5df6a..3c384ba89 100644 --- a/desktop-widgets/simplewidgets.h +++ b/desktop-widgets/simplewidgets.h @@ -18,8 +18,8 @@ class QNetworkReply; #include "ui_divecomponentselection.h" #include "ui_listfilter.h" #include "ui_filterwidget.h" -#include "exif.h" -#include +#include "core/exif.h" +#include "core/dive.h" class MinMaxAvgWidget : public QWidget { diff --git a/desktop-widgets/starwidget.cpp b/desktop-widgets/starwidget.cpp index a810883c2..67e22ddb7 100644 --- a/desktop-widgets/starwidget.cpp +++ b/desktop-widgets/starwidget.cpp @@ -1,8 +1,8 @@ -#include "starwidget.h" -#include "metrics.h" +#include "desktop-widgets/starwidget.h" +#include "core/metrics.h" #include #include -#include "simplewidgets.h" +#include "desktop-widgets/simplewidgets.h" QImage StarWidget::activeStar; QImage StarWidget::inactiveStar; diff --git a/desktop-widgets/statistics/statisticswidget.cpp b/desktop-widgets/statistics/statisticswidget.cpp index d06f51a98..50d5f3149 100644 --- a/desktop-widgets/statistics/statisticswidget.cpp +++ b/desktop-widgets/statistics/statisticswidget.cpp @@ -1,5 +1,5 @@ -#include "statisticswidget.h" -#include "yearlystatisticsmodel.h" +#include "desktop-widgets/statistics/statisticswidget.h" +#include "qt-models/yearlystatisticsmodel.h" #include YearlyStatisticsWidget::YearlyStatisticsWidget(QWidget *parent): diff --git a/desktop-widgets/subsurfacewebservices.cpp b/desktop-widgets/subsurfacewebservices.cpp index 2ca3de187..bd798c729 100644 --- a/desktop-widgets/subsurfacewebservices.cpp +++ b/desktop-widgets/subsurfacewebservices.cpp @@ -1,15 +1,15 @@ -#include "subsurfacewebservices.h" -#include "helpers.h" -#include "webservice.h" -#include "mainwindow.h" -#include "usersurvey.h" -#include "divelist.h" -#include "globe.h" -#include "maintab.h" -#include "display.h" -#include "membuffer.h" +#include "desktop-widgets/subsurfacewebservices.h" +#include "core/helpers.h" +#include "core/webservice.h" +#include "desktop-widgets/mainwindow.h" +#include "desktop-widgets/usersurvey.h" +#include "core/divelist.h" +#include "desktop-widgets/globe.h" +#include "desktop-widgets/maintab.h" +#include "core/display.h" +#include "core/membuffer.h" #include -#include "subsurface-core/cloudstorage.h" +#include "core/cloudstorage.h" #include #include diff --git a/desktop-widgets/tableview.cpp b/desktop-widgets/tableview.cpp index 40d5199ec..20b62ac3c 100644 --- a/desktop-widgets/tableview.cpp +++ b/desktop-widgets/tableview.cpp @@ -1,5 +1,5 @@ -#include "tableview.h" -#include "modeldelegates.h" +#include "desktop-widgets/tableview.h" +#include "desktop-widgets/modeldelegates.h" #include #include diff --git a/desktop-widgets/tableview.h b/desktop-widgets/tableview.h index f72b256ea..c3afdaf12 100644 --- a/desktop-widgets/tableview.h +++ b/desktop-widgets/tableview.h @@ -9,7 +9,7 @@ #include "ui_tableview.h" -#include "metrics.h" +#include "core/metrics.h" class QPushButton; class QAbstractItemModel; diff --git a/desktop-widgets/templatelayout.h b/desktop-widgets/templatelayout.h index 9e420f381..fc77a5079 100644 --- a/desktop-widgets/templatelayout.h +++ b/desktop-widgets/templatelayout.h @@ -7,7 +7,7 @@ #include "statistics.h" #include "qthelper.h" #include "helpers.h" -#include "subsurface-core/subsurface-qt/DiveObjectHelper.h" +#include "core/subsurface-qt/DiveObjectHelper.h" int getTotalWork(print_options *printOptions); void find_all_templates(); diff --git a/desktop-widgets/undocommands.cpp b/desktop-widgets/undocommands.cpp index 0fd182cb3..75fc810c6 100644 --- a/desktop-widgets/undocommands.cpp +++ b/desktop-widgets/undocommands.cpp @@ -1,6 +1,6 @@ -#include "undocommands.h" -#include "mainwindow.h" -#include "divelist.h" +#include "desktop-widgets/undocommands.h" +#include "desktop-widgets/mainwindow.h" +#include "core/divelist.h" UndoDeleteDive::UndoDeleteDive(QList deletedDives) : diveList(deletedDives) { diff --git a/desktop-widgets/undocommands.h b/desktop-widgets/undocommands.h index 8e359db51..cca87ac2d 100644 --- a/desktop-widgets/undocommands.h +++ b/desktop-widgets/undocommands.h @@ -3,7 +3,7 @@ #include #include -#include "dive.h" +#include "core/dive.h" class UndoDeleteDive : public QUndoCommand { public: diff --git a/desktop-widgets/updatemanager.cpp b/desktop-widgets/updatemanager.cpp index daa65e007..b09d12439 100644 --- a/desktop-widgets/updatemanager.cpp +++ b/desktop-widgets/updatemanager.cpp @@ -1,13 +1,13 @@ -#include "updatemanager.h" -#include "helpers.h" -#include "qthelper.h" +#include "desktop-widgets/updatemanager.h" +#include "core/helpers.h" +#include "core/qthelper.h" #include #include #include -#include "subsurfacewebservices.h" -#include "version.h" -#include "mainwindow.h" -#include "subsurface-core/cloudstorage.h" +#include "desktop-widgets/subsurfacewebservices.h" +#include "core/version.h" +#include "desktop-widgets/mainwindow.h" +#include "core/cloudstorage.h" UpdateManager::UpdateManager(QObject *parent) : QObject(parent), diff --git a/desktop-widgets/usermanual.cpp b/desktop-widgets/usermanual.cpp index 6b676f16b..690307961 100644 --- a/desktop-widgets/usermanual.cpp +++ b/desktop-widgets/usermanual.cpp @@ -2,9 +2,9 @@ #include #include -#include "usermanual.h" -#include "mainwindow.h" -#include "helpers.h" +#include "desktop-widgets/usermanual.h" +#include "desktop-widgets/mainwindow.h" +#include "core/helpers.h" SearchBar::SearchBar(QWidget *parent): QWidget(parent) { diff --git a/desktop-widgets/usersurvey.cpp b/desktop-widgets/usersurvey.cpp index 6dba30d8b..c60ff8c83 100644 --- a/desktop-widgets/usersurvey.cpp +++ b/desktop-widgets/usersurvey.cpp @@ -2,14 +2,14 @@ #include #include -#include "usersurvey.h" +#include "desktop-widgets/usersurvey.h" #include "ui_usersurvey.h" -#include "version.h" -#include "subsurfacewebservices.h" -#include "updatemanager.h" +#include "core/version.h" +#include "desktop-widgets/subsurfacewebservices.h" +#include "desktop-widgets/updatemanager.h" -#include "helpers.h" -#include "subsurfacesysinfo.h" +#include "core/helpers.h" +#include "core/subsurfacesysinfo.h" UserSurvey::UserSurvey(QWidget *parent) : QDialog(parent), ui(new Ui::UserSurvey) diff --git a/mobile-widgets/qml/About.qml b/mobile-widgets/qml/About.qml new file mode 100644 index 000000000..b1ca6e6bc --- /dev/null +++ b/mobile-widgets/qml/About.qml @@ -0,0 +1,59 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.1 +import org.kde.kirigami 1.0 as Kirigami +import org.subsurfacedivelog.mobile 1.0 + +Kirigami.ScrollablePage { + id: aboutPage + property int pageWidth: subsurfaceTheme.columnWidth - Kirigami.Units.smallSpacing + title: "About Subsurface-mobile" + + ColumnLayout { + spacing: Kirigami.Units.largeSpacing + width: aboutPage.width + Layout.margins: Kirigami.Units.gridUnit / 2 + + + Kirigami.Heading { + text: "About Subsurface-mobile" + Layout.topMargin: Kirigami.Units.gridUnit + Layout.alignment: Qt.AlignHCenter + Layout.maximumWidth: pageWidth + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + } + Image { + id: image + source: "qrc:/qml/subsurface-mobile-icon.png" + width: pageWidth / 2 + height: width + fillMode: Image.Stretch + Layout.alignment: Qt.AlignCenter + horizontalAlignment: Image.AlignHCenter + } + + Kirigami.Heading { + text: "A mobile version of the free Subsurface divelog software.\n" + + "View your dive logs while on the go." + level: 4 + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Kirigami.Units.largeSpacing * 3 + Layout.maximumWidth: pageWidth + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + anchors.horizontalCenter: parent.Center + horizontalAlignment: Text.AlignHCenter + } + + Kirigami.Heading { + text: "Version: " + manager.getVersion() + "\n\n© Subsurface developer team\n2011-2016" + level: 5 + font.pointSize: subsurfaceTheme.smallPointSize + 1 + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Kirigami.Units.largeSpacing + Layout.maximumWidth: pageWidth + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + anchors.horizontalCenter: parent.Center + horizontalAlignment: Text.AlignHCenter + } + } +} diff --git a/mobile-widgets/qml/CloudCredentials.qml b/mobile-widgets/qml/CloudCredentials.qml new file mode 100644 index 000000000..aa7c57651 --- /dev/null +++ b/mobile-widgets/qml/CloudCredentials.qml @@ -0,0 +1,84 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import org.kde.kirigami 1.0 as Kirigami +import org.subsurfacedivelog.mobile 1.0 + +Item { + id: loginWindow + height: outerLayout.height + 2 * Kirigami.Units.gridUnit + + property string username: login.text; + property string password: password.text; + + function saveCredentials() { + manager.cloudUserName = login.text + manager.cloudPassword = password.text + manager.saveCloudCredentials() + } + + ColumnLayout { + id: outerLayout + width: subsurfaceTheme.columnWidth - 2 * Kirigami.Units.gridUnit + + onVisibleChanged: { + if (visible && manager.accessingCloud < 0) { + manager.appendTextToLog("Credential scrn: show kbd was: " + (Qt.inputMethod.isVisible ? "visible" : "invisible")) + Qt.inputMethod.show() + login.forceActiveFocus() + } else { + manager.appendTextToLog("Credential scrn: hide kbd was: " + (Qt.inputMethod.isVisible ? "visible" : "invisible")) + Qt.inputMethod.hide() + } + } + + Kirigami.Heading { + text: "Cloud credentials" + level: headingLevel + Layout.bottomMargin: Kirigami.Units.largeSpacing / 2 + } + + Kirigami.Label { + text: "Email" + } + + TextField { + id: login + text: manager.cloudUserName + Layout.fillWidth: true + inputMethodHints: Qt.ImhEmailCharactersOnly | + Qt.ImhNoAutoUppercase + } + + Kirigami.Label { + text: "Password" + } + + TextField { + id: password + text: manager.cloudPassword + echoMode: TextInput.Password + inputMethodHints: Qt.ImhSensitiveData | + Qt.ImhHiddenText | + Qt.ImhNoAutoUppercase + Layout.fillWidth: true + } + GridLayout { + columns: 2 + + CheckBox { + checked: false + id: showPassword + onCheckedChanged: { + password.echoMode = checked ? TextInput.Normal : TextInput.Password + } + } + Kirigami.Label { + text: "Show password" + } + } + Item { width: Kirigami.Units.gridUnit; height: width } + } +} diff --git a/mobile-widgets/qml/DiveDetails.qml b/mobile-widgets/qml/DiveDetails.qml new file mode 100644 index 000000000..108833470 --- /dev/null +++ b/mobile-widgets/qml/DiveDetails.qml @@ -0,0 +1,216 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 +import org.subsurfacedivelog.mobile 1.0 +import org.kde.kirigami 1.0 as Kirigami + +Kirigami.Page { + id: diveDetailsPage + property alias currentIndex: diveDetailsListView.currentIndex + property alias dive_id: detailsEdit.dive_id + property alias number: detailsEdit.number + property alias date: detailsEdit.dateText + property alias airtemp: detailsEdit.airtempText + property alias watertemp: detailsEdit.watertempText + property alias buddy: detailsEdit.buddyText + property alias divemaster: detailsEdit.divemasterText + property alias depth: detailsEdit.depthText + property alias duration: detailsEdit.durationText + property alias location: detailsEdit.locationText + property alias notes: detailsEdit.notesText + property alias suit: detailsEdit.suitText + property alias weight: detailsEdit.weightText + property alias startpressure: detailsEdit.startpressureText + property alias endpressure: detailsEdit.endpressureText + property alias gasmix: detailsEdit.gasmixText + + topPadding: applicationWindow().header.Layout.preferredHeight + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + + title: diveDetailsListView.currentItem.modelData.dive.location + state: "view" + + states: [ + State { + name: "view" + PropertyChanges { target: diveDetailsPage; contextualActions: Qt.platform.os == "ios" ? [ deleteAction, backAction ] : [ deleteAction ] } + PropertyChanges { target: detailsEditScroll; opened: false } + }, + State { + name: "edit" + PropertyChanges { target: diveDetailsPage; contextualActions: Qt.platform.os == "ios" ? [ cancelAction ] : null } + PropertyChanges { target: detailsEditScroll; opened: true } + }, + State { + name: "add" + PropertyChanges { target: diveDetailsPage; contextualActions: Qt.platform.os == "ios" ? [ cancelAction ] : null } + PropertyChanges { target: detailsEditScroll; opened: true } + } + + ] + + property QtObject deleteAction: Action { + text: "Delete dive" + iconName: "trash-empty" + onTriggered: { + contextDrawer.close() + var deletedId = diveDetailsListView.currentItem.modelData.dive.id + manager.deleteDive(deletedId) + stackView.pop() + showPassiveNotification("Dive deleted", 3000, "Undo", + function() { + manager.undoDelete(deletedId) + }); + } + } + + property QtObject cancelAction: Kirigami.Action { + text: state === "edit" ? "Cancel edit" : "Cancel dive add" + iconName: "dialog-cancel" + onTriggered: { + contextDrawer.close() + if (state === "add") + returnTopPage() + else + endEditMode() + } + } + + property QtObject backAction: Action { + text: "Back to dive list" + iconName: "go-previous" + onTriggered: { + contextDrawer.close() + returnTopPage() + } + } + + mainAction: Action { + iconName: state !== "view" ? "document-save" : "document-edit" + onTriggered: { + if (state === "edit" || state === "add") { + detailsEdit.saveData() + } else { + startEditMode() + } + } + } + + onBackRequested: { + if (state === "edit") { + endEditMode() + event.accepted = true; + } else if (state === "add") { + endEditMode() + stackView.pop() + event.accepted = true; + } + // if we were in view mode, don't accept the event and pop the page + } + + function showDiveIndex(index) { + currentIndex = index; + diveDetailsListView.positionViewAtIndex(index, ListView.Beginning); + } + + function endEditMode() { + // if we were adding a dive, we need to remove it + if (state === "add") + manager.addDiveAborted(dive_id) + // just cancel the edit/add state + state = "view"; + Qt.inputMethod.hide(); + } + + function startEditMode() { + // set things up for editing - so make sure that the detailsEdit has + // all the right data (using the property aliases set up above) + dive_id = diveDetailsListView.currentItem.modelData.dive.id + number = diveDetailsListView.currentItem.modelData.dive.number + date = diveDetailsListView.currentItem.modelData.dive.date + " " + diveDetailsListView.currentItem.modelData.dive.time + location = diveDetailsListView.currentItem.modelData.dive.location + duration = diveDetailsListView.currentItem.modelData.dive.duration + depth = diveDetailsListView.currentItem.modelData.dive.depth + airtemp = diveDetailsListView.currentItem.modelData.dive.airTemp + watertemp = diveDetailsListView.currentItem.modelData.dive.waterTemp + suit = diveDetailsListView.currentItem.modelData.dive.suit + buddy = diveDetailsListView.currentItem.modelData.dive.buddy + divemaster = diveDetailsListView.currentItem.modelData.dive.divemaster + notes = diveDetailsListView.currentItem.modelData.dive.notes + if (diveDetailsListView.currentItem.modelData.dive.singleWeight) { + // we have only one weight, go ahead, have fun and edit it + weight = diveDetailsListView.currentItem.modelData.dive.sumWeight + } else { + // careful when translating, this text is "magic" in DiveDetailsEdit.qml + weight = "cannot edit multiple weight systems" + } + if (diveDetailsListView.currentItem.modelData.dive.getCylinder != "Multiple" ) { + startpressure = diveDetailsListView.currentItem.modelData.dive.startPressure + endpressure = diveDetailsListView.currentItem.modelData.dive.endPressure + gasmix = diveDetailsListView.currentItem.modelData.dive.firstGas + } else { + // careful when translating, this text is "magic" in DiveDetailsEdit.qml + startpressure = "cannot edit multiple cylinders" + endpressure = "cannot edit multiple cylinders" + gasmix = "cannot edit multiple gases" + } + + diveDetailsPage.state = "edit" + } + + onWidthChanged: diveDetailsListView.positionViewAtIndex(diveDetailsListView.currentIndex, ListView.Beginning); + + Item { + anchors.fill: parent + ScrollView { + id: diveDetailList + anchors.fill: parent + ListView { + id: diveDetailsListView + anchors.fill: parent + model: diveModel + currentIndex: -1 + boundsBehavior: Flickable.StopAtBounds + maximumFlickVelocity: parent.width * 5 + orientation: ListView.Horizontal + focus: true + clip: true + snapMode: ListView.SnapOneItem + onMovementEnded: { + currentIndex = indexAt(contentX+1, 1); + } + delegate: ScrollView { + id: internalScrollView + width: diveDetailsListView.width + height: diveDetailsListView.height + property var modelData: model + Flickable { + //contentWidth: parent.width + contentHeight: diveDetails.height + boundsBehavior: Flickable.StopAtBounds + DiveDetailsView { + id: diveDetails + width: internalScrollView.width + } + } + } + } + } + Kirigami.OverlaySheet { + id: detailsEditScroll + anchors.fill: parent + onOpenedChanged: { + if (!opened) { + endEditMode() + } + } + DiveDetailsEdit { + id: detailsEdit + } + } + } +} diff --git a/mobile-widgets/qml/DiveDetailsEdit.qml b/mobile-widgets/qml/DiveDetailsEdit.qml new file mode 100644 index 000000000..e4338b3b8 --- /dev/null +++ b/mobile-widgets/qml/DiveDetailsEdit.qml @@ -0,0 +1,236 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import org.subsurfacedivelog.mobile 1.0 +import org.kde.kirigami 1.0 as Kirigami + +Item { + id: detailsEdit + property int dive_id + property int number + property alias dateText: txtDate.text + property alias locationText: txtLocation.text + property string gpsText + property alias airtempText: txtAirTemp.text + property alias watertempText: txtWaterTemp.text + property alias suitText: txtSuit.text + property alias buddyText: txtBuddy.text + property alias divemasterText: txtDiveMaster.text + property alias notesText: txtNotes.text + property alias durationText: txtDuration.text + property alias depthText: txtDepth.text + property alias weightText: txtWeight.text + property alias startpressureText: txtStartPressure.text + property alias endpressureText: txtEndPressure.text + property alias gasmixText: txtGasMix.text + + function saveData() { + // apply the changes to the dive_table + manager.commitChanges(dive_id, detailsEdit.dateText, detailsEdit.locationText, detailsEdit.gpsText, detailsEdit.durationText, + detailsEdit.depthText, detailsEdit.airtempText, detailsEdit.watertempText, detailsEdit.suitText, + detailsEdit.buddyText, detailsEdit.divemasterText, detailsEdit.weightText, detailsEdit.notesText, + detailsEdit.startpressureText, detailsEdit.endpressureText, detailsEdit.gasmixText) + // trigger the profile to be redrawn + QMLProfile.diveId = dive_id + + // apply the changes to the dive detail view - since the edit could have changed the order + // first make sure that we are looking at the correct dive - our model allows us to look + // up the index based on the unique dive_id + var newIdx = diveModel.getIdxForId(dive_id) + diveDetailsListView.currentIndex = newIdx + diveDetailsListView.currentItem.modelData.date = detailsEdit.dateText + diveDetailsListView.currentItem.modelData.location = detailsEdit.locationText + diveDetailsListView.currentItem.modelData.duration = detailsEdit.durationText + diveDetailsListView.currentItem.modelData.depth = detailsEdit.depthText + diveDetailsListView.currentItem.modelData.airtemp = detailsEdit.airtempText + diveDetailsListView.currentItem.modelData.watertemp = detailsEdit.watertempText + diveDetailsListView.currentItem.modelData.suit = detailsEdit.suitText + diveDetailsListView.currentItem.modelData.buddy = detailsEdit.buddyText + diveDetailsListView.currentItem.modelData.divemaster = detailsEdit.divemasterText + diveDetailsListView.currentItem.modelData.notes = detailsEdit.notesText + diveDetailsPage.state = "view" + Qt.inputMethod.hide() + // now make sure we directly show the saved dive (this may be a new dive, or it may have moved) + showDiveIndex(newIdx) + } + + height: editArea.height + ColumnLayout { + id: editArea + spacing: Kirigami.Units.smallSpacing + width: subsurfaceTheme.columnWidth - 2 * Kirigami.Units.gridUnit + + GridLayout { + id: editorDetails + width: parent.width + columns: 2 + + Kirigami.Heading { + Layout.columnSpan: 2 + text: "Dive " + number + } + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Date:" + } + TextField { + id: txtDate; + Layout.fillWidth: true + } + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Location:" + } + TextField { + id: txtLocation; + Layout.fillWidth: true + } + + // we should add a checkbox here that allows the user + // to add the current location as the dive location + // (think of someone adding a dive while on the boat or + // at the dive site) + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Use current\nGPS location:" + } + CheckBox { + id: checkboxGPS + onCheckedChanged: { + if (checked) + gpsText = manager.getCurrentPosition() + } + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Depth:" + } + TextField { + id: txtDepth + Layout.fillWidth: true + validator: RegExpValidator { regExp: /[^-]*/ } + } + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Duration:" + } + TextField { + id: txtDuration + Layout.fillWidth: true + validator: RegExpValidator { regExp: /[^-]*/ } + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Air Temp:" + } + TextField { + id: txtAirTemp + Layout.fillWidth: true + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Water Temp:" + } + TextField { + id: txtWaterTemp + Layout.fillWidth: true + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Suit:" + } + TextField { + id: txtSuit + Layout.fillWidth: true + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Buddy:" + } + TextField { + id: txtBuddy + Layout.fillWidth: true + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Dive Master:" + } + TextField { + id: txtDiveMaster + Layout.fillWidth: true + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Weight:" + } + TextField { + id: txtWeight + readOnly: (text == "cannot edit multiple weight systems" ? true : false) + Layout.fillWidth: true + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Gas mix:" + } + TextField { + id: txtGasMix + readOnly: (text == "cannot edit multiple gases" ? true : false) + Layout.fillWidth: true + validator: RegExpValidator { regExp: /(EAN100|EAN\d\d|AIR|100|\d{1,2}|\d{1,2}\/\d{1,2})/ } + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "Start Pressure:" + } + TextField { + id: txtStartPressure + readOnly: (text == "cannot edit multiple cylinders" ? true : false) + Layout.fillWidth: true + } + + Kirigami.Label { + Layout.alignment: Qt.AlignRight + text: "End Pressure:" + } + TextField { + id: txtEndPressure + readOnly: (text == "cannot edit multiple cylinders" ? true : false) + Layout.fillWidth: true + } + + + Kirigami.Label { + Layout.columnSpan: 2 + Layout.alignment: Qt.AlignLeft + text: "Notes:" + } + TextArea { + Layout.columnSpan: 2 + width: parent.width + id: txtNotes + textFormat: TextEdit.RichText + focus: true + Layout.fillWidth: true + Layout.fillHeight: true + Layout.minimumHeight: Kirigami.Units.gridUnit * 6 + selectByMouse: true + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + } + } + Item { + height: Kirigami.Units.gridUnit * 3 + width: height // just to make sure the spacer doesn't produce scrollbars, but also isn't null + } + } +} diff --git a/mobile-widgets/qml/DiveDetailsView.qml b/mobile-widgets/qml/DiveDetailsView.qml new file mode 100644 index 000000000..ef1dc5605 --- /dev/null +++ b/mobile-widgets/qml/DiveDetailsView.qml @@ -0,0 +1,303 @@ +import QtQuick 2.3 +/* +import QtWebView 1.0 +*/ +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import org.subsurfacedivelog.mobile 1.0 +import org.kde.kirigami 1.0 as Kirigami + +Item { + id: detailsView + property real gridWidth: subsurfaceTheme.columnWidth - 2 * Kirigami.Units.gridUnit + property real col1Width: gridWidth * 0.23 + property real col2Width: gridWidth * 0.37 + property real col3Width: gridWidth * 0.20 + property real col4Width: gridWidth * 0.20 + + width: SubsurfaceTheme.columnWidth + height: mainLayout.implicitHeight + bottomLayout.implicitHeight + Kirigami.Units.iconSizes.large + Rectangle { + z: 99 + color: Kirigami.Theme.textColor + opacity: 0.3 + width: Kirigami.Units.smallSpacing/4 + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + } + } + GridLayout { + id: mainLayout + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: Math.round(Kirigami.Units.gridUnit / 2) + } + columns: 4 + rowSpacing: Kirigami.Units.smallSpacing * 2 + columnSpacing: Kirigami.Units.smallSpacing + + Kirigami.Heading { + id: detailsViewHeading + Layout.fillWidth: true + text: dive.location + font.underline: dive.gps !== "" + Layout.columnSpan: 4 + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.topMargin: Kirigami.Units.largeSpacing + MouseArea { + anchors.fill: parent + onClicked: { + if (dive.gps !== "") + showMap(dive.gps) + } + } + } + Kirigami.Label { + id: dateLabel + text: "Date: " + opacity: 0.6 + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + text: dive.date + " " + dive.time + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.columnSpan: 2 + } + Kirigami.Label { + id: numberText + text: "#" + dive.number + color: Kirigami.Theme.textColor + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + } + + Kirigami.Label { + id: depthLabel + text: "Depth: " + opacity: 0.6 + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + text: dive.depth + Layout.fillWidth: true + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + } + Kirigami.Label { + text: "Duration: " + opacity: 0.6 + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + text: dive.duration + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + } + + QMLProfile { + id: qmlProfile + visible: !dive.noDive + Layout.fillWidth: true + Layout.preferredHeight: Layout.minimumHeight + Layout.minimumHeight: width * 0.75 + Layout.columnSpan: 4 + clip: false + Rectangle { + color: "transparent" + opacity: 0.6 + border.width: 1 + border.color: Kirigami.Theme.textColor; + anchors.fill: parent + } + } + Kirigami.Label { + id: noProfile + visible: dive.noDive + Layout.fillWidth: true + Layout.columnSpan: 4 + Layout.margins: Kirigami.Units.gridUnit + horizontalAlignment: Text.AlignHCenter + text: "No profile to show" + } + } + GridLayout { + id: bottomLayout + anchors { + top: mainLayout.bottom + left: parent.left + right: parent.right + margins: Math.round(Kirigami.Units.gridUnit / 2) + } + columns: 4 + rowSpacing: Kirigami.Units.smallSpacing * 2 + columnSpacing: Kirigami.Units.smallSpacing + + Kirigami.Heading { + Layout.fillWidth: true + level: 3 + text: "Dive Details" + Layout.columnSpan: 4 + } + + // first row - here we set up the column widths - total is 90% of width + Kirigami.Label { + text: "Suit:" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + opacity: 0.6 + Layout.maximumWidth: detailsView.col1Width + Layout.preferredWidth: detailsView.col1Width + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + id: txtSuit + text: dive.suit + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.maximumWidth: detailsView.col2Width + Layout.preferredWidth: detailsView.col2Width + } + + Kirigami.Label { + text: "Air Temp:" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + opacity: 0.6 + Layout.maximumWidth: detailsView.col3Width + Layout.preferredWidth: detailsView.col3Width + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + id: txtAirTemp + text: dive.airTemp + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.maximumWidth: detailsView.col4Width + Layout.preferredWidth: detailsView.col4Width + } + + Kirigami.Label { + text: "Cylinder:" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + opacity: 0.6 + Layout.maximumWidth: detailsView.col1Width + Layout.preferredWidth: detailsView.col1Width + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + id: txtCylinder + text: dive.getCylinder + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.maximumWidth: detailsView.col2Width + Layout.preferredWidth: detailsView.col2Width + } + + Kirigami.Label { + text: "Water Temp:" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + opacity: 0.6 + Layout.maximumWidth: detailsView.col3Width + Layout.preferredWidth: detailsView.col3Width + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + id: txtWaterTemp + text: dive.waterTemp + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.maximumWidth: detailsView.col4Width + Layout.preferredWidth: detailsView.col4Width + } + + Kirigami.Label { + text: "Dive Master:" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + opacity: 0.6 + Layout.maximumWidth: detailsView.col1Width + Layout.preferredWidth: detailsView.col1Width + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + id: txtDiveMaster + text: dive.divemaster + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.maximumWidth: detailsView.col2Width + Layout.preferredWidth: detailsView.col2Width + } + + Kirigami.Label { + text: "Weight:" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + opacity: 0.6 + Layout.maximumWidth: detailsView.col3Width + Layout.preferredWidth: detailsView.col3Width + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + id: txtWeight + text: dive.sumWeight + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.maximumWidth: detailsView.col4Width + Layout.preferredWidth: detailsView.col4Width + } + + Kirigami.Label { + text: "Buddy:" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + opacity: 0.6 + Layout.maximumWidth: detailsView.col1Width + Layout.preferredWidth: detailsView.col1Width + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + id: txtBuddy + text: dive.buddy + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.maximumWidth: detailsView.col2Width + Layout.preferredWidth: detailsView.col2Width + } + + Kirigami.Label { + text: "SAC:" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + opacity: 0.6 + Layout.maximumWidth: detailsView.col3Width + Layout.preferredWidth: detailsView.col3Width + Layout.alignment: Qt.AlignRight + } + Kirigami.Label { + id: txtSAC + text: dive.sac + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + Layout.maximumWidth: detailsView.col4Width + Layout.preferredWidth: detailsView.col4Width + } + + Kirigami.Heading { + Layout.fillWidth: true + level: 3 + text: "Notes" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + Layout.columnSpan: 4 + } + + Kirigami.Label { + id: txtNotes + text: dive.notes + focus: true + Layout.columnSpan: 4 + Layout.fillWidth: true + Layout.fillHeight: true + //selectByMouse: true + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + } + Item { + Layout.columnSpan: 4 + Layout.fillWidth: true + Layout.minimumHeight: Kirigami.Units.gridUnit * 3 + } + Component.onCompleted: { + qmlProfile.setMargin(Kirigami.Units.smallSpacing) + qmlProfile.diveId = model.dive.id; + qmlProfile.update(); + } + } +} diff --git a/mobile-widgets/qml/DiveList.qml b/mobile-widgets/qml/DiveList.qml new file mode 100644 index 000000000..95af9a973 --- /dev/null +++ b/mobile-widgets/qml/DiveList.qml @@ -0,0 +1,302 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import org.kde.kirigami 1.0 as Kirigami +import org.subsurfacedivelog.mobile 1.0 + +Kirigami.ScrollablePage { + id: page + objectName: "DiveList" + title: "Subsurface-mobile" + background: Rectangle { + color: Kirigami.Theme.viewBackgroundColor + } + + property int credentialStatus: manager.credentialStatus + property int numDives: diveListView.count + property color textColor: subsurfaceTheme.diveListTextColor + + function scrollToTop() { + diveListView.positionViewAtBeginning() + } + + Component { + id: diveDelegate + Kirigami.AbstractListItem { + enabled: true + supportsMouseEvents: true + checked: diveListView.currentIndex === model.index + width: parent.width + + property real detailsOpacity : 0 + property int horizontalPadding: Kirigami.Units.gridUnit / 2 - Kirigami.Units.smallSpacing + 1 + + // When clicked, the mode changes to details view + onClicked: { + if (detailsWindow.state === "view") { + diveListView.currentIndex = index + detailsWindow.showDiveIndex(index); + stackView.push(detailsWindow); + } + } + + property bool deleteButtonVisible: false + + onPressAndHold: { + deleteButtonVisible = true + timer.restart() + } + + Row { + width: parent.width - Kirigami.Units.gridUnit + height: childrenRect.height - Kirigami.Units.smallSpacing + spacing: horizontalPadding + add: Transition { + NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 } + NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 } + } + Item { + id: diveListEntry + width: parent.width - Kirigami.Units.gridUnit + height: childrenRect.height - Kirigami.Units.smallSpacing + + Kirigami.Label { + id: locationText + text: dive.location + font.weight: Font.Light + elide: Text.ElideRight + maximumLineCount: 1 // needed for elide to work at all + color: textColor + anchors { + left: parent.left + leftMargin: horizontalPadding + top: parent.top + right: dateLabel.left + } + } + Kirigami.Label { + id: dateLabel + text: dive.date + " " + dive.time + font.pointSize: subsurfaceTheme.smallPointSize + color: textColor + anchors { + right: parent.right + top: parent.top + } + } + Row { + anchors { + left: parent.left + leftMargin: horizontalPadding + right: parent.right + rightMargin: horizontalPadding + topMargin: - Kirigami.Units.smallSpacing * 2 + bottom: numberText.bottom + } + Kirigami.Label { + text: 'Depth: ' + font.pointSize: subsurfaceTheme.smallPointSize + color: textColor + } + Kirigami.Label { + text: dive.depth + width: Math.max(Kirigami.Units.gridUnit * 3, paintedWidth) // helps vertical alignment throughout listview + font.pointSize: subsurfaceTheme.smallPointSize + color: textColor + } + Kirigami.Label { + text: 'Duration: ' + font.pointSize: subsurfaceTheme.smallPointSize + color: textColor + } + Kirigami.Label { + text: dive.duration + font.pointSize: subsurfaceTheme.smallPointSize + color: textColor + } + } + Kirigami.Label { + id: numberText + text: "#" + dive.number + font.pointSize: subsurfaceTheme.smallPointSize + color: textColor + anchors { + right: parent.right + top: locationText.bottom + topMargin: - Kirigami.Units.smallSpacing * 2 + } + } + } + Rectangle { + visible: deleteButtonVisible + height: diveListEntry.height - Kirigami.Units.smallSpacing + width: height - 3 * Kirigami.Units.smallSpacing + color: "#FF3030" + antialiasing: true + radius: Kirigami.Units.smallSpacing + Kirigami.Icon { + anchors { + horizontalCenter: parent.horizontalCenter + verticalCenter: parent.verticalCenter + } + source: "trash-empty" + } + MouseArea { + anchors.fill: parent + enabled: parent.visible + onClicked: { + parent.visible = false + timer.stop() + manager.deleteDive(dive.id) + } + } + } + Item { + Timer { + id: timer + interval: 4000 + onTriggered: { + deleteButtonVisible = false + } + } + } + } + } + } + + Component { + id: tripHeading + Item { + width: page.width - Kirigami.Units.gridUnit + height: childrenRect.height + Kirigami.Units.smallSpacing * 2 + Math.max(2, Kirigami.Units.gridUnit / 2) + + Kirigami.Heading { + id: sectionText + text: { + // if the tripMeta (which we get as "section") ends in ::-- we know + // that there's no trip -- otherwise strip the meta information before + // the :: and show the trip location + var shownText + var endsWithDoubleDash = /::--$/; + if (endsWithDoubleDash.test(section) || section === "--") { + shownText = "" + } else { + shownText = section.replace(/.*::/, "") + } + shownText + } + anchors { + top: parent.top + left: parent.left + topMargin: Math.max(2, Kirigami.Units.gridUnit / 2) + leftMargin: Kirigami.Units.gridUnit / 2 + right: parent.right + } + color: textColor + level: 2 + } + Rectangle { + height: Math.max(2, Kirigami.Units.gridUnit / 12) // we want a thicker line + anchors { + top: sectionText.bottom + left: parent.left + leftMargin: Kirigami.Units.gridUnit * -2 + rightMargin: Kirigami.Units.gridUnit * -2 + right: parent.right + } + color: subsurfaceTheme.accentColor + } + } + } + + ScrollView { + id: startPageWrapper + anchors.fill: parent + opacity: (diveListView.count > 0 && (credentialStatus == QMLManager.VALID || credentialStatus == QMLManager.VALID_EMAIL)) ? 0 : 1 + visible: opacity > 0 + Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } } + onVisibleChanged: { + if (visible) { + page.mainAction = page.saveAction + } else { + page.mainAction = page.addDiveAction + } + } + + StartPage { + id: startPage + } + } + + ListView { + id: diveListView + anchors.fill: parent + opacity: 0.8 - startPageWrapper.opacity + visible: opacity > 0 + model: diveModel + currentIndex: -1 + delegate: diveDelegate + //boundsBehavior: Flickable.StopAtBounds + maximumFlickVelocity: parent.height * 5 + bottomMargin: Kirigami.Units.iconSizes.medium + Kirigami.Units.gridUnit + cacheBuffer: 0 // seems to avoid empty rendered profiles + section.property: "dive.tripMeta" + section.criteria: ViewSection.FullString + section.delegate: tripHeading + header: Kirigami.Heading { + x: Kirigami.Units.gridUnit / 2 + height: paintedHeight + Kirigami.Units.gridUnit / 2 + verticalAlignment: Text.AlignBottom + text: "Dive Log" + } + Connections { + target: detailsWindow + onCurrentIndexChanged: diveListView.currentIndex = detailsWindow.currentIndex + } + Connections { + target: stackView + onDepthChanged: { + if (stackView.depth === 1) { + diveListView.currentIndex = -1; + } + } + } + Connections { + target: header + onTitleBarClicked: { + // if we can see the dive list and it's not at the top already, go to the top, + // otherwise have the title bar handle the click (for bread-crumb navigation) + if (stackView.currentItem.objectName === "DiveList" && diveListView.contentY > Kirigami.Units.gridUnit) { + diveListView.positionViewAtBeginning() + event.accepted = true + } else { + event.accepted = false + } + } + } + + } + + property QtObject addDiveAction: Action { + iconName: "list-add" + onTriggered: { + startAddDive() + } + } + + property QtObject saveAction: Action { + iconName: "document-save" + onTriggered: { + startPage.saveCredentials(); + } + } + + onBackRequested: { + if (startPageWrapper.visible && diveListView.count > 0 && manager.credentialStatus != QMLManager.INVALID) { + manager.credentialStatus = oldStatus + event.accepted = true; + } + } +} diff --git a/mobile-widgets/qml/DownloadFromDiveComputer.qml b/mobile-widgets/qml/DownloadFromDiveComputer.qml new file mode 100644 index 000000000..a062ffaa0 --- /dev/null +++ b/mobile-widgets/qml/DownloadFromDiveComputer.qml @@ -0,0 +1,125 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import org.subsurfacedivelog.mobile 1.0 +import org.kde.kirigami 1.0 as Kirigami + +Kirigami.Page { + id: diveComputerDownloadWindow + anchors.top:parent.top + width: parent.width + height: parent.height + Layout.fillWidth: true; + title: "Dive Computer" + +/* this can be done by hitting the back key + contextualActions: [ + Action { + text: "Close Preferences" + iconName: "dialog-cancel" + onTriggered: { + stackView.pop() + contextDrawer.close() + } + } + ] + */ + ColumnLayout { + anchors.top: parent.top + height: parent.height + width: parent.width + Layout.fillWidth: true + RowLayout { + anchors.top:parent.top + Layout.fillWidth: true + Text { text: " Vendor name : " } + ComboBox { Layout.fillWidth: true } + } + RowLayout { + Text { text: " Dive Computer:" } + ComboBox { Layout.fillWidth: true } + } + RowLayout { + Text { text: " Progress:" } + Layout.fillWidth: true + ProgressBar { Layout.fillWidth: true } + } + RowLayout { + SubsurfaceButton { + text: "Download" + onClicked: { + text: "Retry" + stackView.pop(); + } + } + SubsurfaceButton { + id:quitbutton + text: "Quit" + onClicked: { + stackView.pop(); + } + } + } + RowLayout { + Text { + text: " Downloaded dives" + } + } + TableView { + width: parent.width + Layout.fillWidth: true // The tableview should fill + Layout.fillHeight: true // all remaining vertical space + height: parent.height // on this screen + TableViewColumn { + width: parent.width / 2 + role: "datetime" + title: "Date / Time" + } + TableViewColumn { + width: parent.width / 4 + role: "duration" + title: "Duration" + } + TableViewColumn { + width: parent.width / 4 + role: "depth" + title: "Depth" + } + } + RowLayout { + Layout.fillWidth: true + SubsurfaceButton { + text: "Accept" + onClicked: { + stackView.pop(); + } + } + SubsurfaceButton { + text: "Quit" + onClicked: { + stackView.pop(); + } + } + Text { + text: "" // Spacer between 2 button groups + Layout.fillWidth: true + } + SubsurfaceButton { + text: "Select All" + } + SubsurfaceButton { + id: unselectbutton + text: "Unselect All" + } + } + RowLayout { // spacer to make space for silly button + Layout.minimumHeight: 1.2 * unselectbutton.height + Text { + text:"" + } + } + } +} diff --git a/mobile-widgets/qml/GpsList.qml b/mobile-widgets/qml/GpsList.qml new file mode 100644 index 000000000..6903acd80 --- /dev/null +++ b/mobile-widgets/qml/GpsList.qml @@ -0,0 +1,128 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.2 +import org.subsurfacedivelog.mobile 1.0 +import org.kde.kirigami 1.0 as Kirigami + +Kirigami.ScrollablePage { + id: gpsListWindow + width: parent.width - Kirigami.Units.gridUnit + anchors.margins: Kirigami.Units.gridUnit / 2 + objectName: "gpsList" + title: "GPS Fixes" + +/* this can be done by hitting the back key + contextualActions: [ + Action { + text: "Close GPS list" + iconName: "dialog-cancel" + onTriggered: { + stackView.pop() + contextDrawer.close() + } + } + ] + */ + Component { + id: gpsDelegate + Kirigami.SwipeListItem { + id: gpsFix + enabled: true + width: parent.width + property int horizontalPadding: Kirigami.Units.gridUnit / 2 - Kirigami.Units.smallSpacing + 1 + + Kirigami.BasicListItem { + supportsMouseEvents: true + width: parent.width - Kirigami.Units.gridUnit + height: childrenRect.height - Kirigami.Units.smallSpacing + GridLayout { + columns: 4 + id: timeAndName + anchors { + left: parent.left + leftMargin: horizontalPadding + right: parent.right + rightMargin: horizontalPadding + } + Kirigami.Label { + text: 'Date: ' + opacity: 0.6 + font.pointSize: subsurfaceTheme.smallPointSize + } + Kirigami.Label { + text: date + Layout.preferredWidth: Math.max(parent.width / 5, paintedWidth) + font.pointSize: subsurfaceTheme.smallPointSize + } + Kirigami.Label { + text: 'Name: ' + opacity: 0.6 + font.pointSize: subsurfaceTheme.smallPointSize + } + Kirigami.Label { + text: name + Layout.preferredWidth: Math.max(parent.width / 5, paintedWidth) + font.pointSize: subsurfaceTheme.smallPointSize + } + Kirigami.Label { + text: 'Latitude: ' + opacity: 0.6 + font.pointSize: subsurfaceTheme.smallPointSize + } + Kirigami.Label { + text: latitude + font.pointSize: subsurfaceTheme.smallPointSize + } + Kirigami.Label { + text: 'Longitude: ' + opacity: 0.6 + font.pointSize: subsurfaceTheme.smallPointSize + } + Kirigami.Label { + text: longitude + font.pointSize: subsurfaceTheme.smallPointSize + } + } + } + actions: [ + Kirigami.Action { + iconName: "trash-empty" + onTriggered: { + print("delete this!") + manager.deleteGpsFix(when) + } + }, + Kirigami.Action { + iconName: "gps" + onTriggered: { + showMap(latitude + " " + longitude) + } + } + + ] + } + } + + ListView { + id: gpsListView + anchors.fill: parent + model: gpsModel + currentIndex: -1 + delegate: gpsDelegate + boundsBehavior: Flickable.StopAtBounds + maximumFlickVelocity: parent.height * 5 + cacheBuffer: Math.max(5000, parent.height * 5) + focus: true + clip: true + header: Kirigami.Heading { + x: Kirigami.Units.gridUnit / 2 + height: paintedHeight + Kirigami.Units.gridUnit / 2 + verticalAlignment: Text.AlignBottom + text: "List of stored GPS fixes" + } + } +} diff --git a/mobile-widgets/qml/Log.qml b/mobile-widgets/qml/Log.qml new file mode 100644 index 000000000..d617901de --- /dev/null +++ b/mobile-widgets/qml/Log.qml @@ -0,0 +1,40 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.2 +import org.subsurfacedivelog.mobile 1.0 +import org.kde.kirigami 1.0 as Kirigami + +Kirigami.ScrollablePage { + id: logWindow + width: parent.width - Kirigami.Units.gridUnit + anchors.margins: Kirigami.Units.gridUnit / 2 + objectName: "Log" + title: "Application Log" + + property int pageWidth: subsurfaceTheme.columnWidth - Kirigami.Units.smallSpacing + + ColumnLayout { + width: pageWidth + spacing: Kirigami.Units.smallSpacing + Kirigami.Heading { + text: "Application Log" + } + Kirigami.Label { + id: logContent + width: parent.width + Layout.preferredWidth: parent.width + Layout.maximumWidth: parent.width + wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere + text: manager.logText + } + Rectangle { + color: "transparent" + height: Kirigami.Units.gridUnit * 2 + width: pageWidth + } + } +} diff --git a/mobile-widgets/qml/Preferences.qml b/mobile-widgets/qml/Preferences.qml new file mode 100644 index 000000000..3ec96d198 --- /dev/null +++ b/mobile-widgets/qml/Preferences.qml @@ -0,0 +1,74 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import org.kde.kirigami 1.0 as Kirigami +import org.subsurfacedivelog.mobile 1.0 + +Kirigami.Page { + + title: "Preferences" + mainAction: Action { + text: "Save" + iconName: "document-save" + onTriggered: { + manager.distanceThreshold = distanceThreshold.text + manager.timeThreshold = timeThreshold.text + manager.savePreferences() + stackView.pop() + } + } + + GridLayout { + + signal accept + + columns: 2 + width: parent.width - Kirigami.Units.gridUnit + anchors { + fill: parent + margins: Kirigami.Units.gridUnit / 2 + } + + Kirigami.Heading { + text: "Preferences" + Layout.bottomMargin: Kirigami.Units.largeSpacing / 2 + Layout.columnSpan: 2 + } + + Kirigami.Heading { + text: "Subsurface GPS data webservice" + level: 3 + Layout.topMargin: Kirigami.Units.largeSpacing + Layout.bottomMargin: Kirigami.Units.largeSpacing / 2 + Layout.columnSpan: 2 + } + + Kirigami.Label { + text: "Distance threshold (meters)" + Layout.alignment: Qt.AlignRight + } + + TextField { + id: distanceThreshold + text: manager.distanceThreshold + Layout.fillWidth: true + } + + Kirigami.Label { + text: "Time threshold (minutes)" + Layout.alignment: Qt.AlignRight + } + + TextField { + id: timeThreshold + text: manager.timeThreshold + Layout.fillWidth: true + } + + Item { + Layout.fillHeight: true + } + } +} diff --git a/mobile-widgets/qml/StartPage.qml b/mobile-widgets/qml/StartPage.qml new file mode 100644 index 000000000..2d70cfcb3 --- /dev/null +++ b/mobile-widgets/qml/StartPage.qml @@ -0,0 +1,42 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Layouts 1.1 +import org.kde.kirigami 1.0 as Kirigami +import org.subsurfacedivelog.mobile 1.0 + +ColumnLayout { + id: startpage + width: subsurfaceTheme.columnWidth + + function saveCredentials() { cloudCredentials.saveCredentials() } + + Kirigami.Heading { + Layout.margins: Kirigami.Units.gridUnit + text: "Subsurface-mobile" + } + Kirigami.Label { + id: explanationText + Layout.fillWidth: true + Layout.margins: Kirigami.Units.gridUnit + Layout.topMargin: 0 + text: "In order to use Subsurface-mobile you need to have a Subsurface cloud storage account " + + "(which can be created with the Subsurface desktop application)." + wrapMode: Text.WordWrap + } + Kirigami.Label { + id: messageArea + Layout.fillWidth: true + Layout.margins: Kirigami.Units.gridUnit + Layout.topMargin: 0 + text: manager.startPageText + wrapMode: Text.WordWrap + } + CloudCredentials { + id: cloudCredentials + Layout.fillWidth: true + Layout.margins: Kirigami.Units.gridUnit + Layout.topMargin: 0 + property int headingLevel: 3 + } +} diff --git a/mobile-widgets/qml/SubsurfaceButton.qml b/mobile-widgets/qml/SubsurfaceButton.qml new file mode 100644 index 000000000..174d44659 --- /dev/null +++ b/mobile-widgets/qml/SubsurfaceButton.qml @@ -0,0 +1,26 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import org.kde.kirigami 1.0 as Kirigami + +Button { + style: ButtonStyle { + padding { + top: Kirigami.Units.smallSpacing * 2 + left: Kirigami.Units.smallSpacing * 4 + right: Kirigami.Units.smallSpacing * 4 + bottom: Kirigami.Units.smallSpacing * 2 + } + background: Rectangle { + border.width: 1 + radius: height / 3 + color: control.pressed ? subsurfaceTheme.shadedColor : subsurfaceTheme.accentColor + } + label: Text{ + text: control.text + color: subsurfaceTheme.accentTextColor + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + } +} diff --git a/mobile-widgets/qml/TextButton.qml b/mobile-widgets/qml/TextButton.qml new file mode 100644 index 000000000..3e5a36735 --- /dev/null +++ b/mobile-widgets/qml/TextButton.qml @@ -0,0 +1,37 @@ +import QtQuick 2.3 + +Rectangle { + id: container + + property alias text: label.text + + signal clicked + + width: label.width + 20; height: label.height + 6 + smooth: true + radius: 10 + + gradient: Gradient { + GradientStop { id: gradientStop; position: 0.0; color: palette.light } + GradientStop { position: 1.0; color: palette.button } + } + + SystemPalette { id: palette } + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { container.clicked() } + } + + Text { + id: label + anchors.centerIn: parent + } + + states: State { + name: "pressed" + when: mouseArea.pressed + PropertyChanges { target: gradientStop; color: palette.dark } + } +} diff --git a/mobile-widgets/qml/ThemeTest.qml b/mobile-widgets/qml/ThemeTest.qml new file mode 100644 index 000000000..c0916aea0 --- /dev/null +++ b/mobile-widgets/qml/ThemeTest.qml @@ -0,0 +1,115 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.2 +import org.kde.kirigami 1.0 as Kirigami + +Kirigami.Page { + + title: "Theme Information" +/* this can be done by hitting the back key + contextualActions: [ + Action { + text: "Close Theme info" + iconName: "dialog-cancel" + onTriggered: { + stackView.pop() + contextDrawer.close() + } + } + ] + */ + GridLayout { + id: themetest + columns: 2 + anchors.margins: Kirigami.Units.gridUnit / 2 + + Kirigami.Heading { + Layout.columnSpan: 2 + text: "Theme Information" + } + + Kirigami.Heading { + text: "Screen" + Layout.columnSpan: 2 + level: 3 + } + FontMetrics { + id: fm + } + + Kirigami.Label { + text: "Geometry (pixels):" + } + Kirigami.Label { + text: rootItem.width + "x" + rootItem.height + } + + Kirigami.Label { + text: "Geometry (gridUnits):" + } + Kirigami.Label { + text: Math.round(rootItem.width / Kirigami.Units.gridUnit) + "x" + Math.round(rootItem.height / Kirigami.Units.gridUnit) + } + + Kirigami.Label { + text: "Units.gridUnit:" + } + Kirigami.Label { + text: Kirigami.Units.gridUnit + } + + Kirigami.Label { + text: "Units.devicePixelRatio:" + } + Kirigami.Label { + text: Screen.devicePixelRatio + } + + Kirigami.Heading { + text: "Font Metrics" + level: 3 + Layout.columnSpan: 2 + } + + Kirigami.Label { + text: "FontMetrics pointSize:" + } + Kirigami.Label { + text: fm.font.pointSize + } + + Kirigami.Label { + text: "FontMetrics pixelSize:" + } + Kirigami.Label { + text: fm.height + } + + Kirigami.Label { + text: "FontMetrics devicePixelRatio:" + } + Kirigami.Label { + text: fm.height / fm.font.pointSize + } + + Kirigami.Label { + text: "Text item pixelSize:" + } + Text { + text: font.pixelSize + } + + Kirigami.Label { + text: "Text item pointSize:" + } + Text { + text: font.pointSize + } + + Kirigami.Label { + Layout.columnSpan: 2 + Layout.fillHeight: true + } + } +} diff --git a/mobile-widgets/qml/TopBar.qml b/mobile-widgets/qml/TopBar.qml new file mode 100644 index 000000000..024b818b0 --- /dev/null +++ b/mobile-widgets/qml/TopBar.qml @@ -0,0 +1,59 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.2 +import org.kde.kirigami 1.0 as Kirigami +import org.subsurfacedivelog.mobile 1.0 + +Rectangle { + id: topPart + + color: subsurfaceTheme.accentColor + Layout.minimumHeight: Math.round(Kirigami.Units.gridUnit * 1.5) + Layout.fillWidth: true + Layout.margins: 0 + RowLayout { + anchors.verticalCenter: topPart.verticalCenter + Item { + Layout.preferredHeight: subsurfaceLogo.height + Layout.leftMargin: Kirigami.Units.gridUnit / 4 + Image { + id: subsurfaceLogo + source: "qrc:/qml/subsurface-mobile-icon.png" + anchors { + verticalCenter: parent.Center + left: parent.left + } + width: Math.round(Kirigami.Units.gridUnit) + height: width + } + Kirigami.Label { + text: qsTr("Subsurface-mobile") + font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize) + height: subsurfaceLogo.height + anchors { + left: subsurfaceLogo.right + leftMargin: Math.round(Kirigami.Units.gridUnit / 2) + } + font.weight: Font.Light + verticalAlignment: Text.AlignVCenter + Layout.fillWidth: false + color: subsurfaceTheme.accentTextColor + } + } + Item { + Layout.fillWidth: true + } + } + MouseArea { + anchors.fill: topPart + onClicked: { + if (stackView.depth == 1 && showingDiveList) { + scrollToTop() + } + } + } +} diff --git a/mobile-widgets/qml/dive.jpg b/mobile-widgets/qml/dive.jpg new file mode 100644 index 000000000..56445648a Binary files /dev/null and b/mobile-widgets/qml/dive.jpg differ diff --git a/mobile-widgets/qml/icons/context-menu.png b/mobile-widgets/qml/icons/context-menu.png new file mode 100644 index 000000000..df34cfd4f Binary files /dev/null and b/mobile-widgets/qml/icons/context-menu.png differ diff --git a/mobile-widgets/qml/icons/context-menu.svg b/mobile-widgets/qml/icons/context-menu.svg new file mode 100644 index 000000000..e0750c57e --- /dev/null +++ b/mobile-widgets/qml/icons/context-menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/mobile-widgets/qml/icons/main-menu.png b/mobile-widgets/qml/icons/main-menu.png new file mode 100644 index 000000000..20729b8f5 Binary files /dev/null and b/mobile-widgets/qml/icons/main-menu.png differ diff --git a/mobile-widgets/qml/icons/main-menu.svg b/mobile-widgets/qml/icons/main-menu.svg new file mode 100644 index 000000000..1e89193f5 --- /dev/null +++ b/mobile-widgets/qml/icons/main-menu.svg @@ -0,0 +1 @@ + diff --git a/mobile-widgets/qml/icons/menu-back.png b/mobile-widgets/qml/icons/menu-back.png new file mode 100644 index 000000000..dc96b7728 Binary files /dev/null and b/mobile-widgets/qml/icons/menu-back.png differ diff --git a/mobile-widgets/qml/icons/menu-edit.png b/mobile-widgets/qml/icons/menu-edit.png new file mode 100644 index 000000000..ea7dd055a Binary files /dev/null and b/mobile-widgets/qml/icons/menu-edit.png differ diff --git a/mobile-widgets/qml/main.qml b/mobile-widgets/qml/main.qml new file mode 100644 index 000000000..f4f6ea28b --- /dev/null +++ b/mobile-widgets/qml/main.qml @@ -0,0 +1,360 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.2 +import QtQuick.Controls.Styles 1.2 +import QtQuick.Window 2.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.2 +import org.subsurfacedivelog.mobile 1.0 +import org.kde.kirigami 1.0 as Kirigami + +Kirigami.ApplicationWindow { + id: rootItem + title: qsTr("Subsurface-mobile") + + header.minimumHeight: 0 + header.preferredHeight: Kirigami.Units.gridUnit + header.maximumHeight: Kirigami.Units.gridUnit * 2 + property bool fullscreen: true + property int oldStatus: -1 + property alias accessingCloud: manager.accessingCloud + property QtObject notification: null + property bool showingDiveList: false + property alias syncToCloud: manager.syncToCloud + onAccessingCloudChanged: { + if (accessingCloud >= 0) { + // we now keep updating this to show progress, so timing out after 30 seconds is more useful + // but should still be very conservative + showPassiveNotification("Accessing Subsurface Cloud Storage " + accessingCloud +"%", 30000); + } else { + hidePassiveNotification(); + } + } + + FontMetrics { + id: fontMetrics + } + + visible: false + opacity: 0 + + function returnTopPage() { + for (var i=stackView.depth; i>1; i--) { + stackView.pop() + } + detailsWindow.endEditMode() + } + + function scrollToTop() { + diveList.scrollToTop() + } + + function showMap(location) { + var urlPrefix = "https://www.google.com/maps/place/" + var locationPair = location + "/@" + location + var urlSuffix = ",5000m/data=!3m1!1e3!4m2!3m1!1s0x0:0x0" + Qt.openUrlExternally(urlPrefix + locationPair + urlSuffix) + + } + + function startAddDive() { + detailsWindow.state = "add" + detailsWindow.dive_id = manager.addDive(); + detailsWindow.number = manager.getNumber(detailsWindow.dive_id) + detailsWindow.date = manager.getDate(detailsWindow.dive_id) + detailsWindow.airtemp = "" + detailsWindow.watertemp = "" + detailsWindow.buddy = "" + detailsWindow.depth = "" + detailsWindow.divemaster = "" + detailsWindow.notes = "" + detailsWindow.location = "" + detailsWindow.duration = "" + detailsWindow.suit = "" + detailsWindow.weight = "" + detailsWindow.gasmix = "" + detailsWindow.startpressure = "" + detailsWindow.endpressure = "" + stackView.push(detailsWindow) + } + + globalDrawer: Kirigami.GlobalDrawer { + title: "Subsurface" + titleIcon: "qrc:/qml/subsurface-mobile-icon.png" + + bannerImageSource: "dive.jpg" + actions: [ + Kirigami.Action { + text: "Dive list" + onTriggered: { + manager.appendTextToLog("requested dive list with credential status " + manager.credentialStatus) + if (manager.credentialStatus == QMLManager.UNKNOWN) { + // the user has asked to change credentials - if the credentials before that + // were valid, go back to dive list + if (oldStatus == QMLManager.VALID || oldStatus == QMLManager.VALID_EMAIL) { + manager.credentialStatus = oldStatus + } + } + returnTopPage() + globalDrawer.close() + } + }, + Kirigami.Action { + text: "Cloud credentials" + onTriggered: { + returnTopPage() + oldStatus = manager.credentialStatus + if (diveList.numDives > 0) { + manager.startPageText = "Enter different credentials or return to dive list" + } else { + manager.startPageText = "Enter valid cloud storage credentials" + } + + manager.credentialStatus = QMLManager.UNKNOWN + } + }, + Kirigami.Action { + text: "Manage dives" + enabled: manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL + /* + * disable for the beta to avoid confusion + Action { + text: "Download from computer" + onTriggered: { + detailsWindow.endEditMode() + stackView.push(downloadDivesWindow) + } + } + */ + Kirigami.Action { + text: "Add dive manually" + onTriggered: { + returnTopPage() // otherwise odd things happen with the page stack + startAddDive() + } + } + Kirigami.Action { + text: "Manual sync with cloud" + onTriggered: { + globalDrawer.close() + detailsWindow.endEditMode() + manager.saveChanges(); + } + } + Kirigami.Action { + text: syncToCloud ? "Disable auto cloud sync" : "Enable auto cloud sync" + onTriggered: { + syncToCloud = !syncToCloud + if (!syncToCloud) { + var alertText = "Turning off automatic sync to cloud causes all data to only be stored locally.\n" + alertText += "This can be very useful in situations with limited or no network access.\n" + alertText += "Please chose 'Manual sync with cloud' if you have network connectivity\n" + alertText += "and want to sync your data to cloud storage." + showPassiveNotification(alertText, 10000) + } + } + } + }, + + Kirigami.Action { + text: "GPS" + enabled: manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL + Kirigami.Action { + text: "GPS-tag dives" + onTriggered: { + manager.applyGpsData(); + } + } + + Kirigami.Action { + text: "Upload GPS data" + onTriggered: { + manager.sendGpsData(); + } + } + + Kirigami.Action { + text: "Download GPS data" + onTriggered: { + manager.downloadGpsData(); + } + } + + Kirigami.Action { + text: "Show GPS fixes" + onTriggered: { + returnTopPage() + manager.populateGpsData(); + stackView.push(gpsWindow) + } + } + + Kirigami.Action { + text: "Clear GPS cache" + onTriggered: { + manager.clearGpsData(); + } + } + Kirigami.Action { + text: "Preferences" + onTriggered: { + stackView.push(prefsWindow) + detailsWindow.endEditMode() + } + } + }, + + Kirigami.Action { + text: "Developer" + Kirigami.Action { + text: "App log" + onTriggered: { + stackView.push(logWindow) + } + } + + Kirigami.Action { + text: "Theme information" + onTriggered: { + stackView.push(themetest) + } + } + }, + Kirigami.Action { + text: "User manual" + onTriggered: { + Qt.openUrlExternally("https://subsurface-divelog.org/documentation/subsurface-mobile-user-manual/") + } + }, + Kirigami.Action { + text: "About" + onTriggered: { + stackView.push(aboutWindow) + detailsWindow.endEditMode() + } + } + ] // end actions + + MouseArea { + height: childrenRect.height + width: Kirigami.Units.gridUnit * 10 + CheckBox { + //text: "Run location service" + id: locationCheckbox + anchors { + left: parent.left + top: parent.top + } + checked: manager.locationServiceEnabled + onCheckedChanged: { + manager.locationServiceEnabled = checked; + } + } + Kirigami.Label { + x: Kirigami.Units.gridUnit * 1.5 + anchors { + left: locationCheckbox.right + //leftMargin: units.smallSpacing + verticalCenter: locationCheckbox.verticalCenter + } + text: "Run location service" + } + onClicked: { + print("Click.") + locationCheckbox.checked = !locationCheckbox.checked + } + } + } + + contextDrawer: Kirigami.ContextDrawer { + id: contextDrawer + actions: rootItem.pageStack.currentPage ? rootItem.pageStack.currentPage.contextualActions : null + title: "Actions" + } + + QtObject { + id: subsurfaceTheme + property int titlePointSize: Math.round(fontMetrics.font.pointSize * 1.5) + property int smallPointSize: Math.round(fontMetrics.font.pointSize * 0.8) + property color accentColor: "#2d5b9a" + property color shadedColor: "#132744" + property color accentTextColor: "#ececec" + property color diveListTextColor: "#000000" // the Kirigami theme text color is too light + property int columnWidth: Math.round(rootItem.width/(Kirigami.Units.gridUnit*30)) > 0 ? Math.round(rootItem.width / Math.round(rootItem.width/(Kirigami.Units.gridUnit*30))) : rootItem.width + } +/* + toolBar: TopBar { + width: parent.width + height: Layout.minimumHeight + } + */ + + property Item stackView: pageStack + pageStack.initialPage: DiveList { + anchors.fill: detailsPage + id: diveList + opacity: 0 + Behavior on opacity { + NumberAnimation { + duration: 200 + easing.type: Easing.OutQuad + } + } + + } + + QMLManager { + id: manager + } + + Preferences { + id: prefsWindow + visible: false + } + + About { + id: aboutWindow + visible: false + } + + DiveDetails { + id: detailsWindow + visible: false + width: parent.width + height: parent.height + } + + DownloadFromDiveComputer { + id: downloadDivesWindow + visible: false + } + + Log { + id: logWindow + visible: false + } + + GpsList { + id: gpsWindow + visible: false + } + + ThemeTest { + id: themetest + visible: false + } + + Component.onCompleted: { + Kirigami.Theme.highlightColor = subsurfaceTheme.accentColor + manager.finishSetup(); + rootItem.visible = true + diveList.opacity = 1 + rootItem.opacity = 1 + } + Behavior on opacity { + NumberAnimation { + duration: 200 + easing.type: Easing.OutQuad + } + } +} diff --git a/mobile-widgets/qml/mobile-resources.qrc b/mobile-widgets/qml/mobile-resources.qrc new file mode 100644 index 000000000..e6c1fba65 --- /dev/null +++ b/mobile-widgets/qml/mobile-resources.qrc @@ -0,0 +1,66 @@ + + + main.qml + TextButton.qml + Preferences.qml + About.qml + CloudCredentials.qml + DiveList.qml + DiveDetails.qml + DiveDetailsEdit.qml + DiveDetailsView.qml + DownloadFromDiveComputer.qml + GpsList.qml + Log.qml + TopBar.qml + ThemeTest.qml + StartPage.qml + dive.jpg + SubsurfaceButton.qml + ../../icons/subsurface-mobile-icon.png + icons/main-menu.png + icons/context-menu.png + icons/menu-edit.png + icons/menu-back.png + + + kirigami/qmldir + kirigami/Action.qml + kirigami/ApplicationWindow.qml + kirigami/BasicListItem.qml + kirigami/GlobalDrawer.qml + kirigami/ContextDrawer.qml + kirigami/Page.qml + kirigami/ScrollablePage.qml + kirigami/Icon.qml + kirigami/Heading.qml + kirigami/OverlaySheet.qml + kirigami/ApplicationHeader.qml + kirigami/private/PageRow.qml + kirigami/Label.qml + kirigami/AbstractListItem.qml + kirigami/SwipeListItem.qml + kirigami/OverlayDrawer.qml + kirigami/Theme.qml + kirigami/Units.qml + kirigami/private/RefreshableScrollView.qml + kirigami/private/ActionButton.qml + kirigami/private/BackButton.qml + kirigami/private/MenuIcon.qml + kirigami/private/ContextIcon.qml + kirigami/private/AbstractDrawer.qml + kirigami/private/PageStack.js + kirigami/private/PassiveNotification.qml + kirigami/icons/go-next.svg + kirigami/icons/go-previous.svg + kirigami/icons/distribute-horizontal-x.svg + kirigami/icons/document-edit.svg + kirigami/icons/document-save.svg + kirigami/icons/view-readermode.svg + kirigami/icons/dialog-cancel.svg + kirigami/icons/application-menu.svg + kirigami/icons/gps.svg + kirigami/icons/trash-empty.svg + kirigami/icons/list-add.svg + + diff --git a/mobile-widgets/qml/theme/Theme.qml b/mobile-widgets/qml/theme/Theme.qml new file mode 100644 index 000000000..2c51ae00f --- /dev/null +++ b/mobile-widgets/qml/theme/Theme.qml @@ -0,0 +1,57 @@ +/* + * Copyright 2015 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program 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 Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.0 + +//pragma Singleton + +/*! + \qmltype Theme + \inqmlmodule Material 0.1 + + \brief Provides access to standard colors that follow the Material Design specification. + + See \l {http://www.google.com/design/spec/style/color.html#color-ui-color-application} for + details about choosing a color scheme for your application. + */ +QtObject { + id: theme + + property color textColor: Qt.rgba(0,0,0, 0.54) + + property color highlightColor: "#2196F3" + property color backgroundColor: "#f3f3f3" + property color linkColor: "#2196F3" + property color visitedLinkColor: "#2196F3" + + property color buttonTextColor: Qt.rgba(0,0,0, 0.54) + property color buttonBackgroundColor: "#f3f3f3" + property color buttonHoverColor: "#2196F3" + property color buttonFocusColor: "#2196F3" + + property color viewTextColor: Qt.rgba(0,0,0, 0.54) + property color viewBackgroundColor: "#f3f3f3" + property color viewHoverColor: "#2196F3" + property color viewFocusColor: "#2196F3" + + property color complementaryTextColor: "#f3f3f3" + property color complementaryBackgroundColor: Qt.rgba(0,0,0, 0.54) + property color complementaryHoverColor: "#2196F3" + property color complementaryFocusColor: "#2196F3" +} diff --git a/mobile-widgets/qml/theme/Units.qml b/mobile-widgets/qml/theme/Units.qml new file mode 100644 index 000000000..7cfa5c23b --- /dev/null +++ b/mobile-widgets/qml/theme/Units.qml @@ -0,0 +1,99 @@ +/* + * Copyright 2015 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program 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 Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.5 +import QtQuick.Window 2.2 + +//pragma Singleton + + +QtObject { + id: units + + /** + * The fundamental unit of space that should be used for sizes, expressed in pixels. + * Given the screen has an accurate DPI settings, it corresponds to a width of + * the capital letter M + */ + property int gridUnit: fontMetrics.height + + /** + * units.iconSizes provides access to platform-dependent icon sizing + * + * The icon sizes provided are normalized for different DPI, so icons + * will scale depending on the DPI. + * + * Icon sizes from KIconLoader, adjusted to devicePixelRatio: + * * small + * * smallMedium + * * medium + * * large + * * huge + * * enormous + * + * Not devicePixelRation-adjusted:: + * * desktop + */ + property QtObject iconSizes: QtObject { + property int small: 16 * devicePixelRatio + property int smallMedium: 22 * devicePixelRatio + property int medium: 32 * devicePixelRatio + property int large: 48 * devicePixelRatio + property int huge: 64 * devicePixelRatio + property int enormous: 128 * devicePixelRatio + } + + /** + * units.smallSpacing is the amount of spacing that should be used around smaller UI elements, + * for example as spacing in Columns. Internally, this size depends on the size of + * the default font as rendered on the screen, so it takes user-configured font size and DPI + * into account. + */ + property int smallSpacing: gridUnit/4 + + /** + * units.largeSpacing is the amount of spacing that should be used inside bigger UI elements, + * for example between an icon and the corresponding text. Internally, this size depends on + * the size of the default font as rendered on the screen, so it takes user-configured font + * size and DPI into account. + */ + property int largeSpacing: gridUnit + + /** + * The ratio between physical and device-independent pixels. This value does not depend on the \ + * size of the configured font. If you want to take font sizes into account when scaling elements, + * use theme.mSize(theme.defaultFont), units.smallSpacing and units.largeSpacing. + * The devicePixelRatio follows the definition of "device independent pixel" by Microsoft. + */ + property real devicePixelRatio: Screen.devicePixelRatio + + /** + * units.longDuration should be used for longer, screen-covering animations, for opening and + * closing of dialogs and other "not too small" animations + */ + property int longDuration: 250 + + /** + * units.shortDuration should be used for short animations, such as accentuating a UI event, + * hover events, etc.. + */ + property int shortDuration: 150 + + property QtObject fontMetrics: FontMetrics {} +} diff --git a/mobile-widgets/qml/theme/qmldir b/mobile-widgets/qml/theme/qmldir new file mode 100644 index 000000000..c654dbad6 --- /dev/null +++ b/mobile-widgets/qml/theme/qmldir @@ -0,0 +1,2 @@ +#singleton Units Units.qml +#//singleton Theme Theme.qml diff --git a/mobile-widgets/qmlmanager.cpp b/mobile-widgets/qmlmanager.cpp new file mode 100644 index 000000000..0bfde62aa --- /dev/null +++ b/mobile-widgets/qmlmanager.cpp @@ -0,0 +1,1078 @@ +#include "qmlmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qt-models/divelistmodel.h" +#include "qt-models/gpslistmodel.h" +#include "core/divelist.h" +#include "core/device.h" +#include "core/pref.h" +#include "core/qthelper.h" +#include "core/qt-gui.h" +#include "core/git-access.h" +#include "core/cloudstorage.h" + +QMLManager *QMLManager::m_instance = NULL; + +#define RED_FONT QLatin1Literal("") +#define END_FONT QLatin1Literal("") + +static void appendTextToLogStandalone(const char *text) +{ + QMLManager *self = QMLManager::instance(); + if (self) + self->appendTextToLog(QString(text)); +} + +extern "C" int gitProgressCB(int percent, const char *text) +{ + static QElapsedTimer timer; + static qint64 lastTime = 0; + static int lastPercent = -100; + + if (!timer.isValid() || percent == 0) { + timer.restart(); + lastTime = 0; + lastPercent = -100; + } + QMLManager *self = QMLManager::instance(); + if (self) { + qint64 elapsed = timer.elapsed(); + // don't show the same status twice in 200ms + if (percent == lastPercent && elapsed - lastTime < 200) + return 0; + self->loadDiveProgress(percent); + QString logText = QString::number(elapsed / 1000.0, 'f', 1) + " / " + QString::number((elapsed - lastTime) / 1000.0, 'f', 3) + + QString(" : git progress %1 (%2)").arg(percent).arg(text); + self->appendTextToLog(logText); + qDebug() << logText; + qApp->processEvents(); + qApp->flush(); + lastTime = elapsed; + } + // return 0 so that we don't end the download + return 0; +} + +QMLManager::QMLManager() : m_locationServiceEnabled(false), + m_verboseEnabled(false), + reply(0), + deletedDive(0), + deletedTrip(0), + m_credentialStatus(UNKNOWN), + m_lastDevicePixelRatio(1.0), + alreadySaving(false) +{ + m_instance = this; + connect(qobject_cast(QApplication::instance()), &QApplication::applicationStateChanged, this, &QMLManager::applicationStateChanged); + appendTextToLog(getUserAgent()); + appendTextToLog(QStringLiteral("build with Qt Version %1, runtime from Qt Version %2").arg(QT_VERSION_STR).arg(qVersion())); + qDebug() << "Starting" << getUserAgent(); + qDebug() << QStringLiteral("build with Qt Version %1, runtime from Qt Version %2").arg(QT_VERSION_STR).arg(qVersion()); + setStartPageText(tr("Starting...")); + setAccessingCloud(-1); + setSyncToCloud(true); + // create location manager service + locationProvider = new GpsLocation(&appendTextToLogStandalone, this); + set_git_update_cb(&gitProgressCB); + + // make sure we know if the current cloud repo has been successfully synced + syncLoadFromCloud(); +} + +void QMLManager::applicationStateChanged(Qt::ApplicationState state) +{ + if (!timer.isValid()) + timer.start(); + QString stateText; + switch (state) { + case Qt::ApplicationActive: stateText = "active"; break; + case Qt::ApplicationHidden: stateText = "hidden"; break; + case Qt::ApplicationSuspended: stateText = "suspended"; break; + case Qt::ApplicationInactive: stateText = "inactive"; break; + default: stateText = QString("none of the four: 0x") + QString::number(state, 16); + } + stateText.prepend(QString::number(timer.elapsed() / 1000.0,'f', 3) + ": AppState changed to "); + stateText.append(" with "); + stateText.append((alreadySaving ? QLatin1Literal("") : QLatin1Literal("no ")) + QLatin1Literal("save ongoing")); + stateText.append(" and "); + stateText.append((unsaved_changes() ? QLatin1Literal("") : QLatin1Literal("no ")) + QLatin1Literal("unsaved changes")); + appendTextToLog(stateText); + qDebug() << stateText; + + if (!alreadySaving && state == Qt::ApplicationInactive && unsaved_changes()) { + // FIXME + // make sure the user sees that we are saving data if they come back + // while this is running + alreadySaving = true; + saveChanges(); + alreadySaving = false; + appendTextToLog(QString::number(timer.elapsed() / 1000.0,'f', 3) + ": done saving to git local / remote"); + mark_divelist_changed(false); + } +} + +void QMLManager::openLocalThenRemote(QString url) +{ + clear_dive_file_data(); + QByteArray fileNamePrt = QFile::encodeName(url); + bool glo = prefs.git_local_only; + prefs.git_local_only = true; + int error = parse_file(fileNamePrt.data()); + setAccessingCloud(-1); + prefs.git_local_only = glo; + if (error) { + appendTextToLog(QStringLiteral("loading dives from cache failed %1").arg(error)); + } else { + // if we can load from the cache, we know that we have at least a valid email + if (credentialStatus() == UNKNOWN) + setCredentialStatus(VALID_EMAIL); + prefs.unit_system = informational_prefs.unit_system; + if (informational_prefs.unit_system == IMPERIAL) + informational_prefs.units = IMPERIAL_units; + prefs.units = informational_prefs.units; + int i; + struct dive *d; + process_dives(false, false); + DiveListModel::instance()->clear(); + for_each_dive (i, d) { + DiveListModel::instance()->addDive(d); + } + appendTextToLog(QStringLiteral("%1 dives loaded from cache").arg(i)); + } + appendTextToLog(QStringLiteral("have cloud credentials, trying to connect")); + tryRetrieveDataFromBackend(); +} + +void QMLManager::finishSetup() +{ + // Initialize cloud credentials. + setCloudUserName(prefs.cloud_storage_email); + setCloudPassword(prefs.cloud_storage_password); + // if the cloud credentials are valid, we should get the GPS Webservice ID as well + QString url; + if (!cloudUserName().isEmpty() && + !cloudPassword().isEmpty() && + getCloudURL(url) == 0) { + openLocalThenRemote(url); + } else { + setCredentialStatus(INCOMPLETE); + appendTextToLog(QStringLiteral("no cloud credentials")); + setStartPageText(RED_FONT + tr("Please enter valid cloud credentials.") + END_FONT); + } + setDistanceThreshold(prefs.distance_threshold); + setTimeThreshold(prefs.time_threshold / 60); +} + +QMLManager::~QMLManager() +{ + m_instance = NULL; +} + +QMLManager *QMLManager::instance() +{ + return m_instance; +} + +void QMLManager::savePreferences() +{ + QSettings s; + s.beginGroup("LocationService"); + s.setValue("time_threshold", timeThreshold() * 60); + prefs.time_threshold = timeThreshold() * 60; + s.setValue("distance_threshold", distanceThreshold()); + prefs.distance_threshold = distanceThreshold(); + s.sync(); +} + +#define CLOUDURL QString(prefs.cloud_base_url) +#define CLOUDREDIRECTURL CLOUDURL + "/cgi-bin/redirect.pl" + +void QMLManager::saveCloudCredentials() +{ + QSettings s; + bool cloudCredentialsChanged = false; + s.beginGroup("CloudStorage"); + s.setValue("email", cloudUserName()); + s.setValue("password", cloudPassword()); + s.sync(); + if (!same_string(prefs.cloud_storage_email, qPrintable(cloudUserName()))) { + free(prefs.cloud_storage_email); + prefs.cloud_storage_email = strdup(qPrintable(cloudUserName())); + cloudCredentialsChanged = true; + } + + cloudCredentialsChanged |= !same_string(prefs.cloud_storage_password, qPrintable(cloudPassword())); + + if (!same_string(prefs.cloud_storage_password, qPrintable(cloudPassword()))) { + free(prefs.cloud_storage_password); + prefs.cloud_storage_password = strdup(qPrintable(cloudPassword())); + } + if (cloudUserName().isEmpty() || cloudPassword().isEmpty()) { + setStartPageText(RED_FONT + tr("Please enter valid cloud credentials.") + END_FONT); + } else if (cloudCredentialsChanged) { + free(prefs.userid); + prefs.userid = NULL; + syncLoadFromCloud(); + QString url; + getCloudURL(url); + manager()->clearAccessCache(); // remove any chached credentials + clear_git_id(); // invalidate our remembered GIT SHA + clear_dive_file_data(); + DiveListModel::instance()->clear(); + GpsListModel::instance()->clear(); + setStartPageText(tr("Attempting to open cloud storage with new credentials")); + openLocalThenRemote(url); + } +} + +void QMLManager::checkCredentialsAndExecute(execute_function_type execute) +{ + // if the cloud credentials are present, we should try to get the GPS Webservice ID + // and (if we haven't done so) load the dive list + if (!same_string(prefs.cloud_storage_email, "") && + !same_string(prefs.cloud_storage_password, "")) { + setAccessingCloud(0); + setStartPageText(tr("Testing cloud credentials")); + appendTextToLog("Have credentials, let's see if they are valid"); + connect(manager(), &QNetworkAccessManager::authenticationRequired, this, &QMLManager::provideAuth, Qt::UniqueConnection); + connect(manager(), &QNetworkAccessManager::finished, this, execute, Qt::UniqueConnection); + QUrl url(CLOUDREDIRECTURL); + request = QNetworkRequest(url); + request.setRawHeader("User-Agent", getUserAgent().toUtf8()); + request.setRawHeader("Accept", "text/html"); + reply = manager()->get(request); + connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleError(QNetworkReply::NetworkError))); + connect(reply, &QNetworkReply::sslErrors, this, &QMLManager::handleSslErrors); + } +} + +void QMLManager::tryRetrieveDataFromBackend() +{ + checkCredentialsAndExecute(&QMLManager::retrieveUserid); +} + +void QMLManager::provideAuth(QNetworkReply *reply, QAuthenticator *auth) +{ + if (auth->user() == QString(prefs.cloud_storage_email) && + auth->password() == QString(prefs.cloud_storage_password)) { + // OK, credentials have been tried and didn't work, so they are invalid + appendTextToLog("Cloud credentials are invalid"); + setStartPageText(RED_FONT + tr("Cloud credentials are invalid") + END_FONT); + setCredentialStatus(INVALID); + reply->disconnect(); + reply->abort(); + reply->deleteLater(); + return; + } + auth->setUser(prefs.cloud_storage_email); + auth->setPassword(prefs.cloud_storage_password); +} + +void QMLManager::handleSslErrors(const QList &errors) +{ + setStartPageText(RED_FONT + tr("Cannot open cloud storage: Error creating https connection") + END_FONT); + Q_FOREACH (QSslError e, errors) { + qDebug() << e.errorString(); + } + reply->abort(); + reply->deleteLater(); + setAccessingCloud(-1); +} + +void QMLManager::handleError(QNetworkReply::NetworkError nError) +{ + QString errorString = reply->errorString(); + qDebug() << "handleError" << nError << errorString; + setStartPageText(RED_FONT + tr("Cannot open cloud storage: %1").arg(errorString) + END_FONT); + reply->abort(); + reply->deleteLater(); + setAccessingCloud(-1); +} + +void QMLManager::retrieveUserid() +{ + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 302) { + appendTextToLog(QStringLiteral("Cloud storage connection not working correctly: %1").arg(QString(reply->readAll()))); + setAccessingCloud(-1); + return; + } + setCredentialStatus(VALID); + QString userid(prefs.userid); + if (userid.isEmpty()) { + if (same_string(prefs.cloud_storage_email, "") || same_string(prefs.cloud_storage_password, "")) { + appendTextToLog("cloud user name or password are empty, can't retrieve web user id"); + setAccessingCloud(-1); + return; + } + appendTextToLog(QStringLiteral("calling getUserid with user %1").arg(prefs.cloud_storage_email)); + userid = locationProvider->getUserid(prefs.cloud_storage_email, prefs.cloud_storage_password); + } + if (!userid.isEmpty()) { + // overwrite the existing userid + free(prefs.userid); + prefs.userid = strdup(qPrintable(userid)); + QSettings s; + s.setValue("subsurface_webservice_uid", prefs.userid); + s.sync(); + } + loadDivesWithValidCredentials(); +} + +void QMLManager::loadDiveProgress(int percent) +{ + QString text(tr("Loading dive list from cloud storage.")); + setAccessingCloud(percent); + while (percent > 0) { + text.append("."); + percent -= 10; + } + setStartPageText(text); +} + +void QMLManager::loadDivesWithValidCredentials() +{ + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 302) { + appendTextToLog(QStringLiteral("Cloud storage connection not working correctly: ") + reply->readAll()); + setStartPageText(RED_FONT + tr("Cannot connect to cloud storage") + END_FONT); + setAccessingCloud(-1); + return; + } + setCredentialStatus(VALID); + appendTextToLog("Cloud credentials valid, loading dives..."); + setStartPageText("Cloud credentials valid, loading dives..."); + git_storage_update_progress(0, "load dives with valid credentials"); + QString url; + if (getCloudURL(url)) { + QString errorString(get_error_string()); + appendTextToLog(errorString); + setStartPageText(RED_FONT + tr("Cloud storage error: %1").arg(errorString) + END_FONT); + setAccessingCloud(-1); + return; + } + QByteArray fileNamePrt = QFile::encodeName(url); + if (check_git_sha(fileNamePrt.data()) == 0) { + qDebug() << "local cache was current, no need to modify dive list"; + appendTextToLog("Cloud sync shows local cache was current"); + setLoadFromCloud(true); + setAccessingCloud(-1); + return; + } + clear_dive_file_data(); + DiveListModel::instance()->clear(); + + int error = parse_file(fileNamePrt.data()); + setAccessingCloud(-1); + if (!error) { + report_error("filename is now %s", fileNamePrt.data()); + const char *error_string = get_error_string(); + appendTextToLog(error_string); + set_filename(fileNamePrt.data(), true); + } else { + report_error("failed to open file %s", fileNamePrt.data()); + QString errorString(get_error_string()); + appendTextToLog(errorString); + setStartPageText(RED_FONT + tr("Cloud storage error: %1").arg(errorString) + END_FONT); + return; + } + prefs.unit_system = informational_prefs.unit_system; + if (informational_prefs.unit_system == IMPERIAL) + informational_prefs.units = IMPERIAL_units; + prefs.units = informational_prefs.units; + process_dives(false, false); + + int i; + struct dive *d; + + for_each_dive (i, d) { + DiveListModel::instance()->addDive(d); + } + appendTextToLog(QStringLiteral("%1 dives loaded").arg(i)); + if (dive_table.nr == 0) + setStartPageText(tr("Cloud storage open successfully. No dives in dive list.")); + setLoadFromCloud(true); +} + +void QMLManager::refreshDiveList() +{ + int i; + struct dive *d; + DiveListModel::instance()->clear(); + for_each_dive (i, d) { + DiveListModel::instance()->addDive(d); + } +} + +// update the dive and return the notes field, stripped of the HTML junk +void QMLManager::commitChanges(QString diveId, QString date, QString location, QString gps, QString duration, QString depth, + QString airtemp, QString watertemp, QString suit, QString buddy, QString diveMaster, QString weight, QString notes, + QString startpressure, QString endpressure, QString gasmix) +{ +#define DROP_EMPTY_PLACEHOLDER(_s) if ((_s) == QLatin1Literal("--")) (_s).clear() + + DROP_EMPTY_PLACEHOLDER(location); + DROP_EMPTY_PLACEHOLDER(duration); + DROP_EMPTY_PLACEHOLDER(depth); + DROP_EMPTY_PLACEHOLDER(airtemp); + DROP_EMPTY_PLACEHOLDER(watertemp); + DROP_EMPTY_PLACEHOLDER(suit); + DROP_EMPTY_PLACEHOLDER(buddy); + DROP_EMPTY_PLACEHOLDER(diveMaster); + DROP_EMPTY_PLACEHOLDER(weight); + DROP_EMPTY_PLACEHOLDER(gasmix); + DROP_EMPTY_PLACEHOLDER(startpressure); + DROP_EMPTY_PLACEHOLDER(endpressure); + DROP_EMPTY_PLACEHOLDER(notes); + +#undef DROP_EMPTY_PLACEHOLDER + + struct dive *d = get_dive_by_uniq_id(diveId.toInt()); + // notes comes back as rich text - let's convert this into plain text + QTextDocument doc; + doc.setHtml(notes); + notes = doc.toPlainText(); + + if (!d) { + qDebug() << "don't touch this... no dive"; + return; + } + bool diveChanged = false; + bool needResort = false; + + invalidate_dive_cache(d); + if (date != get_dive_date_string(d->when)) { + diveChanged = needResort = true; + QDateTime newDate; + // what a pain - Qt will not parse dates if the day of the week is incorrect + // so if the user changed the date but didn't update the day of the week (most likely behavior, actually), + // we need to make sure we don't try to parse that + QString format(QString(prefs.date_format) + QChar(' ') + prefs.time_format); + if (format.contains(QLatin1String("ddd")) || format.contains(QLatin1String("dddd"))) { + QString dateFormatToDrop = format.contains(QLatin1String("ddd")) ? QStringLiteral("ddd") : QStringLiteral("dddd"); + QDateTime ts; + QLocale loc = getLocale(); + ts.setMSecsSinceEpoch(d->when * 1000L); + QString drop = loc.toString(ts.toUTC(), dateFormatToDrop); + format.replace(dateFormatToDrop, ""); + date.replace(drop, ""); + } + newDate = QDateTime::fromString(date, format); + if (!newDate.isValid()) { + qDebug() << "unable to parse date" << date << "with the given format" << format; + QRegularExpression isoDate("\\d+-\\d+-\\d+[^\\d]+\\d+:\\d+"); + if (date.contains(isoDate)) { + newDate = QDateTime::fromString(date, "yyyy-M-d h:m:s"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date, "yy-M-d h:m:s"); + if (newDate.isValid()) + goto parsed; + } + QRegularExpression isoDateNoSecs("\\d+-\\d+-\\d+[^\\d]+\\d+"); + if (date.contains(isoDateNoSecs)) { + newDate = QDateTime::fromString(date, "yyyy-M-d h:m"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date, "yy-M-d h:m"); + if (newDate.isValid()) + goto parsed; + } + QRegularExpression usDate("\\d+/\\d+/\\d+[^\\d]+\\d+:\\d+:\\d+"); + if (date.contains(usDate)) { + newDate = QDateTime::fromString(date, "M/d/yyyy h:m:s"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date, "M/d/yy h:m:s"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date.toLower(), "M/d/yyyy h:m:sap"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date.toLower(), "M/d/yy h:m:sap"); + if (newDate.isValid()) + goto parsed; + } + QRegularExpression usDateNoSecs("\\d+/\\d+/\\d+[^\\d]+\\d+:\\d+"); + if (date.contains(usDateNoSecs)) { + newDate = QDateTime::fromString(date, "M/d/yyyy h:m"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date, "M/d/yy h:m"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date.toLower(), "M/d/yyyy h:map"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date.toLower(), "M/d/yy h:map"); + if (newDate.isValid()) + goto parsed; + } + QRegularExpression leDate("\\d+\\.\\d+\\.\\d+[^\\d]+\\d+:\\d+:\\d+"); + if (date.contains(leDate)) { + newDate = QDateTime::fromString(date, "d.M.yyyy h:m:s"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date, "d.M.yy h:m:s"); + if (newDate.isValid()) + goto parsed; + } + QRegularExpression leDateNoSecs("\\d+\\.\\d+\\.\\d+[^\\d]+\\d+:\\d+"); + if (date.contains(leDateNoSecs)) { + newDate = QDateTime::fromString(date, "d.M.yyyy h:m"); + if (newDate.isValid()) + goto parsed; + newDate = QDateTime::fromString(date, "d.M.yy h:m"); + if (newDate.isValid()) + goto parsed; + } + } +parsed: + if (newDate.isValid()) { + // stupid Qt... two digit years are always 19xx - WTF??? + // so if adding a hundred years gets you into something before a year from now... + // add a hundred years. + if (newDate.addYears(100) < QDateTime::currentDateTime().addYears(1)) + newDate = newDate.addYears(100); + d->dc.when = d->when = newDate.toMSecsSinceEpoch() / 1000 + gettimezoneoffset(newDate.toMSecsSinceEpoch() / 1000); + } else { + qDebug() << "none of our parsing attempts worked for the date string"; + } + } + struct dive_site *ds = get_dive_site_by_uuid(d->dive_site_uuid); + char *locationtext = NULL; + if (ds) + locationtext = ds->name; + if (!same_string(locationtext, qPrintable(location))) { + diveChanged = true; + // this is not ideal - and it's missing the gps information + // but for now let's just create a new dive site + ds = get_dive_site_by_uuid(create_dive_site(qPrintable(location), d->when)); + d->dive_site_uuid = ds->uuid; + } + if (!gps.isEmpty()) { + QString gpsString = getCurrentPosition(); + if (gpsString != QString("waiting for the next gps location")) { + qDebug() << "from commitChanges call to getCurrentPosition returns" << gpsString; + double lat, lon; + if (parseGpsText(qPrintable(gpsString), &lat, &lon)) { + struct dive_site *ds = get_dive_site_by_uuid(d->dive_site_uuid); + if (ds) { + ds->latitude.udeg = lat * 1000000; + ds->longitude.udeg = lon * 1000000; + } else { + degrees_t latData, lonData; + latData.udeg = lat; + lonData.udeg = lon; + d->dive_site_uuid = create_dive_site_with_gps("new site", latData, lonData, d->when); + } + qDebug() << "set up dive site with new GPS data"; + } + } else { + qDebug() << "still don't have a position - will need to implement some sort of callback"; + } + } + if (get_dive_duration_string(d->duration.seconds, tr("h:"), tr("min")) != duration) { + diveChanged = true; + int h = 0, m = 0, s = 0; + QRegExp r1(QStringLiteral("(\\d*)\\s*%1[\\s,:]*(\\d*)\\s*%2[\\s,:]*(\\d*)\\s*%3").arg(tr("h")).arg(tr("min")).arg(tr("sec")), Qt::CaseInsensitive); + QRegExp r2(QStringLiteral("(\\d*)\\s*%1[\\s,:]*(\\d*)\\s*%2").arg(tr("h")).arg(tr("min")), Qt::CaseInsensitive); + QRegExp r3(QStringLiteral("(\\d*)\\s*%1").arg(tr("min")), Qt::CaseInsensitive); + QRegExp r4(QStringLiteral("(\\d*):(\\d*):(\\d*)")); + QRegExp r5(QStringLiteral("(\\d*):(\\d*)")); + QRegExp r6(QStringLiteral("(\\d*)")); + if (r1.indexIn(duration) >= 0) { + h = r1.cap(1).toInt(); + m = r1.cap(2).toInt(); + s = r1.cap(3).toInt(); + } else if (r2.indexIn(duration) >= 0) { + h = r2.cap(1).toInt(); + m = r2.cap(2).toInt(); + } else if (r3.indexIn(duration) >= 0) { + m = r3.cap(1).toInt(); + } else if (r4.indexIn(duration) >= 0) { + h = r4.cap(1).toInt(); + m = r4.cap(2).toInt(); + s = r4.cap(3).toInt(); + } else if (r5.indexIn(duration) >= 0) { + h = r5.cap(1).toInt(); + m = r5.cap(2).toInt(); + } else if (r6.indexIn(duration) >= 0) { + m = r6.cap(1).toInt(); + } + d->dc.duration.seconds = d->duration.seconds = h * 3600 + m * 60 + s; + if (same_string(d->dc.model, "manually added dive")) { + free(d->dc.sample); + d->dc.sample = 0; + d->dc.samples = 0; + } else { + qDebug() << "changing the duration on a dive that wasn't manually added - Uh-oh"; + } + + } + if (get_depth_string(d->maxdepth.mm, true, true) != depth) { + int depthValue = parseLengthToMm(depth); + // the QML code should stop negative depth, but massively huge depth can make + // the profile extremely slow or even run out of memory and crash, so keep + // the depth <= 500m + if (0 <= depthValue && depthValue <= 500000) { + diveChanged = true; + d->maxdepth.mm = depthValue; + if (same_string(d->dc.model, "manually added dive")) { + d->dc.maxdepth.mm = d->maxdepth.mm; + free(d->dc.sample); + d->dc.sample = 0; + d->dc.samples = 0; + } + } + } + if (get_temperature_string(d->airtemp, true) != airtemp) { + diveChanged = true; + d->airtemp.mkelvin = parseTemperatureToMkelvin(airtemp); + } + if (get_temperature_string(d->watertemp, true) != watertemp) { + diveChanged = true; + d->watertemp.mkelvin = parseTemperatureToMkelvin(watertemp); + } + // not sure what we'd do if there was more than one weight system + // defined - for now just ignore that case + if (weightsystem_none((void *)&d->weightsystem[1])) { + if (get_weight_string(d->weightsystem[0].weight, true) != weight) { + diveChanged = true; + d->weightsystem[0].weight.grams = parseWeightToGrams(weight); + } + } + // start and end pressures for first cylinder only + if (get_pressure_string(d->cylinder[0].start, true) != startpressure || get_pressure_string(d->cylinder[0].end, true) != endpressure) { + diveChanged = true; + d->cylinder[0].start.mbar = parsePressureToMbar(startpressure); + d->cylinder[0].end.mbar = parsePressureToMbar(endpressure); + if (d->cylinder[0].end.mbar > d->cylinder[0].start.mbar) + d->cylinder[0].end.mbar = d->cylinder[0].start.mbar; + } + // gasmix for first cylinder + if (get_gas_string(d->cylinder[0].gasmix) != gasmix) { + int o2 = parseGasMixO2(gasmix); + int he = parseGasMixHE(gasmix); + // the QML code SHOULD only accept valid gas mixes, but just to make sure + if (o2 >= 0 && o2 <= 1000 && + he >= 0 && he <= 1000 && + o2 + he <= 1000) { + diveChanged = true; + d->cylinder[0].gasmix.o2.permille = o2; + d->cylinder[0].gasmix.he.permille = he; + } + } + if (!same_string(d->suit, qPrintable(suit))) { + diveChanged = true; + free(d->suit); + d->suit = strdup(qPrintable(suit)); + } + if (!same_string(d->buddy, qPrintable(buddy))) { + diveChanged = true; + free(d->buddy); + d->buddy = strdup(qPrintable(buddy)); + } + if (!same_string(d->divemaster, qPrintable(diveMaster))) { + diveChanged = true; + free(d->divemaster); + d->divemaster = strdup(qPrintable(diveMaster)); + } + if (!same_string(d->notes, qPrintable(notes))) { + diveChanged = true; + free(d->notes); + d->notes = strdup(qPrintable(notes)); + } + // now that we have it all figured out, let's see what we need + // to update + DiveListModel *dm = DiveListModel::instance(); + int oldModelIdx = dm->getDiveIdx(d->id); + int oldIdx = get_idx_by_uniq_id(d->id); + if (needResort) { + // we know that the only thing that might happen in a resort is that + // this one dive moves to a different spot in the dive list + sort_table(&dive_table); + int newIdx = get_idx_by_uniq_id(d->id); + if (newIdx != oldIdx) { + DiveObjectHelper *newDive = new DiveObjectHelper(d); + DiveListModel::instance()->removeDive(oldModelIdx); + DiveListModel::instance()->insertDive(oldModelIdx - (newIdx - oldIdx), newDive); + diveChanged = false; // because we already modified things + } + } + if (diveChanged) { + if (d->maxdepth.mm == d->dc.maxdepth.mm && + d->maxdepth.mm > 0 && + same_string(d->dc.model, "manually added dive") && + d->dc.samples == 0) { + // so we have depth > 0, a manually added dive and no samples + // let's create an actual profile so the desktop version can work it + // first clear out the mean depth (or the fake_dc() function tries + // to be too clever + d->meandepth.mm = d->dc.meandepth.mm = 0; + d->dc = *fake_dc(&d->dc, true); + } + DiveListModel::instance()->updateDive(oldModelIdx, d); + } + if (diveChanged || needResort) + // we no longer save right away, but only the next time the app is not + // in the foreground (or when explicitly requested) + mark_divelist_changed(true); + +} + +void QMLManager::saveChanges() +{ + if (!loadFromCloud()) { + appendTextToLog("Don't save dives without loading from the cloud, first."); + return; + } + appendTextToLog("Saving dives."); + git_storage_update_progress(0, "saveChanges"); // reset the timers + QString fileName; + if (getCloudURL(fileName)) { + appendTextToLog(get_error_string()); + return; + } + if (prefs.git_local_only == false) { + setAccessingCloud(0); + qApp->processEvents(); // make sure that the notification is actually shown + } + if (save_dives(fileName.toUtf8().data())) { + appendTextToLog(get_error_string()); + setAccessingCloud(-1); + return; + } + setAccessingCloud(-1); + appendTextToLog("Updated dive list saved."); + set_filename(fileName.toUtf8().data(), true); + mark_divelist_changed(false); +} + +void QMLManager::undoDelete(int id) +{ + if (!deletedDive || deletedDive->id != id) { + qDebug() << "can't find the deleted dive"; + return; + } + if (deletedTrip) + insert_trip(&deletedTrip); + if (deletedDive->divetrip) { + struct dive_trip *trip = deletedDive->divetrip; + tripflag_t tripflag = deletedDive->tripflag; // this gets overwritten in add_dive_to_trip() + deletedDive->divetrip = NULL; + deletedDive->next = NULL; + deletedDive->pprev = NULL; + add_dive_to_trip(deletedDive, trip); + deletedDive->tripflag = tripflag; + } + record_dive(deletedDive); + DiveListModel::instance()->addDive(deletedDive); + // make sure the changes get saved if the app is no longer in the foreground + // or if the user requests a save + mark_divelist_changed(true); + deletedDive = NULL; + deletedTrip = NULL; +} + +void QMLManager::deleteDive(int id) +{ + struct dive *d = get_dive_by_uniq_id(id); + if (!d) { + qDebug() << "oops, trying to delete non-existing dive"; + return; + } + // clean up (or create) the storage for the deleted dive and trip (if applicable) + if (!deletedDive) + deletedDive = alloc_dive(); + else + clear_dive(deletedDive); + copy_dive(d, deletedDive); + if (!deletedTrip) { + deletedTrip = (struct dive_trip *)calloc(1, sizeof(struct dive_trip)); + } else { + free(deletedTrip->location); + free(deletedTrip->notes); + memset(deletedTrip, 0, sizeof(struct dive_trip)); + } + // if this is the last dive in that trip, remember the trip as well + if (d->divetrip && d->divetrip->nrdives == 1) { + *deletedTrip = *d->divetrip; + deletedTrip->location = copy_string(d->divetrip->location); + deletedTrip->notes = copy_string(d->divetrip->notes); + deletedTrip->nrdives = 0; + deletedDive->divetrip = deletedTrip; + } + DiveListModel::instance()->removeDiveById(id); + delete_single_dive(get_idx_by_uniq_id(id)); + // make sure the changes get saved if the app is no longer in the foreground + // or if the user requests a save + mark_divelist_changed(true); +} + +QString QMLManager::addDive() +{ + appendTextToLog("Adding new dive."); + return DiveListModel::instance()->startAddDive(); +} + +void QMLManager::addDiveAborted(int id) +{ + DiveListModel::instance()->removeDiveById(id); +} + +QString QMLManager::getCurrentPosition() +{ + return locationProvider->currentPosition(); +} + +void QMLManager::applyGpsData() +{ + if (locationProvider->applyLocations()) + refreshDiveList(); +} + +void QMLManager::sendGpsData() +{ + locationProvider->uploadToServer(); +} + +void QMLManager::downloadGpsData() +{ + locationProvider->downloadFromServer(); + populateGpsData(); +} + +void QMLManager::populateGpsData() +{ + if (GpsListModel::instance()) + GpsListModel::instance()->update(); +} + +void QMLManager::clearGpsData() +{ + locationProvider->clearGpsData(); + populateGpsData(); +} + +void QMLManager::deleteGpsFix(quint64 when) +{ + locationProvider->deleteGpsFix(when); + populateGpsData(); +} + + +QString QMLManager::logText() const +{ + QString logText = m_logText + QString("\nNumer of GPS fixes: %1").arg(locationProvider->getGpsNum()); + return logText; +} + +void QMLManager::setLogText(const QString &logText) +{ + m_logText = logText; + emit logTextChanged(); +} + +void QMLManager::appendTextToLog(const QString &newText) +{ + m_logText += "\n" + newText; + emit logTextChanged(); +} + +bool QMLManager::locationServiceEnabled() const +{ + return m_locationServiceEnabled; +} + +void QMLManager::setLocationServiceEnabled(bool locationServiceEnabled) +{ + m_locationServiceEnabled = locationServiceEnabled; + locationProvider->serviceEnable(m_locationServiceEnabled); +} + +bool QMLManager::verboseEnabled() const +{ + return m_verboseEnabled; +} + +void QMLManager::setVerboseEnabled(bool verboseMode) +{ + m_verboseEnabled = verboseMode; + verbose = verboseMode; + qDebug() << "verbose is" << verbose; + emit verboseEnabledChanged(); +} + +QString QMLManager::cloudPassword() const +{ + return m_cloudPassword; +} + +void QMLManager::setCloudPassword(const QString &cloudPassword) +{ + m_cloudPassword = cloudPassword; + emit cloudPasswordChanged(); +} + +QString QMLManager::cloudUserName() const +{ + return m_cloudUserName; +} + +void QMLManager::setCloudUserName(const QString &cloudUserName) +{ + m_cloudUserName = cloudUserName.toLower(); + emit cloudUserNameChanged(); +} + +int QMLManager::distanceThreshold() const +{ + return m_distanceThreshold; +} + +void QMLManager::setDistanceThreshold(int distance) +{ + m_distanceThreshold = distance; + emit distanceThresholdChanged(); +} + +int QMLManager::timeThreshold() const +{ + return m_timeThreshold; +} + +void QMLManager::setTimeThreshold(int time) +{ + m_timeThreshold = time; + emit timeThresholdChanged(); +} + +bool QMLManager::loadFromCloud() const +{ + return m_loadFromCloud; +} + +void QMLManager::syncLoadFromCloud() +{ + QSettings s; + QString cloudMarker = QLatin1Literal("loadFromCloud") + QString(prefs.cloud_storage_email); + m_loadFromCloud = s.contains(cloudMarker) && s.value(cloudMarker).toBool(); +} + +void QMLManager::setLoadFromCloud(bool done) +{ + QSettings s; + QString cloudMarker = QLatin1Literal("loadFromCloud") + QString(prefs.cloud_storage_email); + s.setValue(cloudMarker, done); + m_loadFromCloud = done; + emit loadFromCloudChanged(); +} + +QString QMLManager::startPageText() const +{ + return m_startPageText; +} + +void QMLManager::setStartPageText(const QString& text) +{ + m_startPageText = text; + emit startPageTextChanged(); +} + +// this is an enum, but I don't know how to do enums in QML +QMLManager::credentialStatus_t QMLManager::credentialStatus() const +{ + return m_credentialStatus; +} + +void QMLManager::setCredentialStatus(const credentialStatus_t value) +{ + if (m_credentialStatus != value) { + m_credentialStatus = value; + emit credentialStatusChanged(); + } +} + +// where in the QML dive list is that dive? +int QMLManager::getIndex(const QString &diveId) +{ + int dive_id = diveId.toInt(); + int idx = DiveListModel::instance()->getDiveIdx(dive_id); + return idx; +} + +QString QMLManager::getNumber(const QString& diveId) +{ + int dive_id = diveId.toInt(); + struct dive *d = get_dive_by_uniq_id(dive_id); + QString number; + if (d) + number = QString::number(d->number); + return number; +} + +QString QMLManager::getDate(const QString& diveId) +{ + int dive_id = diveId.toInt(); + struct dive *d = get_dive_by_uniq_id(dive_id); + QString datestring; + if (d) + datestring = get_dive_date_string(d->when); + return datestring; +} + +QString QMLManager::getVersion() const +{ + QRegExp versionRe(".*:([()\\.,\\d]+).*"); + if (!versionRe.exactMatch(getUserAgent())) + return QString(); + + return versionRe.cap(1); +} + +int QMLManager::accessingCloud() const +{ + return m_accessingCloud; +} + +void QMLManager::setAccessingCloud(int status) +{ + m_accessingCloud = status; + emit accessingCloudChanged(); +} + +bool QMLManager::syncToCloud() const +{ + return m_syncToCloud; +} + +void QMLManager::setSyncToCloud(bool status) +{ + m_syncToCloud = status; + prefs.git_local_only = !status; + prefs.cloud_background_sync = status; + QSettings s; + s.beginGroup("CloudStorage"); + s.setValue("git_local_only", prefs.git_local_only); + s.setValue("cloud_background_sync", prefs.cloud_background_sync); + emit syncToCloudChanged(); +} + +qreal QMLManager::lastDevicePixelRatio() +{ + return m_lastDevicePixelRatio; +} + +void QMLManager::screenChanged(QScreen *screen) +{ + m_lastDevicePixelRatio = screen->devicePixelRatio(); + emit sendScreenChanged(screen); +} diff --git a/mobile-widgets/qmlmanager.h b/mobile-widgets/qmlmanager.h new file mode 100644 index 000000000..bd55f68e4 --- /dev/null +++ b/mobile-widgets/qmlmanager.h @@ -0,0 +1,162 @@ +#ifndef QMLMANAGER_H +#define QMLMANAGER_H + +#include +#include +#include +#include +#include + +#include "core/gpslocation.h" + +class QMLManager : public QObject { + Q_OBJECT + Q_ENUMS(credentialStatus_t) + Q_PROPERTY(QString cloudUserName READ cloudUserName WRITE setCloudUserName NOTIFY cloudUserNameChanged) + Q_PROPERTY(QString cloudPassword READ cloudPassword WRITE setCloudPassword NOTIFY cloudPasswordChanged) + Q_PROPERTY(QString logText READ logText WRITE setLogText NOTIFY logTextChanged) + Q_PROPERTY(bool locationServiceEnabled READ locationServiceEnabled WRITE setLocationServiceEnabled NOTIFY locationServiceEnabledChanged) + Q_PROPERTY(int distanceThreshold READ distanceThreshold WRITE setDistanceThreshold NOTIFY distanceThresholdChanged) + Q_PROPERTY(int timeThreshold READ timeThreshold WRITE setTimeThreshold NOTIFY timeThresholdChanged) + Q_PROPERTY(bool loadFromCloud READ loadFromCloud WRITE setLoadFromCloud NOTIFY loadFromCloudChanged) + Q_PROPERTY(QString startPageText READ startPageText WRITE setStartPageText NOTIFY startPageTextChanged) + Q_PROPERTY(bool verboseEnabled READ verboseEnabled WRITE setVerboseEnabled NOTIFY verboseEnabledChanged) + Q_PROPERTY(credentialStatus_t credentialStatus READ credentialStatus WRITE setCredentialStatus NOTIFY credentialStatusChanged) + Q_PROPERTY(int accessingCloud READ accessingCloud WRITE setAccessingCloud NOTIFY accessingCloudChanged) + Q_PROPERTY(bool syncToCloud READ syncToCloud WRITE setSyncToCloud NOTIFY syncToCloudChanged) + +public: + QMLManager(); + ~QMLManager(); + + enum credentialStatus_t { + INCOMPLETE, + UNKNOWN, + INVALID, + VALID_EMAIL, + VALID + }; + + static QMLManager *instance(); + + QString cloudUserName() const; + void setCloudUserName(const QString &cloudUserName); + + QString cloudPassword() const; + void setCloudPassword(const QString &cloudPassword); + + bool locationServiceEnabled() const; + void setLocationServiceEnabled(bool locationServiceEnable); + + bool verboseEnabled() const; + void setVerboseEnabled(bool verboseMode); + + int distanceThreshold() const; + void setDistanceThreshold(int distance); + + int timeThreshold() const; + void setTimeThreshold(int time); + + bool loadFromCloud() const; + void setLoadFromCloud(bool done); + void syncLoadFromCloud(); + + QString startPageText() const; + void setStartPageText(const QString& text); + + credentialStatus_t credentialStatus() const; + void setCredentialStatus(const credentialStatus_t value); + + QString logText() const; + void setLogText(const QString &logText); + + int accessingCloud() const; + void setAccessingCloud(int status); + + bool syncToCloud() const; + void setSyncToCloud(bool status); + + typedef void (QMLManager::*execute_function_type)(); + +public slots: + void applicationStateChanged(Qt::ApplicationState state); + void savePreferences(); + void saveCloudCredentials(); + void checkCredentialsAndExecute(execute_function_type execute); + void tryRetrieveDataFromBackend(); + void handleError(QNetworkReply::NetworkError nError); + void handleSslErrors(const QList &errors); + void retrieveUserid(); + void loadDivesWithValidCredentials(); + void loadDiveProgress(int percent); + void provideAuth(QNetworkReply *reply, QAuthenticator *auth); + void commitChanges(QString diveId, QString date, QString location, + QString gps, QString duration, QString depth, + QString airtemp, QString watertemp, QString suit, + QString buddy, QString diveMaster, QString weight, QString notes, + QString startpressure, QString endpressure, QString gasmix); + + void saveChanges(); + void deleteDive(int id); + void undoDelete(int id); + QString addDive(); + void addDiveAborted(int id); + void applyGpsData(); + void sendGpsData(); + void downloadGpsData(); + void populateGpsData(); + void clearGpsData(); + void finishSetup(); + void openLocalThenRemote(QString url); + int getIndex(const QString& diveId); + QString getNumber(const QString& diveId); + QString getDate(const QString& diveId); + QString getCurrentPosition(); + QString getVersion() const; + void deleteGpsFix(quint64 when); + void refreshDiveList(); + void screenChanged(QScreen *screen); + qreal lastDevicePixelRatio(); + void appendTextToLog(const QString &newText); + +private: + QString m_cloudUserName; + QString m_cloudPassword; + QString m_ssrfGpsWebUserid; + QString m_startPageText; + QString m_logText; + bool m_locationServiceEnabled; + bool m_verboseEnabled; + int m_distanceThreshold; + int m_timeThreshold; + GpsLocation *locationProvider; + bool m_loadFromCloud; + static QMLManager *m_instance; + QNetworkReply *reply; + QNetworkRequest request; + struct dive *deletedDive; + struct dive_trip *deletedTrip; + int m_accessingCloud; + bool m_syncToCloud; + credentialStatus_t m_credentialStatus; + qreal m_lastDevicePixelRatio; + QElapsedTimer timer; + bool alreadySaving; + +signals: + void cloudUserNameChanged(); + void cloudPasswordChanged(); + void locationServiceEnabledChanged(); + void verboseEnabledChanged(); + void logTextChanged(); + void timeThresholdChanged(); + void distanceThresholdChanged(); + void loadFromCloudChanged(); + void startPageTextChanged(); + void credentialStatusChanged(); + void accessingCloudChanged(); + void syncToCloudChanged(); + void sendScreenChanged(QScreen *screen); +}; + +#endif diff --git a/mobile-widgets/qmlprofile.cpp b/mobile-widgets/qmlprofile.cpp new file mode 100644 index 000000000..b023741ef --- /dev/null +++ b/mobile-widgets/qmlprofile.cpp @@ -0,0 +1,111 @@ +#include "qmlprofile.h" +#include "qmlmanager.h" +#include "profile-widget/profilewidget2.h" +#include "core/dive.h" +#include "core/metrics.h" +#include +#include + +QMLProfile::QMLProfile(QQuickItem *parent) : + QQuickPaintedItem(parent), + m_devicePixelRatio(1.0), + m_margin(0) +{ + setAntialiasing(true); + m_profileWidget = new ProfileWidget2(0); + m_profileWidget->setProfileState(); + m_profileWidget->setPrintMode(true); + m_profileWidget->setFontPrintScale(0.8); + connect(QMLManager::instance(), &QMLManager::sendScreenChanged, this, &QMLProfile::screenChanged); + setDevicePixelRatio(QMLManager::instance()->lastDevicePixelRatio()); +} + +QMLProfile::~QMLProfile() +{ + m_profileWidget->deleteLater(); +} + +void QMLProfile::paint(QPainter *painter) +{ + // let's look at the intended size of the content and scale our scene accordingly + QRect painterRect = painter->viewport(); + QRect profileRect = m_profileWidget->viewport()->rect(); + // qDebug() << "profile viewport and painter viewport" << profileRect << painterRect; + qreal sceneSize = 104; // that should give us 2% margin all around (100x100 scene) + qreal dprComp = devicePixelRatio() * painterRect.width() / profileRect.width(); + qreal sx = painterRect.width() / sceneSize / dprComp; + qreal sy = painterRect.height() / sceneSize / dprComp; + + // next figure out the weird magic by which we need to shift the painter so the profile is shown + int dpr = rint(devicePixelRatio()); + qreal magicShiftFactor = (dpr == 2 ? 0.25 : (dpr == 3 ? 0.33 : 0.0)); + + // now set up the transformations scale the profile and + // shift the painter (taking its existing transformation into account) + QTransform profileTransform = QTransform(); + profileTransform.scale(sx, sy); + QTransform painterTransform = painter->transform(); + painterTransform.translate(-painterRect.width() * magicShiftFactor ,-painterRect.height() * magicShiftFactor); + +#if PROFILE_SCALING_DEBUG + // some debugging messages to help adjust this in case the magic above is insufficient + QMLManager::instance()->appendTextToLog(QString("dpr %1 profile viewport %2 %3 painter viewport %4 %5").arg(dpr).arg(profileRect.width()).arg(profileRect.height()) + .arg(painterRect.width()).arg(painterRect.height())); + QMLManager::instance()->appendTextToLog(QString("profile matrix %1 %2 %3 %4 %5 %6 %7 %8 %9").arg(profileTransform.m11()).arg(profileTransform.m12()).arg(profileTransform.m13()) + .arg(profileTransform.m21()).arg(profileTransform.m22()).arg(profileTransform.m23()) + .arg(profileTransform.m31()).arg(profileTransform.m32()).arg(profileTransform.m33())); + QMLManager::instance()->appendTextToLog(QString("painter matrix %1 %2 %3 %4 %5 %6 %7 %8 %9").arg(painterTransform.m11()).arg(painterTransform.m12()).arg(painterTransform.m13()) + .arg(painterTransform.m21()).arg(painterTransform.m22()).arg(painterTransform.m23()) + .arg(painterTransform.m31()).arg(painterTransform.m32()).arg(painterTransform.m33())); + qDebug() << "profile scaled by" << profileTransform.m11() << profileTransform.m22() << "and translated by" << profileTransform.m31() << profileTransform.m32(); + qDebug() << "exist profile transform" << m_profileWidget->transform() << "painter transform" << painter->transform(); +#endif + // apply the transformation + painter->setTransform(painterTransform); + m_profileWidget->setTransform(profileTransform); + + // finally, render the profile + m_profileWidget->render(painter); +} + +void QMLProfile::setMargin(int margin) +{ + m_margin = margin; +} + +QString QMLProfile::diveId() const +{ + return m_diveId; +} + +void QMLProfile::setDiveId(const QString &diveId) +{ + m_diveId = diveId; + struct dive *d = get_dive_by_uniq_id(m_diveId.toInt()); + if (m_diveId.toInt() < 1) + return; + if (!d) + return; + qDebug() << "setDiveId called with valid dive" << d->number; + m_profileWidget->plotDive(d, true); +} + +qreal QMLProfile::devicePixelRatio() const +{ + return m_devicePixelRatio; +} + +void QMLProfile::setDevicePixelRatio(qreal dpr) +{ + if (dpr != m_devicePixelRatio) { + m_devicePixelRatio = dpr; + m_profileWidget->setFontPrintScale(0.8 * dpr); + updateDevicePixelRatio(dpr); + emit devicePixelRatioChanged(); + } +} + +void QMLProfile::screenChanged(QScreen *screen) +{ + setDevicePixelRatio(screen->devicePixelRatio()); +} diff --git a/mobile-widgets/qmlprofile.h b/mobile-widgets/qmlprofile.h new file mode 100644 index 000000000..c8a77d700 --- /dev/null +++ b/mobile-widgets/qmlprofile.h @@ -0,0 +1,40 @@ +#ifndef QMLPROFILE_H +#define QMLPROFILE_H + +#include + +class ProfileWidget2; + +class QMLProfile : public QQuickPaintedItem +{ + Q_OBJECT + Q_PROPERTY(QString diveId READ diveId WRITE setDiveId NOTIFY diveIdChanged) + Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged) + +public: + explicit QMLProfile(QQuickItem *parent = 0); + virtual ~QMLProfile(); + + void paint(QPainter *painter); + + QString diveId() const; + void setDiveId(const QString &diveId); + qreal devicePixelRatio() const; + void setDevicePixelRatio(qreal dpr); + +public slots: + void setMargin(int margin); + void screenChanged(QScreen *screen); +private: + QString m_diveId; + qreal m_devicePixelRatio; + int m_margin; + ProfileWidget2 *m_profileWidget; + +signals: + void rightAlignedChanged(); + void diveIdChanged(); + void devicePixelRatioChanged(); +}; + +#endif // QMLPROFILE_H diff --git a/packaging/ios/Subsurface-mobile/Subsurface-mobile.pro b/packaging/ios/Subsurface-mobile/Subsurface-mobile.pro index e3f657eb2..27125fe50 100644 --- a/packaging/ios/Subsurface-mobile/Subsurface-mobile.pro +++ b/packaging/ios/Subsurface-mobile/Subsurface-mobile.pro @@ -8,64 +8,64 @@ CONFIG += c++11 SOURCES += ../../../subsurface-mobile-main.cpp \ ../../../subsurface-mobile-helper.cpp \ - ../../../subsurface-core/cloudstorage.cpp \ - ../../../subsurface-core/configuredivecomputerthreads.cpp \ - ../../../subsurface-core/devicedetails.cpp \ - ../../../subsurface-core/gpslocation.cpp \ - ../../../subsurface-core/imagedownloader.cpp \ - ../../../subsurface-core/qthelper.cpp \ - ../../../subsurface-core/checkcloudconnection.cpp \ - ../../../subsurface-core/color.cpp \ - ../../../subsurface-core/configuredivecomputer.cpp \ - ../../../subsurface-core/divecomputer.cpp \ - ../../../subsurface-core/divelogexportlogic.cpp \ - ../../../subsurface-core/divesite.cpp \ - ../../../subsurface-core/divesitehelpers.cpp \ - ../../../subsurface-core/exif.cpp \ - ../../../subsurface-core/gettextfromc.cpp \ - ../../../subsurface-core/isocialnetworkintegration.cpp \ - ../../../subsurface-core/metrics.cpp \ - ../../../subsurface-core/pluginmanager.cpp \ - ../../../subsurface-core/qt-init.cpp \ - ../../../subsurface-core/subsurfacesysinfo.cpp \ - ../../../subsurface-core/windowtitleupdate.cpp \ - ../../../subsurface-core/file.c \ - ../../../subsurface-core/subsurfacestartup.c \ - ../../../subsurface-core/macos.c \ - ../../../subsurface-core/profile.c \ - ../../../subsurface-core/device.c \ - ../../../subsurface-core/dive.c \ - ../../../subsurface-core/divelist.c \ - ../../../subsurface-core/gas-model.c \ - ../../../subsurface-core/gaspressures.c \ - ../../../subsurface-core/git-access.c \ - ../../../subsurface-core/liquivision.c \ - ../../../subsurface-core/load-git.c \ - ../../../subsurface-core/parse-xml.c \ - ../../../subsurface-core/save-html.c \ - ../../../subsurface-core/statistics.c \ - ../../../subsurface-core/worldmap-save.c \ - ../../../subsurface-core/libdivecomputer.c \ - ../../../subsurface-core/version.c \ - ../../../subsurface-core/save-git.c \ - ../../../subsurface-core/datatrak.c \ - ../../../subsurface-core/ostctools.c \ - ../../../subsurface-core/planner.c \ - ../../../subsurface-core/save-xml.c \ - ../../../subsurface-core/cochran.c \ - ../../../subsurface-core/deco.c \ - ../../../subsurface-core/divesite.c \ - ../../../subsurface-core/equipment.c \ - ../../../subsurface-core/membuffer.c \ - ../../../subsurface-core/sha1.c \ - ../../../subsurface-core/strtod.c \ - ../../../subsurface-core/taxonomy.c \ - ../../../subsurface-core/time.c \ - ../../../subsurface-core/uemis.c \ - ../../../subsurface-core/subsurface-qt/DiveObjectHelper.cpp \ - ../../../subsurface-core/subsurface-qt/SettingsObjectWrapper.cpp \ - ../../../qt-mobile/qmlmanager.cpp \ - ../../../qt-mobile/qmlprofile.cpp \ + ../../../core/cloudstorage.cpp \ + ../../../core/configuredivecomputerthreads.cpp \ + ../../../core/devicedetails.cpp \ + ../../../core/gpslocation.cpp \ + ../../../core/imagedownloader.cpp \ + ../../../core/qthelper.cpp \ + ../../../core/checkcloudconnection.cpp \ + ../../../core/color.cpp \ + ../../../core/configuredivecomputer.cpp \ + ../../../core/divecomputer.cpp \ + ../../../core/divelogexportlogic.cpp \ + ../../../core/divesite.cpp \ + ../../../core/divesitehelpers.cpp \ + ../../../core/exif.cpp \ + ../../../core/gettextfromc.cpp \ + ../../../core/isocialnetworkintegration.cpp \ + ../../../core/metrics.cpp \ + ../../../core/pluginmanager.cpp \ + ../../../core/qt-init.cpp \ + ../../../core/subsurfacesysinfo.cpp \ + ../../../core/windowtitleupdate.cpp \ + ../../../core/file.c \ + ../../../core/subsurfacestartup.c \ + ../../../core/macos.c \ + ../../../core/profile.c \ + ../../../core/device.c \ + ../../../core/dive.c \ + ../../../core/divelist.c \ + ../../../core/gas-model.c \ + ../../../core/gaspressures.c \ + ../../../core/git-access.c \ + ../../../core/liquivision.c \ + ../../../core/load-git.c \ + ../../../core/parse-xml.c \ + ../../../core/save-html.c \ + ../../../core/statistics.c \ + ../../../core/worldmap-save.c \ + ../../../core/libdivecomputer.c \ + ../../../core/version.c \ + ../../../core/save-git.c \ + ../../../core/datatrak.c \ + ../../../core/ostctools.c \ + ../../../core/planner.c \ + ../../../core/save-xml.c \ + ../../../core/cochran.c \ + ../../../core/deco.c \ + ../../../core/divesite.c \ + ../../../core/equipment.c \ + ../../../core/membuffer.c \ + ../../../core/sha1.c \ + ../../../core/strtod.c \ + ../../../core/taxonomy.c \ + ../../../core/time.c \ + ../../../core/uemis.c \ + ../../../core/subsurface-qt/DiveObjectHelper.cpp \ + ../../../core/subsurface-qt/SettingsObjectWrapper.cpp \ + ../../../mobile-widgets/qmlmanager.cpp \ + ../../../mobile-widgets/qmlprofile.cpp \ ../../../qt-models/cylindermodel.cpp \ ../../../qt-models/divelistmodel.cpp \ ../../../qt-models/diveplotdatamodel.cpp \ @@ -100,7 +100,7 @@ SOURCES += ../../../subsurface-mobile-main.cpp \ ../../../profile-widget/diverectitem.cpp \ ../../../profile-widget/divetextitem.cpp -RESOURCES += qml.qrc ../../../subsurface.qrc ../../../qt-mobile/qml/mobile-resources.qrc +RESOURCES += qml.qrc ../../../subsurface.qrc ../../../mobile-widgets/qml/mobile-resources.qrc LIBS += ../install-root/lib/libcrypto.a \ ../install-root/lib/libdivecomputer.a \ @@ -120,65 +120,65 @@ INCLUDEPATH += ../install-root/include/ \ ../install-root/include/libexstl \ ../install-root/include/openssl \ ../../.. \ - ../../../subsurface-core + ../../../core HEADERS += \ - ../../../subsurface-core/libdivecomputer.h \ - ../../../subsurface-core/cloudstorage.h \ - ../../../subsurface-core/configuredivecomputerthreads.h \ - ../../../subsurface-core/device.h \ - ../../../subsurface-core/devicedetails.h \ - ../../../subsurface-core/dive.h \ - ../../../subsurface-core/git-access.h \ - ../../../subsurface-core/gpslocation.h \ - ../../../subsurface-core/helpers.h \ - ../../../subsurface-core/imagedownloader.h \ - ../../../subsurface-core/pref.h \ - ../../../subsurface-core/profile.h \ - ../../../subsurface-core/qthelper.h \ - ../../../subsurface-core/save-html.h \ - ../../../subsurface-core/statistics.h \ - ../../../subsurface-core/units.h \ - ../../../subsurface-core/qthelperfromc.h \ - ../../../subsurface-core/version.h \ - ../../../subsurface-core/planner.h \ - ../../../subsurface-core/divesite.h \ - ../../../subsurface-core/checkcloudconnection.h \ - ../../../subsurface-core/cochran.h \ - ../../../subsurface-core/color.h \ - ../../../subsurface-core/configuredivecomputer.h \ - ../../../subsurface-core/datatrak.h \ - ../../../subsurface-core/deco.h \ - ../../../subsurface-core/display.h \ - ../../../subsurface-core/divecomputer.h \ - ../../../subsurface-core/divelist.h \ - ../../../subsurface-core/divelogexportlogic.h \ - ../../../subsurface-core/divesitehelpers.h \ - ../../../subsurface-core/exif.h \ - ../../../subsurface-core/file.h \ - ../../../subsurface-core/gaspressures.h \ - ../../../subsurface-core/gettext.h \ - ../../../subsurface-core/gettextfromc.h \ - ../../../subsurface-core/isocialnetworkintegration.h \ - ../../../subsurface-core/membuffer.h \ - ../../../subsurface-core/metrics.h \ - ../../../subsurface-core/pluginmanager.h \ - ../../../subsurface-core/prefs-macros.h \ - ../../../subsurface-core/qt-gui.h \ - ../../../subsurface-core/sha1.h \ - ../../../subsurface-core/strndup.h \ - ../../../subsurface-core/subsurfacestartup.h \ - ../../../subsurface-core/subsurfacesysinfo.h \ - ../../../subsurface-core/taxonomy.h \ - ../../../subsurface-core/uemis.h \ - ../../../subsurface-core/webservice.h \ - ../../../subsurface-core/windowtitleupdate.h \ - ../../../subsurface-core/worldmap-options.h \ - ../../../subsurface-core/worldmap-save.h \ - ../../../subsurface-core/subsurface-qt/DiveObjectHelper.h \ - ../../../subsurface-core/subsurface-qt/SettingsObjectWrapper.h \ - ../../../qt-mobile/qmlmanager.h \ - ../../../qt-mobile/qmlprofile.h \ + ../../../core/libdivecomputer.h \ + ../../../core/cloudstorage.h \ + ../../../core/configuredivecomputerthreads.h \ + ../../../core/device.h \ + ../../../core/devicedetails.h \ + ../../../core/dive.h \ + ../../../core/git-access.h \ + ../../../core/gpslocation.h \ + ../../../core/helpers.h \ + ../../../core/imagedownloader.h \ + ../../../core/pref.h \ + ../../../core/profile.h \ + ../../../core/qthelper.h \ + ../../../core/save-html.h \ + ../../../core/statistics.h \ + ../../../core/units.h \ + ../../../core/qthelperfromc.h \ + ../../../core/version.h \ + ../../../core/planner.h \ + ../../../core/divesite.h \ + ../../../core/checkcloudconnection.h \ + ../../../core/cochran.h \ + ../../../core/color.h \ + ../../../core/configuredivecomputer.h \ + ../../../core/datatrak.h \ + ../../../core/deco.h \ + ../../../core/display.h \ + ../../../core/divecomputer.h \ + ../../../core/divelist.h \ + ../../../core/divelogexportlogic.h \ + ../../../core/divesitehelpers.h \ + ../../../core/exif.h \ + ../../../core/file.h \ + ../../../core/gaspressures.h \ + ../../../core/gettext.h \ + ../../../core/gettextfromc.h \ + ../../../core/isocialnetworkintegration.h \ + ../../../core/membuffer.h \ + ../../../core/metrics.h \ + ../../../core/pluginmanager.h \ + ../../../core/prefs-macros.h \ + ../../../core/qt-gui.h \ + ../../../core/sha1.h \ + ../../../core/strndup.h \ + ../../../core/subsurfacestartup.h \ + ../../../core/subsurfacesysinfo.h \ + ../../../core/taxonomy.h \ + ../../../core/uemis.h \ + ../../../core/webservice.h \ + ../../../core/windowtitleupdate.h \ + ../../../core/worldmap-options.h \ + ../../../core/worldmap-save.h \ + ../../../core/subsurface-qt/DiveObjectHelper.h \ + ../../../core/subsurface-qt/SettingsObjectWrapper.h \ + ../../../mobile-widgets/qmlmanager.h \ + ../../../mobile-widgets/qmlprofile.h \ ../../../qt-models/divelistmodel.h \ ../../../qt-models/diveplotdatamodel.h \ ../../../qt-models/gpslistmodel.h \ diff --git a/profile-widget/animationfunctions.cpp b/profile-widget/animationfunctions.cpp index a19d50c9d..bf47f3204 100644 --- a/profile-widget/animationfunctions.cpp +++ b/profile-widget/animationfunctions.cpp @@ -1,5 +1,5 @@ -#include "animationfunctions.h" -#include "pref.h" +#include "profile-widget/animationfunctions.h" +#include "core/pref.h" #include namespace Animations { diff --git a/profile-widget/divecartesianaxis.cpp b/profile-widget/divecartesianaxis.cpp index a453d69ea..4270d4b89 100644 --- a/profile-widget/divecartesianaxis.cpp +++ b/profile-widget/divecartesianaxis.cpp @@ -1,14 +1,14 @@ -#include "divecartesianaxis.h" -#include "divetextitem.h" -#include "helpers.h" -#include +#include "profile-widget/divecartesianaxis.h" +#include "profile-widget/divetextitem.h" +#include "core/helpers.h" +#include "core/subsurface-qt/SettingsObjectWrapper.h" #ifndef SUBSURFACE_MOBILE -#include "preferences/preferencesdialog.h" +#include "desktop-widgets/preferences/preferencesdialog.h" #endif -#include "diveplotdatamodel.h" -#include "animationfunctions.h" -#include "divelineitem.h" -#include "profilewidget2.h" +#include "qt-models/diveplotdatamodel.h" +#include "profile-widget/animationfunctions.h" +#include "profile-widget/divelineitem.h" +#include "profile-widget/profilewidget2.h" QPen DiveCartesianAxis::gridPen() { diff --git a/profile-widget/divecartesianaxis.h b/profile-widget/divecartesianaxis.h index a603cebc7..d35af88a1 100644 --- a/profile-widget/divecartesianaxis.h +++ b/profile-widget/divecartesianaxis.h @@ -3,7 +3,7 @@ #include #include -#include "subsurface-core/color.h" +#include "core/color.h" #include "profilewidget2.h" class QPropertyAnimation; diff --git a/profile-widget/diveeventitem.cpp b/profile-widget/diveeventitem.cpp index 3e1de48f3..daef227d8 100644 --- a/profile-widget/diveeventitem.cpp +++ b/profile-widget/diveeventitem.cpp @@ -1,11 +1,11 @@ -#include "diveeventitem.h" -#include "diveplotdatamodel.h" -#include "divecartesianaxis.h" -#include "animationfunctions.h" -#include "libdivecomputer.h" -#include "profile.h" -#include "gettextfromc.h" -#include "metrics.h" +#include "profile-widget/diveeventitem.h" +#include "qt-models/diveplotdatamodel.h" +#include "profile-widget/divecartesianaxis.h" +#include "profile-widget/animationfunctions.h" +#include "core/libdivecomputer.h" +#include "core/profile.h" +#include "core/gettextfromc.h" +#include "core/metrics.h" extern struct ev_select *ev_namelist; extern int evn_used; diff --git a/profile-widget/divepixmapitem.cpp b/profile-widget/divepixmapitem.cpp index 39c41d19d..2373e992d 100644 --- a/profile-widget/divepixmapitem.cpp +++ b/profile-widget/divepixmapitem.cpp @@ -1,9 +1,9 @@ -#include "divepixmapitem.h" -#include "animationfunctions.h" +#include "profile-widget/divepixmapitem.h" +#include "profile-widget/animationfunctions.h" #include "qt-models/divepicturemodel.h" -#include "pref.h" +#include "core/pref.h" #ifndef SUBSURFACE_MOBILE -#include "preferences/preferencesdialog.h" +#include "desktop-widgets/preferences/preferencesdialog.h" #endif #include diff --git a/profile-widget/diveprofileitem.cpp b/profile-widget/diveprofileitem.cpp index 022616ef1..d62c35f93 100644 --- a/profile-widget/diveprofileitem.cpp +++ b/profile-widget/diveprofileitem.cpp @@ -1,18 +1,18 @@ -#include "diveprofileitem.h" -#include "diveplotdatamodel.h" -#include "divecartesianaxis.h" -#include "divetextitem.h" -#include "animationfunctions.h" -#include "dive.h" -#include "profile.h" +#include "profile-widget/diveprofileitem.h" +#include "qt-models/diveplotdatamodel.h" +#include "profile-widget/divecartesianaxis.h" +#include "profile-widget/divetextitem.h" +#include "profile-widget/animationfunctions.h" +#include "core/dive.h" +#include "core/profile.h" #ifndef SUBSURFACE_MOBILE -#include "preferences/preferencesdialog.h" +#include "desktop-widgets/preferences/preferencesdialog.h" #endif #include "qt-models/diveplannermodel.h" -#include "helpers.h" -#include +#include "core/helpers.h" +#include "core/subsurface-qt/SettingsObjectWrapper.h" #include "libdivecomputer/parser.h" -#include "profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include diff --git a/profile-widget/divetextitem.cpp b/profile-widget/divetextitem.cpp index ab816b32a..1f4576dc6 100644 --- a/profile-widget/divetextitem.cpp +++ b/profile-widget/divetextitem.cpp @@ -1,6 +1,6 @@ #include "divetextitem.h" #include "profilewidget2.h" -#include "subsurface-core/color.h" +#include "core/color.h" #include #include diff --git a/profile-widget/divetooltipitem.cpp b/profile-widget/divetooltipitem.cpp index 6d6405056..49e9bdca7 100644 --- a/profile-widget/divetooltipitem.cpp +++ b/profile-widget/divetooltipitem.cpp @@ -1,19 +1,14 @@ -#include "divetooltipitem.h" -#include "divecartesianaxis.h" -#include "dive.h" -#include "profile.h" -#include "membuffer.h" -#include "metrics.h" +#include "profile-widget/divetooltipitem.h" +#include "profile-widget/divecartesianaxis.h" +#include "core/dive.h" +#include "core/profile.h" +#include "core/membuffer.h" +#include "core/metrics.h" #include #include #include #include -#define PORT_IN_PROGRESS 1 -#ifdef PORT_IN_PROGRESS -#include "display.h" -#endif - void ToolTipItem::addToolTip(const QString &toolTip, const QIcon &icon, const QPixmap& pixmap) { const IconMetrics& iconMetrics = defaultIconMetrics(); diff --git a/profile-widget/divetooltipitem.h b/profile-widget/divetooltipitem.h index 4fa7ec2d7..a26593a18 100644 --- a/profile-widget/divetooltipitem.h +++ b/profile-widget/divetooltipitem.h @@ -7,7 +7,7 @@ #include #include #include -#include "display.h" +#include "core/display.h" class DiveCartesianAxis; class QGraphicsLineItem; diff --git a/profile-widget/profilewidget2.cpp b/profile-widget/profilewidget2.cpp index 35a9594a3..5aa0c53a5 100644 --- a/profile-widget/profilewidget2.cpp +++ b/profile-widget/profilewidget2.cpp @@ -1,24 +1,24 @@ -#include "profilewidget2.h" +#include "profile-widget/profilewidget2.h" #include "qt-models/diveplotdatamodel.h" -#include "helpers.h" -#include "profile.h" -#include "diveeventitem.h" -#include "divetextitem.h" -#include "divetooltipitem.h" -#include "planner.h" -#include "device.h" -#include "ruleritem.h" -#include "tankitem.h" -#include "pref.h" +#include "core/helpers.h" +#include "core/profile.h" +#include "profile-widget/diveeventitem.h" +#include "profile-widget/divetextitem.h" +#include "profile-widget/divetooltipitem.h" +#include "core/planner.h" +#include "core/device.h" +#include "profile-widget/ruleritem.h" +#include "profile-widget/tankitem.h" +#include "core/pref.h" #include "qt-models/diveplannermodel.h" #include "qt-models/models.h" #include "qt-models/divepicturemodel.h" -#include "divelist.h" -#include +#include "core/divelist.h" +#include "core/subsurface-qt/SettingsObjectWrapper.h" #ifndef SUBSURFACE_MOBILE -#include "diveplanner.h" -#include "simplewidgets.h" -#include "divepicturewidget.h" +#include "desktop-widgets/diveplanner.h" +#include "desktop-widgets/simplewidgets.h" +#include "desktop-widgets/divepicturewidget.h" #endif #include @@ -35,7 +35,7 @@ #include #endif #ifndef SUBSURFACE_MOBILE -#include "preferences/preferencesdialog.h" +#include "desktop-widgets/preferences/preferencesdialog.h" #endif #include diff --git a/profile-widget/profilewidget2.h b/profile-widget/profilewidget2.h index 5e05b14f8..d745517f1 100644 --- a/profile-widget/profilewidget2.h +++ b/profile-widget/profilewidget2.h @@ -13,9 +13,9 @@ // * // * It needs to be dynamic, things should *flow* on it, not just appear / disappear. // */ -#include "divelineitem.h" -#include "diveprofileitem.h" -#include "display.h" +#include "profile-widget/divelineitem.h" +#include "profile-widget/diveprofileitem.h" +#include "core/display.h" class RulerItem2; struct dive; diff --git a/profile-widget/ruleritem.cpp b/profile-widget/ruleritem.cpp index ce217b918..c5712de54 100644 --- a/profile-widget/ruleritem.cpp +++ b/profile-widget/ruleritem.cpp @@ -1,14 +1,14 @@ -#include "ruleritem.h" +#include "profile-widget/ruleritem.h" #ifndef SUBSURFACE_MOBILE -#include "preferences/preferencesdialog.h" +#include "desktop-widgets/preferences/preferencesdialog.h" #endif -#include "profilewidget2.h" -#include "display.h" -#include "subsurface-core/subsurface-qt/SettingsObjectWrapper.h" +#include "profile-widget/profilewidget2.h" +#include "core/display.h" +#include "core/subsurface-qt/SettingsObjectWrapper.h" #include -#include "profile.h" +#include "core/profile.h" RulerNodeItem2::RulerNodeItem2() : entry(NULL), diff --git a/profile-widget/ruleritem.h b/profile-widget/ruleritem.h index 343a24862..dceab9178 100644 --- a/profile-widget/ruleritem.h +++ b/profile-widget/ruleritem.h @@ -4,8 +4,8 @@ #include #include #include -#include "divecartesianaxis.h" -#include "display.h" +#include "profile-widget/divecartesianaxis.h" +#include "core/display.h" struct plot_data; class RulerItem2; diff --git a/profile-widget/tankitem.cpp b/profile-widget/tankitem.cpp index aba41d660..e4663b8f9 100644 --- a/profile-widget/tankitem.cpp +++ b/profile-widget/tankitem.cpp @@ -1,7 +1,7 @@ -#include "tankitem.h" -#include "diveplotdatamodel.h" -#include "divetextitem.h" -#include "profile.h" +#include "profile-widget/tankitem.h" +#include "qt-models/diveplotdatamodel.h" +#include "profile-widget/divetextitem.h" +#include "core/profile.h" #include TankItem::TankItem(QObject *parent) : diff --git a/profile-widget/tankitem.h b/profile-widget/tankitem.h index fd685fc82..2efcd34a8 100644 --- a/profile-widget/tankitem.h +++ b/profile-widget/tankitem.h @@ -4,9 +4,9 @@ #include #include #include -#include "divelineitem.h" -#include "divecartesianaxis.h" -#include "dive.h" +#include "profile-widget/divelineitem.h" +#include "profile-widget/divecartesianaxis.h" +#include "core/dive.h" class TankItem : public QObject, public QGraphicsRectItem { diff --git a/qt-mobile/qml/About.qml b/qt-mobile/qml/About.qml deleted file mode 100644 index b1ca6e6bc..000000000 --- a/qt-mobile/qml/About.qml +++ /dev/null @@ -1,59 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Layouts 1.1 -import org.kde.kirigami 1.0 as Kirigami -import org.subsurfacedivelog.mobile 1.0 - -Kirigami.ScrollablePage { - id: aboutPage - property int pageWidth: subsurfaceTheme.columnWidth - Kirigami.Units.smallSpacing - title: "About Subsurface-mobile" - - ColumnLayout { - spacing: Kirigami.Units.largeSpacing - width: aboutPage.width - Layout.margins: Kirigami.Units.gridUnit / 2 - - - Kirigami.Heading { - text: "About Subsurface-mobile" - Layout.topMargin: Kirigami.Units.gridUnit - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: pageWidth - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - } - Image { - id: image - source: "qrc:/qml/subsurface-mobile-icon.png" - width: pageWidth / 2 - height: width - fillMode: Image.Stretch - Layout.alignment: Qt.AlignCenter - horizontalAlignment: Image.AlignHCenter - } - - Kirigami.Heading { - text: "A mobile version of the free Subsurface divelog software.\n" + - "View your dive logs while on the go." - level: 4 - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: Kirigami.Units.largeSpacing * 3 - Layout.maximumWidth: pageWidth - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - anchors.horizontalCenter: parent.Center - horizontalAlignment: Text.AlignHCenter - } - - Kirigami.Heading { - text: "Version: " + manager.getVersion() + "\n\n© Subsurface developer team\n2011-2016" - level: 5 - font.pointSize: subsurfaceTheme.smallPointSize + 1 - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: Kirigami.Units.largeSpacing - Layout.maximumWidth: pageWidth - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - anchors.horizontalCenter: parent.Center - horizontalAlignment: Text.AlignHCenter - } - } -} diff --git a/qt-mobile/qml/CloudCredentials.qml b/qt-mobile/qml/CloudCredentials.qml deleted file mode 100644 index aa7c57651..000000000 --- a/qt-mobile/qml/CloudCredentials.qml +++ /dev/null @@ -1,84 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import org.kde.kirigami 1.0 as Kirigami -import org.subsurfacedivelog.mobile 1.0 - -Item { - id: loginWindow - height: outerLayout.height + 2 * Kirigami.Units.gridUnit - - property string username: login.text; - property string password: password.text; - - function saveCredentials() { - manager.cloudUserName = login.text - manager.cloudPassword = password.text - manager.saveCloudCredentials() - } - - ColumnLayout { - id: outerLayout - width: subsurfaceTheme.columnWidth - 2 * Kirigami.Units.gridUnit - - onVisibleChanged: { - if (visible && manager.accessingCloud < 0) { - manager.appendTextToLog("Credential scrn: show kbd was: " + (Qt.inputMethod.isVisible ? "visible" : "invisible")) - Qt.inputMethod.show() - login.forceActiveFocus() - } else { - manager.appendTextToLog("Credential scrn: hide kbd was: " + (Qt.inputMethod.isVisible ? "visible" : "invisible")) - Qt.inputMethod.hide() - } - } - - Kirigami.Heading { - text: "Cloud credentials" - level: headingLevel - Layout.bottomMargin: Kirigami.Units.largeSpacing / 2 - } - - Kirigami.Label { - text: "Email" - } - - TextField { - id: login - text: manager.cloudUserName - Layout.fillWidth: true - inputMethodHints: Qt.ImhEmailCharactersOnly | - Qt.ImhNoAutoUppercase - } - - Kirigami.Label { - text: "Password" - } - - TextField { - id: password - text: manager.cloudPassword - echoMode: TextInput.Password - inputMethodHints: Qt.ImhSensitiveData | - Qt.ImhHiddenText | - Qt.ImhNoAutoUppercase - Layout.fillWidth: true - } - GridLayout { - columns: 2 - - CheckBox { - checked: false - id: showPassword - onCheckedChanged: { - password.echoMode = checked ? TextInput.Normal : TextInput.Password - } - } - Kirigami.Label { - text: "Show password" - } - } - Item { width: Kirigami.Units.gridUnit; height: width } - } -} diff --git a/qt-mobile/qml/DiveDetails.qml b/qt-mobile/qml/DiveDetails.qml deleted file mode 100644 index 108833470..000000000 --- a/qt-mobile/qml/DiveDetails.qml +++ /dev/null @@ -1,216 +0,0 @@ -import QtQuick 2.4 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.2 -import org.subsurfacedivelog.mobile 1.0 -import org.kde.kirigami 1.0 as Kirigami - -Kirigami.Page { - id: diveDetailsPage - property alias currentIndex: diveDetailsListView.currentIndex - property alias dive_id: detailsEdit.dive_id - property alias number: detailsEdit.number - property alias date: detailsEdit.dateText - property alias airtemp: detailsEdit.airtempText - property alias watertemp: detailsEdit.watertempText - property alias buddy: detailsEdit.buddyText - property alias divemaster: detailsEdit.divemasterText - property alias depth: detailsEdit.depthText - property alias duration: detailsEdit.durationText - property alias location: detailsEdit.locationText - property alias notes: detailsEdit.notesText - property alias suit: detailsEdit.suitText - property alias weight: detailsEdit.weightText - property alias startpressure: detailsEdit.startpressureText - property alias endpressure: detailsEdit.endpressureText - property alias gasmix: detailsEdit.gasmixText - - topPadding: applicationWindow().header.Layout.preferredHeight - leftPadding: 0 - rightPadding: 0 - bottomPadding: 0 - - title: diveDetailsListView.currentItem.modelData.dive.location - state: "view" - - states: [ - State { - name: "view" - PropertyChanges { target: diveDetailsPage; contextualActions: Qt.platform.os == "ios" ? [ deleteAction, backAction ] : [ deleteAction ] } - PropertyChanges { target: detailsEditScroll; opened: false } - }, - State { - name: "edit" - PropertyChanges { target: diveDetailsPage; contextualActions: Qt.platform.os == "ios" ? [ cancelAction ] : null } - PropertyChanges { target: detailsEditScroll; opened: true } - }, - State { - name: "add" - PropertyChanges { target: diveDetailsPage; contextualActions: Qt.platform.os == "ios" ? [ cancelAction ] : null } - PropertyChanges { target: detailsEditScroll; opened: true } - } - - ] - - property QtObject deleteAction: Action { - text: "Delete dive" - iconName: "trash-empty" - onTriggered: { - contextDrawer.close() - var deletedId = diveDetailsListView.currentItem.modelData.dive.id - manager.deleteDive(deletedId) - stackView.pop() - showPassiveNotification("Dive deleted", 3000, "Undo", - function() { - manager.undoDelete(deletedId) - }); - } - } - - property QtObject cancelAction: Kirigami.Action { - text: state === "edit" ? "Cancel edit" : "Cancel dive add" - iconName: "dialog-cancel" - onTriggered: { - contextDrawer.close() - if (state === "add") - returnTopPage() - else - endEditMode() - } - } - - property QtObject backAction: Action { - text: "Back to dive list" - iconName: "go-previous" - onTriggered: { - contextDrawer.close() - returnTopPage() - } - } - - mainAction: Action { - iconName: state !== "view" ? "document-save" : "document-edit" - onTriggered: { - if (state === "edit" || state === "add") { - detailsEdit.saveData() - } else { - startEditMode() - } - } - } - - onBackRequested: { - if (state === "edit") { - endEditMode() - event.accepted = true; - } else if (state === "add") { - endEditMode() - stackView.pop() - event.accepted = true; - } - // if we were in view mode, don't accept the event and pop the page - } - - function showDiveIndex(index) { - currentIndex = index; - diveDetailsListView.positionViewAtIndex(index, ListView.Beginning); - } - - function endEditMode() { - // if we were adding a dive, we need to remove it - if (state === "add") - manager.addDiveAborted(dive_id) - // just cancel the edit/add state - state = "view"; - Qt.inputMethod.hide(); - } - - function startEditMode() { - // set things up for editing - so make sure that the detailsEdit has - // all the right data (using the property aliases set up above) - dive_id = diveDetailsListView.currentItem.modelData.dive.id - number = diveDetailsListView.currentItem.modelData.dive.number - date = diveDetailsListView.currentItem.modelData.dive.date + " " + diveDetailsListView.currentItem.modelData.dive.time - location = diveDetailsListView.currentItem.modelData.dive.location - duration = diveDetailsListView.currentItem.modelData.dive.duration - depth = diveDetailsListView.currentItem.modelData.dive.depth - airtemp = diveDetailsListView.currentItem.modelData.dive.airTemp - watertemp = diveDetailsListView.currentItem.modelData.dive.waterTemp - suit = diveDetailsListView.currentItem.modelData.dive.suit - buddy = diveDetailsListView.currentItem.modelData.dive.buddy - divemaster = diveDetailsListView.currentItem.modelData.dive.divemaster - notes = diveDetailsListView.currentItem.modelData.dive.notes - if (diveDetailsListView.currentItem.modelData.dive.singleWeight) { - // we have only one weight, go ahead, have fun and edit it - weight = diveDetailsListView.currentItem.modelData.dive.sumWeight - } else { - // careful when translating, this text is "magic" in DiveDetailsEdit.qml - weight = "cannot edit multiple weight systems" - } - if (diveDetailsListView.currentItem.modelData.dive.getCylinder != "Multiple" ) { - startpressure = diveDetailsListView.currentItem.modelData.dive.startPressure - endpressure = diveDetailsListView.currentItem.modelData.dive.endPressure - gasmix = diveDetailsListView.currentItem.modelData.dive.firstGas - } else { - // careful when translating, this text is "magic" in DiveDetailsEdit.qml - startpressure = "cannot edit multiple cylinders" - endpressure = "cannot edit multiple cylinders" - gasmix = "cannot edit multiple gases" - } - - diveDetailsPage.state = "edit" - } - - onWidthChanged: diveDetailsListView.positionViewAtIndex(diveDetailsListView.currentIndex, ListView.Beginning); - - Item { - anchors.fill: parent - ScrollView { - id: diveDetailList - anchors.fill: parent - ListView { - id: diveDetailsListView - anchors.fill: parent - model: diveModel - currentIndex: -1 - boundsBehavior: Flickable.StopAtBounds - maximumFlickVelocity: parent.width * 5 - orientation: ListView.Horizontal - focus: true - clip: true - snapMode: ListView.SnapOneItem - onMovementEnded: { - currentIndex = indexAt(contentX+1, 1); - } - delegate: ScrollView { - id: internalScrollView - width: diveDetailsListView.width - height: diveDetailsListView.height - property var modelData: model - Flickable { - //contentWidth: parent.width - contentHeight: diveDetails.height - boundsBehavior: Flickable.StopAtBounds - DiveDetailsView { - id: diveDetails - width: internalScrollView.width - } - } - } - } - } - Kirigami.OverlaySheet { - id: detailsEditScroll - anchors.fill: parent - onOpenedChanged: { - if (!opened) { - endEditMode() - } - } - DiveDetailsEdit { - id: detailsEdit - } - } - } -} diff --git a/qt-mobile/qml/DiveDetailsEdit.qml b/qt-mobile/qml/DiveDetailsEdit.qml deleted file mode 100644 index e4338b3b8..000000000 --- a/qt-mobile/qml/DiveDetailsEdit.qml +++ /dev/null @@ -1,236 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import org.subsurfacedivelog.mobile 1.0 -import org.kde.kirigami 1.0 as Kirigami - -Item { - id: detailsEdit - property int dive_id - property int number - property alias dateText: txtDate.text - property alias locationText: txtLocation.text - property string gpsText - property alias airtempText: txtAirTemp.text - property alias watertempText: txtWaterTemp.text - property alias suitText: txtSuit.text - property alias buddyText: txtBuddy.text - property alias divemasterText: txtDiveMaster.text - property alias notesText: txtNotes.text - property alias durationText: txtDuration.text - property alias depthText: txtDepth.text - property alias weightText: txtWeight.text - property alias startpressureText: txtStartPressure.text - property alias endpressureText: txtEndPressure.text - property alias gasmixText: txtGasMix.text - - function saveData() { - // apply the changes to the dive_table - manager.commitChanges(dive_id, detailsEdit.dateText, detailsEdit.locationText, detailsEdit.gpsText, detailsEdit.durationText, - detailsEdit.depthText, detailsEdit.airtempText, detailsEdit.watertempText, detailsEdit.suitText, - detailsEdit.buddyText, detailsEdit.divemasterText, detailsEdit.weightText, detailsEdit.notesText, - detailsEdit.startpressureText, detailsEdit.endpressureText, detailsEdit.gasmixText) - // trigger the profile to be redrawn - QMLProfile.diveId = dive_id - - // apply the changes to the dive detail view - since the edit could have changed the order - // first make sure that we are looking at the correct dive - our model allows us to look - // up the index based on the unique dive_id - var newIdx = diveModel.getIdxForId(dive_id) - diveDetailsListView.currentIndex = newIdx - diveDetailsListView.currentItem.modelData.date = detailsEdit.dateText - diveDetailsListView.currentItem.modelData.location = detailsEdit.locationText - diveDetailsListView.currentItem.modelData.duration = detailsEdit.durationText - diveDetailsListView.currentItem.modelData.depth = detailsEdit.depthText - diveDetailsListView.currentItem.modelData.airtemp = detailsEdit.airtempText - diveDetailsListView.currentItem.modelData.watertemp = detailsEdit.watertempText - diveDetailsListView.currentItem.modelData.suit = detailsEdit.suitText - diveDetailsListView.currentItem.modelData.buddy = detailsEdit.buddyText - diveDetailsListView.currentItem.modelData.divemaster = detailsEdit.divemasterText - diveDetailsListView.currentItem.modelData.notes = detailsEdit.notesText - diveDetailsPage.state = "view" - Qt.inputMethod.hide() - // now make sure we directly show the saved dive (this may be a new dive, or it may have moved) - showDiveIndex(newIdx) - } - - height: editArea.height - ColumnLayout { - id: editArea - spacing: Kirigami.Units.smallSpacing - width: subsurfaceTheme.columnWidth - 2 * Kirigami.Units.gridUnit - - GridLayout { - id: editorDetails - width: parent.width - columns: 2 - - Kirigami.Heading { - Layout.columnSpan: 2 - text: "Dive " + number - } - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Date:" - } - TextField { - id: txtDate; - Layout.fillWidth: true - } - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Location:" - } - TextField { - id: txtLocation; - Layout.fillWidth: true - } - - // we should add a checkbox here that allows the user - // to add the current location as the dive location - // (think of someone adding a dive while on the boat or - // at the dive site) - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Use current\nGPS location:" - } - CheckBox { - id: checkboxGPS - onCheckedChanged: { - if (checked) - gpsText = manager.getCurrentPosition() - } - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Depth:" - } - TextField { - id: txtDepth - Layout.fillWidth: true - validator: RegExpValidator { regExp: /[^-]*/ } - } - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Duration:" - } - TextField { - id: txtDuration - Layout.fillWidth: true - validator: RegExpValidator { regExp: /[^-]*/ } - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Air Temp:" - } - TextField { - id: txtAirTemp - Layout.fillWidth: true - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Water Temp:" - } - TextField { - id: txtWaterTemp - Layout.fillWidth: true - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Suit:" - } - TextField { - id: txtSuit - Layout.fillWidth: true - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Buddy:" - } - TextField { - id: txtBuddy - Layout.fillWidth: true - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Dive Master:" - } - TextField { - id: txtDiveMaster - Layout.fillWidth: true - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Weight:" - } - TextField { - id: txtWeight - readOnly: (text == "cannot edit multiple weight systems" ? true : false) - Layout.fillWidth: true - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Gas mix:" - } - TextField { - id: txtGasMix - readOnly: (text == "cannot edit multiple gases" ? true : false) - Layout.fillWidth: true - validator: RegExpValidator { regExp: /(EAN100|EAN\d\d|AIR|100|\d{1,2}|\d{1,2}\/\d{1,2})/ } - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "Start Pressure:" - } - TextField { - id: txtStartPressure - readOnly: (text == "cannot edit multiple cylinders" ? true : false) - Layout.fillWidth: true - } - - Kirigami.Label { - Layout.alignment: Qt.AlignRight - text: "End Pressure:" - } - TextField { - id: txtEndPressure - readOnly: (text == "cannot edit multiple cylinders" ? true : false) - Layout.fillWidth: true - } - - - Kirigami.Label { - Layout.columnSpan: 2 - Layout.alignment: Qt.AlignLeft - text: "Notes:" - } - TextArea { - Layout.columnSpan: 2 - width: parent.width - id: txtNotes - textFormat: TextEdit.RichText - focus: true - Layout.fillWidth: true - Layout.fillHeight: true - Layout.minimumHeight: Kirigami.Units.gridUnit * 6 - selectByMouse: true - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - } - } - Item { - height: Kirigami.Units.gridUnit * 3 - width: height // just to make sure the spacer doesn't produce scrollbars, but also isn't null - } - } -} diff --git a/qt-mobile/qml/DiveDetailsView.qml b/qt-mobile/qml/DiveDetailsView.qml deleted file mode 100644 index ef1dc5605..000000000 --- a/qt-mobile/qml/DiveDetailsView.qml +++ /dev/null @@ -1,303 +0,0 @@ -import QtQuick 2.3 -/* -import QtWebView 1.0 -*/ -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import org.subsurfacedivelog.mobile 1.0 -import org.kde.kirigami 1.0 as Kirigami - -Item { - id: detailsView - property real gridWidth: subsurfaceTheme.columnWidth - 2 * Kirigami.Units.gridUnit - property real col1Width: gridWidth * 0.23 - property real col2Width: gridWidth * 0.37 - property real col3Width: gridWidth * 0.20 - property real col4Width: gridWidth * 0.20 - - width: SubsurfaceTheme.columnWidth - height: mainLayout.implicitHeight + bottomLayout.implicitHeight + Kirigami.Units.iconSizes.large - Rectangle { - z: 99 - color: Kirigami.Theme.textColor - opacity: 0.3 - width: Kirigami.Units.smallSpacing/4 - anchors { - right: parent.right - top: parent.top - bottom: parent.bottom - } - } - GridLayout { - id: mainLayout - anchors { - top: parent.top - left: parent.left - right: parent.right - margins: Math.round(Kirigami.Units.gridUnit / 2) - } - columns: 4 - rowSpacing: Kirigami.Units.smallSpacing * 2 - columnSpacing: Kirigami.Units.smallSpacing - - Kirigami.Heading { - id: detailsViewHeading - Layout.fillWidth: true - text: dive.location - font.underline: dive.gps !== "" - Layout.columnSpan: 4 - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.topMargin: Kirigami.Units.largeSpacing - MouseArea { - anchors.fill: parent - onClicked: { - if (dive.gps !== "") - showMap(dive.gps) - } - } - } - Kirigami.Label { - id: dateLabel - text: "Date: " - opacity: 0.6 - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - text: dive.date + " " + dive.time - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.columnSpan: 2 - } - Kirigami.Label { - id: numberText - text: "#" + dive.number - color: Kirigami.Theme.textColor - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - } - - Kirigami.Label { - id: depthLabel - text: "Depth: " - opacity: 0.6 - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - text: dive.depth - Layout.fillWidth: true - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - } - Kirigami.Label { - text: "Duration: " - opacity: 0.6 - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - text: dive.duration - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - } - - QMLProfile { - id: qmlProfile - visible: !dive.noDive - Layout.fillWidth: true - Layout.preferredHeight: Layout.minimumHeight - Layout.minimumHeight: width * 0.75 - Layout.columnSpan: 4 - clip: false - Rectangle { - color: "transparent" - opacity: 0.6 - border.width: 1 - border.color: Kirigami.Theme.textColor; - anchors.fill: parent - } - } - Kirigami.Label { - id: noProfile - visible: dive.noDive - Layout.fillWidth: true - Layout.columnSpan: 4 - Layout.margins: Kirigami.Units.gridUnit - horizontalAlignment: Text.AlignHCenter - text: "No profile to show" - } - } - GridLayout { - id: bottomLayout - anchors { - top: mainLayout.bottom - left: parent.left - right: parent.right - margins: Math.round(Kirigami.Units.gridUnit / 2) - } - columns: 4 - rowSpacing: Kirigami.Units.smallSpacing * 2 - columnSpacing: Kirigami.Units.smallSpacing - - Kirigami.Heading { - Layout.fillWidth: true - level: 3 - text: "Dive Details" - Layout.columnSpan: 4 - } - - // first row - here we set up the column widths - total is 90% of width - Kirigami.Label { - text: "Suit:" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - opacity: 0.6 - Layout.maximumWidth: detailsView.col1Width - Layout.preferredWidth: detailsView.col1Width - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - id: txtSuit - text: dive.suit - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.maximumWidth: detailsView.col2Width - Layout.preferredWidth: detailsView.col2Width - } - - Kirigami.Label { - text: "Air Temp:" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - opacity: 0.6 - Layout.maximumWidth: detailsView.col3Width - Layout.preferredWidth: detailsView.col3Width - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - id: txtAirTemp - text: dive.airTemp - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.maximumWidth: detailsView.col4Width - Layout.preferredWidth: detailsView.col4Width - } - - Kirigami.Label { - text: "Cylinder:" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - opacity: 0.6 - Layout.maximumWidth: detailsView.col1Width - Layout.preferredWidth: detailsView.col1Width - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - id: txtCylinder - text: dive.getCylinder - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.maximumWidth: detailsView.col2Width - Layout.preferredWidth: detailsView.col2Width - } - - Kirigami.Label { - text: "Water Temp:" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - opacity: 0.6 - Layout.maximumWidth: detailsView.col3Width - Layout.preferredWidth: detailsView.col3Width - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - id: txtWaterTemp - text: dive.waterTemp - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.maximumWidth: detailsView.col4Width - Layout.preferredWidth: detailsView.col4Width - } - - Kirigami.Label { - text: "Dive Master:" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - opacity: 0.6 - Layout.maximumWidth: detailsView.col1Width - Layout.preferredWidth: detailsView.col1Width - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - id: txtDiveMaster - text: dive.divemaster - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.maximumWidth: detailsView.col2Width - Layout.preferredWidth: detailsView.col2Width - } - - Kirigami.Label { - text: "Weight:" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - opacity: 0.6 - Layout.maximumWidth: detailsView.col3Width - Layout.preferredWidth: detailsView.col3Width - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - id: txtWeight - text: dive.sumWeight - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.maximumWidth: detailsView.col4Width - Layout.preferredWidth: detailsView.col4Width - } - - Kirigami.Label { - text: "Buddy:" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - opacity: 0.6 - Layout.maximumWidth: detailsView.col1Width - Layout.preferredWidth: detailsView.col1Width - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - id: txtBuddy - text: dive.buddy - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.maximumWidth: detailsView.col2Width - Layout.preferredWidth: detailsView.col2Width - } - - Kirigami.Label { - text: "SAC:" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - opacity: 0.6 - Layout.maximumWidth: detailsView.col3Width - Layout.preferredWidth: detailsView.col3Width - Layout.alignment: Qt.AlignRight - } - Kirigami.Label { - id: txtSAC - text: dive.sac - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - Layout.maximumWidth: detailsView.col4Width - Layout.preferredWidth: detailsView.col4Width - } - - Kirigami.Heading { - Layout.fillWidth: true - level: 3 - text: "Notes" - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - Layout.columnSpan: 4 - } - - Kirigami.Label { - id: txtNotes - text: dive.notes - focus: true - Layout.columnSpan: 4 - Layout.fillWidth: true - Layout.fillHeight: true - //selectByMouse: true - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - } - Item { - Layout.columnSpan: 4 - Layout.fillWidth: true - Layout.minimumHeight: Kirigami.Units.gridUnit * 3 - } - Component.onCompleted: { - qmlProfile.setMargin(Kirigami.Units.smallSpacing) - qmlProfile.diveId = model.dive.id; - qmlProfile.update(); - } - } -} diff --git a/qt-mobile/qml/DiveList.qml b/qt-mobile/qml/DiveList.qml deleted file mode 100644 index 95af9a973..000000000 --- a/qt-mobile/qml/DiveList.qml +++ /dev/null @@ -1,302 +0,0 @@ -import QtQuick 2.4 -import QtQuick.Controls 1.2 -import QtQuick.Layouts 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import org.kde.kirigami 1.0 as Kirigami -import org.subsurfacedivelog.mobile 1.0 - -Kirigami.ScrollablePage { - id: page - objectName: "DiveList" - title: "Subsurface-mobile" - background: Rectangle { - color: Kirigami.Theme.viewBackgroundColor - } - - property int credentialStatus: manager.credentialStatus - property int numDives: diveListView.count - property color textColor: subsurfaceTheme.diveListTextColor - - function scrollToTop() { - diveListView.positionViewAtBeginning() - } - - Component { - id: diveDelegate - Kirigami.AbstractListItem { - enabled: true - supportsMouseEvents: true - checked: diveListView.currentIndex === model.index - width: parent.width - - property real detailsOpacity : 0 - property int horizontalPadding: Kirigami.Units.gridUnit / 2 - Kirigami.Units.smallSpacing + 1 - - // When clicked, the mode changes to details view - onClicked: { - if (detailsWindow.state === "view") { - diveListView.currentIndex = index - detailsWindow.showDiveIndex(index); - stackView.push(detailsWindow); - } - } - - property bool deleteButtonVisible: false - - onPressAndHold: { - deleteButtonVisible = true - timer.restart() - } - - Row { - width: parent.width - Kirigami.Units.gridUnit - height: childrenRect.height - Kirigami.Units.smallSpacing - spacing: horizontalPadding - add: Transition { - NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 400 } - NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 400 } - } - Item { - id: diveListEntry - width: parent.width - Kirigami.Units.gridUnit - height: childrenRect.height - Kirigami.Units.smallSpacing - - Kirigami.Label { - id: locationText - text: dive.location - font.weight: Font.Light - elide: Text.ElideRight - maximumLineCount: 1 // needed for elide to work at all - color: textColor - anchors { - left: parent.left - leftMargin: horizontalPadding - top: parent.top - right: dateLabel.left - } - } - Kirigami.Label { - id: dateLabel - text: dive.date + " " + dive.time - font.pointSize: subsurfaceTheme.smallPointSize - color: textColor - anchors { - right: parent.right - top: parent.top - } - } - Row { - anchors { - left: parent.left - leftMargin: horizontalPadding - right: parent.right - rightMargin: horizontalPadding - topMargin: - Kirigami.Units.smallSpacing * 2 - bottom: numberText.bottom - } - Kirigami.Label { - text: 'Depth: ' - font.pointSize: subsurfaceTheme.smallPointSize - color: textColor - } - Kirigami.Label { - text: dive.depth - width: Math.max(Kirigami.Units.gridUnit * 3, paintedWidth) // helps vertical alignment throughout listview - font.pointSize: subsurfaceTheme.smallPointSize - color: textColor - } - Kirigami.Label { - text: 'Duration: ' - font.pointSize: subsurfaceTheme.smallPointSize - color: textColor - } - Kirigami.Label { - text: dive.duration - font.pointSize: subsurfaceTheme.smallPointSize - color: textColor - } - } - Kirigami.Label { - id: numberText - text: "#" + dive.number - font.pointSize: subsurfaceTheme.smallPointSize - color: textColor - anchors { - right: parent.right - top: locationText.bottom - topMargin: - Kirigami.Units.smallSpacing * 2 - } - } - } - Rectangle { - visible: deleteButtonVisible - height: diveListEntry.height - Kirigami.Units.smallSpacing - width: height - 3 * Kirigami.Units.smallSpacing - color: "#FF3030" - antialiasing: true - radius: Kirigami.Units.smallSpacing - Kirigami.Icon { - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } - source: "trash-empty" - } - MouseArea { - anchors.fill: parent - enabled: parent.visible - onClicked: { - parent.visible = false - timer.stop() - manager.deleteDive(dive.id) - } - } - } - Item { - Timer { - id: timer - interval: 4000 - onTriggered: { - deleteButtonVisible = false - } - } - } - } - } - } - - Component { - id: tripHeading - Item { - width: page.width - Kirigami.Units.gridUnit - height: childrenRect.height + Kirigami.Units.smallSpacing * 2 + Math.max(2, Kirigami.Units.gridUnit / 2) - - Kirigami.Heading { - id: sectionText - text: { - // if the tripMeta (which we get as "section") ends in ::-- we know - // that there's no trip -- otherwise strip the meta information before - // the :: and show the trip location - var shownText - var endsWithDoubleDash = /::--$/; - if (endsWithDoubleDash.test(section) || section === "--") { - shownText = "" - } else { - shownText = section.replace(/.*::/, "") - } - shownText - } - anchors { - top: parent.top - left: parent.left - topMargin: Math.max(2, Kirigami.Units.gridUnit / 2) - leftMargin: Kirigami.Units.gridUnit / 2 - right: parent.right - } - color: textColor - level: 2 - } - Rectangle { - height: Math.max(2, Kirigami.Units.gridUnit / 12) // we want a thicker line - anchors { - top: sectionText.bottom - left: parent.left - leftMargin: Kirigami.Units.gridUnit * -2 - rightMargin: Kirigami.Units.gridUnit * -2 - right: parent.right - } - color: subsurfaceTheme.accentColor - } - } - } - - ScrollView { - id: startPageWrapper - anchors.fill: parent - opacity: (diveListView.count > 0 && (credentialStatus == QMLManager.VALID || credentialStatus == QMLManager.VALID_EMAIL)) ? 0 : 1 - visible: opacity > 0 - Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration } } - onVisibleChanged: { - if (visible) { - page.mainAction = page.saveAction - } else { - page.mainAction = page.addDiveAction - } - } - - StartPage { - id: startPage - } - } - - ListView { - id: diveListView - anchors.fill: parent - opacity: 0.8 - startPageWrapper.opacity - visible: opacity > 0 - model: diveModel - currentIndex: -1 - delegate: diveDelegate - //boundsBehavior: Flickable.StopAtBounds - maximumFlickVelocity: parent.height * 5 - bottomMargin: Kirigami.Units.iconSizes.medium + Kirigami.Units.gridUnit - cacheBuffer: 0 // seems to avoid empty rendered profiles - section.property: "dive.tripMeta" - section.criteria: ViewSection.FullString - section.delegate: tripHeading - header: Kirigami.Heading { - x: Kirigami.Units.gridUnit / 2 - height: paintedHeight + Kirigami.Units.gridUnit / 2 - verticalAlignment: Text.AlignBottom - text: "Dive Log" - } - Connections { - target: detailsWindow - onCurrentIndexChanged: diveListView.currentIndex = detailsWindow.currentIndex - } - Connections { - target: stackView - onDepthChanged: { - if (stackView.depth === 1) { - diveListView.currentIndex = -1; - } - } - } - Connections { - target: header - onTitleBarClicked: { - // if we can see the dive list and it's not at the top already, go to the top, - // otherwise have the title bar handle the click (for bread-crumb navigation) - if (stackView.currentItem.objectName === "DiveList" && diveListView.contentY > Kirigami.Units.gridUnit) { - diveListView.positionViewAtBeginning() - event.accepted = true - } else { - event.accepted = false - } - } - } - - } - - property QtObject addDiveAction: Action { - iconName: "list-add" - onTriggered: { - startAddDive() - } - } - - property QtObject saveAction: Action { - iconName: "document-save" - onTriggered: { - startPage.saveCredentials(); - } - } - - onBackRequested: { - if (startPageWrapper.visible && diveListView.count > 0 && manager.credentialStatus != QMLManager.INVALID) { - manager.credentialStatus = oldStatus - event.accepted = true; - } - } -} diff --git a/qt-mobile/qml/DownloadFromDiveComputer.qml b/qt-mobile/qml/DownloadFromDiveComputer.qml deleted file mode 100644 index a062ffaa0..000000000 --- a/qt-mobile/qml/DownloadFromDiveComputer.qml +++ /dev/null @@ -1,125 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import org.subsurfacedivelog.mobile 1.0 -import org.kde.kirigami 1.0 as Kirigami - -Kirigami.Page { - id: diveComputerDownloadWindow - anchors.top:parent.top - width: parent.width - height: parent.height - Layout.fillWidth: true; - title: "Dive Computer" - -/* this can be done by hitting the back key - contextualActions: [ - Action { - text: "Close Preferences" - iconName: "dialog-cancel" - onTriggered: { - stackView.pop() - contextDrawer.close() - } - } - ] - */ - ColumnLayout { - anchors.top: parent.top - height: parent.height - width: parent.width - Layout.fillWidth: true - RowLayout { - anchors.top:parent.top - Layout.fillWidth: true - Text { text: " Vendor name : " } - ComboBox { Layout.fillWidth: true } - } - RowLayout { - Text { text: " Dive Computer:" } - ComboBox { Layout.fillWidth: true } - } - RowLayout { - Text { text: " Progress:" } - Layout.fillWidth: true - ProgressBar { Layout.fillWidth: true } - } - RowLayout { - SubsurfaceButton { - text: "Download" - onClicked: { - text: "Retry" - stackView.pop(); - } - } - SubsurfaceButton { - id:quitbutton - text: "Quit" - onClicked: { - stackView.pop(); - } - } - } - RowLayout { - Text { - text: " Downloaded dives" - } - } - TableView { - width: parent.width - Layout.fillWidth: true // The tableview should fill - Layout.fillHeight: true // all remaining vertical space - height: parent.height // on this screen - TableViewColumn { - width: parent.width / 2 - role: "datetime" - title: "Date / Time" - } - TableViewColumn { - width: parent.width / 4 - role: "duration" - title: "Duration" - } - TableViewColumn { - width: parent.width / 4 - role: "depth" - title: "Depth" - } - } - RowLayout { - Layout.fillWidth: true - SubsurfaceButton { - text: "Accept" - onClicked: { - stackView.pop(); - } - } - SubsurfaceButton { - text: "Quit" - onClicked: { - stackView.pop(); - } - } - Text { - text: "" // Spacer between 2 button groups - Layout.fillWidth: true - } - SubsurfaceButton { - text: "Select All" - } - SubsurfaceButton { - id: unselectbutton - text: "Unselect All" - } - } - RowLayout { // spacer to make space for silly button - Layout.minimumHeight: 1.2 * unselectbutton.height - Text { - text:"" - } - } - } -} diff --git a/qt-mobile/qml/GpsList.qml b/qt-mobile/qml/GpsList.qml deleted file mode 100644 index 6903acd80..000000000 --- a/qt-mobile/qml/GpsList.qml +++ /dev/null @@ -1,128 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.2 -import org.subsurfacedivelog.mobile 1.0 -import org.kde.kirigami 1.0 as Kirigami - -Kirigami.ScrollablePage { - id: gpsListWindow - width: parent.width - Kirigami.Units.gridUnit - anchors.margins: Kirigami.Units.gridUnit / 2 - objectName: "gpsList" - title: "GPS Fixes" - -/* this can be done by hitting the back key - contextualActions: [ - Action { - text: "Close GPS list" - iconName: "dialog-cancel" - onTriggered: { - stackView.pop() - contextDrawer.close() - } - } - ] - */ - Component { - id: gpsDelegate - Kirigami.SwipeListItem { - id: gpsFix - enabled: true - width: parent.width - property int horizontalPadding: Kirigami.Units.gridUnit / 2 - Kirigami.Units.smallSpacing + 1 - - Kirigami.BasicListItem { - supportsMouseEvents: true - width: parent.width - Kirigami.Units.gridUnit - height: childrenRect.height - Kirigami.Units.smallSpacing - GridLayout { - columns: 4 - id: timeAndName - anchors { - left: parent.left - leftMargin: horizontalPadding - right: parent.right - rightMargin: horizontalPadding - } - Kirigami.Label { - text: 'Date: ' - opacity: 0.6 - font.pointSize: subsurfaceTheme.smallPointSize - } - Kirigami.Label { - text: date - Layout.preferredWidth: Math.max(parent.width / 5, paintedWidth) - font.pointSize: subsurfaceTheme.smallPointSize - } - Kirigami.Label { - text: 'Name: ' - opacity: 0.6 - font.pointSize: subsurfaceTheme.smallPointSize - } - Kirigami.Label { - text: name - Layout.preferredWidth: Math.max(parent.width / 5, paintedWidth) - font.pointSize: subsurfaceTheme.smallPointSize - } - Kirigami.Label { - text: 'Latitude: ' - opacity: 0.6 - font.pointSize: subsurfaceTheme.smallPointSize - } - Kirigami.Label { - text: latitude - font.pointSize: subsurfaceTheme.smallPointSize - } - Kirigami.Label { - text: 'Longitude: ' - opacity: 0.6 - font.pointSize: subsurfaceTheme.smallPointSize - } - Kirigami.Label { - text: longitude - font.pointSize: subsurfaceTheme.smallPointSize - } - } - } - actions: [ - Kirigami.Action { - iconName: "trash-empty" - onTriggered: { - print("delete this!") - manager.deleteGpsFix(when) - } - }, - Kirigami.Action { - iconName: "gps" - onTriggered: { - showMap(latitude + " " + longitude) - } - } - - ] - } - } - - ListView { - id: gpsListView - anchors.fill: parent - model: gpsModel - currentIndex: -1 - delegate: gpsDelegate - boundsBehavior: Flickable.StopAtBounds - maximumFlickVelocity: parent.height * 5 - cacheBuffer: Math.max(5000, parent.height * 5) - focus: true - clip: true - header: Kirigami.Heading { - x: Kirigami.Units.gridUnit / 2 - height: paintedHeight + Kirigami.Units.gridUnit / 2 - verticalAlignment: Text.AlignBottom - text: "List of stored GPS fixes" - } - } -} diff --git a/qt-mobile/qml/Log.qml b/qt-mobile/qml/Log.qml deleted file mode 100644 index d617901de..000000000 --- a/qt-mobile/qml/Log.qml +++ /dev/null @@ -1,40 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.2 -import org.subsurfacedivelog.mobile 1.0 -import org.kde.kirigami 1.0 as Kirigami - -Kirigami.ScrollablePage { - id: logWindow - width: parent.width - Kirigami.Units.gridUnit - anchors.margins: Kirigami.Units.gridUnit / 2 - objectName: "Log" - title: "Application Log" - - property int pageWidth: subsurfaceTheme.columnWidth - Kirigami.Units.smallSpacing - - ColumnLayout { - width: pageWidth - spacing: Kirigami.Units.smallSpacing - Kirigami.Heading { - text: "Application Log" - } - Kirigami.Label { - id: logContent - width: parent.width - Layout.preferredWidth: parent.width - Layout.maximumWidth: parent.width - wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere - text: manager.logText - } - Rectangle { - color: "transparent" - height: Kirigami.Units.gridUnit * 2 - width: pageWidth - } - } -} diff --git a/qt-mobile/qml/Preferences.qml b/qt-mobile/qml/Preferences.qml deleted file mode 100644 index 3ec96d198..000000000 --- a/qt-mobile/qml/Preferences.qml +++ /dev/null @@ -1,74 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import org.kde.kirigami 1.0 as Kirigami -import org.subsurfacedivelog.mobile 1.0 - -Kirigami.Page { - - title: "Preferences" - mainAction: Action { - text: "Save" - iconName: "document-save" - onTriggered: { - manager.distanceThreshold = distanceThreshold.text - manager.timeThreshold = timeThreshold.text - manager.savePreferences() - stackView.pop() - } - } - - GridLayout { - - signal accept - - columns: 2 - width: parent.width - Kirigami.Units.gridUnit - anchors { - fill: parent - margins: Kirigami.Units.gridUnit / 2 - } - - Kirigami.Heading { - text: "Preferences" - Layout.bottomMargin: Kirigami.Units.largeSpacing / 2 - Layout.columnSpan: 2 - } - - Kirigami.Heading { - text: "Subsurface GPS data webservice" - level: 3 - Layout.topMargin: Kirigami.Units.largeSpacing - Layout.bottomMargin: Kirigami.Units.largeSpacing / 2 - Layout.columnSpan: 2 - } - - Kirigami.Label { - text: "Distance threshold (meters)" - Layout.alignment: Qt.AlignRight - } - - TextField { - id: distanceThreshold - text: manager.distanceThreshold - Layout.fillWidth: true - } - - Kirigami.Label { - text: "Time threshold (minutes)" - Layout.alignment: Qt.AlignRight - } - - TextField { - id: timeThreshold - text: manager.timeThreshold - Layout.fillWidth: true - } - - Item { - Layout.fillHeight: true - } - } -} diff --git a/qt-mobile/qml/StartPage.qml b/qt-mobile/qml/StartPage.qml deleted file mode 100644 index 2d70cfcb3..000000000 --- a/qt-mobile/qml/StartPage.qml +++ /dev/null @@ -1,42 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Layouts 1.1 -import org.kde.kirigami 1.0 as Kirigami -import org.subsurfacedivelog.mobile 1.0 - -ColumnLayout { - id: startpage - width: subsurfaceTheme.columnWidth - - function saveCredentials() { cloudCredentials.saveCredentials() } - - Kirigami.Heading { - Layout.margins: Kirigami.Units.gridUnit - text: "Subsurface-mobile" - } - Kirigami.Label { - id: explanationText - Layout.fillWidth: true - Layout.margins: Kirigami.Units.gridUnit - Layout.topMargin: 0 - text: "In order to use Subsurface-mobile you need to have a Subsurface cloud storage account " + - "(which can be created with the Subsurface desktop application)." - wrapMode: Text.WordWrap - } - Kirigami.Label { - id: messageArea - Layout.fillWidth: true - Layout.margins: Kirigami.Units.gridUnit - Layout.topMargin: 0 - text: manager.startPageText - wrapMode: Text.WordWrap - } - CloudCredentials { - id: cloudCredentials - Layout.fillWidth: true - Layout.margins: Kirigami.Units.gridUnit - Layout.topMargin: 0 - property int headingLevel: 3 - } -} diff --git a/qt-mobile/qml/SubsurfaceButton.qml b/qt-mobile/qml/SubsurfaceButton.qml deleted file mode 100644 index 174d44659..000000000 --- a/qt-mobile/qml/SubsurfaceButton.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import org.kde.kirigami 1.0 as Kirigami - -Button { - style: ButtonStyle { - padding { - top: Kirigami.Units.smallSpacing * 2 - left: Kirigami.Units.smallSpacing * 4 - right: Kirigami.Units.smallSpacing * 4 - bottom: Kirigami.Units.smallSpacing * 2 - } - background: Rectangle { - border.width: 1 - radius: height / 3 - color: control.pressed ? subsurfaceTheme.shadedColor : subsurfaceTheme.accentColor - } - label: Text{ - text: control.text - color: subsurfaceTheme.accentTextColor - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - } - } -} diff --git a/qt-mobile/qml/TextButton.qml b/qt-mobile/qml/TextButton.qml deleted file mode 100644 index 3e5a36735..000000000 --- a/qt-mobile/qml/TextButton.qml +++ /dev/null @@ -1,37 +0,0 @@ -import QtQuick 2.3 - -Rectangle { - id: container - - property alias text: label.text - - signal clicked - - width: label.width + 20; height: label.height + 6 - smooth: true - radius: 10 - - gradient: Gradient { - GradientStop { id: gradientStop; position: 0.0; color: palette.light } - GradientStop { position: 1.0; color: palette.button } - } - - SystemPalette { id: palette } - - MouseArea { - id: mouseArea - anchors.fill: parent - onClicked: { container.clicked() } - } - - Text { - id: label - anchors.centerIn: parent - } - - states: State { - name: "pressed" - when: mouseArea.pressed - PropertyChanges { target: gradientStop; color: palette.dark } - } -} diff --git a/qt-mobile/qml/ThemeTest.qml b/qt-mobile/qml/ThemeTest.qml deleted file mode 100644 index c0916aea0..000000000 --- a/qt-mobile/qml/ThemeTest.qml +++ /dev/null @@ -1,115 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.2 -import org.kde.kirigami 1.0 as Kirigami - -Kirigami.Page { - - title: "Theme Information" -/* this can be done by hitting the back key - contextualActions: [ - Action { - text: "Close Theme info" - iconName: "dialog-cancel" - onTriggered: { - stackView.pop() - contextDrawer.close() - } - } - ] - */ - GridLayout { - id: themetest - columns: 2 - anchors.margins: Kirigami.Units.gridUnit / 2 - - Kirigami.Heading { - Layout.columnSpan: 2 - text: "Theme Information" - } - - Kirigami.Heading { - text: "Screen" - Layout.columnSpan: 2 - level: 3 - } - FontMetrics { - id: fm - } - - Kirigami.Label { - text: "Geometry (pixels):" - } - Kirigami.Label { - text: rootItem.width + "x" + rootItem.height - } - - Kirigami.Label { - text: "Geometry (gridUnits):" - } - Kirigami.Label { - text: Math.round(rootItem.width / Kirigami.Units.gridUnit) + "x" + Math.round(rootItem.height / Kirigami.Units.gridUnit) - } - - Kirigami.Label { - text: "Units.gridUnit:" - } - Kirigami.Label { - text: Kirigami.Units.gridUnit - } - - Kirigami.Label { - text: "Units.devicePixelRatio:" - } - Kirigami.Label { - text: Screen.devicePixelRatio - } - - Kirigami.Heading { - text: "Font Metrics" - level: 3 - Layout.columnSpan: 2 - } - - Kirigami.Label { - text: "FontMetrics pointSize:" - } - Kirigami.Label { - text: fm.font.pointSize - } - - Kirigami.Label { - text: "FontMetrics pixelSize:" - } - Kirigami.Label { - text: fm.height - } - - Kirigami.Label { - text: "FontMetrics devicePixelRatio:" - } - Kirigami.Label { - text: fm.height / fm.font.pointSize - } - - Kirigami.Label { - text: "Text item pixelSize:" - } - Text { - text: font.pixelSize - } - - Kirigami.Label { - text: "Text item pointSize:" - } - Text { - text: font.pointSize - } - - Kirigami.Label { - Layout.columnSpan: 2 - Layout.fillHeight: true - } - } -} diff --git a/qt-mobile/qml/TopBar.qml b/qt-mobile/qml/TopBar.qml deleted file mode 100644 index 024b818b0..000000000 --- a/qt-mobile/qml/TopBar.qml +++ /dev/null @@ -1,59 +0,0 @@ -import QtQuick 2.3 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.2 -import org.kde.kirigami 1.0 as Kirigami -import org.subsurfacedivelog.mobile 1.0 - -Rectangle { - id: topPart - - color: subsurfaceTheme.accentColor - Layout.minimumHeight: Math.round(Kirigami.Units.gridUnit * 1.5) - Layout.fillWidth: true - Layout.margins: 0 - RowLayout { - anchors.verticalCenter: topPart.verticalCenter - Item { - Layout.preferredHeight: subsurfaceLogo.height - Layout.leftMargin: Kirigami.Units.gridUnit / 4 - Image { - id: subsurfaceLogo - source: "qrc:/qml/subsurface-mobile-icon.png" - anchors { - verticalCenter: parent.Center - left: parent.left - } - width: Math.round(Kirigami.Units.gridUnit) - height: width - } - Kirigami.Label { - text: qsTr("Subsurface-mobile") - font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize) - height: subsurfaceLogo.height - anchors { - left: subsurfaceLogo.right - leftMargin: Math.round(Kirigami.Units.gridUnit / 2) - } - font.weight: Font.Light - verticalAlignment: Text.AlignVCenter - Layout.fillWidth: false - color: subsurfaceTheme.accentTextColor - } - } - Item { - Layout.fillWidth: true - } - } - MouseArea { - anchors.fill: topPart - onClicked: { - if (stackView.depth == 1 && showingDiveList) { - scrollToTop() - } - } - } -} diff --git a/qt-mobile/qml/dive.jpg b/qt-mobile/qml/dive.jpg deleted file mode 100644 index 56445648a..000000000 Binary files a/qt-mobile/qml/dive.jpg and /dev/null differ diff --git a/qt-mobile/qml/icons/context-menu.png b/qt-mobile/qml/icons/context-menu.png deleted file mode 100644 index df34cfd4f..000000000 Binary files a/qt-mobile/qml/icons/context-menu.png and /dev/null differ diff --git a/qt-mobile/qml/icons/context-menu.svg b/qt-mobile/qml/icons/context-menu.svg deleted file mode 100644 index e0750c57e..000000000 --- a/qt-mobile/qml/icons/context-menu.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/qt-mobile/qml/icons/main-menu.png b/qt-mobile/qml/icons/main-menu.png deleted file mode 100644 index 20729b8f5..000000000 Binary files a/qt-mobile/qml/icons/main-menu.png and /dev/null differ diff --git a/qt-mobile/qml/icons/main-menu.svg b/qt-mobile/qml/icons/main-menu.svg deleted file mode 100644 index 1e89193f5..000000000 --- a/qt-mobile/qml/icons/main-menu.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/qt-mobile/qml/icons/menu-back.png b/qt-mobile/qml/icons/menu-back.png deleted file mode 100644 index dc96b7728..000000000 Binary files a/qt-mobile/qml/icons/menu-back.png and /dev/null differ diff --git a/qt-mobile/qml/icons/menu-edit.png b/qt-mobile/qml/icons/menu-edit.png deleted file mode 100644 index ea7dd055a..000000000 Binary files a/qt-mobile/qml/icons/menu-edit.png and /dev/null differ diff --git a/qt-mobile/qml/main.qml b/qt-mobile/qml/main.qml deleted file mode 100644 index f4f6ea28b..000000000 --- a/qt-mobile/qml/main.qml +++ /dev/null @@ -1,360 +0,0 @@ -import QtQuick 2.4 -import QtQuick.Controls 1.2 -import QtQuick.Controls.Styles 1.2 -import QtQuick.Window 2.2 -import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.2 -import org.subsurfacedivelog.mobile 1.0 -import org.kde.kirigami 1.0 as Kirigami - -Kirigami.ApplicationWindow { - id: rootItem - title: qsTr("Subsurface-mobile") - - header.minimumHeight: 0 - header.preferredHeight: Kirigami.Units.gridUnit - header.maximumHeight: Kirigami.Units.gridUnit * 2 - property bool fullscreen: true - property int oldStatus: -1 - property alias accessingCloud: manager.accessingCloud - property QtObject notification: null - property bool showingDiveList: false - property alias syncToCloud: manager.syncToCloud - onAccessingCloudChanged: { - if (accessingCloud >= 0) { - // we now keep updating this to show progress, so timing out after 30 seconds is more useful - // but should still be very conservative - showPassiveNotification("Accessing Subsurface Cloud Storage " + accessingCloud +"%", 30000); - } else { - hidePassiveNotification(); - } - } - - FontMetrics { - id: fontMetrics - } - - visible: false - opacity: 0 - - function returnTopPage() { - for (var i=stackView.depth; i>1; i--) { - stackView.pop() - } - detailsWindow.endEditMode() - } - - function scrollToTop() { - diveList.scrollToTop() - } - - function showMap(location) { - var urlPrefix = "https://www.google.com/maps/place/" - var locationPair = location + "/@" + location - var urlSuffix = ",5000m/data=!3m1!1e3!4m2!3m1!1s0x0:0x0" - Qt.openUrlExternally(urlPrefix + locationPair + urlSuffix) - - } - - function startAddDive() { - detailsWindow.state = "add" - detailsWindow.dive_id = manager.addDive(); - detailsWindow.number = manager.getNumber(detailsWindow.dive_id) - detailsWindow.date = manager.getDate(detailsWindow.dive_id) - detailsWindow.airtemp = "" - detailsWindow.watertemp = "" - detailsWindow.buddy = "" - detailsWindow.depth = "" - detailsWindow.divemaster = "" - detailsWindow.notes = "" - detailsWindow.location = "" - detailsWindow.duration = "" - detailsWindow.suit = "" - detailsWindow.weight = "" - detailsWindow.gasmix = "" - detailsWindow.startpressure = "" - detailsWindow.endpressure = "" - stackView.push(detailsWindow) - } - - globalDrawer: Kirigami.GlobalDrawer { - title: "Subsurface" - titleIcon: "qrc:/qml/subsurface-mobile-icon.png" - - bannerImageSource: "dive.jpg" - actions: [ - Kirigami.Action { - text: "Dive list" - onTriggered: { - manager.appendTextToLog("requested dive list with credential status " + manager.credentialStatus) - if (manager.credentialStatus == QMLManager.UNKNOWN) { - // the user has asked to change credentials - if the credentials before that - // were valid, go back to dive list - if (oldStatus == QMLManager.VALID || oldStatus == QMLManager.VALID_EMAIL) { - manager.credentialStatus = oldStatus - } - } - returnTopPage() - globalDrawer.close() - } - }, - Kirigami.Action { - text: "Cloud credentials" - onTriggered: { - returnTopPage() - oldStatus = manager.credentialStatus - if (diveList.numDives > 0) { - manager.startPageText = "Enter different credentials or return to dive list" - } else { - manager.startPageText = "Enter valid cloud storage credentials" - } - - manager.credentialStatus = QMLManager.UNKNOWN - } - }, - Kirigami.Action { - text: "Manage dives" - enabled: manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL - /* - * disable for the beta to avoid confusion - Action { - text: "Download from computer" - onTriggered: { - detailsWindow.endEditMode() - stackView.push(downloadDivesWindow) - } - } - */ - Kirigami.Action { - text: "Add dive manually" - onTriggered: { - returnTopPage() // otherwise odd things happen with the page stack - startAddDive() - } - } - Kirigami.Action { - text: "Manual sync with cloud" - onTriggered: { - globalDrawer.close() - detailsWindow.endEditMode() - manager.saveChanges(); - } - } - Kirigami.Action { - text: syncToCloud ? "Disable auto cloud sync" : "Enable auto cloud sync" - onTriggered: { - syncToCloud = !syncToCloud - if (!syncToCloud) { - var alertText = "Turning off automatic sync to cloud causes all data to only be stored locally.\n" - alertText += "This can be very useful in situations with limited or no network access.\n" - alertText += "Please chose 'Manual sync with cloud' if you have network connectivity\n" - alertText += "and want to sync your data to cloud storage." - showPassiveNotification(alertText, 10000) - } - } - } - }, - - Kirigami.Action { - text: "GPS" - enabled: manager.credentialStatus === QMLManager.VALID || manager.credentialStatus === QMLManager.VALID_EMAIL - Kirigami.Action { - text: "GPS-tag dives" - onTriggered: { - manager.applyGpsData(); - } - } - - Kirigami.Action { - text: "Upload GPS data" - onTriggered: { - manager.sendGpsData(); - } - } - - Kirigami.Action { - text: "Download GPS data" - onTriggered: { - manager.downloadGpsData(); - } - } - - Kirigami.Action { - text: "Show GPS fixes" - onTriggered: { - returnTopPage() - manager.populateGpsData(); - stackView.push(gpsWindow) - } - } - - Kirigami.Action { - text: "Clear GPS cache" - onTriggered: { - manager.clearGpsData(); - } - } - Kirigami.Action { - text: "Preferences" - onTriggered: { - stackView.push(prefsWindow) - detailsWindow.endEditMode() - } - } - }, - - Kirigami.Action { - text: "Developer" - Kirigami.Action { - text: "App log" - onTriggered: { - stackView.push(logWindow) - } - } - - Kirigami.Action { - text: "Theme information" - onTriggered: { - stackView.push(themetest) - } - } - }, - Kirigami.Action { - text: "User manual" - onTriggered: { - Qt.openUrlExternally("https://subsurface-divelog.org/documentation/subsurface-mobile-user-manual/") - } - }, - Kirigami.Action { - text: "About" - onTriggered: { - stackView.push(aboutWindow) - detailsWindow.endEditMode() - } - } - ] // end actions - - MouseArea { - height: childrenRect.height - width: Kirigami.Units.gridUnit * 10 - CheckBox { - //text: "Run location service" - id: locationCheckbox - anchors { - left: parent.left - top: parent.top - } - checked: manager.locationServiceEnabled - onCheckedChanged: { - manager.locationServiceEnabled = checked; - } - } - Kirigami.Label { - x: Kirigami.Units.gridUnit * 1.5 - anchors { - left: locationCheckbox.right - //leftMargin: units.smallSpacing - verticalCenter: locationCheckbox.verticalCenter - } - text: "Run location service" - } - onClicked: { - print("Click.") - locationCheckbox.checked = !locationCheckbox.checked - } - } - } - - contextDrawer: Kirigami.ContextDrawer { - id: contextDrawer - actions: rootItem.pageStack.currentPage ? rootItem.pageStack.currentPage.contextualActions : null - title: "Actions" - } - - QtObject { - id: subsurfaceTheme - property int titlePointSize: Math.round(fontMetrics.font.pointSize * 1.5) - property int smallPointSize: Math.round(fontMetrics.font.pointSize * 0.8) - property color accentColor: "#2d5b9a" - property color shadedColor: "#132744" - property color accentTextColor: "#ececec" - property color diveListTextColor: "#000000" // the Kirigami theme text color is too light - property int columnWidth: Math.round(rootItem.width/(Kirigami.Units.gridUnit*30)) > 0 ? Math.round(rootItem.width / Math.round(rootItem.width/(Kirigami.Units.gridUnit*30))) : rootItem.width - } -/* - toolBar: TopBar { - width: parent.width - height: Layout.minimumHeight - } - */ - - property Item stackView: pageStack - pageStack.initialPage: DiveList { - anchors.fill: detailsPage - id: diveList - opacity: 0 - Behavior on opacity { - NumberAnimation { - duration: 200 - easing.type: Easing.OutQuad - } - } - - } - - QMLManager { - id: manager - } - - Preferences { - id: prefsWindow - visible: false - } - - About { - id: aboutWindow - visible: false - } - - DiveDetails { - id: detailsWindow - visible: false - width: parent.width - height: parent.height - } - - DownloadFromDiveComputer { - id: downloadDivesWindow - visible: false - } - - Log { - id: logWindow - visible: false - } - - GpsList { - id: gpsWindow - visible: false - } - - ThemeTest { - id: themetest - visible: false - } - - Component.onCompleted: { - Kirigami.Theme.highlightColor = subsurfaceTheme.accentColor - manager.finishSetup(); - rootItem.visible = true - diveList.opacity = 1 - rootItem.opacity = 1 - } - Behavior on opacity { - NumberAnimation { - duration: 200 - easing.type: Easing.OutQuad - } - } -} diff --git a/qt-mobile/qml/mobile-resources.qrc b/qt-mobile/qml/mobile-resources.qrc deleted file mode 100644 index e6c1fba65..000000000 --- a/qt-mobile/qml/mobile-resources.qrc +++ /dev/null @@ -1,66 +0,0 @@ - - - main.qml - TextButton.qml - Preferences.qml - About.qml - CloudCredentials.qml - DiveList.qml - DiveDetails.qml - DiveDetailsEdit.qml - DiveDetailsView.qml - DownloadFromDiveComputer.qml - GpsList.qml - Log.qml - TopBar.qml - ThemeTest.qml - StartPage.qml - dive.jpg - SubsurfaceButton.qml - ../../icons/subsurface-mobile-icon.png - icons/main-menu.png - icons/context-menu.png - icons/menu-edit.png - icons/menu-back.png - - - kirigami/qmldir - kirigami/Action.qml - kirigami/ApplicationWindow.qml - kirigami/BasicListItem.qml - kirigami/GlobalDrawer.qml - kirigami/ContextDrawer.qml - kirigami/Page.qml - kirigami/ScrollablePage.qml - kirigami/Icon.qml - kirigami/Heading.qml - kirigami/OverlaySheet.qml - kirigami/ApplicationHeader.qml - kirigami/private/PageRow.qml - kirigami/Label.qml - kirigami/AbstractListItem.qml - kirigami/SwipeListItem.qml - kirigami/OverlayDrawer.qml - kirigami/Theme.qml - kirigami/Units.qml - kirigami/private/RefreshableScrollView.qml - kirigami/private/ActionButton.qml - kirigami/private/BackButton.qml - kirigami/private/MenuIcon.qml - kirigami/private/ContextIcon.qml - kirigami/private/AbstractDrawer.qml - kirigami/private/PageStack.js - kirigami/private/PassiveNotification.qml - kirigami/icons/go-next.svg - kirigami/icons/go-previous.svg - kirigami/icons/distribute-horizontal-x.svg - kirigami/icons/document-edit.svg - kirigami/icons/document-save.svg - kirigami/icons/view-readermode.svg - kirigami/icons/dialog-cancel.svg - kirigami/icons/application-menu.svg - kirigami/icons/gps.svg - kirigami/icons/trash-empty.svg - kirigami/icons/list-add.svg - - diff --git a/qt-mobile/qml/theme/Theme.qml b/qt-mobile/qml/theme/Theme.qml deleted file mode 100644 index 2c51ae00f..000000000 --- a/qt-mobile/qml/theme/Theme.qml +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2015 Marco Martin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, or - * (at your option) any later version. - * - * This program 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 Library General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.0 - -//pragma Singleton - -/*! - \qmltype Theme - \inqmlmodule Material 0.1 - - \brief Provides access to standard colors that follow the Material Design specification. - - See \l {http://www.google.com/design/spec/style/color.html#color-ui-color-application} for - details about choosing a color scheme for your application. - */ -QtObject { - id: theme - - property color textColor: Qt.rgba(0,0,0, 0.54) - - property color highlightColor: "#2196F3" - property color backgroundColor: "#f3f3f3" - property color linkColor: "#2196F3" - property color visitedLinkColor: "#2196F3" - - property color buttonTextColor: Qt.rgba(0,0,0, 0.54) - property color buttonBackgroundColor: "#f3f3f3" - property color buttonHoverColor: "#2196F3" - property color buttonFocusColor: "#2196F3" - - property color viewTextColor: Qt.rgba(0,0,0, 0.54) - property color viewBackgroundColor: "#f3f3f3" - property color viewHoverColor: "#2196F3" - property color viewFocusColor: "#2196F3" - - property color complementaryTextColor: "#f3f3f3" - property color complementaryBackgroundColor: Qt.rgba(0,0,0, 0.54) - property color complementaryHoverColor: "#2196F3" - property color complementaryFocusColor: "#2196F3" -} diff --git a/qt-mobile/qml/theme/Units.qml b/qt-mobile/qml/theme/Units.qml deleted file mode 100644 index 7cfa5c23b..000000000 --- a/qt-mobile/qml/theme/Units.qml +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2015 Marco Martin - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Library General Public License as - * published by the Free Software Foundation; either version 2, or - * (at your option) any later version. - * - * This program 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 Library General Public License for more details - * - * You should have received a copy of the GNU Library General Public - * License along with this program; if not, write to the - * Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -import QtQuick 2.5 -import QtQuick.Window 2.2 - -//pragma Singleton - - -QtObject { - id: units - - /** - * The fundamental unit of space that should be used for sizes, expressed in pixels. - * Given the screen has an accurate DPI settings, it corresponds to a width of - * the capital letter M - */ - property int gridUnit: fontMetrics.height - - /** - * units.iconSizes provides access to platform-dependent icon sizing - * - * The icon sizes provided are normalized for different DPI, so icons - * will scale depending on the DPI. - * - * Icon sizes from KIconLoader, adjusted to devicePixelRatio: - * * small - * * smallMedium - * * medium - * * large - * * huge - * * enormous - * - * Not devicePixelRation-adjusted:: - * * desktop - */ - property QtObject iconSizes: QtObject { - property int small: 16 * devicePixelRatio - property int smallMedium: 22 * devicePixelRatio - property int medium: 32 * devicePixelRatio - property int large: 48 * devicePixelRatio - property int huge: 64 * devicePixelRatio - property int enormous: 128 * devicePixelRatio - } - - /** - * units.smallSpacing is the amount of spacing that should be used around smaller UI elements, - * for example as spacing in Columns. Internally, this size depends on the size of - * the default font as rendered on the screen, so it takes user-configured font size and DPI - * into account. - */ - property int smallSpacing: gridUnit/4 - - /** - * units.largeSpacing is the amount of spacing that should be used inside bigger UI elements, - * for example between an icon and the corresponding text. Internally, this size depends on - * the size of the default font as rendered on the screen, so it takes user-configured font - * size and DPI into account. - */ - property int largeSpacing: gridUnit - - /** - * The ratio between physical and device-independent pixels. This value does not depend on the \ - * size of the configured font. If you want to take font sizes into account when scaling elements, - * use theme.mSize(theme.defaultFont), units.smallSpacing and units.largeSpacing. - * The devicePixelRatio follows the definition of "device independent pixel" by Microsoft. - */ - property real devicePixelRatio: Screen.devicePixelRatio - - /** - * units.longDuration should be used for longer, screen-covering animations, for opening and - * closing of dialogs and other "not too small" animations - */ - property int longDuration: 250 - - /** - * units.shortDuration should be used for short animations, such as accentuating a UI event, - * hover events, etc.. - */ - property int shortDuration: 150 - - property QtObject fontMetrics: FontMetrics {} -} diff --git a/qt-mobile/qml/theme/qmldir b/qt-mobile/qml/theme/qmldir deleted file mode 100644 index c654dbad6..000000000 --- a/qt-mobile/qml/theme/qmldir +++ /dev/null @@ -1,2 +0,0 @@ -#singleton Units Units.qml -#//singleton Theme Theme.qml diff --git a/qt-mobile/qmlmanager.cpp b/qt-mobile/qmlmanager.cpp deleted file mode 100644 index c6e6c5025..000000000 --- a/qt-mobile/qmlmanager.cpp +++ /dev/null @@ -1,1078 +0,0 @@ -#include "qmlmanager.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "qt-models/divelistmodel.h" -#include -#include "divelist.h" -#include "device.h" -#include "pref.h" -#include "qthelper.h" -#include "qt-gui.h" -#include "git-access.h" -#include "subsurface-core/cloudstorage.h" - -QMLManager *QMLManager::m_instance = NULL; - -#define RED_FONT QLatin1Literal("") -#define END_FONT QLatin1Literal("") - -static void appendTextToLogStandalone(const char *text) -{ - QMLManager *self = QMLManager::instance(); - if (self) - self->appendTextToLog(QString(text)); -} - -extern "C" int gitProgressCB(int percent, const char *text) -{ - static QElapsedTimer timer; - static qint64 lastTime = 0; - static int lastPercent = -100; - - if (!timer.isValid() || percent == 0) { - timer.restart(); - lastTime = 0; - lastPercent = -100; - } - QMLManager *self = QMLManager::instance(); - if (self) { - qint64 elapsed = timer.elapsed(); - // don't show the same status twice in 200ms - if (percent == lastPercent && elapsed - lastTime < 200) - return 0; - self->loadDiveProgress(percent); - QString logText = QString::number(elapsed / 1000.0, 'f', 1) + " / " + QString::number((elapsed - lastTime) / 1000.0, 'f', 3) + - QString(" : git progress %1 (%2)").arg(percent).arg(text); - self->appendTextToLog(logText); - qDebug() << logText; - qApp->processEvents(); - qApp->flush(); - lastTime = elapsed; - } - // return 0 so that we don't end the download - return 0; -} - -QMLManager::QMLManager() : m_locationServiceEnabled(false), - m_verboseEnabled(false), - reply(0), - deletedDive(0), - deletedTrip(0), - m_credentialStatus(UNKNOWN), - m_lastDevicePixelRatio(1.0), - alreadySaving(false) -{ - m_instance = this; - connect(qobject_cast(QApplication::instance()), &QApplication::applicationStateChanged, this, &QMLManager::applicationStateChanged); - appendTextToLog(getUserAgent()); - appendTextToLog(QStringLiteral("build with Qt Version %1, runtime from Qt Version %2").arg(QT_VERSION_STR).arg(qVersion())); - qDebug() << "Starting" << getUserAgent(); - qDebug() << QStringLiteral("build with Qt Version %1, runtime from Qt Version %2").arg(QT_VERSION_STR).arg(qVersion()); - setStartPageText(tr("Starting...")); - setAccessingCloud(-1); - setSyncToCloud(true); - // create location manager service - locationProvider = new GpsLocation(&appendTextToLogStandalone, this); - set_git_update_cb(&gitProgressCB); - - // make sure we know if the current cloud repo has been successfully synced - syncLoadFromCloud(); -} - -void QMLManager::applicationStateChanged(Qt::ApplicationState state) -{ - if (!timer.isValid()) - timer.start(); - QString stateText; - switch (state) { - case Qt::ApplicationActive: stateText = "active"; break; - case Qt::ApplicationHidden: stateText = "hidden"; break; - case Qt::ApplicationSuspended: stateText = "suspended"; break; - case Qt::ApplicationInactive: stateText = "inactive"; break; - default: stateText = QString("none of the four: 0x") + QString::number(state, 16); - } - stateText.prepend(QString::number(timer.elapsed() / 1000.0,'f', 3) + ": AppState changed to "); - stateText.append(" with "); - stateText.append((alreadySaving ? QLatin1Literal("") : QLatin1Literal("no ")) + QLatin1Literal("save ongoing")); - stateText.append(" and "); - stateText.append((unsaved_changes() ? QLatin1Literal("") : QLatin1Literal("no ")) + QLatin1Literal("unsaved changes")); - appendTextToLog(stateText); - qDebug() << stateText; - - if (!alreadySaving && state == Qt::ApplicationInactive && unsaved_changes()) { - // FIXME - // make sure the user sees that we are saving data if they come back - // while this is running - alreadySaving = true; - saveChanges(); - alreadySaving = false; - appendTextToLog(QString::number(timer.elapsed() / 1000.0,'f', 3) + ": done saving to git local / remote"); - mark_divelist_changed(false); - } -} - -void QMLManager::openLocalThenRemote(QString url) -{ - clear_dive_file_data(); - QByteArray fileNamePrt = QFile::encodeName(url); - bool glo = prefs.git_local_only; - prefs.git_local_only = true; - int error = parse_file(fileNamePrt.data()); - setAccessingCloud(-1); - prefs.git_local_only = glo; - if (error) { - appendTextToLog(QStringLiteral("loading dives from cache failed %1").arg(error)); - } else { - // if we can load from the cache, we know that we have at least a valid email - if (credentialStatus() == UNKNOWN) - setCredentialStatus(VALID_EMAIL); - prefs.unit_system = informational_prefs.unit_system; - if (informational_prefs.unit_system == IMPERIAL) - informational_prefs.units = IMPERIAL_units; - prefs.units = informational_prefs.units; - int i; - struct dive *d; - process_dives(false, false); - DiveListModel::instance()->clear(); - for_each_dive (i, d) { - DiveListModel::instance()->addDive(d); - } - appendTextToLog(QStringLiteral("%1 dives loaded from cache").arg(i)); - } - appendTextToLog(QStringLiteral("have cloud credentials, trying to connect")); - tryRetrieveDataFromBackend(); -} - -void QMLManager::finishSetup() -{ - // Initialize cloud credentials. - setCloudUserName(prefs.cloud_storage_email); - setCloudPassword(prefs.cloud_storage_password); - // if the cloud credentials are valid, we should get the GPS Webservice ID as well - QString url; - if (!cloudUserName().isEmpty() && - !cloudPassword().isEmpty() && - getCloudURL(url) == 0) { - openLocalThenRemote(url); - } else { - setCredentialStatus(INCOMPLETE); - appendTextToLog(QStringLiteral("no cloud credentials")); - setStartPageText(RED_FONT + tr("Please enter valid cloud credentials.") + END_FONT); - } - setDistanceThreshold(prefs.distance_threshold); - setTimeThreshold(prefs.time_threshold / 60); -} - -QMLManager::~QMLManager() -{ - m_instance = NULL; -} - -QMLManager *QMLManager::instance() -{ - return m_instance; -} - -void QMLManager::savePreferences() -{ - QSettings s; - s.beginGroup("LocationService"); - s.setValue("time_threshold", timeThreshold() * 60); - prefs.time_threshold = timeThreshold() * 60; - s.setValue("distance_threshold", distanceThreshold()); - prefs.distance_threshold = distanceThreshold(); - s.sync(); -} - -#define CLOUDURL QString(prefs.cloud_base_url) -#define CLOUDREDIRECTURL CLOUDURL + "/cgi-bin/redirect.pl" - -void QMLManager::saveCloudCredentials() -{ - QSettings s; - bool cloudCredentialsChanged = false; - s.beginGroup("CloudStorage"); - s.setValue("email", cloudUserName()); - s.setValue("password", cloudPassword()); - s.sync(); - if (!same_string(prefs.cloud_storage_email, qPrintable(cloudUserName()))) { - free(prefs.cloud_storage_email); - prefs.cloud_storage_email = strdup(qPrintable(cloudUserName())); - cloudCredentialsChanged = true; - } - - cloudCredentialsChanged |= !same_string(prefs.cloud_storage_password, qPrintable(cloudPassword())); - - if (!same_string(prefs.cloud_storage_password, qPrintable(cloudPassword()))) { - free(prefs.cloud_storage_password); - prefs.cloud_storage_password = strdup(qPrintable(cloudPassword())); - } - if (cloudUserName().isEmpty() || cloudPassword().isEmpty()) { - setStartPageText(RED_FONT + tr("Please enter valid cloud credentials.") + END_FONT); - } else if (cloudCredentialsChanged) { - free(prefs.userid); - prefs.userid = NULL; - syncLoadFromCloud(); - QString url; - getCloudURL(url); - manager()->clearAccessCache(); // remove any chached credentials - clear_git_id(); // invalidate our remembered GIT SHA - clear_dive_file_data(); - DiveListModel::instance()->clear(); - GpsListModel::instance()->clear(); - setStartPageText(tr("Attempting to open cloud storage with new credentials")); - openLocalThenRemote(url); - } -} - -void QMLManager::checkCredentialsAndExecute(execute_function_type execute) -{ - // if the cloud credentials are present, we should try to get the GPS Webservice ID - // and (if we haven't done so) load the dive list - if (!same_string(prefs.cloud_storage_email, "") && - !same_string(prefs.cloud_storage_password, "")) { - setAccessingCloud(0); - setStartPageText(tr("Testing cloud credentials")); - appendTextToLog("Have credentials, let's see if they are valid"); - connect(manager(), &QNetworkAccessManager::authenticationRequired, this, &QMLManager::provideAuth, Qt::UniqueConnection); - connect(manager(), &QNetworkAccessManager::finished, this, execute, Qt::UniqueConnection); - QUrl url(CLOUDREDIRECTURL); - request = QNetworkRequest(url); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Accept", "text/html"); - reply = manager()->get(request); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(handleError(QNetworkReply::NetworkError))); - connect(reply, &QNetworkReply::sslErrors, this, &QMLManager::handleSslErrors); - } -} - -void QMLManager::tryRetrieveDataFromBackend() -{ - checkCredentialsAndExecute(&QMLManager::retrieveUserid); -} - -void QMLManager::provideAuth(QNetworkReply *reply, QAuthenticator *auth) -{ - if (auth->user() == QString(prefs.cloud_storage_email) && - auth->password() == QString(prefs.cloud_storage_password)) { - // OK, credentials have been tried and didn't work, so they are invalid - appendTextToLog("Cloud credentials are invalid"); - setStartPageText(RED_FONT + tr("Cloud credentials are invalid") + END_FONT); - setCredentialStatus(INVALID); - reply->disconnect(); - reply->abort(); - reply->deleteLater(); - return; - } - auth->setUser(prefs.cloud_storage_email); - auth->setPassword(prefs.cloud_storage_password); -} - -void QMLManager::handleSslErrors(const QList &errors) -{ - setStartPageText(RED_FONT + tr("Cannot open cloud storage: Error creating https connection") + END_FONT); - Q_FOREACH (QSslError e, errors) { - qDebug() << e.errorString(); - } - reply->abort(); - reply->deleteLater(); - setAccessingCloud(-1); -} - -void QMLManager::handleError(QNetworkReply::NetworkError nError) -{ - QString errorString = reply->errorString(); - qDebug() << "handleError" << nError << errorString; - setStartPageText(RED_FONT + tr("Cannot open cloud storage: %1").arg(errorString) + END_FONT); - reply->abort(); - reply->deleteLater(); - setAccessingCloud(-1); -} - -void QMLManager::retrieveUserid() -{ - if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 302) { - appendTextToLog(QStringLiteral("Cloud storage connection not working correctly: %1").arg(QString(reply->readAll()))); - setAccessingCloud(-1); - return; - } - setCredentialStatus(VALID); - QString userid(prefs.userid); - if (userid.isEmpty()) { - if (same_string(prefs.cloud_storage_email, "") || same_string(prefs.cloud_storage_password, "")) { - appendTextToLog("cloud user name or password are empty, can't retrieve web user id"); - setAccessingCloud(-1); - return; - } - appendTextToLog(QStringLiteral("calling getUserid with user %1").arg(prefs.cloud_storage_email)); - userid = locationProvider->getUserid(prefs.cloud_storage_email, prefs.cloud_storage_password); - } - if (!userid.isEmpty()) { - // overwrite the existing userid - free(prefs.userid); - prefs.userid = strdup(qPrintable(userid)); - QSettings s; - s.setValue("subsurface_webservice_uid", prefs.userid); - s.sync(); - } - loadDivesWithValidCredentials(); -} - -void QMLManager::loadDiveProgress(int percent) -{ - QString text(tr("Loading dive list from cloud storage.")); - setAccessingCloud(percent); - while (percent > 0) { - text.append("."); - percent -= 10; - } - setStartPageText(text); -} - -void QMLManager::loadDivesWithValidCredentials() -{ - if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 302) { - appendTextToLog(QStringLiteral("Cloud storage connection not working correctly: ") + reply->readAll()); - setStartPageText(RED_FONT + tr("Cannot connect to cloud storage") + END_FONT); - setAccessingCloud(-1); - return; - } - setCredentialStatus(VALID); - appendTextToLog("Cloud credentials valid, loading dives..."); - setStartPageText("Cloud credentials valid, loading dives..."); - git_storage_update_progress(0, "load dives with valid credentials"); - QString url; - if (getCloudURL(url)) { - QString errorString(get_error_string()); - appendTextToLog(errorString); - setStartPageText(RED_FONT + tr("Cloud storage error: %1").arg(errorString) + END_FONT); - setAccessingCloud(-1); - return; - } - QByteArray fileNamePrt = QFile::encodeName(url); - if (check_git_sha(fileNamePrt.data()) == 0) { - qDebug() << "local cache was current, no need to modify dive list"; - appendTextToLog("Cloud sync shows local cache was current"); - setLoadFromCloud(true); - setAccessingCloud(-1); - return; - } - clear_dive_file_data(); - DiveListModel::instance()->clear(); - - int error = parse_file(fileNamePrt.data()); - setAccessingCloud(-1); - if (!error) { - report_error("filename is now %s", fileNamePrt.data()); - const char *error_string = get_error_string(); - appendTextToLog(error_string); - set_filename(fileNamePrt.data(), true); - } else { - report_error("failed to open file %s", fileNamePrt.data()); - QString errorString(get_error_string()); - appendTextToLog(errorString); - setStartPageText(RED_FONT + tr("Cloud storage error: %1").arg(errorString) + END_FONT); - return; - } - prefs.unit_system = informational_prefs.unit_system; - if (informational_prefs.unit_system == IMPERIAL) - informational_prefs.units = IMPERIAL_units; - prefs.units = informational_prefs.units; - process_dives(false, false); - - int i; - struct dive *d; - - for_each_dive (i, d) { - DiveListModel::instance()->addDive(d); - } - appendTextToLog(QStringLiteral("%1 dives loaded").arg(i)); - if (dive_table.nr == 0) - setStartPageText(tr("Cloud storage open successfully. No dives in dive list.")); - setLoadFromCloud(true); -} - -void QMLManager::refreshDiveList() -{ - int i; - struct dive *d; - DiveListModel::instance()->clear(); - for_each_dive (i, d) { - DiveListModel::instance()->addDive(d); - } -} - -// update the dive and return the notes field, stripped of the HTML junk -void QMLManager::commitChanges(QString diveId, QString date, QString location, QString gps, QString duration, QString depth, - QString airtemp, QString watertemp, QString suit, QString buddy, QString diveMaster, QString weight, QString notes, - QString startpressure, QString endpressure, QString gasmix) -{ -#define DROP_EMPTY_PLACEHOLDER(_s) if ((_s) == QLatin1Literal("--")) (_s).clear() - - DROP_EMPTY_PLACEHOLDER(location); - DROP_EMPTY_PLACEHOLDER(duration); - DROP_EMPTY_PLACEHOLDER(depth); - DROP_EMPTY_PLACEHOLDER(airtemp); - DROP_EMPTY_PLACEHOLDER(watertemp); - DROP_EMPTY_PLACEHOLDER(suit); - DROP_EMPTY_PLACEHOLDER(buddy); - DROP_EMPTY_PLACEHOLDER(diveMaster); - DROP_EMPTY_PLACEHOLDER(weight); - DROP_EMPTY_PLACEHOLDER(gasmix); - DROP_EMPTY_PLACEHOLDER(startpressure); - DROP_EMPTY_PLACEHOLDER(endpressure); - DROP_EMPTY_PLACEHOLDER(notes); - -#undef DROP_EMPTY_PLACEHOLDER - - struct dive *d = get_dive_by_uniq_id(diveId.toInt()); - // notes comes back as rich text - let's convert this into plain text - QTextDocument doc; - doc.setHtml(notes); - notes = doc.toPlainText(); - - if (!d) { - qDebug() << "don't touch this... no dive"; - return; - } - bool diveChanged = false; - bool needResort = false; - - invalidate_dive_cache(d); - if (date != get_dive_date_string(d->when)) { - diveChanged = needResort = true; - QDateTime newDate; - // what a pain - Qt will not parse dates if the day of the week is incorrect - // so if the user changed the date but didn't update the day of the week (most likely behavior, actually), - // we need to make sure we don't try to parse that - QString format(QString(prefs.date_format) + QChar(' ') + prefs.time_format); - if (format.contains(QLatin1String("ddd")) || format.contains(QLatin1String("dddd"))) { - QString dateFormatToDrop = format.contains(QLatin1String("ddd")) ? QStringLiteral("ddd") : QStringLiteral("dddd"); - QDateTime ts; - QLocale loc = getLocale(); - ts.setMSecsSinceEpoch(d->when * 1000L); - QString drop = loc.toString(ts.toUTC(), dateFormatToDrop); - format.replace(dateFormatToDrop, ""); - date.replace(drop, ""); - } - newDate = QDateTime::fromString(date, format); - if (!newDate.isValid()) { - qDebug() << "unable to parse date" << date << "with the given format" << format; - QRegularExpression isoDate("\\d+-\\d+-\\d+[^\\d]+\\d+:\\d+"); - if (date.contains(isoDate)) { - newDate = QDateTime::fromString(date, "yyyy-M-d h:m:s"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date, "yy-M-d h:m:s"); - if (newDate.isValid()) - goto parsed; - } - QRegularExpression isoDateNoSecs("\\d+-\\d+-\\d+[^\\d]+\\d+"); - if (date.contains(isoDateNoSecs)) { - newDate = QDateTime::fromString(date, "yyyy-M-d h:m"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date, "yy-M-d h:m"); - if (newDate.isValid()) - goto parsed; - } - QRegularExpression usDate("\\d+/\\d+/\\d+[^\\d]+\\d+:\\d+:\\d+"); - if (date.contains(usDate)) { - newDate = QDateTime::fromString(date, "M/d/yyyy h:m:s"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date, "M/d/yy h:m:s"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date.toLower(), "M/d/yyyy h:m:sap"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date.toLower(), "M/d/yy h:m:sap"); - if (newDate.isValid()) - goto parsed; - } - QRegularExpression usDateNoSecs("\\d+/\\d+/\\d+[^\\d]+\\d+:\\d+"); - if (date.contains(usDateNoSecs)) { - newDate = QDateTime::fromString(date, "M/d/yyyy h:m"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date, "M/d/yy h:m"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date.toLower(), "M/d/yyyy h:map"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date.toLower(), "M/d/yy h:map"); - if (newDate.isValid()) - goto parsed; - } - QRegularExpression leDate("\\d+\\.\\d+\\.\\d+[^\\d]+\\d+:\\d+:\\d+"); - if (date.contains(leDate)) { - newDate = QDateTime::fromString(date, "d.M.yyyy h:m:s"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date, "d.M.yy h:m:s"); - if (newDate.isValid()) - goto parsed; - } - QRegularExpression leDateNoSecs("\\d+\\.\\d+\\.\\d+[^\\d]+\\d+:\\d+"); - if (date.contains(leDateNoSecs)) { - newDate = QDateTime::fromString(date, "d.M.yyyy h:m"); - if (newDate.isValid()) - goto parsed; - newDate = QDateTime::fromString(date, "d.M.yy h:m"); - if (newDate.isValid()) - goto parsed; - } - } -parsed: - if (newDate.isValid()) { - // stupid Qt... two digit years are always 19xx - WTF??? - // so if adding a hundred years gets you into something before a year from now... - // add a hundred years. - if (newDate.addYears(100) < QDateTime::currentDateTime().addYears(1)) - newDate = newDate.addYears(100); - d->dc.when = d->when = newDate.toMSecsSinceEpoch() / 1000 + gettimezoneoffset(newDate.toMSecsSinceEpoch() / 1000); - } else { - qDebug() << "none of our parsing attempts worked for the date string"; - } - } - struct dive_site *ds = get_dive_site_by_uuid(d->dive_site_uuid); - char *locationtext = NULL; - if (ds) - locationtext = ds->name; - if (!same_string(locationtext, qPrintable(location))) { - diveChanged = true; - // this is not ideal - and it's missing the gps information - // but for now let's just create a new dive site - ds = get_dive_site_by_uuid(create_dive_site(qPrintable(location), d->when)); - d->dive_site_uuid = ds->uuid; - } - if (!gps.isEmpty()) { - QString gpsString = getCurrentPosition(); - if (gpsString != QString("waiting for the next gps location")) { - qDebug() << "from commitChanges call to getCurrentPosition returns" << gpsString; - double lat, lon; - if (parseGpsText(qPrintable(gpsString), &lat, &lon)) { - struct dive_site *ds = get_dive_site_by_uuid(d->dive_site_uuid); - if (ds) { - ds->latitude.udeg = lat * 1000000; - ds->longitude.udeg = lon * 1000000; - } else { - degrees_t latData, lonData; - latData.udeg = lat; - lonData.udeg = lon; - d->dive_site_uuid = create_dive_site_with_gps("new site", latData, lonData, d->when); - } - qDebug() << "set up dive site with new GPS data"; - } - } else { - qDebug() << "still don't have a position - will need to implement some sort of callback"; - } - } - if (get_dive_duration_string(d->duration.seconds, tr("h:"), tr("min")) != duration) { - diveChanged = true; - int h = 0, m = 0, s = 0; - QRegExp r1(QStringLiteral("(\\d*)\\s*%1[\\s,:]*(\\d*)\\s*%2[\\s,:]*(\\d*)\\s*%3").arg(tr("h")).arg(tr("min")).arg(tr("sec")), Qt::CaseInsensitive); - QRegExp r2(QStringLiteral("(\\d*)\\s*%1[\\s,:]*(\\d*)\\s*%2").arg(tr("h")).arg(tr("min")), Qt::CaseInsensitive); - QRegExp r3(QStringLiteral("(\\d*)\\s*%1").arg(tr("min")), Qt::CaseInsensitive); - QRegExp r4(QStringLiteral("(\\d*):(\\d*):(\\d*)")); - QRegExp r5(QStringLiteral("(\\d*):(\\d*)")); - QRegExp r6(QStringLiteral("(\\d*)")); - if (r1.indexIn(duration) >= 0) { - h = r1.cap(1).toInt(); - m = r1.cap(2).toInt(); - s = r1.cap(3).toInt(); - } else if (r2.indexIn(duration) >= 0) { - h = r2.cap(1).toInt(); - m = r2.cap(2).toInt(); - } else if (r3.indexIn(duration) >= 0) { - m = r3.cap(1).toInt(); - } else if (r4.indexIn(duration) >= 0) { - h = r4.cap(1).toInt(); - m = r4.cap(2).toInt(); - s = r4.cap(3).toInt(); - } else if (r5.indexIn(duration) >= 0) { - h = r5.cap(1).toInt(); - m = r5.cap(2).toInt(); - } else if (r6.indexIn(duration) >= 0) { - m = r6.cap(1).toInt(); - } - d->dc.duration.seconds = d->duration.seconds = h * 3600 + m * 60 + s; - if (same_string(d->dc.model, "manually added dive")) { - free(d->dc.sample); - d->dc.sample = 0; - d->dc.samples = 0; - } else { - qDebug() << "changing the duration on a dive that wasn't manually added - Uh-oh"; - } - - } - if (get_depth_string(d->maxdepth.mm, true, true) != depth) { - int depthValue = parseLengthToMm(depth); - // the QML code should stop negative depth, but massively huge depth can make - // the profile extremely slow or even run out of memory and crash, so keep - // the depth <= 500m - if (0 <= depthValue && depthValue <= 500000) { - diveChanged = true; - d->maxdepth.mm = depthValue; - if (same_string(d->dc.model, "manually added dive")) { - d->dc.maxdepth.mm = d->maxdepth.mm; - free(d->dc.sample); - d->dc.sample = 0; - d->dc.samples = 0; - } - } - } - if (get_temperature_string(d->airtemp, true) != airtemp) { - diveChanged = true; - d->airtemp.mkelvin = parseTemperatureToMkelvin(airtemp); - } - if (get_temperature_string(d->watertemp, true) != watertemp) { - diveChanged = true; - d->watertemp.mkelvin = parseTemperatureToMkelvin(watertemp); - } - // not sure what we'd do if there was more than one weight system - // defined - for now just ignore that case - if (weightsystem_none((void *)&d->weightsystem[1])) { - if (get_weight_string(d->weightsystem[0].weight, true) != weight) { - diveChanged = true; - d->weightsystem[0].weight.grams = parseWeightToGrams(weight); - } - } - // start and end pressures for first cylinder only - if (get_pressure_string(d->cylinder[0].start, true) != startpressure || get_pressure_string(d->cylinder[0].end, true) != endpressure) { - diveChanged = true; - d->cylinder[0].start.mbar = parsePressureToMbar(startpressure); - d->cylinder[0].end.mbar = parsePressureToMbar(endpressure); - if (d->cylinder[0].end.mbar > d->cylinder[0].start.mbar) - d->cylinder[0].end.mbar = d->cylinder[0].start.mbar; - } - // gasmix for first cylinder - if (get_gas_string(d->cylinder[0].gasmix) != gasmix) { - int o2 = parseGasMixO2(gasmix); - int he = parseGasMixHE(gasmix); - // the QML code SHOULD only accept valid gas mixes, but just to make sure - if (o2 >= 0 && o2 <= 1000 && - he >= 0 && he <= 1000 && - o2 + he <= 1000) { - diveChanged = true; - d->cylinder[0].gasmix.o2.permille = o2; - d->cylinder[0].gasmix.he.permille = he; - } - } - if (!same_string(d->suit, qPrintable(suit))) { - diveChanged = true; - free(d->suit); - d->suit = strdup(qPrintable(suit)); - } - if (!same_string(d->buddy, qPrintable(buddy))) { - diveChanged = true; - free(d->buddy); - d->buddy = strdup(qPrintable(buddy)); - } - if (!same_string(d->divemaster, qPrintable(diveMaster))) { - diveChanged = true; - free(d->divemaster); - d->divemaster = strdup(qPrintable(diveMaster)); - } - if (!same_string(d->notes, qPrintable(notes))) { - diveChanged = true; - free(d->notes); - d->notes = strdup(qPrintable(notes)); - } - // now that we have it all figured out, let's see what we need - // to update - DiveListModel *dm = DiveListModel::instance(); - int oldModelIdx = dm->getDiveIdx(d->id); - int oldIdx = get_idx_by_uniq_id(d->id); - if (needResort) { - // we know that the only thing that might happen in a resort is that - // this one dive moves to a different spot in the dive list - sort_table(&dive_table); - int newIdx = get_idx_by_uniq_id(d->id); - if (newIdx != oldIdx) { - DiveObjectHelper *newDive = new DiveObjectHelper(d); - DiveListModel::instance()->removeDive(oldModelIdx); - DiveListModel::instance()->insertDive(oldModelIdx - (newIdx - oldIdx), newDive); - diveChanged = false; // because we already modified things - } - } - if (diveChanged) { - if (d->maxdepth.mm == d->dc.maxdepth.mm && - d->maxdepth.mm > 0 && - same_string(d->dc.model, "manually added dive") && - d->dc.samples == 0) { - // so we have depth > 0, a manually added dive and no samples - // let's create an actual profile so the desktop version can work it - // first clear out the mean depth (or the fake_dc() function tries - // to be too clever - d->meandepth.mm = d->dc.meandepth.mm = 0; - d->dc = *fake_dc(&d->dc, true); - } - DiveListModel::instance()->updateDive(oldModelIdx, d); - } - if (diveChanged || needResort) - // we no longer save right away, but only the next time the app is not - // in the foreground (or when explicitly requested) - mark_divelist_changed(true); - -} - -void QMLManager::saveChanges() -{ - if (!loadFromCloud()) { - appendTextToLog("Don't save dives without loading from the cloud, first."); - return; - } - appendTextToLog("Saving dives."); - git_storage_update_progress(0, "saveChanges"); // reset the timers - QString fileName; - if (getCloudURL(fileName)) { - appendTextToLog(get_error_string()); - return; - } - if (prefs.git_local_only == false) { - setAccessingCloud(0); - qApp->processEvents(); // make sure that the notification is actually shown - } - if (save_dives(fileName.toUtf8().data())) { - appendTextToLog(get_error_string()); - setAccessingCloud(-1); - return; - } - setAccessingCloud(-1); - appendTextToLog("Updated dive list saved."); - set_filename(fileName.toUtf8().data(), true); - mark_divelist_changed(false); -} - -void QMLManager::undoDelete(int id) -{ - if (!deletedDive || deletedDive->id != id) { - qDebug() << "can't find the deleted dive"; - return; - } - if (deletedTrip) - insert_trip(&deletedTrip); - if (deletedDive->divetrip) { - struct dive_trip *trip = deletedDive->divetrip; - tripflag_t tripflag = deletedDive->tripflag; // this gets overwritten in add_dive_to_trip() - deletedDive->divetrip = NULL; - deletedDive->next = NULL; - deletedDive->pprev = NULL; - add_dive_to_trip(deletedDive, trip); - deletedDive->tripflag = tripflag; - } - record_dive(deletedDive); - DiveListModel::instance()->addDive(deletedDive); - // make sure the changes get saved if the app is no longer in the foreground - // or if the user requests a save - mark_divelist_changed(true); - deletedDive = NULL; - deletedTrip = NULL; -} - -void QMLManager::deleteDive(int id) -{ - struct dive *d = get_dive_by_uniq_id(id); - if (!d) { - qDebug() << "oops, trying to delete non-existing dive"; - return; - } - // clean up (or create) the storage for the deleted dive and trip (if applicable) - if (!deletedDive) - deletedDive = alloc_dive(); - else - clear_dive(deletedDive); - copy_dive(d, deletedDive); - if (!deletedTrip) { - deletedTrip = (struct dive_trip *)calloc(1, sizeof(struct dive_trip)); - } else { - free(deletedTrip->location); - free(deletedTrip->notes); - memset(deletedTrip, 0, sizeof(struct dive_trip)); - } - // if this is the last dive in that trip, remember the trip as well - if (d->divetrip && d->divetrip->nrdives == 1) { - *deletedTrip = *d->divetrip; - deletedTrip->location = copy_string(d->divetrip->location); - deletedTrip->notes = copy_string(d->divetrip->notes); - deletedTrip->nrdives = 0; - deletedDive->divetrip = deletedTrip; - } - DiveListModel::instance()->removeDiveById(id); - delete_single_dive(get_idx_by_uniq_id(id)); - // make sure the changes get saved if the app is no longer in the foreground - // or if the user requests a save - mark_divelist_changed(true); -} - -QString QMLManager::addDive() -{ - appendTextToLog("Adding new dive."); - return DiveListModel::instance()->startAddDive(); -} - -void QMLManager::addDiveAborted(int id) -{ - DiveListModel::instance()->removeDiveById(id); -} - -QString QMLManager::getCurrentPosition() -{ - return locationProvider->currentPosition(); -} - -void QMLManager::applyGpsData() -{ - if (locationProvider->applyLocations()) - refreshDiveList(); -} - -void QMLManager::sendGpsData() -{ - locationProvider->uploadToServer(); -} - -void QMLManager::downloadGpsData() -{ - locationProvider->downloadFromServer(); - populateGpsData(); -} - -void QMLManager::populateGpsData() -{ - if (GpsListModel::instance()) - GpsListModel::instance()->update(); -} - -void QMLManager::clearGpsData() -{ - locationProvider->clearGpsData(); - populateGpsData(); -} - -void QMLManager::deleteGpsFix(quint64 when) -{ - locationProvider->deleteGpsFix(when); - populateGpsData(); -} - - -QString QMLManager::logText() const -{ - QString logText = m_logText + QString("\nNumer of GPS fixes: %1").arg(locationProvider->getGpsNum()); - return logText; -} - -void QMLManager::setLogText(const QString &logText) -{ - m_logText = logText; - emit logTextChanged(); -} - -void QMLManager::appendTextToLog(const QString &newText) -{ - m_logText += "\n" + newText; - emit logTextChanged(); -} - -bool QMLManager::locationServiceEnabled() const -{ - return m_locationServiceEnabled; -} - -void QMLManager::setLocationServiceEnabled(bool locationServiceEnabled) -{ - m_locationServiceEnabled = locationServiceEnabled; - locationProvider->serviceEnable(m_locationServiceEnabled); -} - -bool QMLManager::verboseEnabled() const -{ - return m_verboseEnabled; -} - -void QMLManager::setVerboseEnabled(bool verboseMode) -{ - m_verboseEnabled = verboseMode; - verbose = verboseMode; - qDebug() << "verbose is" << verbose; - emit verboseEnabledChanged(); -} - -QString QMLManager::cloudPassword() const -{ - return m_cloudPassword; -} - -void QMLManager::setCloudPassword(const QString &cloudPassword) -{ - m_cloudPassword = cloudPassword; - emit cloudPasswordChanged(); -} - -QString QMLManager::cloudUserName() const -{ - return m_cloudUserName; -} - -void QMLManager::setCloudUserName(const QString &cloudUserName) -{ - m_cloudUserName = cloudUserName.toLower(); - emit cloudUserNameChanged(); -} - -int QMLManager::distanceThreshold() const -{ - return m_distanceThreshold; -} - -void QMLManager::setDistanceThreshold(int distance) -{ - m_distanceThreshold = distance; - emit distanceThresholdChanged(); -} - -int QMLManager::timeThreshold() const -{ - return m_timeThreshold; -} - -void QMLManager::setTimeThreshold(int time) -{ - m_timeThreshold = time; - emit timeThresholdChanged(); -} - -bool QMLManager::loadFromCloud() const -{ - return m_loadFromCloud; -} - -void QMLManager::syncLoadFromCloud() -{ - QSettings s; - QString cloudMarker = QLatin1Literal("loadFromCloud") + QString(prefs.cloud_storage_email); - m_loadFromCloud = s.contains(cloudMarker) && s.value(cloudMarker).toBool(); -} - -void QMLManager::setLoadFromCloud(bool done) -{ - QSettings s; - QString cloudMarker = QLatin1Literal("loadFromCloud") + QString(prefs.cloud_storage_email); - s.setValue(cloudMarker, done); - m_loadFromCloud = done; - emit loadFromCloudChanged(); -} - -QString QMLManager::startPageText() const -{ - return m_startPageText; -} - -void QMLManager::setStartPageText(const QString& text) -{ - m_startPageText = text; - emit startPageTextChanged(); -} - -// this is an enum, but I don't know how to do enums in QML -QMLManager::credentialStatus_t QMLManager::credentialStatus() const -{ - return m_credentialStatus; -} - -void QMLManager::setCredentialStatus(const credentialStatus_t value) -{ - if (m_credentialStatus != value) { - m_credentialStatus = value; - emit credentialStatusChanged(); - } -} - -// where in the QML dive list is that dive? -int QMLManager::getIndex(const QString &diveId) -{ - int dive_id = diveId.toInt(); - int idx = DiveListModel::instance()->getDiveIdx(dive_id); - return idx; -} - -QString QMLManager::getNumber(const QString& diveId) -{ - int dive_id = diveId.toInt(); - struct dive *d = get_dive_by_uniq_id(dive_id); - QString number; - if (d) - number = QString::number(d->number); - return number; -} - -QString QMLManager::getDate(const QString& diveId) -{ - int dive_id = diveId.toInt(); - struct dive *d = get_dive_by_uniq_id(dive_id); - QString datestring; - if (d) - datestring = get_dive_date_string(d->when); - return datestring; -} - -QString QMLManager::getVersion() const -{ - QRegExp versionRe(".*:([()\\.,\\d]+).*"); - if (!versionRe.exactMatch(getUserAgent())) - return QString(); - - return versionRe.cap(1); -} - -int QMLManager::accessingCloud() const -{ - return m_accessingCloud; -} - -void QMLManager::setAccessingCloud(int status) -{ - m_accessingCloud = status; - emit accessingCloudChanged(); -} - -bool QMLManager::syncToCloud() const -{ - return m_syncToCloud; -} - -void QMLManager::setSyncToCloud(bool status) -{ - m_syncToCloud = status; - prefs.git_local_only = !status; - prefs.cloud_background_sync = status; - QSettings s; - s.beginGroup("CloudStorage"); - s.setValue("git_local_only", prefs.git_local_only); - s.setValue("cloud_background_sync", prefs.cloud_background_sync); - emit syncToCloudChanged(); -} - -qreal QMLManager::lastDevicePixelRatio() -{ - return m_lastDevicePixelRatio; -} - -void QMLManager::screenChanged(QScreen *screen) -{ - m_lastDevicePixelRatio = screen->devicePixelRatio(); - emit sendScreenChanged(screen); -} diff --git a/qt-mobile/qmlmanager.h b/qt-mobile/qmlmanager.h deleted file mode 100644 index 7c56119d5..000000000 --- a/qt-mobile/qmlmanager.h +++ /dev/null @@ -1,162 +0,0 @@ -#ifndef QMLMANAGER_H -#define QMLMANAGER_H - -#include -#include -#include -#include -#include - -#include "gpslocation.h" - -class QMLManager : public QObject { - Q_OBJECT - Q_ENUMS(credentialStatus_t) - Q_PROPERTY(QString cloudUserName READ cloudUserName WRITE setCloudUserName NOTIFY cloudUserNameChanged) - Q_PROPERTY(QString cloudPassword READ cloudPassword WRITE setCloudPassword NOTIFY cloudPasswordChanged) - Q_PROPERTY(QString logText READ logText WRITE setLogText NOTIFY logTextChanged) - Q_PROPERTY(bool locationServiceEnabled READ locationServiceEnabled WRITE setLocationServiceEnabled NOTIFY locationServiceEnabledChanged) - Q_PROPERTY(int distanceThreshold READ distanceThreshold WRITE setDistanceThreshold NOTIFY distanceThresholdChanged) - Q_PROPERTY(int timeThreshold READ timeThreshold WRITE setTimeThreshold NOTIFY timeThresholdChanged) - Q_PROPERTY(bool loadFromCloud READ loadFromCloud WRITE setLoadFromCloud NOTIFY loadFromCloudChanged) - Q_PROPERTY(QString startPageText READ startPageText WRITE setStartPageText NOTIFY startPageTextChanged) - Q_PROPERTY(bool verboseEnabled READ verboseEnabled WRITE setVerboseEnabled NOTIFY verboseEnabledChanged) - Q_PROPERTY(credentialStatus_t credentialStatus READ credentialStatus WRITE setCredentialStatus NOTIFY credentialStatusChanged) - Q_PROPERTY(int accessingCloud READ accessingCloud WRITE setAccessingCloud NOTIFY accessingCloudChanged) - Q_PROPERTY(bool syncToCloud READ syncToCloud WRITE setSyncToCloud NOTIFY syncToCloudChanged) - -public: - QMLManager(); - ~QMLManager(); - - enum credentialStatus_t { - INCOMPLETE, - UNKNOWN, - INVALID, - VALID_EMAIL, - VALID - }; - - static QMLManager *instance(); - - QString cloudUserName() const; - void setCloudUserName(const QString &cloudUserName); - - QString cloudPassword() const; - void setCloudPassword(const QString &cloudPassword); - - bool locationServiceEnabled() const; - void setLocationServiceEnabled(bool locationServiceEnable); - - bool verboseEnabled() const; - void setVerboseEnabled(bool verboseMode); - - int distanceThreshold() const; - void setDistanceThreshold(int distance); - - int timeThreshold() const; - void setTimeThreshold(int time); - - bool loadFromCloud() const; - void setLoadFromCloud(bool done); - void syncLoadFromCloud(); - - QString startPageText() const; - void setStartPageText(const QString& text); - - credentialStatus_t credentialStatus() const; - void setCredentialStatus(const credentialStatus_t value); - - QString logText() const; - void setLogText(const QString &logText); - - int accessingCloud() const; - void setAccessingCloud(int status); - - bool syncToCloud() const; - void setSyncToCloud(bool status); - - typedef void (QMLManager::*execute_function_type)(); - -public slots: - void applicationStateChanged(Qt::ApplicationState state); - void savePreferences(); - void saveCloudCredentials(); - void checkCredentialsAndExecute(execute_function_type execute); - void tryRetrieveDataFromBackend(); - void handleError(QNetworkReply::NetworkError nError); - void handleSslErrors(const QList &errors); - void retrieveUserid(); - void loadDivesWithValidCredentials(); - void loadDiveProgress(int percent); - void provideAuth(QNetworkReply *reply, QAuthenticator *auth); - void commitChanges(QString diveId, QString date, QString location, - QString gps, QString duration, QString depth, - QString airtemp, QString watertemp, QString suit, - QString buddy, QString diveMaster, QString weight, QString notes, - QString startpressure, QString endpressure, QString gasmix); - - void saveChanges(); - void deleteDive(int id); - void undoDelete(int id); - QString addDive(); - void addDiveAborted(int id); - void applyGpsData(); - void sendGpsData(); - void downloadGpsData(); - void populateGpsData(); - void clearGpsData(); - void finishSetup(); - void openLocalThenRemote(QString url); - int getIndex(const QString& diveId); - QString getNumber(const QString& diveId); - QString getDate(const QString& diveId); - QString getCurrentPosition(); - QString getVersion() const; - void deleteGpsFix(quint64 when); - void refreshDiveList(); - void screenChanged(QScreen *screen); - qreal lastDevicePixelRatio(); - void appendTextToLog(const QString &newText); - -private: - QString m_cloudUserName; - QString m_cloudPassword; - QString m_ssrfGpsWebUserid; - QString m_startPageText; - QString m_logText; - bool m_locationServiceEnabled; - bool m_verboseEnabled; - int m_distanceThreshold; - int m_timeThreshold; - GpsLocation *locationProvider; - bool m_loadFromCloud; - static QMLManager *m_instance; - QNetworkReply *reply; - QNetworkRequest request; - struct dive *deletedDive; - struct dive_trip *deletedTrip; - int m_accessingCloud; - bool m_syncToCloud; - credentialStatus_t m_credentialStatus; - qreal m_lastDevicePixelRatio; - QElapsedTimer timer; - bool alreadySaving; - -signals: - void cloudUserNameChanged(); - void cloudPasswordChanged(); - void locationServiceEnabledChanged(); - void verboseEnabledChanged(); - void logTextChanged(); - void timeThresholdChanged(); - void distanceThresholdChanged(); - void loadFromCloudChanged(); - void startPageTextChanged(); - void credentialStatusChanged(); - void accessingCloudChanged(); - void syncToCloudChanged(); - void sendScreenChanged(QScreen *screen); -}; - -#endif diff --git a/qt-mobile/qmlprofile.cpp b/qt-mobile/qmlprofile.cpp deleted file mode 100644 index ad686561d..000000000 --- a/qt-mobile/qmlprofile.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include "qmlprofile.h" -#include "qmlmanager.h" -#include "profile-widget/profilewidget2.h" -#include "subsurface-core/dive.h" -#include "subsurface-core/metrics.h" -#include -#include - -QMLProfile::QMLProfile(QQuickItem *parent) : - QQuickPaintedItem(parent), - m_devicePixelRatio(1.0), - m_margin(0) -{ - setAntialiasing(true); - m_profileWidget = new ProfileWidget2(0); - m_profileWidget->setProfileState(); - m_profileWidget->setPrintMode(true); - m_profileWidget->setFontPrintScale(0.8); - connect(QMLManager::instance(), &QMLManager::sendScreenChanged, this, &QMLProfile::screenChanged); - setDevicePixelRatio(QMLManager::instance()->lastDevicePixelRatio()); -} - -QMLProfile::~QMLProfile() -{ - m_profileWidget->deleteLater(); -} - -void QMLProfile::paint(QPainter *painter) -{ - // let's look at the intended size of the content and scale our scene accordingly - QRect painterRect = painter->viewport(); - QRect profileRect = m_profileWidget->viewport()->rect(); - // qDebug() << "profile viewport and painter viewport" << profileRect << painterRect; - qreal sceneSize = 104; // that should give us 2% margin all around (100x100 scene) - qreal dprComp = devicePixelRatio() * painterRect.width() / profileRect.width(); - qreal sx = painterRect.width() / sceneSize / dprComp; - qreal sy = painterRect.height() / sceneSize / dprComp; - - // next figure out the weird magic by which we need to shift the painter so the profile is shown - int dpr = rint(devicePixelRatio()); - qreal magicShiftFactor = (dpr == 2 ? 0.25 : (dpr == 3 ? 0.33 : 0.0)); - - // now set up the transformations scale the profile and - // shift the painter (taking its existing transformation into account) - QTransform profileTransform = QTransform(); - profileTransform.scale(sx, sy); - QTransform painterTransform = painter->transform(); - painterTransform.translate(-painterRect.width() * magicShiftFactor ,-painterRect.height() * magicShiftFactor); - -#if PROFILE_SCALING_DEBUG - // some debugging messages to help adjust this in case the magic above is insufficient - QMLManager::instance()->appendTextToLog(QString("dpr %1 profile viewport %2 %3 painter viewport %4 %5").arg(dpr).arg(profileRect.width()).arg(profileRect.height()) - .arg(painterRect.width()).arg(painterRect.height())); - QMLManager::instance()->appendTextToLog(QString("profile matrix %1 %2 %3 %4 %5 %6 %7 %8 %9").arg(profileTransform.m11()).arg(profileTransform.m12()).arg(profileTransform.m13()) - .arg(profileTransform.m21()).arg(profileTransform.m22()).arg(profileTransform.m23()) - .arg(profileTransform.m31()).arg(profileTransform.m32()).arg(profileTransform.m33())); - QMLManager::instance()->appendTextToLog(QString("painter matrix %1 %2 %3 %4 %5 %6 %7 %8 %9").arg(painterTransform.m11()).arg(painterTransform.m12()).arg(painterTransform.m13()) - .arg(painterTransform.m21()).arg(painterTransform.m22()).arg(painterTransform.m23()) - .arg(painterTransform.m31()).arg(painterTransform.m32()).arg(painterTransform.m33())); - qDebug() << "profile scaled by" << profileTransform.m11() << profileTransform.m22() << "and translated by" << profileTransform.m31() << profileTransform.m32(); - qDebug() << "exist profile transform" << m_profileWidget->transform() << "painter transform" << painter->transform(); -#endif - // apply the transformation - painter->setTransform(painterTransform); - m_profileWidget->setTransform(profileTransform); - - // finally, render the profile - m_profileWidget->render(painter); -} - -void QMLProfile::setMargin(int margin) -{ - m_margin = margin; -} - -QString QMLProfile::diveId() const -{ - return m_diveId; -} - -void QMLProfile::setDiveId(const QString &diveId) -{ - m_diveId = diveId; - struct dive *d = get_dive_by_uniq_id(m_diveId.toInt()); - if (m_diveId.toInt() < 1) - return; - if (!d) - return; - qDebug() << "setDiveId called with valid dive" << d->number; - m_profileWidget->plotDive(d, true); -} - -qreal QMLProfile::devicePixelRatio() const -{ - return m_devicePixelRatio; -} - -void QMLProfile::setDevicePixelRatio(qreal dpr) -{ - if (dpr != m_devicePixelRatio) { - m_devicePixelRatio = dpr; - m_profileWidget->setFontPrintScale(0.8 * dpr); - updateDevicePixelRatio(dpr); - emit devicePixelRatioChanged(); - } -} - -void QMLProfile::screenChanged(QScreen *screen) -{ - setDevicePixelRatio(screen->devicePixelRatio()); -} diff --git a/qt-mobile/qmlprofile.h b/qt-mobile/qmlprofile.h deleted file mode 100644 index c8a77d700..000000000 --- a/qt-mobile/qmlprofile.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef QMLPROFILE_H -#define QMLPROFILE_H - -#include - -class ProfileWidget2; - -class QMLProfile : public QQuickPaintedItem -{ - Q_OBJECT - Q_PROPERTY(QString diveId READ diveId WRITE setDiveId NOTIFY diveIdChanged) - Q_PROPERTY(qreal devicePixelRatio READ devicePixelRatio WRITE setDevicePixelRatio NOTIFY devicePixelRatioChanged) - -public: - explicit QMLProfile(QQuickItem *parent = 0); - virtual ~QMLProfile(); - - void paint(QPainter *painter); - - QString diveId() const; - void setDiveId(const QString &diveId); - qreal devicePixelRatio() const; - void setDevicePixelRatio(qreal dpr); - -public slots: - void setMargin(int margin); - void screenChanged(QScreen *screen); -private: - QString m_diveId; - qreal m_devicePixelRatio; - int m_margin; - ProfileWidget2 *m_profileWidget; - -signals: - void rightAlignedChanged(); - void diveIdChanged(); - void devicePixelRatioChanged(); -}; - -#endif // QMLPROFILE_H diff --git a/qt-models/cleanertablemodel.cpp b/qt-models/cleanertablemodel.cpp index 77d3f3369..ac8d10a33 100644 --- a/qt-models/cleanertablemodel.cpp +++ b/qt-models/cleanertablemodel.cpp @@ -1,5 +1,5 @@ #include "cleanertablemodel.h" -#include "metrics.h" +#include "core/metrics.h" CleanerTableModel::CleanerTableModel(QObject *parent) : QAbstractTableModel(parent) { diff --git a/qt-models/completionmodels.cpp b/qt-models/completionmodels.cpp index a8b61aed5..7839398ee 100644 --- a/qt-models/completionmodels.cpp +++ b/qt-models/completionmodels.cpp @@ -1,5 +1,5 @@ -#include "completionmodels.h" -#include "dive.h" +#include "qt-models/completionmodels.h" +#include "core/dive.h" #include #include diff --git a/qt-models/cylindermodel.cpp b/qt-models/cylindermodel.cpp index af01650a4..b1ce0be24 100644 --- a/qt-models/cylindermodel.cpp +++ b/qt-models/cylindermodel.cpp @@ -1,11 +1,11 @@ #include "cylindermodel.h" #include "tankinfomodel.h" #include "models.h" -#include "helpers.h" -#include "dive.h" -#include "color.h" -#include "diveplannermodel.h" -#include "gettextfromc.h" +#include "core/helpers.h" +#include "core/dive.h" +#include "core/color.h" +#include "qt-models/diveplannermodel.h" +#include "core/gettextfromc.h" CylindersModel::CylindersModel(QObject *parent) : CleanerTableModel(parent), diff --git a/qt-models/cylindermodel.h b/qt-models/cylindermodel.h index 9556bcc02..7115dbe25 100644 --- a/qt-models/cylindermodel.h +++ b/qt-models/cylindermodel.h @@ -2,7 +2,7 @@ #define CYLINDERMODEL_H #include "cleanertablemodel.h" -#include "dive.h" +#include "core/dive.h" /* Encapsulation of the Cylinder Model, that presents the * Current cylinders that are used on a dive. */ diff --git a/qt-models/divecomputerextradatamodel.cpp b/qt-models/divecomputerextradatamodel.cpp index 0f89a2a6b..d71c606a4 100644 --- a/qt-models/divecomputerextradatamodel.cpp +++ b/qt-models/divecomputerextradatamodel.cpp @@ -1,6 +1,6 @@ -#include "divecomputerextradatamodel.h" -#include "dive.h" -#include "metrics.h" +#include "qt-models/divecomputerextradatamodel.h" +#include "core/dive.h" +#include "core/metrics.h" ExtraDataModel::ExtraDataModel(QObject *parent) : CleanerTableModel(parent), diff --git a/qt-models/divecomputermodel.cpp b/qt-models/divecomputermodel.cpp index 58641ff77..0dd110fcc 100644 --- a/qt-models/divecomputermodel.cpp +++ b/qt-models/divecomputermodel.cpp @@ -1,6 +1,6 @@ -#include "divecomputermodel.h" -#include "dive.h" -#include "divelist.h" +#include "qt-models/divecomputermodel.h" +#include "core/dive.h" +#include "core/divelist.h" DiveComputerModel::DiveComputerModel(QMultiMap &dcMap, QObject *parent) : CleanerTableModel(parent) { diff --git a/qt-models/divecomputermodel.h b/qt-models/divecomputermodel.h index bed48b81a..fe1b437ba 100644 --- a/qt-models/divecomputermodel.h +++ b/qt-models/divecomputermodel.h @@ -1,8 +1,8 @@ #ifndef DIVECOMPUTERMODEL_H #define DIVECOMPUTERMODEL_H -#include "cleanertablemodel.h" -#include "divecomputer.h" +#include "qt-models/cleanertablemodel.h" +#include "core/divecomputer.h" class DiveComputerModel : public CleanerTableModel { Q_OBJECT diff --git a/qt-models/divelistmodel.cpp b/qt-models/divelistmodel.cpp index af6825285..79d770853 100644 --- a/qt-models/divelistmodel.cpp +++ b/qt-models/divelistmodel.cpp @@ -1,5 +1,5 @@ -#include "divelistmodel.h" -#include "helpers.h" +#include "qt-models/divelistmodel.h" +#include "core/helpers.h" #include DiveListSortModel::DiveListSortModel(QObject *parent) : QSortFilterProxyModel(parent) diff --git a/qt-models/divelistmodel.h b/qt-models/divelistmodel.h index aae3bfc74..39c3497b9 100644 --- a/qt-models/divelistmodel.h +++ b/qt-models/divelistmodel.h @@ -4,9 +4,9 @@ #include #include -#include "dive.h" -#include "helpers.h" -#include "subsurface-qt/DiveObjectHelper.h" +#include "core/dive.h" +#include "core/helpers.h" +#include "core/subsurface-qt/DiveObjectHelper.h" class DiveListSortModel : public QSortFilterProxyModel { diff --git a/qt-models/divelocationmodel.cpp b/qt-models/divelocationmodel.cpp index f77953760..877ca0552 100644 --- a/qt-models/divelocationmodel.cpp +++ b/qt-models/divelocationmodel.cpp @@ -1,6 +1,6 @@ -#include "units.h" -#include "divelocationmodel.h" -#include "dive.h" +#include "core/units.h" +#include "qt-models/divelocationmodel.h" +#include "core/dive.h" #include #include #include diff --git a/qt-models/divelocationmodel.h b/qt-models/divelocationmodel.h index 185e0bc5f..cee67c5fe 100644 --- a/qt-models/divelocationmodel.h +++ b/qt-models/divelocationmodel.h @@ -4,7 +4,7 @@ #include #include #include -#include "units.h" +#include "core/units.h" #include "ssrfsortfilterproxymodel.h" class QLineEdit; diff --git a/qt-models/divepicturemodel.cpp b/qt-models/divepicturemodel.cpp index 173cf9dd0..04738eed0 100644 --- a/qt-models/divepicturemodel.cpp +++ b/qt-models/divepicturemodel.cpp @@ -1,8 +1,8 @@ -#include "divepicturemodel.h" -#include "dive.h" -#include "metrics.h" -#include "divelist.h" -#include "imagedownloader.h" +#include "qt-models/divepicturemodel.h" +#include "core/dive.h" +#include "core/metrics.h" +#include "core/divelist.h" +#include "core/imagedownloader.h" #include diff --git a/qt-models/diveplannermodel.cpp b/qt-models/diveplannermodel.cpp index c3996d077..16a2e40eb 100644 --- a/qt-models/diveplannermodel.cpp +++ b/qt-models/diveplannermodel.cpp @@ -1,9 +1,9 @@ #include "diveplannermodel.h" -#include "dive.h" -#include "helpers.h" -#include "cylindermodel.h" -#include "planner.h" -#include "models.h" +#include "core/dive.h" +#include "core/helpers.h" +#include "qt-models/cylindermodel.h" +#include "core/planner.h" +#include "qt-models/models.h" /* TODO: Port this to CleanerTableModel to remove a bit of boilerplate and * use the signal warningMessage() to communicate errors to the MainWindow. diff --git a/qt-models/diveplannermodel.h b/qt-models/diveplannermodel.h index b87ed84c3..0770aa077 100644 --- a/qt-models/diveplannermodel.h +++ b/qt-models/diveplannermodel.h @@ -4,7 +4,7 @@ #include #include -#include "dive.h" +#include "core/dive.h" class DivePlannerPointsModel : public QAbstractTableModel { Q_OBJECT diff --git a/qt-models/diveplotdatamodel.cpp b/qt-models/diveplotdatamodel.cpp index 7a7ab195f..5bb9ebbd1 100644 --- a/qt-models/diveplotdatamodel.cpp +++ b/qt-models/diveplotdatamodel.cpp @@ -1,8 +1,8 @@ -#include "diveplotdatamodel.h" -#include "dive.h" -#include "profile.h" -#include "divelist.h" -#include "subsurface-core/color.h" +#include "qt-models/diveplotdatamodel.h" +#include "core/dive.h" +#include "core/profile.h" +#include "core/divelist.h" +#include "core/color.h" DivePlotDataModel::DivePlotDataModel(QObject *parent) : QAbstractTableModel(parent), diff --git a/qt-models/diveplotdatamodel.h b/qt-models/diveplotdatamodel.h index 21b0ffd5b..ef5b544cb 100644 --- a/qt-models/diveplotdatamodel.h +++ b/qt-models/diveplotdatamodel.h @@ -3,7 +3,7 @@ #include -#include "display.h" +#include "core/display.h" struct dive; struct plot_data; diff --git a/qt-models/divesitepicturesmodel.cpp b/qt-models/divesitepicturesmodel.cpp index 3777f1d36..272716b07 100644 --- a/qt-models/divesitepicturesmodel.cpp +++ b/qt-models/divesitepicturesmodel.cpp @@ -1,6 +1,6 @@ -#include "divesitepicturesmodel.h" -#include "dive.h" -#include "stdint.h" +#include "qt-models/divesitepicturesmodel.h" +#include "core/dive.h" +#include #include #include diff --git a/qt-models/divetripmodel.cpp b/qt-models/divetripmodel.cpp index 78a7a7420..e0f95b0b3 100644 --- a/qt-models/divetripmodel.cpp +++ b/qt-models/divetripmodel.cpp @@ -1,8 +1,8 @@ -#include "divetripmodel.h" -#include "gettextfromc.h" -#include "metrics.h" -#include "divelist.h" -#include "helpers.h" +#include "qt-models/divetripmodel.h" +#include "core/gettextfromc.h" +#include "core/metrics.h" +#include "core/divelist.h" +#include "core/helpers.h" #include static int nitrox_sort_value(struct dive *dive) diff --git a/qt-models/divetripmodel.h b/qt-models/divetripmodel.h index 31cee9633..7844d813f 100644 --- a/qt-models/divetripmodel.h +++ b/qt-models/divetripmodel.h @@ -2,7 +2,7 @@ #define DIVETRIPMODEL_H #include "treemodel.h" -#include "dive.h" +#include "core/dive.h" struct DiveItem : public TreeItem { Q_DECLARE_TR_FUNCTIONS(TripItem) diff --git a/qt-models/filtermodels.cpp b/qt-models/filtermodels.cpp index 452b1077f..b4aacf1bd 100644 --- a/qt-models/filtermodels.cpp +++ b/qt-models/filtermodels.cpp @@ -1,7 +1,7 @@ -#include "filtermodels.h" -#include "models.h" -#include "display.h" -#include "divetripmodel.h" +#include "qt-models/filtermodels.h" +#include "qt-models/models.h" +#include "core/display.h" +#include "qt-models/divetripmodel.h" #include @@ -309,7 +309,7 @@ MultiFilterSortModel::MultiFilterSortModel(QObject *parent) : QSortFilterProxyModel(parent), divesDisplayed(0), justCleared(false), - curr_dive_site(NULL) + curr_dive_site(NULL) { } diff --git a/qt-models/gpslistmodel.cpp b/qt-models/gpslistmodel.cpp index 3ea03f040..0023198f7 100644 --- a/qt-models/gpslistmodel.cpp +++ b/qt-models/gpslistmodel.cpp @@ -1,5 +1,5 @@ -#include "gpslistmodel.h" -#include "helpers.h" +#include "qt-models/gpslistmodel.h" +#include "core/helpers.h" #include GpsListModel *GpsListModel::m_instance = NULL; diff --git a/qt-models/gpslistmodel.h b/qt-models/gpslistmodel.h index 870540b4f..34eae4c56 100644 --- a/qt-models/gpslistmodel.h +++ b/qt-models/gpslistmodel.h @@ -1,7 +1,7 @@ #ifndef GPSLISTMODEL_H #define GPSLISTMODEL_H -#include "gpslocation.h" +#include "core/gpslocation.h" #include #include diff --git a/qt-models/models.cpp b/qt-models/models.cpp index 48b22797c..d02f466a4 100644 --- a/qt-models/models.cpp +++ b/qt-models/models.cpp @@ -4,8 +4,8 @@ * classes for the equipment models of Subsurface * */ -#include "models.h" -#include "helpers.h" +#include "qt-models/models.h" +#include "core/helpers.h" #include #include diff --git a/qt-models/models.h b/qt-models/models.h index f152af469..8e4aba6af 100644 --- a/qt-models/models.h +++ b/qt-models/models.h @@ -13,11 +13,11 @@ #include #include -#include "metrics.h" +#include "core/metrics.h" -#include "subsurface-core/dive.h" -#include "subsurface-core/divelist.h" -#include "subsurface-core/divecomputer.h" +#include "core/dive.h" +#include "core/divelist.h" +#include "core/divecomputer.h" #include "cleanertablemodel.h" #include "treemodel.h" diff --git a/qt-models/tankinfomodel.cpp b/qt-models/tankinfomodel.cpp index b32437392..3e9e654bf 100644 --- a/qt-models/tankinfomodel.cpp +++ b/qt-models/tankinfomodel.cpp @@ -1,7 +1,7 @@ -#include "tankinfomodel.h" -#include "dive.h" -#include "gettextfromc.h" -#include "metrics.h" +#include "qt-models/tankinfomodel.h" +#include "core/dive.h" +#include "core/gettextfromc.h" +#include "core/metrics.h" TankInfoModel *TankInfoModel::instance() { diff --git a/qt-models/treemodel.cpp b/qt-models/treemodel.cpp index 01db3520f..b98f3f4db 100644 --- a/qt-models/treemodel.cpp +++ b/qt-models/treemodel.cpp @@ -1,5 +1,5 @@ -#include "treemodel.h" -#include "metrics.h" +#include "qt-models/treemodel.h" +#include "core/metrics.h" TreeItem::TreeItem() { diff --git a/qt-models/weightmodel.cpp b/qt-models/weightmodel.cpp index 1ab8c24c8..ec1398d0e 100644 --- a/qt-models/weightmodel.cpp +++ b/qt-models/weightmodel.cpp @@ -1,9 +1,9 @@ -#include "weightmodel.h" -#include "dive.h" -#include "gettextfromc.h" -#include "metrics.h" -#include "helpers.h" -#include "weigthsysteminfomodel.h" +#include "qt-models/weightmodel.h" +#include "core/dive.h" +#include "core/gettextfromc.h" +#include "core/metrics.h" +#include "core/helpers.h" +#include "qt-models/weigthsysteminfomodel.h" WeightModel::WeightModel(QObject *parent) : CleanerTableModel(parent), changed(false), diff --git a/qt-models/weightmodel.h b/qt-models/weightmodel.h index 5ab67e592..d251dec68 100644 --- a/qt-models/weightmodel.h +++ b/qt-models/weightmodel.h @@ -2,7 +2,7 @@ #define WEIGHTMODEL_H #include "cleanertablemodel.h" -#include "dive.h" +#include "core/dive.h" /* Encapsulation of the Weight Model, that represents * the current weights on a dive. */ diff --git a/qt-models/weigthsysteminfomodel.cpp b/qt-models/weigthsysteminfomodel.cpp index daa305284..c7d9e7fad 100644 --- a/qt-models/weigthsysteminfomodel.cpp +++ b/qt-models/weigthsysteminfomodel.cpp @@ -1,7 +1,7 @@ -#include "weigthsysteminfomodel.h" -#include "dive.h" -#include "metrics.h" -#include "gettextfromc.h" +#include "qt-models/weigthsysteminfomodel.h" +#include "core/dive.h" +#include "core/metrics.h" +#include "core/gettextfromc.h" WSInfoModel *WSInfoModel::instance() { diff --git a/qt-models/yearlystatisticsmodel.cpp b/qt-models/yearlystatisticsmodel.cpp index f6579f499..8c96ff14a 100644 --- a/qt-models/yearlystatisticsmodel.cpp +++ b/qt-models/yearlystatisticsmodel.cpp @@ -1,8 +1,8 @@ -#include "yearlystatisticsmodel.h" -#include "dive.h" -#include "helpers.h" -#include "metrics.h" -#include "statistics.h" +#include "qt-models/yearlystatisticsmodel.h" +#include "core/dive.h" +#include "core/helpers.h" +#include "core/metrics.h" +#include "core/statistics.h" class YearStatisticsItem : public TreeItem { public: diff --git a/scripts/mobilecomponents.sh b/scripts/mobilecomponents.sh index 7f589309c..d406f7148 100755 --- a/scripts/mobilecomponents.sh +++ b/scripts/mobilecomponents.sh @@ -9,7 +9,7 @@ SRC=$(cd .. ; pwd) -if [ ! -d "$SRC/subsurface" ] || [ ! -d "qt-mobile" ] || [ ! -d "subsurface-core" ] ; then +if [ ! -d "$SRC/subsurface" ] || [ ! -d "mobile-widgets" ] || [ ! -d "core" ] ; then echo "please start this script from the Subsurface source directory (which needs to be named \"subsurface\")." exit 1 fi @@ -43,7 +43,7 @@ if [ "$NOPULL" = "" ] ; then fi # now copy the components and a couple of icons into plae -MC=$SRC/subsurface/qt-mobile/qml/kirigami +MC=$SRC/subsurface/mobile-widgets/qml/kirigami PMMC=kirigami/src/qml BREEZE=breeze-icons diff --git a/subsurface-core/CMakeLists.txt b/subsurface-core/CMakeLists.txt deleted file mode 100644 index d9b1d3421..000000000 --- a/subsurface-core/CMakeLists.txt +++ /dev/null @@ -1,98 +0,0 @@ -set(PLATFORM_SRC unknown_platform.c) -message(STATUS "system name ${CMAKE_SYSTEM_NAME}") -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - if(ANDROID) - set(PLATFORM_SRC android.cpp) - else() - set(PLATFORM_SRC linux.c) - endif() -elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") - set(PLATFORM_SRC android.cpp) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(PLATFORM_SRC macos.c) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set(PLATFORM_SRC windows.c) -endif() - -if(FTDISUPPORT) - set(SERIAL_FTDI serial_ftdi.c) -endif() - -if(BTSUPPORT) - add_definitions(-DBT_SUPPORT) - set(BT_SRC_FILES desktop-widgets/btdeviceselectiondialog.cpp) - set(BT_CORE_SRC_FILES qtserialbluetooth.cpp) -endif() - -# compile the core library, in C. -set(SUBSURFACE_CORE_LIB_SRCS - cochran.c - datatrak.c - deco.c - device.c - dive.c - divesite.c - divesite.cpp - divelist.c - equipment.c - file.c - gas-model.c - git-access.c - libdivecomputer.c - liquivision.c - load-git.c - membuffer.c - ostctools.c - parse-xml.c - planner.c - profile.c - gaspressures.c - worldmap-save.c - save-git.c - save-xml.c - save-html.c - sha1.c - statistics.c - strtod.c - subsurfacestartup.c - time.c - uemis.c - uemis-downloader.c - version.c - # gettextfrommoc should be added because we are using it on the c-code. - gettextfromc.cpp - # dirk ported some core functionality to c++. - qthelper.cpp - divecomputer.cpp - exif.cpp - subsurfacesysinfo.cpp - devicedetails.cpp - configuredivecomputer.cpp - configuredivecomputerthreads.cpp - divesitehelpers.cpp - taxonomy.c - checkcloudconnection.cpp - windowtitleupdate.cpp - divelogexportlogic.cpp - qt-init.cpp - qtserialbluetooth.cpp - metrics.cpp - color.cpp - pluginmanager.cpp - imagedownloader.cpp - isocialnetworkintegration.cpp - gpslocation.cpp - cloudstorage.cpp - - #Subsurface Qt have the Subsurface structs QObjectified for easy access via QML. - subsurface-qt/DiveObjectHelper.cpp - subsurface-qt/SettingsObjectWrapper.cpp - ${SERIAL_FTDI} - ${PLATFORM_SRC} - ${BT_CORE_SRC_FILES} -) -source_group("Subsurface Core" FILES ${SUBSURFACE_CORE_LIB_SRCS}) - -add_library(subsurface_corelib STATIC ${SUBSURFACE_CORE_LIB_SRCS} ) -target_link_libraries(subsurface_corelib ${QT_LIBRARIES}) - diff --git a/subsurface-core/android.cpp b/subsurface-core/android.cpp deleted file mode 100644 index 3631b07a1..000000000 --- a/subsurface-core/android.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/* implements Android specific functions */ -#include "dive.h" -#include "display.h" -#include -#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 = -1; - -int get_usb_fd(uint16_t idVendor, uint16_t idProduct); -void subsurface_OS_pref_setup(void) -{ - // Abusing this function to get a decent place where we can wire in - // our open callback into libusb -#ifdef libusb_android_open_callback_func - libusb_set_android_open_callback(get_usb_fd); -#elif __ANDROID__ -#error we need libusb_android_open_callback -#endif -} - -bool subsurface_ignore_font(const char *font) -{ - // there are no old default fonts that we would want to ignore - return false; -} - -void subsurface_user_info(struct user_info *user) -{ /* Encourage use of at least libgit2-0.20 */ } - -static const char *system_default_path_append(const char *append) -{ - // Qt appears to find a working path for us - let's just go with that - QString path = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first(); - - if (append) - path += QString("/%1").arg(append); - - return strdup(path.toUtf8().data()); -} - -const char *system_default_directory(void) -{ - static const char *path = NULL; - if (!path) - path = system_default_path_append(NULL); - return path; -} - -const char *system_default_filename(void) -{ - static const char *filename = "subsurface.xml"; - static const char *path = NULL; - if (!path) - path = system_default_path_append(filename); - return path; -} - -int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) -{ - /* FIXME: we need to enumerate in some other way on android */ - /* qtserialport maybee? */ - return -1; -} - -/** - * Get the file descriptor of first available matching device attached to usb in android. - * - * returns a fd to the device, or -1 and errno is set. - */ -int get_usb_fd(uint16_t idVendor, uint16_t idProduct) -{ - int i; - jint fd, vendorid, productid; - QAndroidJniObject usbName, usbDevice; - - // Get the current main activity of the application. - QAndroidJniObject activity = QtAndroid::androidActivity(); - - QAndroidJniObject usb_service = QAndroidJniObject::fromString(USB_SERVICE); - - // Get UsbManager from activity - QAndroidJniObject usbManager = activity.callObjectMethod("getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", usb_service.object()); - - // Get a HashMap 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 deleted file mode 100644 index f29d971ba..000000000 --- a/subsurface-core/checkcloudconnection.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include -#include -#include -#include -#include - -#include "pref.h" -#include "helpers.h" -#include "git-access.h" - -#include "checkcloudconnection.h" - - -CheckCloudConnection::CheckCloudConnection(QObject *parent) : - QObject(parent), - reply(0) -{ - -} - -#define TEAPOT "/make-latte?number-of-shots=3" -#define HTTP_I_AM_A_TEAPOT 418 -#define MILK "Linus does not like non-fat milk" -bool CheckCloudConnection::checkServer() -{ - if (verbose) - fprintf(stderr, "Checking cloud connection...\n"); - - QTimer timer; - timer.setSingleShot(true); - QEventLoop loop; - QNetworkRequest request; - request.setRawHeader("Accept", "text/plain"); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Client-Id", getUUID().toUtf8()); - request.setUrl(QString(prefs.cloud_base_url) + TEAPOT); - QNetworkAccessManager *mgr = new QNetworkAccessManager(); - reply = mgr->get(request); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::sslErrors, this, &CheckCloudConnection::sslErrors); - for (int seconds = 1; seconds <= 5; seconds++) { - timer.start(1000); // wait five seconds - loop.exec(); - if (timer.isActive()) { - // didn't time out, did we get the right response? - timer.stop(); - if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == HTTP_I_AM_A_TEAPOT && - reply->readAll() == QByteArray(MILK)) { - reply->deleteLater(); - mgr->deleteLater(); - if (verbose > 1) - qWarning() << "Cloud storage: successfully checked connection to cloud server"; - git_storage_update_progress(last_git_storage_update_val + 1, "successfully checked cloud connection"); - return true; - } - } else if (seconds < 5) { - git_storage_update_progress(last_git_storage_update_val + 1, "waited 1 sec for cloud connection"); - } else { - disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - reply->abort(); - } - } - git_storage_update_progress(last_git_storage_update_val + 1, "cloud connection failed"); - if (verbose) - qDebug() << "connection test to cloud server failed" << - reply->error() << reply->errorString() << - reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() << - reply->readAll(); - reply->deleteLater(); - mgr->deleteLater(); - if (verbose) - qWarning() << "Cloud storage: unable to connect to cloud server"; - return false; -} - -void CheckCloudConnection::sslErrors(QList errorList) -{ - if (verbose) { - qDebug() << "Received error response trying to set up https connection with cloud storage backend:"; - Q_FOREACH (QSslError err, errorList) { - qDebug() << err.errorString(); - } - } - QSslConfiguration conf = reply->sslConfiguration(); - QSslCertificate cert = conf.peerCertificate(); - QByteArray hexDigest = cert.digest().toHex(); - if (reply->url().toString().contains(prefs.cloud_base_url) && - hexDigest == "13ff44c62996cfa5cd69d6810675490e") { - if (verbose) - qDebug() << "Overriding SSL check as I recognize the certificate digest" << hexDigest; - reply->ignoreSslErrors(); - } else { - if (verbose) - qDebug() << "got invalid SSL certificate with hex digest" << hexDigest; - } -} - -// helper to be used from C code -extern "C" bool canReachCloudServer() -{ - if (verbose) - qWarning() << "Cloud storage: checking connection to cloud server"; - CheckCloudConnection *checker = new CheckCloudConnection; - return checker->checkServer(); -} diff --git a/subsurface-core/checkcloudconnection.h b/subsurface-core/checkcloudconnection.h deleted file mode 100644 index 58a412797..000000000 --- a/subsurface-core/checkcloudconnection.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef CHECKCLOUDCONNECTION_H -#define CHECKCLOUDCONNECTION_H - -#include -#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/cloudstorage.cpp b/subsurface-core/cloudstorage.cpp deleted file mode 100644 index 575191891..000000000 --- a/subsurface-core/cloudstorage.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include "cloudstorage.h" -#include "pref.h" -#include "dive.h" -#include "helpers.h" - -#include - -CloudStorageAuthenticate::CloudStorageAuthenticate(QObject *parent) : - QObject(parent), - reply(NULL) -{ - userAgent = getUserAgent(); -} - -#define CLOUDURL QString(prefs.cloud_base_url) -#define CLOUDBACKENDSTORAGE CLOUDURL + "/storage" -#define CLOUDBACKENDVERIFY CLOUDURL + "/verify" -#define CLOUDBACKENDUPDATE CLOUDURL + "/update" - -QNetworkReply* CloudStorageAuthenticate::backend(const QString& email,const QString& password,const QString& pin,const QString& newpasswd) -{ - QString payload(email + QChar(' ') + password); - QUrl requestUrl; - if (pin.isEmpty() && newpasswd.isEmpty()) { - requestUrl = QUrl(CLOUDBACKENDSTORAGE); - } else if (!newpasswd.isEmpty()) { - requestUrl = QUrl(CLOUDBACKENDUPDATE); - payload += QChar(' ') + newpasswd; - } else { - requestUrl = QUrl(CLOUDBACKENDVERIFY); - payload += QChar(' ') + pin; - } - QNetworkRequest *request = new QNetworkRequest(requestUrl); - request->setRawHeader("Accept", "text/xml, text/plain"); - request->setRawHeader("User-Agent", userAgent.toUtf8()); - request->setHeader(QNetworkRequest::ContentTypeHeader, "text/plain"); - reply = manager()->post(*request, qPrintable(payload)); - connect(reply, SIGNAL(finished()), this, SLOT(uploadFinished())); - connect(reply, SIGNAL(sslErrors(QList)), this, SLOT(sslErrors(QList))); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, - SLOT(uploadError(QNetworkReply::NetworkError))); - return reply; -} - -void CloudStorageAuthenticate::uploadFinished() -{ - static QString myLastError; - - QString cloudAuthReply(reply->readAll()); - qDebug() << "Completed connection with cloud storage backend, response" << cloudAuthReply; - if (cloudAuthReply == QLatin1String("[VERIFIED]") || cloudAuthReply == QLatin1String("[OK]")) { - prefs.cloud_verification_status = CS_VERIFIED; - /* TODO: Move this to a correct place - NotificationWidget *nw = MainWindow::instance()->getNotificationWidget(); - if (nw->getNotificationText() == myLastError) - nw->hideNotification(); - */ - myLastError.clear(); - } else if (cloudAuthReply == QLatin1String("[VERIFY]")) { - prefs.cloud_verification_status = CS_NEED_TO_VERIFY; - } else if (cloudAuthReply == QLatin1String("[PASSWDCHANGED]")) { - free(prefs.cloud_storage_password); - prefs.cloud_storage_password = prefs.cloud_storage_newpassword; - prefs.cloud_storage_newpassword = NULL; - emit passwordChangeSuccessful(); - return; - } else { - prefs.cloud_verification_status = CS_INCORRECT_USER_PASSWD; - myLastError = cloudAuthReply; - report_error("%s", qPrintable(cloudAuthReply)); - /* TODO: Emit a signal with the error - MainWindow::instance()->getNotificationWidget()->showNotification(get_error_string(), KMessageWidget::Error); - */ - } - emit finishedAuthenticate(); -} - -void CloudStorageAuthenticate::uploadError(QNetworkReply::NetworkError) -{ - qDebug() << "Received error response from cloud storage backend:" << reply->errorString(); -} - -void CloudStorageAuthenticate::sslErrors(QList errorList) -{ - if (verbose) { - qDebug() << "Received error response trying to set up https connection with cloud storage backend:"; - Q_FOREACH (QSslError err, errorList) { - qDebug() << err.errorString(); - } - } - QSslConfiguration conf = reply->sslConfiguration(); - QSslCertificate cert = conf.peerCertificate(); - QByteArray hexDigest = cert.digest().toHex(); - if (reply->url().toString().contains(prefs.cloud_base_url) && - hexDigest == "13ff44c62996cfa5cd69d6810675490e") { - if (verbose) - qDebug() << "Overriding SSL check as I recognize the certificate digest" << hexDigest; - reply->ignoreSslErrors(); - } else { - if (verbose) - qDebug() << "got invalid SSL certificate with hex digest" << hexDigest; - } -} - -QNetworkAccessManager *manager() -{ - static QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - return manager; -} diff --git a/subsurface-core/cloudstorage.h b/subsurface-core/cloudstorage.h deleted file mode 100644 index 6addb739d..000000000 --- a/subsurface-core/cloudstorage.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef CLOUD_STORAGE_H -#define CLOUD_STORAGE_H - -#include -#include - -class CloudStorageAuthenticate : public QObject { - Q_OBJECT -public: - QNetworkReply* backend(const QString& email,const QString& password,const QString& pin = QString(),const QString& newpasswd = QString()); - explicit CloudStorageAuthenticate(QObject *parent); -signals: - void finishedAuthenticate(); - void passwordChangeSuccessful(); -private -slots: - void uploadError(QNetworkReply::NetworkError error); - void sslErrors(QList errorList); - void uploadFinished(); -private: - QNetworkReply *reply; - QString userAgent; - bool verbose; -}; - -QNetworkAccessManager *manager(); -#endif \ No newline at end of file diff --git a/subsurface-core/cochran.c b/subsurface-core/cochran.c deleted file mode 100644 index b42ed8233..000000000 --- a/subsurface-core/cochran.c +++ /dev/null @@ -1,809 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#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, unsigned int size, - unsigned int *duration, double *max_depth, - double *avg_depth, double *min_temp) -{ - const unsigned char *s; - unsigned int offset = 0, seconds = 0; - double depth = 0, temp = 0, depth_sample = 0, psi = 0, sgc_rate = 0; - int ascent_rate = 0; - unsigned int ndl = 0; - unsigned int in_deco = 0, deco_ceiling = 0, deco_time = 0; - - struct divecomputer *dc = &dive->dc; - struct sample *sample; - - // Initialize stat variables - *max_depth = 0, *avg_depth = 0, *min_temp = 0xFF; - - // Get starting depth and temp (tank PSI???) - switch (config.type) { - case TYPE_GEMINI: - depth = (float) (log[CMD_START_DEPTH] - + log[CMD_START_DEPTH + 1] * 256) / 4; - temp = log[CMD_START_TEMP]; - psi = log[CMD_START_PSI] + log[CMD_START_PSI + 1] * 256; - sgc_rate = (float)(log[CMD_START_SGC] - + log[CMD_START_SGC + 1] * 256) / 2; - break; - case TYPE_COMMANDER: - depth = (float) (log[CMD_START_DEPTH] - + log[CMD_START_DEPTH + 1] * 256) / 4; - temp = log[CMD_START_TEMP]; - break; - - case TYPE_EMC: - depth = (float) log [EMC_START_DEPTH] / 256 - + log[EMC_START_DEPTH + 1]; - temp = log[EMC_START_TEMP]; - break; - } - - // Skip past pre-dive events - unsigned int x = 0; - if (samples[x] != 0x40) { - unsigned int c; - while ((samples[x] & 0x80) == 0 && samples[x] != 0x40 && x < size) { - c = cochran_predive_event_bytes(samples[x]) + 1; -#ifdef COCHRAN_DEBUG - printf("Predive event: ", samples[x]); - for (int y = 0; y < c; y++) printf("%02x ", samples[x + y]); - putchar('\n'); -#endif - x += c; - } - } - - // Now process samples - offset = x; - while (offset < size) { - s = samples + offset; - - // Start with an empty sample - sample = prepare_sample(dc); - sample->time.seconds = seconds; - - // Check for event - if (s[0] & 0x80) { - cochran_dive_event(dc, s, seconds, &in_deco, &deco_ceiling, &deco_time); - offset += cochran_dive_event_bytes(s[0]) + 1; - continue; - } - - // Depth is in every sample - depth_sample = (float)(s[0] & 0x3F) / 4 * (s[0] & 0x40 ? -1 : 1); - depth += depth_sample; - -#ifdef COCHRAN_DEBUG - cochran_debug_sample(s, seconds); -#endif - - switch (config.type) { - case TYPE_COMMANDER: - switch (seconds % 2) { - case 0: // Ascent rate - ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1); - break; - case 1: // Temperature - temp = s[1] / 2 + 20; - break; - } - break; - case TYPE_GEMINI: - // Gemini with tank pressure and SAC rate. - switch (seconds % 4) { - case 0: // Ascent rate - ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1); - break; - case 2: // PSI change - psi -= (float)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 4; - break; - case 1: // SGC rate - sgc_rate -= (float)(s[1] & 0x7f) * (s[1] & 0x80 ? 1 : -1) / 2; - break; - case 3: // Temperature - temp = (float)s[1] / 2 + 20; - break; - } - break; - case TYPE_EMC: - switch (seconds % 2) { - case 0: // Ascent rate - ascent_rate = (s[1] & 0x7f) * (s[1] & 0x80 ? 1: -1); - break; - case 1: // Temperature - temp = (float)s[1] / 2 + 20; - break; - } - // Get NDL and deco information - switch (seconds % 24) { - case 20: - if (in_deco) { - // Fist stop time - //first_deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds - ndl = 0; - } else { - // NDL - ndl = (s[2] + s[5] * 256 + 1) * 60; // seconds - deco_time = 0; - } - break; - case 22: - if (in_deco) { - // Total stop time - deco_time = (s[2] + s[5] * 256 + 1) * 60; // seconds - ndl = 0; - } - break; - } - } - - // Track dive stats - if (depth > *max_depth) *max_depth = depth; - if (temp < *min_temp) *min_temp = temp; - *avg_depth = (*avg_depth * seconds + depth) / (seconds + 1); - - sample->depth.mm = depth * FEET * 1000; - sample->ndl.seconds = ndl; - sample->in_deco = in_deco; - sample->stoptime.seconds = deco_time; - sample->stopdepth.mm = deco_ceiling * FEET * 1000; - sample->temperature.mkelvin = C_to_mkelvin((temp - 32) / 1.8); - sample->sensor = 0; - sample->cylinderpressure.mbar = psi * PSI / 100; - - finish_sample(dc); - - offset += config.sample_size; - seconds++; - } - (void)ascent_rate; // mark the variable as unused - - if (seconds > 0) - *duration = seconds - 1; -} - -static void cochran_parse_dive(const unsigned char *decode, unsigned mod, - const unsigned char *in, unsigned size) -{ - unsigned char *buf = malloc(size); - struct dive *dive; - struct divecomputer *dc; - struct tm tm = {0}; - uint32_t csum[5]; - - double max_depth, avg_depth, min_temp; - unsigned int duration = 0, corrupt_dive = 0; - - /* - * The scrambling has odd boundaries. I think the boundaries - * match some data structure size, but I don't know. They were - * discovered the same way we dynamically discover the decode - * size: automatically looking for least random output. - * - * The boundaries are also this confused "off-by-one" thing, - * the same way the file size is off by one. It's as if the - * cochran software forgot to write one byte at the beginning. - */ - partial_decode(0, 0x0fff, decode, 1, mod, in, size, buf); - partial_decode(0x0fff, 0x1fff, decode, 0, mod, in, size, buf); - partial_decode(0x1fff, 0x2fff, decode, 0, mod, in, size, buf); - partial_decode(0x2fff, 0x48ff, decode, 0, mod, in, size, buf); - - /* - * This is not all the descrambling you need - the above are just - * what appears to be the fixed-size blocks. The rest is also - * scrambled, but there seems to be size differences in the data, - * so this just descrambles part of it: - */ - // Decode log entry (512 bytes + random prefix) - partial_decode(0x48ff, 0x4914 + config.logbook_size, decode, - 0, mod, in, size, buf); - - unsigned int sample_size = size - 0x4914 - config.logbook_size; - int g; - - // Decode sample data - partial_decode(0x4914 + config.logbook_size, size, decode, - 0, mod, in, size, buf); - -#ifdef COCHRAN_DEBUG - // Display pre-logbook data - puts("\nPre Logbook Data\n"); - cochran_debug_write(buf, 0x4914); - - // Display log book - puts("\nLogbook Data\n"); - cochran_debug_write(buf + 0x4914, config.logbook_size + 0x400); - - // Display sample data - puts("\nSample Data\n"); -#endif - - dive = alloc_dive(); - dc = &dive->dc; - - unsigned char *log = (buf + 0x4914); - - switch (config.type) { - case TYPE_GEMINI: - case TYPE_COMMANDER: - if (config.type == TYPE_GEMINI) { - dc->model = "Gemini"; - dc->deviceid = buf[0x18c] * 256 + buf[0x18d]; // serial no - fill_default_cylinder(&dive->cylinder[0]); - dive->cylinder[0].gasmix.o2.permille = (log[CMD_O2_PERCENT] / 256 - + log[CMD_O2_PERCENT + 1]) * 10; - dive->cylinder[0].gasmix.he.permille = 0; - } else { - dc->model = "Commander"; - dc->deviceid = array_uint32_le(buf + 0x31e); // serial no - for (g = 0; g < 2; g++) { - fill_default_cylinder(&dive->cylinder[g]); - dive->cylinder[g].gasmix.o2.permille = (log[CMD_O2_PERCENT + g * 2] / 256 - + log[CMD_O2_PERCENT + g * 2 + 1]) * 10; - dive->cylinder[g].gasmix.he.permille = 0; - } - } - - tm.tm_year = log[CMD_YEAR]; - tm.tm_mon = log[CMD_MON] - 1; - tm.tm_mday = log[CMD_DAY]; - tm.tm_hour = log[CMD_HOUR]; - tm.tm_min = log[CMD_MIN]; - tm.tm_sec = log[CMD_SEC]; - tm.tm_isdst = -1; - - dive->when = dc->when = utc_mktime(&tm); - dive->number = log[CMD_NUMBER] + log[CMD_NUMBER + 1] * 256 + 1; - dc->duration.seconds = (log[CMD_BT] + log[CMD_BT + 1] * 256) * 60; - dc->surfacetime.seconds = (log[CMD_SIT] + log[CMD_SIT + 1] * 256) * 60; - dc->maxdepth.mm = (log[CMD_MAX_DEPTH] + - log[CMD_MAX_DEPTH + 1] * 256) / 4 * FEET * 1000; - dc->meandepth.mm = (log[CMD_AVG_DEPTH] + - log[CMD_AVG_DEPTH + 1] * 256) / 4 * FEET * 1000; - dc->watertemp.mkelvin = C_to_mkelvin((log[CMD_MIN_TEMP] / 32) - 1.8); - dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577 - * (double) log[CMD_ALTITUDE] * 250 * FEET, 5.25588) * 1000; - dc->salinity = 10000 + 150 * log[CMD_WATER_CONDUCTIVITY]; - - SHA1(log + CMD_NUMBER, 2, (unsigned char *)csum); - dc->diveid = csum[0]; - - if (log[CMD_MAX_DEPTH] == 0xff && log[CMD_MAX_DEPTH + 1] == 0xff) - corrupt_dive = 1; - - break; - case TYPE_EMC: - dc->model = "EMC"; - dc->deviceid = array_uint32_le(buf + 0x31e); // serial no - for (g = 0; g < 4; g++) { - fill_default_cylinder(&dive->cylinder[g]); - dive->cylinder[g].gasmix.o2.permille = - (log[EMC_O2_PERCENT + g * 2] / 256 - + log[EMC_O2_PERCENT + g * 2 + 1]) * 10; - dive->cylinder[g].gasmix.he.permille = - (log[EMC_HE_PERCENT + g * 2] / 256 - + log[EMC_HE_PERCENT + g * 2 + 1]) * 10; - } - - tm.tm_year = log[EMC_YEAR]; - tm.tm_mon = log[EMC_MON] - 1; - tm.tm_mday = log[EMC_DAY]; - tm.tm_hour = log[EMC_HOUR]; - tm.tm_min = log[EMC_MIN]; - tm.tm_sec = log[EMC_SEC]; - tm.tm_isdst = -1; - - dive->when = dc->when = utc_mktime(&tm); - dive->number = log[EMC_NUMBER] + log[EMC_NUMBER + 1] * 256 + 1; - dc->duration.seconds = (log[EMC_BT] + log[EMC_BT + 1] * 256) * 60; - dc->surfacetime.seconds = (log[EMC_SIT] + log[EMC_SIT + 1] * 256) * 60; - dc->maxdepth.mm = (log[EMC_MAX_DEPTH] + - log[EMC_MAX_DEPTH + 1] * 256) / 4 * FEET * 1000; - dc->meandepth.mm = (log[EMC_AVG_DEPTH] + - log[EMC_AVG_DEPTH + 1] * 256) / 4 * FEET * 1000; - dc->watertemp.mkelvin = C_to_mkelvin((log[EMC_MIN_TEMP] - 32) / 1.8); - dc->surface_pressure.mbar = ATM / BAR * pow(1 - 0.0000225577 - * (double) log[EMC_ALTITUDE] * 250 * FEET, 5.25588) * 1000; - dc->salinity = 10000 + 150 * (log[EMC_WATER_CONDUCTIVITY] & 0x3); - - SHA1(log + EMC_NUMBER, 2, (unsigned char *)csum); - dc->diveid = csum[0]; - - if (log[EMC_MAX_DEPTH] == 0xff && log[EMC_MAX_DEPTH + 1] == 0xff) - corrupt_dive = 1; - - break; - } - - cochran_parse_samples(dive, buf + 0x4914, buf + 0x4914 - + config.logbook_size, sample_size, - &duration, &max_depth, &avg_depth, &min_temp); - - // Check for corrupt dive - if (corrupt_dive) { - dc->maxdepth.mm = max_depth * FEET * 1000; - dc->meandepth.mm = avg_depth * FEET * 1000; - dc->watertemp.mkelvin = C_to_mkelvin((min_temp - 32) / 1.8); - dc->duration.seconds = duration; - } - - dive->downloaded = true; - record_dive(dive); - mark_divelist_changed(true); - - free(buf); -} - -int try_to_open_cochran(const char *filename, struct memblock *mem) -{ - (void) filename; - unsigned int i; - unsigned int mod; - unsigned int *offsets, dive1, dive2; - unsigned char *decode = mem->buffer + 0x40001; - - if (mem->size < 0x40000) - return 0; - - offsets = (unsigned int *) mem->buffer; - dive1 = offsets[0]; - dive2 = offsets[1]; - - if (dive1 < 0x40000 || dive2 < dive1 || dive2 > mem->size) - return 0; - - mod = decode[0x100] + 1; - cochran_parse_header(decode, mod, mem->buffer + 0x40000, dive1 - 0x40000); - - // Decode each dive - for (i = 0; i < 65534; i++) { - dive1 = offsets[i]; - dive2 = offsets[i + 1]; - if (dive2 < dive1) - break; - if (dive2 > mem->size) - break; - - cochran_parse_dive(decode, mod, mem->buffer + dive1, - dive2 - dive1); - } - - return 1; // no further processing needed -} diff --git a/subsurface-core/cochran.h b/subsurface-core/cochran.h deleted file mode 100644 index 97d4361c8..000000000 --- a/subsurface-core/cochran.h +++ /dev/null @@ -1,44 +0,0 @@ -// Commander log fields -#define CMD_SEC 1 -#define CMD_MIN 0 -#define CMD_HOUR 3 -#define CMD_DAY 2 -#define CMD_MON 5 -#define CMD_YEAR 4 -#define CME_START_OFFSET 6 // 4 bytes -#define CMD_WATER_CONDUCTIVITY 25 // 1 byte, 0=low, 2=high -#define CMD_START_SGC 42 // 2 bytes -#define CMD_START_TEMP 45 // 1 byte, F -#define CMD_START_DEPTH 56 // 2 bytes, /4=ft -#define CMD_START_PSI 62 -#define CMD_SIT 68 // 2 bytes, minutes -#define CMD_NUMBER 70 // 2 bytes -#define CMD_ALTITUDE 73 // 1 byte, /4=Kilofeet -#define CMD_END_OFFSET 128 // 4 bytes -#define CMD_MIN_TEMP 153 // 1 byte, F -#define CMD_BT 166 // 2 bytes, minutes -#define CMD_MAX_DEPTH 168 // 2 bytes, /4=ft -#define CMD_AVG_DEPTH 170 // 2 bytes, /4=ft -#define CMD_O2_PERCENT 210 // 8 bytes, 4 x 2 byte, /256=% - -// EMC log fields -#define EMC_SEC 0 -#define EMC_MIN 1 -#define EMC_HOUR 2 -#define EMC_DAY 3 -#define EMC_MON 4 -#define EMC_YEAR 5 -#define EMC_START_OFFSET 6 // 4 bytes -#define EMC_WATER_CONDUCTIVITY 24 // 1 byte bits 0:1, 0=low, 2=high -#define EMC_START_DEPTH 42 // 2 byte, /256=ft -#define EMC_START_TEMP 55 // 1 byte, F -#define EMC_SIT 84 // 2 bytes, minutes, LE -#define EMC_NUMBER 86 // 2 bytes -#define EMC_ALTITUDE 89 // 1 byte, /4=Kilofeet -#define EMC_O2_PERCENT 144 // 20 bytes, 10 x 2 bytes, /256=% -#define EMC_HE_PERCENT 164 // 20 bytes, 10 x 2 bytes, /256=% -#define EMC_END_OFFSET 256 // 4 bytes -#define EMC_MIN_TEMP 293 // 1 byte, F -#define EMC_BT 304 // 2 bytes, minutes -#define EMC_MAX_DEPTH 306 // 2 bytes, /4=ft -#define EMC_AVG_DEPTH 310 // 2 bytes, /4=ft diff --git a/subsurface-core/color.cpp b/subsurface-core/color.cpp deleted file mode 100644 index cf6f43916..000000000 --- a/subsurface-core/color.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "color.h" - -QMap > profile_color; - -void fill_profile_color() -{ -#define COLOR(x, y, z) QVector() << x << y << z; - profile_color[SAC_1] = COLOR(FUNGREEN1, BLACK1_LOW_TRANS, FUNGREEN1); - profile_color[SAC_2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); - profile_color[SAC_3] = COLOR(ATLANTIS1, BLACK1_LOW_TRANS, ATLANTIS1); - profile_color[SAC_4] = COLOR(ATLANTIS2, BLACK1_LOW_TRANS, ATLANTIS2); - profile_color[SAC_5] = COLOR(EARLSGREEN1, BLACK1_LOW_TRANS, EARLSGREEN1); - profile_color[SAC_6] = COLOR(HOKEYPOKEY1, BLACK1_LOW_TRANS, HOKEYPOKEY1); - profile_color[SAC_7] = COLOR(TUSCANY1, BLACK1_LOW_TRANS, TUSCANY1); - profile_color[SAC_8] = COLOR(CINNABAR1, BLACK1_LOW_TRANS, CINNABAR1); - profile_color[SAC_9] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); - - profile_color[VELO_STABLE] = COLOR(CAMARONE1, BLACK1_LOW_TRANS, CAMARONE1); - profile_color[VELO_SLOW] = COLOR(LIMENADE1, BLACK1_LOW_TRANS, LIMENADE1); - profile_color[VELO_MODERATE] = COLOR(RIOGRANDE1, BLACK1_LOW_TRANS, RIOGRANDE1); - profile_color[VELO_FAST] = COLOR(PIRATEGOLD1, BLACK1_LOW_TRANS, PIRATEGOLD1); - profile_color[VELO_CRAZY] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - - profile_color[PO2] = COLOR(APPLE1, BLACK1_LOW_TRANS, APPLE1); - profile_color[PO2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - profile_color[PN2] = COLOR(BLACK1_LOW_TRANS, BLACK1_LOW_TRANS, BLACK1_LOW_TRANS); - profile_color[PN2_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - profile_color[PHE] = COLOR(PEANUT, BLACK1_LOW_TRANS, PEANUT); - profile_color[PHE_ALERT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - profile_color[O2SETPOINT] = COLOR(RED1, BLACK1_LOW_TRANS, RED1); - profile_color[CCRSENSOR1] = COLOR(TUNDORA1_MED_TRANS, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS); - profile_color[CCRSENSOR2] = COLOR(ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS); - profile_color[CCRSENSOR3] = COLOR(PEANUT, BLACK1_LOW_TRANS, PEANUT); - profile_color[PP_LINES] = COLOR(BLACK1_HIGH_TRANS, BLACK1_LOW_TRANS, BLACK1_HIGH_TRANS); - - profile_color[TEXT_BACKGROUND] = COLOR(CONCRETE1_LOWER_TRANS, WHITE1, CONCRETE1_LOWER_TRANS); - profile_color[ALERT_BG] = COLOR(BROOM1_LOWER_TRANS, BLACK1_LOW_TRANS, BROOM1_LOWER_TRANS); - profile_color[ALERT_FG] = COLOR(BLACK1_LOW_TRANS, WHITE1, BLACK1_LOW_TRANS); - profile_color[EVENTS] = COLOR(REDORANGE1, BLACK1_LOW_TRANS, REDORANGE1); - profile_color[SAMPLE_DEEP] = COLOR(QColor(Qt::red).darker(), BLACK1, PERSIANRED1); - profile_color[SAMPLE_SHALLOW] = COLOR(QColor(Qt::red).lighter(), BLACK1_LOW_TRANS, PERSIANRED1); - profile_color[SMOOTHED] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_LOW_TRANS, REDORANGE1_HIGH_TRANS); - profile_color[MINUTE] = COLOR(MEDIUMREDVIOLET1_HIGHER_TRANS, BLACK1_LOW_TRANS, MEDIUMREDVIOLET1_HIGHER_TRANS); - profile_color[TIME_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); - profile_color[TIME_TEXT] = COLOR(FORESTGREEN1, BLACK1, FORESTGREEN1); - profile_color[DEPTH_GRID] = COLOR(WHITE1, BLACK1_HIGH_TRANS, TUNDORA1_MED_TRANS); - profile_color[MEAN_DEPTH] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); - profile_color[HR_PLOT] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); - profile_color[HR_TEXT] = COLOR(REDORANGE1_MED_TRANS, BLACK1_LOW_TRANS, REDORANGE1_MED_TRANS); - profile_color[HR_AXIS] = COLOR(MED_GRAY_HIGH_TRANS, MED_GRAY_HIGH_TRANS, MED_GRAY_HIGH_TRANS); - profile_color[DEPTH_BOTTOM] = COLOR(GOVERNORBAY1_MED_TRANS, BLACK1_HIGH_TRANS, GOVERNORBAY1_MED_TRANS); - profile_color[DEPTH_TOP] = COLOR(MERCURY1_MED_TRANS, WHITE1_MED_TRANS, MERCURY1_MED_TRANS); - profile_color[TEMP_TEXT] = COLOR(GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2); - profile_color[TEMP_PLOT] = COLOR(ROYALBLUE2_LOW_TRANS, BLACK1_LOW_TRANS, ROYALBLUE2_LOW_TRANS); - profile_color[SAC_DEFAULT] = COLOR(WHITE1, BLACK1_LOW_TRANS, FORESTGREEN1); - profile_color[BOUNDING_BOX] = COLOR(WHITE1, BLACK1_LOW_TRANS, TUNDORA1_MED_TRANS); - profile_color[PRESSURE_TEXT] = COLOR(KILLARNEY1, BLACK1_LOW_TRANS, KILLARNEY1); - profile_color[BACKGROUND] = COLOR(SPRINGWOOD1, WHITE1, SPRINGWOOD1); - profile_color[BACKGROUND_TRANS] = COLOR(SPRINGWOOD1_MED_TRANS, WHITE1_MED_TRANS, SPRINGWOOD1_MED_TRANS); - profile_color[CEILING_SHALLOW] = COLOR(REDORANGE1_HIGH_TRANS, BLACK1_HIGH_TRANS, REDORANGE1_HIGH_TRANS); - profile_color[CEILING_DEEP] = COLOR(RED1_MED_TRANS, BLACK1_HIGH_TRANS, RED1_MED_TRANS); - profile_color[CALC_CEILING_SHALLOW] = COLOR(FUNGREEN1_HIGH_TRANS, BLACK1_HIGH_TRANS, FUNGREEN1_HIGH_TRANS); - profile_color[CALC_CEILING_DEEP] = COLOR(APPLE1_HIGH_TRANS, BLACK1_HIGH_TRANS, APPLE1_HIGH_TRANS); - profile_color[TISSUE_PERCENTAGE] = COLOR(GOVERNORBAY2, BLACK1_LOW_TRANS, GOVERNORBAY2); - profile_color[GF_LINE] = COLOR(BLACK1, BLACK1_LOW_TRANS, BLACK1); - profile_color[AMB_PRESSURE_LINE] = COLOR(TUNDORA1_MED_TRANS, BLACK1_LOW_TRANS, ATLANTIS1); -#undef COLOR -} - -QColor getColor(const color_indice_t i, bool isGrayscale) -{ - if (profile_color.count() > i && i >= 0) - return profile_color[i].at((isGrayscale) ? 1 : 0); - return QColor(Qt::black); -} - -QColor getSacColor(int sac, int avg_sac) -{ - int sac_index = 0; - int delta = sac - avg_sac + 7000; - - sac_index = delta / 2000; - if (sac_index < 0) - sac_index = 0; - if (sac_index > SAC_COLORS - 1) - sac_index = SAC_COLORS - 1; - return getColor((color_indice_t)(SAC_COLORS_START_IDX + sac_index), false); -} diff --git a/subsurface-core/color.h b/subsurface-core/color.h deleted file mode 100644 index 57ad77242..000000000 --- a/subsurface-core/color.h +++ /dev/null @@ -1,152 +0,0 @@ -#ifndef COLOR_H -#define COLOR_H - -/* The colors are named by picking the closest match - from http://chir.ag/projects/name-that-color */ - -#include -#include -#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) - -#define SAC_COLORS_START_IDX SAC_1 -#define SAC_COLORS 9 -#define VELOCITY_COLORS_START_IDX VELO_STABLE -#define VELOCITY_COLORS 5 - -typedef enum { - /* SAC colors. Order is important, the SAC_COLORS_START_IDX define above. */ - SAC_1, - SAC_2, - SAC_3, - SAC_4, - SAC_5, - SAC_6, - SAC_7, - SAC_8, - SAC_9, - - /* Velocity colors. Order is still important, ref VELOCITY_COLORS_START_IDX. */ - VELO_STABLE, - VELO_SLOW, - VELO_MODERATE, - VELO_FAST, - VELO_CRAZY, - - /* gas colors */ - PO2, - PO2_ALERT, - PN2, - PN2_ALERT, - PHE, - PHE_ALERT, - O2SETPOINT, - CCRSENSOR1, - CCRSENSOR2, - CCRSENSOR3, - PP_LINES, - - /* Other colors */ - TEXT_BACKGROUND, - ALERT_BG, - ALERT_FG, - EVENTS, - SAMPLE_DEEP, - SAMPLE_SHALLOW, - SMOOTHED, - MINUTE, - TIME_GRID, - TIME_TEXT, - DEPTH_GRID, - MEAN_DEPTH, - HR_TEXT, - HR_PLOT, - HR_AXIS, - DEPTH_TOP, - DEPTH_BOTTOM, - TEMP_TEXT, - TEMP_PLOT, - SAC_DEFAULT, - BOUNDING_BOX, - PRESSURE_TEXT, - BACKGROUND, - BACKGROUND_TRANS, - CEILING_SHALLOW, - CEILING_DEEP, - CALC_CEILING_SHALLOW, - CALC_CEILING_DEEP, - TISSUE_PERCENTAGE, - GF_LINE, - AMB_PRESSURE_LINE -} color_indice_t; - -extern QMap > profile_color; -void fill_profile_color(); -QColor getColor(const color_indice_t i, bool isGrayscale = false); -QColor getSacColor(int sac, int diveSac); -struct text_render_options { - double size; - color_indice_t color; - double hpos, vpos; -}; - -typedef text_render_options text_render_options_t; - -#endif // COLOR_H diff --git a/subsurface-core/compressibility.r b/subsurface-core/compressibility.r deleted file mode 100644 index 66310f3aa..000000000 --- a/subsurface-core/compressibility.r +++ /dev/null @@ -1,115 +0,0 @@ -# Compressibility data gathered by Lubomir I Ivanov: -# -# "Data obtained by finding two books online: -# -# [1] -# PERRY’S CHEMICAL ENGINEERS’ HANDBOOK SEVENTH EDITION -# pretty serious book, from which the wiki AIR values come from! -# -# http://www.unhas.ac.id/rhiza/arsip/kuliah/Sistem-dan-Tekn-Kendali-Proses/PDF_Collections/REFERENSI/Perrys_Chemical_Engineering_Handbook.pdf -# page 2-165 -# -# [*](Computed from pressure-volume-temperature tables in Vasserman monographs) -# ^ i have no idea idea what this means, but the values might not be exactly -# experimental?! -# -# the only thing this book is missing is helium, thus [2]! -# -# [2] -# VOLUMETRIC BEHAVIOR OF HELIUM-ARGON MIXTURES AT HIGH PRESSURE AND MODERATE TEMPERATURE. -# -# https://shareok.org/bitstream/handle/11244/2062/6614196.PDF?sequence=1 -# page 108 -# -# -# the book has some tables with pressure values in atmosphere units. i'm -# converting them bars. one of the relevant tables is for 323K and one for 273K -# (both almost equal distance from 300K). -# -# this again is a linear mix operation between isotherms, which is probably not -# the most accurate solution but it works. -# -# all data sets contain Z values at 300k, while the pressures are in bars in -# the 1 to 500 range -# -# - -x = c(1, 5, 10, 20, 40, 60, 80, 100, 200, 300, 400, 500) -o2 = c(0.9994, 0.9968, 0.9941, 0.9884, 0.9771, 0.9676, 0.9597, 0.9542, 0.9560, 0.9972, 1.0689, 1.1572) -n2 = c(0.9998, 0.9990, 0.9983, 0.9971, 0.9964, 0.9973, 1.0000, 1.0052, 1.0559, 1.1422, 1.2480, 1.3629) -he = c(1.0005, 1.0024, 1.0048, 1.0096, 1.0191, 1.0286, 1.0381, 1.0476, 1.0943, 1.1402, 1.1854, 1.2297) - -options(digits=15) - -# -# Get the O2 virial coefficients -# -plot(x,o2) -o2fit = nls(o2 ~ 1.0 + p1*x + p2 *x^2 + p3*x^3, start=list(p1=0,p2=0,p3=0)) -summary(o2fit) - -new = data.frame(x = seq(min(x),max(x),len=200)) -lines(new$x,predict(o2fit,newdata=new)) - -# -# Get the N2 virial coefficients -# -plot(x,n2) -n2fit = nls(n2 ~ 1.0 + p1*x + p2 *x^2 + p3*x^3, start=list(p1=0,p2=0,p3=0)) -summary(n2fit) - -new = data.frame(x = seq(min(x),max(x),len=200)) -lines(new$x,predict(n2fit,newdata=new)) - -# -# Get the He virial coefficients -# -# NOTE! This will not confirm convergence, thus the warnOnly. -# That may be a sign that the data is possibly artificial. -# -plot(x,he) -hefit = nls(he ~ 1.0 + p1*x + p2 *x^2 + p3*x^3, - start=list(p1=0,p2=0,p3=0), - control=nls.control(warnOnly=TRUE)) -summary(hefit) - -new = data.frame(x = seq(min(x),max(x),len=200)) -lines(new$x,predict(hefit,newdata=new)) - -# -# Raw data from VOLUMETRIC BEHAVIOR OF HELIUM-ARGON MIXTURES [..] -# T=323.15K (50 C) -p323atm = c(674.837, 393.223, 237.310, 146.294, 91.4027, 57.5799, 36.4620, 23.1654, 14.7478, 9.4017, 5.9987, 3.8300, - 540.204, 319.943, 195.008, 120.951, 75.8599, 47.9005, 30.3791, 19.3193, 12.3080, 7.8495, 5.0100, 3.1992) - -Hez323 = c(1.28067, 1.16782, 1.10289, 1.06407, 1.04028, 1.02548, 1.01617, 1.01029, 1.00656, 1.00418, 1.00267, 1.00171, - 1.22738, 1.13754, 1.08493, 1.05312, 1.03349, 1.02122, 1.01349, 1.00859, 1.00548, 1.00349, 1.00223, 1.00143) - - -# T=273.15 (0 C) -p273atm = c(683.599, 391.213, 233.607, 143.091, 89.0521, 55.9640, 35.3851, 22.4593, 14.2908, 9.1072, 5.8095, 3.7083, - 534.047, 312.144, 188.741, 116.508, 72.8529, 45.9194, 29.0883, 18.4851, 11.7702, 7.5040, 4.7881, 3.0570) - -Hez273 = c(1.33969, 1.19985, 1.12121, 1.07494, 1.04689, 1.02957, 1.01874, 1.01191, 1.00758, 1.00484, 1.00309, 1.00197, - 1.26914, 1.16070, 1.09837, 1.06118, 1.03843, 1.02429, 1.01541, 1.00980, 1.00625, 1.00398, 1.00254, 1.00162) - -p323 = p323atm * 1.01325 -p273 = p273atm * 1.01325 - -x2=append(p323,p273) -he2=append(Hez323,Hez273) - -plot(x2,he2) - -hefit2 = nls(he2 ~ 1.0 + p1*x2 + p2*x2^2 + p3*x2^3, - start=list(p1=0,p2=0,p3=0)) -summary(hefit2) - -he3 = function(bar) -{ - 1.0 +0.00047961098687979363 * bar -0.00000004077670019935 * bar^2 +0.00000000000077707035 * bar^3 -} - -new = data.frame(x2 = seq(min(x2),max(x2),len=200)) -lines(new$x2,predict(hefit2,newdata=new)) -curve(he3, min(x2),max(x2),add=TRUE) diff --git a/subsurface-core/configuredivecomputer.cpp b/subsurface-core/configuredivecomputer.cpp deleted file mode 100644 index 2457ffe82..000000000 --- a/subsurface-core/configuredivecomputer.cpp +++ /dev/null @@ -1,681 +0,0 @@ -#include "configuredivecomputer.h" -#include "libdivecomputer/hw.h" -#include -#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 deleted file mode 100644 index f14eeeca3..000000000 --- a/subsurface-core/configuredivecomputer.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef CONFIGUREDIVECOMPUTER_H -#define CONFIGUREDIVECOMPUTER_H - -#include -#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 deleted file mode 100644 index b229fc808..000000000 --- a/subsurface-core/configuredivecomputerthreads.cpp +++ /dev/null @@ -1,1778 +0,0 @@ -#include "configuredivecomputerthreads.h" -#include "libdivecomputer/hw.h" -#include "libdivecomputer.h" -#include - -#define OSTC3_GAS1 0x10 -#define OSTC3_GAS2 0x11 -#define OSTC3_GAS3 0x12 -#define OSTC3_GAS4 0x13 -#define OSTC3_GAS5 0x14 -#define OSTC3_DIL1 0x15 -#define OSTC3_DIL2 0x16 -#define OSTC3_DIL3 0x17 -#define OSTC3_DIL4 0x18 -#define OSTC3_DIL5 0x19 -#define OSTC3_SP1 0x1A -#define OSTC3_SP2 0x1B -#define OSTC3_SP3 0x1C -#define OSTC3_SP4 0x1D -#define OSTC3_SP5 0x1E -#define OSTC3_CCR_MODE 0x1F -#define OSTC3_DIVE_MODE 0x20 -#define OSTC3_DECO_TYPE 0x21 -#define OSTC3_PPO2_MAX 0x22 -#define OSTC3_PPO2_MIN 0x23 -#define OSTC3_FUTURE_TTS 0x24 -#define OSTC3_GF_LOW 0x25 -#define OSTC3_GF_HIGH 0x26 -#define OSTC3_AGF_LOW 0x27 -#define OSTC3_AGF_HIGH 0x28 -#define OSTC3_AGF_SELECTABLE 0x29 -#define OSTC3_SATURATION 0x2A -#define OSTC3_DESATURATION 0x2B -#define OSTC3_LAST_DECO 0x2C -#define OSTC3_BRIGHTNESS 0x2D -#define OSTC3_UNITS 0x2E -#define OSTC3_SAMPLING_RATE 0x2F -#define OSTC3_SALINITY 0x30 -#define OSTC3_DIVEMODE_COLOR 0x31 -#define OSTC3_LANGUAGE 0x32 -#define OSTC3_DATE_FORMAT 0x33 -#define OSTC3_COMPASS_GAIN 0x34 -#define OSTC3_PRESSURE_SENSOR_OFFSET 0x35 -#define OSTC3_SAFETY_STOP 0x36 -#define OSTC3_CALIBRATION_GAS_O2 0x37 -#define OSTC3_SETPOINT_FALLBACK 0x38 -#define OSTC3_FLIP_SCREEN 0x39 -#define OSTC3_LEFT_BUTTON_SENSIVITY 0x3A -#define OSTC3_RIGHT_BUTTON_SENSIVITY 0x3A -#define OSTC3_BOTTOM_GAS_CONSUMPTION 0x3C -#define OSTC3_DECO_GAS_CONSUMPTION 0x3D -#define OSTC3_MOD_WARNING 0x3E -#define OSTC3_DYNAMIC_ASCEND_RATE 0x3F -#define OSTC3_GRAPHICAL_SPEED_INDICATOR 0x40 -#define OSTC3_ALWAYS_SHOW_PPO2 0x41 -#define OSTC3_TEMP_SENSOR_OFFSET 0x42 -#define OSTC3_SAFETY_STOP_LENGTH 0x43 -#define OSTC3_SAFETY_STOP_START_DEPTH 0x44 -#define OSTC3_SAFETY_STOP_END_DEPTH 0x45 -#define OSTC3_SAFETY_STOP_RESET_DEPTH 0x46 - -#define OSTC3_HW_OSTC_3 0x0A -#define OSTC3_HW_OSTC_3P 0x1A -#define OSTC3_HW_OSTC_CR 0x05 -#define OSTC3_HW_OSTC_SPORT 0x12 -#define OSTC3_HW_OSTC_2 0x11 - - -#define SUUNTO_VYPER_MAXDEPTH 0x1e -#define SUUNTO_VYPER_TOTAL_TIME 0x20 -#define SUUNTO_VYPER_NUMBEROFDIVES 0x22 -#define SUUNTO_VYPER_COMPUTER_TYPE 0x24 -#define SUUNTO_VYPER_FIRMWARE 0x25 -#define SUUNTO_VYPER_SERIALNUMBER 0x26 -#define SUUNTO_VYPER_CUSTOM_TEXT 0x2c -#define SUUNTO_VYPER_SAMPLING_RATE 0x53 -#define SUUNTO_VYPER_ALTITUDE_SAFETY 0x54 -#define SUUNTO_VYPER_TIMEFORMAT 0x60 -#define SUUNTO_VYPER_UNITS 0x62 -#define SUUNTO_VYPER_MODEL 0x63 -#define SUUNTO_VYPER_LIGHT 0x64 -#define SUUNTO_VYPER_ALARM_DEPTH_TIME 0x65 -#define SUUNTO_VYPER_ALARM_TIME 0x66 -#define SUUNTO_VYPER_ALARM_DEPTH 0x68 -#define SUUNTO_VYPER_CUSTOM_TEXT_LENGHT 30 - -#ifdef DEBUG_OSTC -// Fake io to ostc memory banks -#define hw_ostc_device_eeprom_read local_hw_ostc_device_eeprom_read -#define hw_ostc_device_eeprom_write local_hw_ostc_device_eeprom_write -#define hw_ostc_device_clock local_hw_ostc_device_clock -#define OSTC_FILE "../OSTC-data-dump.bin" - -// Fake the open function. -static dc_status_t local_dc_device_open(dc_device_t **out, dc_context_t *context, dc_descriptor_t *descriptor, const char *name) -{ - if (strcmp(dc_descriptor_get_vendor(descriptor), "Heinrichs Weikamp") == 0 &&strcmp(dc_descriptor_get_product(descriptor), "OSTC 2N") == 0) - return DC_STATUS_SUCCESS; - else - return dc_device_open(out, context, descriptor, name); -} -#define dc_device_open local_dc_device_open - -// Fake the custom open function -static dc_status_t local_dc_device_custom_open(dc_device_t **out, dc_context_t *context, dc_descriptor_t *descriptor, dc_serial_t *serial) -{ - if (strcmp(dc_descriptor_get_vendor(descriptor), "Heinrichs Weikamp") == 0 &&strcmp(dc_descriptor_get_product(descriptor), "OSTC 2N") == 0) - return DC_STATUS_SUCCESS; - else - return dc_device_custom_open(out, context, descriptor, serial); -} -#define dc_device_custom_open local_dc_device_custom_open - -static dc_status_t local_hw_ostc_device_eeprom_read(void *ignored, unsigned char bank, unsigned char data[], unsigned int data_size) -{ - FILE *f; - if ((f = fopen(OSTC_FILE, "r")) == NULL) - return DC_STATUS_NODEVICE; - fseek(f, bank * 256, SEEK_SET); - if (fread(data, sizeof(unsigned char), data_size, f) != data_size) { - fclose(f); - return DC_STATUS_IO; - } - fclose(f); - - return DC_STATUS_SUCCESS; -} - -static dc_status_t local_hw_ostc_device_eeprom_write(void *ignored, unsigned char bank, unsigned char data[], unsigned int data_size) -{ - FILE *f; - if ((f = fopen(OSTC_FILE, "r+")) == NULL) - f = fopen(OSTC_FILE, "w"); - fseek(f, bank * 256, SEEK_SET); - fwrite(data, sizeof(unsigned char), data_size, f); - fclose(f); - - return DC_STATUS_SUCCESS; -} - -static dc_status_t local_hw_ostc_device_clock(void *ignored, dc_datetime_t *time) -{ - return DC_STATUS_SUCCESS; -} -#endif - -static int read_ostc_cf(unsigned char data[], unsigned char cf) -{ - return data[128 + (cf % 32) * 4 + 3] << 8 ^ data[128 + (cf % 32) * 4 + 2]; -} - -static void write_ostc_cf(unsigned char data[], unsigned char cf, unsigned char max_CF, unsigned int value) -{ - // Only write settings supported by this firmware. - if (cf > max_CF) - return; - - data[128 + (cf % 32) * 4 + 3] = (value & 0xff00) >> 8; - data[128 + (cf % 32) * 4 + 2] = (value & 0x00ff); -} - -#define EMIT_PROGRESS() do { \ - progress.current++; \ - progress_cb(device, DC_EVENT_PROGRESS, &progress, userdata); \ - } while (0) - -static dc_status_t read_suunto_vyper_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - unsigned char data[SUUNTO_VYPER_CUSTOM_TEXT_LENGHT + 1]; - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 16; - - rc = dc_device_read(device, SUUNTO_VYPER_COMPUTER_TYPE, data, 1); - if (rc == DC_STATUS_SUCCESS) { - const char *model; - // FIXME: grab this info from libdivecomputer descriptor - // instead of hard coded here - switch (data[0]) { - case 0x03: - model = "Stinger"; - break; - case 0x04: - model = "Mosquito"; - break; - case 0x05: - model = "D3"; - break; - case 0x0A: - model = "Vyper"; - break; - case 0x0B: - model = "Vytec"; - break; - case 0x0C: - model = "Cobra"; - break; - case 0x0D: - model = "Gekko"; - break; - case 0x16: - model = "Zoop"; - break; - case 20: - case 30: - case 60: - // Suunto Spyder have there sample interval at this position - // Fallthrough - default: - return DC_STATUS_UNSUPPORTED; - } - // We found a supported device - // we can safely proceed with reading/writing to this device. - m_deviceDetails->model = model; - } - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_MAXDEPTH, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - // in ft * 128.0 - int depth = feet_to_mm(data[0] << 8 ^ data[1]) / 128; - m_deviceDetails->maxDepth = depth; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_TOTAL_TIME, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - int total_time = data[0] << 8 ^ data[1]; - m_deviceDetails->totalTime = total_time; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_NUMBEROFDIVES, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - int number_of_dives = data[0] << 8 ^ data[1]; - m_deviceDetails->numberOfDives = number_of_dives; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_FIRMWARE, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->firmwareVersion = QString::number(data[0]) + ".0.0"; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_SERIALNUMBER, data, 4); - if (rc != DC_STATUS_SUCCESS) - return rc; - int serial_number = data[0] * 1000000 + data[1] * 10000 + data[2] * 100 + data[3]; - m_deviceDetails->serialNo = QString::number(serial_number); - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_CUSTOM_TEXT, data, SUUNTO_VYPER_CUSTOM_TEXT_LENGHT); - if (rc != DC_STATUS_SUCCESS) - return rc; - data[SUUNTO_VYPER_CUSTOM_TEXT_LENGHT] = 0; - m_deviceDetails->customText = (const char *)data; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_SAMPLING_RATE, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->samplingRate = (int)data[0]; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_ALTITUDE_SAFETY, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->altitude = data[0] & 0x03; - m_deviceDetails->personalSafety = data[0] >> 2 & 0x03; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_TIMEFORMAT, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->timeFormat = data[0] & 0x01; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_UNITS, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->units = data[0] & 0x01; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_MODEL, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->diveMode = data[0] & 0x03; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_LIGHT, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->lightEnabled = data[0] >> 7; - m_deviceDetails->light = data[0] & 0x7F; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_ALARM_DEPTH_TIME, data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - m_deviceDetails->alarmTimeEnabled = data[0] & 0x01; - m_deviceDetails->alarmDepthEnabled = data[0] >> 1 & 0x01; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_ALARM_TIME, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - int time = data[0] << 8 ^ data[1]; - // The stinger stores alarm time in seconds instead of minutes. - if (m_deviceDetails->model == "Stinger") - time /= 60; - m_deviceDetails->alarmTime = time; - EMIT_PROGRESS(); - - rc = dc_device_read(device, SUUNTO_VYPER_ALARM_DEPTH, data, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - depth = feet_to_mm(data[0] << 8 ^ data[1]) / 128; - m_deviceDetails->alarmDepth = depth; - EMIT_PROGRESS(); - - return DC_STATUS_SUCCESS; -} - -static dc_status_t write_suunto_vyper_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 10; - unsigned char data; - unsigned char data2[2]; - int time; - - // Maybee we should read the model from the device to sanity check it here too.. - // For now we just check that we actually read a device before writing to one. - if (m_deviceDetails->model == "") - return DC_STATUS_UNSUPPORTED; - - rc = dc_device_write(device, SUUNTO_VYPER_CUSTOM_TEXT, - // Convert the customText to a 30 char wide padded with " " - (const unsigned char *)QString("%1").arg(m_deviceDetails->customText, -30, QChar(' ')).toUtf8().data(), - SUUNTO_VYPER_CUSTOM_TEXT_LENGHT); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->samplingRate; - rc = dc_device_write(device, SUUNTO_VYPER_SAMPLING_RATE, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->personalSafety << 2 ^ m_deviceDetails->altitude; - rc = dc_device_write(device, SUUNTO_VYPER_ALTITUDE_SAFETY, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->timeFormat; - rc = dc_device_write(device, SUUNTO_VYPER_TIMEFORMAT, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->units; - rc = dc_device_write(device, SUUNTO_VYPER_UNITS, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->diveMode; - rc = dc_device_write(device, SUUNTO_VYPER_MODEL, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->lightEnabled << 7 ^ (m_deviceDetails->light & 0x7F); - rc = dc_device_write(device, SUUNTO_VYPER_LIGHT, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data = m_deviceDetails->alarmDepthEnabled << 1 ^ m_deviceDetails->alarmTimeEnabled; - rc = dc_device_write(device, SUUNTO_VYPER_ALARM_DEPTH_TIME, &data, 1); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - // The stinger stores alarm time in seconds instead of minutes. - time = m_deviceDetails->alarmTime; - if (m_deviceDetails->model == "Stinger") - time *= 60; - data2[0] = time >> 8; - data2[1] = time & 0xFF; - rc = dc_device_write(device, SUUNTO_VYPER_ALARM_TIME, data2, 2); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - data2[0] = (int)(mm_to_feet(m_deviceDetails->alarmDepth) * 128) >> 8; - data2[1] = (int)(mm_to_feet(m_deviceDetails->alarmDepth) * 128) & 0x0FF; - rc = dc_device_write(device, SUUNTO_VYPER_ALARM_DEPTH, data2, 2); - EMIT_PROGRESS(); - return rc; -} - -#if DC_VERSION_CHECK(0, 5, 0) -static dc_status_t read_ostc3_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 57; - unsigned char hardware[1]; - - //Read hardware type - rc = hw_ostc3_device_hardware (device, hardware, sizeof (hardware)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - // FIXME: can we grab this info from libdivecomputer descriptor - // instead of hard coded here? - switch(hardware[0]) { - case OSTC3_HW_OSTC_3: - m_deviceDetails->model = "3"; - break; - case OSTC3_HW_OSTC_3P: - m_deviceDetails->model = "3+"; - break; - case OSTC3_HW_OSTC_CR: - m_deviceDetails->model = "CR"; - break; - case OSTC3_HW_OSTC_SPORT: - m_deviceDetails->model = "Sport"; - break; - case OSTC3_HW_OSTC_2: - m_deviceDetails->model = "2"; - break; - } - - //Read gas mixes - gas gas1; - gas gas2; - gas gas3; - gas gas4; - gas gas5; - unsigned char gasData[4] = { 0, 0, 0, 0 }; - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS1, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas1.oxygen = gasData[0]; - gas1.helium = gasData[1]; - gas1.type = gasData[2]; - gas1.depth = gasData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS2, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas2.oxygen = gasData[0]; - gas2.helium = gasData[1]; - gas2.type = gasData[2]; - gas2.depth = gasData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS3, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas3.oxygen = gasData[0]; - gas3.helium = gasData[1]; - gas3.type = gasData[2]; - gas3.depth = gasData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS4, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas4.oxygen = gasData[0]; - gas4.helium = gasData[1]; - gas4.type = gasData[2]; - gas4.depth = gasData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_GAS5, gasData, sizeof(gasData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - gas5.oxygen = gasData[0]; - gas5.helium = gasData[1]; - gas5.type = gasData[2]; - gas5.depth = gasData[3]; - EMIT_PROGRESS(); - - m_deviceDetails->gas1 = gas1; - m_deviceDetails->gas2 = gas2; - m_deviceDetails->gas3 = gas3; - m_deviceDetails->gas4 = gas4; - m_deviceDetails->gas5 = gas5; - EMIT_PROGRESS(); - - //Read Dil Values - gas dil1; - gas dil2; - gas dil3; - gas dil4; - gas dil5; - unsigned char dilData[4] = { 0, 0, 0, 0 }; - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL1, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil1.oxygen = dilData[0]; - dil1.helium = dilData[1]; - dil1.type = dilData[2]; - dil1.depth = dilData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL2, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil2.oxygen = dilData[0]; - dil2.helium = dilData[1]; - dil2.type = dilData[2]; - dil2.depth = dilData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL3, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil3.oxygen = dilData[0]; - dil3.helium = dilData[1]; - dil3.type = dilData[2]; - dil3.depth = dilData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL4, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil4.oxygen = dilData[0]; - dil4.helium = dilData[1]; - dil4.type = dilData[2]; - dil4.depth = dilData[3]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_DIL5, dilData, sizeof(dilData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - dil5.oxygen = dilData[0]; - dil5.helium = dilData[1]; - dil5.type = dilData[2]; - dil5.depth = dilData[3]; - EMIT_PROGRESS(); - - m_deviceDetails->dil1 = dil1; - m_deviceDetails->dil2 = dil2; - m_deviceDetails->dil3 = dil3; - m_deviceDetails->dil4 = dil4; - m_deviceDetails->dil5 = dil5; - - //Read set point Values - setpoint sp1; - setpoint sp2; - setpoint sp3; - setpoint sp4; - setpoint sp5; - unsigned char spData[2] = { 0, 0 }; - - rc = hw_ostc3_device_config_read(device, OSTC3_SP1, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp1.sp = spData[0]; - sp1.depth = spData[1]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_SP2, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp2.sp = spData[0]; - sp2.depth = spData[1]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_SP3, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp3.sp = spData[0]; - sp3.depth = spData[1]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_SP4, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp4.sp = spData[0]; - sp4.depth = spData[1]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_SP5, spData, sizeof(spData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - sp5.sp = spData[0]; - sp5.depth = spData[1]; - EMIT_PROGRESS(); - - m_deviceDetails->sp1 = sp1; - m_deviceDetails->sp2 = sp2; - m_deviceDetails->sp3 = sp3; - m_deviceDetails->sp4 = sp4; - m_deviceDetails->sp5 = sp5; - - //Read other settings - unsigned char uData[1] = { 0 }; - -#define READ_SETTING(_OSTC3_SETTING, _DEVICE_DETAIL) \ - do { \ - rc = hw_ostc3_device_config_read(device, _OSTC3_SETTING, uData, sizeof(uData)); \ - if (rc != DC_STATUS_SUCCESS) \ - return rc; \ - m_deviceDetails->_DEVICE_DETAIL = uData[0]; \ - EMIT_PROGRESS(); \ - } while (0) - - READ_SETTING(OSTC3_DIVE_MODE, diveMode); - READ_SETTING(OSTC3_SATURATION, saturation); - READ_SETTING(OSTC3_DESATURATION, desaturation); - READ_SETTING(OSTC3_LAST_DECO, lastDeco); - READ_SETTING(OSTC3_BRIGHTNESS, brightness); - READ_SETTING(OSTC3_UNITS, units); - READ_SETTING(OSTC3_SAMPLING_RATE, samplingRate); - READ_SETTING(OSTC3_SALINITY, salinity); - READ_SETTING(OSTC3_DIVEMODE_COLOR, diveModeColor); - READ_SETTING(OSTC3_LANGUAGE, language); - READ_SETTING(OSTC3_DATE_FORMAT, dateFormat); - READ_SETTING(OSTC3_COMPASS_GAIN, compassGain); - READ_SETTING(OSTC3_SAFETY_STOP, safetyStop); - READ_SETTING(OSTC3_GF_HIGH, gfHigh); - READ_SETTING(OSTC3_GF_LOW, gfLow); - READ_SETTING(OSTC3_PPO2_MIN, ppO2Min); - READ_SETTING(OSTC3_PPO2_MAX, ppO2Max); - READ_SETTING(OSTC3_FUTURE_TTS, futureTTS); - READ_SETTING(OSTC3_CCR_MODE, ccrMode); - READ_SETTING(OSTC3_DECO_TYPE, decoType); - READ_SETTING(OSTC3_AGF_SELECTABLE, aGFSelectable); - READ_SETTING(OSTC3_AGF_HIGH, aGFHigh); - READ_SETTING(OSTC3_AGF_LOW, aGFLow); - READ_SETTING(OSTC3_CALIBRATION_GAS_O2, calibrationGas); - READ_SETTING(OSTC3_FLIP_SCREEN, flipScreen); - READ_SETTING(OSTC3_SETPOINT_FALLBACK, setPointFallback); - READ_SETTING(OSTC3_LEFT_BUTTON_SENSIVITY, leftButtonSensitivity); - READ_SETTING(OSTC3_RIGHT_BUTTON_SENSIVITY, rightButtonSensitivity); - READ_SETTING(OSTC3_BOTTOM_GAS_CONSUMPTION, bottomGasConsumption); - READ_SETTING(OSTC3_DECO_GAS_CONSUMPTION, decoGasConsumption); - READ_SETTING(OSTC3_MOD_WARNING, modWarning); - READ_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); - READ_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); - READ_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); - READ_SETTING(OSTC3_SAFETY_STOP_LENGTH, safetyStopLength); - READ_SETTING(OSTC3_SAFETY_STOP_START_DEPTH, safetyStopStartDepth); - READ_SETTING(OSTC3_SAFETY_STOP_END_DEPTH, safetyStopEndDepth); - READ_SETTING(OSTC3_SAFETY_STOP_RESET_DEPTH, safetyStopResetDepth); - -#undef READ_SETTING - - rc = hw_ostc3_device_config_read(device, OSTC3_PRESSURE_SENSOR_OFFSET, uData, sizeof(uData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - // OSTC3 stores the pressureSensorOffset in two-complement - m_deviceDetails->pressureSensorOffset = (signed char)uData[0]; - EMIT_PROGRESS(); - - rc = hw_ostc3_device_config_read(device, OSTC3_TEMP_SENSOR_OFFSET, uData, sizeof(uData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - // OSTC3 stores the tempSensorOffset in two-complement - m_deviceDetails->tempSensorOffset = (signed char)uData[0]; - EMIT_PROGRESS(); - - //read firmware settings - unsigned char fData[64] = { 0 }; - rc = hw_ostc3_device_version(device, fData, sizeof(fData)); - if (rc != DC_STATUS_SUCCESS) - return rc; - int serial = fData[0] + (fData[1] << 8); - m_deviceDetails->serialNo = QString::number(serial); - m_deviceDetails->firmwareVersion = QString::number(fData[2]) + "." + QString::number(fData[3]); - QByteArray ar((char *)fData + 4, 60); - m_deviceDetails->customText = ar.trimmed(); - EMIT_PROGRESS(); - - return rc; -} - -static dc_status_t write_ostc3_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 56; - - //write gas values - unsigned char gas1Data[4] = { - m_deviceDetails->gas1.oxygen, - m_deviceDetails->gas1.helium, - m_deviceDetails->gas1.type, - m_deviceDetails->gas1.depth - }; - - unsigned char gas2Data[4] = { - m_deviceDetails->gas2.oxygen, - m_deviceDetails->gas2.helium, - m_deviceDetails->gas2.type, - m_deviceDetails->gas2.depth - }; - - unsigned char gas3Data[4] = { - m_deviceDetails->gas3.oxygen, - m_deviceDetails->gas3.helium, - m_deviceDetails->gas3.type, - m_deviceDetails->gas3.depth - }; - - unsigned char gas4Data[4] = { - m_deviceDetails->gas4.oxygen, - m_deviceDetails->gas4.helium, - m_deviceDetails->gas4.type, - m_deviceDetails->gas4.depth - }; - - unsigned char gas5Data[4] = { - m_deviceDetails->gas5.oxygen, - m_deviceDetails->gas5.helium, - m_deviceDetails->gas5.type, - m_deviceDetails->gas5.depth - }; - //gas 1 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS1, gas1Data, sizeof(gas1Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //gas 2 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS2, gas2Data, sizeof(gas2Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //gas 3 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS3, gas3Data, sizeof(gas3Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //gas 4 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS4, gas4Data, sizeof(gas4Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //gas 5 - rc = hw_ostc3_device_config_write(device, OSTC3_GAS5, gas5Data, sizeof(gas5Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //write set point values - unsigned char sp1Data[2] = { - m_deviceDetails->sp1.sp, - m_deviceDetails->sp1.depth - }; - - unsigned char sp2Data[2] = { - m_deviceDetails->sp2.sp, - m_deviceDetails->sp2.depth - }; - - unsigned char sp3Data[2] = { - m_deviceDetails->sp3.sp, - m_deviceDetails->sp3.depth - }; - - unsigned char sp4Data[2] = { - m_deviceDetails->sp4.sp, - m_deviceDetails->sp4.depth - }; - - unsigned char sp5Data[2] = { - m_deviceDetails->sp5.sp, - m_deviceDetails->sp5.depth - }; - - //sp 1 - rc = hw_ostc3_device_config_write(device, OSTC3_SP1, sp1Data, sizeof(sp1Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //sp 2 - rc = hw_ostc3_device_config_write(device, OSTC3_SP2, sp2Data, sizeof(sp2Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //sp 3 - rc = hw_ostc3_device_config_write(device, OSTC3_SP3, sp3Data, sizeof(sp3Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //sp 4 - rc = hw_ostc3_device_config_write(device, OSTC3_SP4, sp4Data, sizeof(sp4Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //sp 5 - rc = hw_ostc3_device_config_write(device, OSTC3_SP5, sp5Data, sizeof(sp5Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //write dil values - unsigned char dil1Data[4] = { - m_deviceDetails->dil1.oxygen, - m_deviceDetails->dil1.helium, - m_deviceDetails->dil1.type, - m_deviceDetails->dil1.depth - }; - - unsigned char dil2Data[4] = { - m_deviceDetails->dil2.oxygen, - m_deviceDetails->dil2.helium, - m_deviceDetails->dil2.type, - m_deviceDetails->dil2.depth - }; - - unsigned char dil3Data[4] = { - m_deviceDetails->dil3.oxygen, - m_deviceDetails->dil3.helium, - m_deviceDetails->dil3.type, - m_deviceDetails->dil3.depth - }; - - unsigned char dil4Data[4] = { - m_deviceDetails->dil4.oxygen, - m_deviceDetails->dil4.helium, - m_deviceDetails->dil4.type, - m_deviceDetails->dil4.depth - }; - - unsigned char dil5Data[4] = { - m_deviceDetails->dil5.oxygen, - m_deviceDetails->dil5.helium, - m_deviceDetails->dil5.type, - m_deviceDetails->dil5.depth - }; - //dil 1 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL1, dil1Data, sizeof(gas1Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //dil 2 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL2, dil2Data, sizeof(dil2Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //dil 3 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL3, dil3Data, sizeof(dil3Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //dil 4 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL4, dil4Data, sizeof(dil4Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //dil 5 - rc = hw_ostc3_device_config_write(device, OSTC3_DIL5, dil5Data, sizeof(dil5Data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //write general settings - //custom text - rc = hw_ostc3_device_customtext(device, m_deviceDetails->customText.toUtf8().data()); - if (rc != DC_STATUS_SUCCESS) - return rc; - - unsigned char data[1] = { 0 }; -#define WRITE_SETTING(_OSTC3_SETTING, _DEVICE_DETAIL) \ - do { \ - data[0] = m_deviceDetails->_DEVICE_DETAIL; \ - rc = hw_ostc3_device_config_write(device, _OSTC3_SETTING, data, sizeof(data)); \ - if (rc != DC_STATUS_SUCCESS) \ - return rc; \ - EMIT_PROGRESS(); \ - } while (0) - - WRITE_SETTING(OSTC3_DIVE_MODE, diveMode); - WRITE_SETTING(OSTC3_SATURATION, saturation); - WRITE_SETTING(OSTC3_DESATURATION, desaturation); - WRITE_SETTING(OSTC3_LAST_DECO, lastDeco); - WRITE_SETTING(OSTC3_BRIGHTNESS, brightness); - WRITE_SETTING(OSTC3_UNITS, units); - WRITE_SETTING(OSTC3_SAMPLING_RATE, samplingRate); - WRITE_SETTING(OSTC3_SALINITY, salinity); - WRITE_SETTING(OSTC3_DIVEMODE_COLOR, diveModeColor); - WRITE_SETTING(OSTC3_LANGUAGE, language); - WRITE_SETTING(OSTC3_DATE_FORMAT, dateFormat); - WRITE_SETTING(OSTC3_COMPASS_GAIN, compassGain); - WRITE_SETTING(OSTC3_SAFETY_STOP, safetyStop); - WRITE_SETTING(OSTC3_GF_HIGH, gfHigh); - WRITE_SETTING(OSTC3_GF_LOW, gfLow); - WRITE_SETTING(OSTC3_PPO2_MIN, ppO2Min); - WRITE_SETTING(OSTC3_PPO2_MAX, ppO2Max); - WRITE_SETTING(OSTC3_FUTURE_TTS, futureTTS); - WRITE_SETTING(OSTC3_CCR_MODE, ccrMode); - WRITE_SETTING(OSTC3_DECO_TYPE, decoType); - WRITE_SETTING(OSTC3_AGF_SELECTABLE, aGFSelectable); - WRITE_SETTING(OSTC3_AGF_HIGH, aGFHigh); - WRITE_SETTING(OSTC3_AGF_LOW, aGFLow); - WRITE_SETTING(OSTC3_CALIBRATION_GAS_O2, calibrationGas); - WRITE_SETTING(OSTC3_FLIP_SCREEN, flipScreen); - WRITE_SETTING(OSTC3_SETPOINT_FALLBACK, setPointFallback); - WRITE_SETTING(OSTC3_LEFT_BUTTON_SENSIVITY, leftButtonSensitivity); - WRITE_SETTING(OSTC3_RIGHT_BUTTON_SENSIVITY, rightButtonSensitivity); - WRITE_SETTING(OSTC3_BOTTOM_GAS_CONSUMPTION, bottomGasConsumption); - WRITE_SETTING(OSTC3_DECO_GAS_CONSUMPTION, decoGasConsumption); - WRITE_SETTING(OSTC3_MOD_WARNING, modWarning); - WRITE_SETTING(OSTC3_DYNAMIC_ASCEND_RATE, dynamicAscendRate); - WRITE_SETTING(OSTC3_GRAPHICAL_SPEED_INDICATOR, graphicalSpeedIndicator); - WRITE_SETTING(OSTC3_ALWAYS_SHOW_PPO2, alwaysShowppO2); - WRITE_SETTING(OSTC3_TEMP_SENSOR_OFFSET, tempSensorOffset); - WRITE_SETTING(OSTC3_SAFETY_STOP_LENGTH, safetyStopLength); - WRITE_SETTING(OSTC3_SAFETY_STOP_START_DEPTH, safetyStopStartDepth); - WRITE_SETTING(OSTC3_SAFETY_STOP_END_DEPTH, safetyStopEndDepth); - WRITE_SETTING(OSTC3_SAFETY_STOP_RESET_DEPTH, safetyStopResetDepth); - -#undef WRITE_SETTING - - // OSTC3 stores the pressureSensorOffset in two-complement - data[0] = (unsigned char)m_deviceDetails->pressureSensorOffset; - rc = hw_ostc3_device_config_write(device, OSTC3_PRESSURE_SENSOR_OFFSET, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - // OSTC3 stores the tempSensorOffset in two-complement - data[0] = (unsigned char)m_deviceDetails->pressureSensorOffset; - rc = hw_ostc3_device_config_write(device, OSTC3_TEMP_SENSOR_OFFSET, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //sync date and time - if (m_deviceDetails->syncTime) { - dc_datetime_t now; - dc_datetime_localtime(&now, dc_datetime_now()); - - rc = hw_ostc3_device_clock(device, &now); - } - EMIT_PROGRESS(); - - return rc; -} -#endif /* DC_VERSION_CHECK(0, 5, 0) */ - -static dc_status_t read_ostc_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 3; - - unsigned char data[256] = {}; -#ifdef DEBUG_OSTC_CF - // FIXME: how should we report settings not supported back? - unsigned char max_CF = 0; -#endif - rc = hw_ostc_device_eeprom_read(device, 0, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - m_deviceDetails->serialNo = QString::number(data[1] << 8 ^ data[0]); - m_deviceDetails->numberOfDives = data[3] << 8 ^ data[2]; - //Byte5-6: - //Gas 1 default (%O2=21, %He=0) - gas gas1; - gas1.oxygen = data[6]; - gas1.helium = data[7]; - //Byte9-10: - //Gas 2 default (%O2=21, %He=0) - gas gas2; - gas2.oxygen = data[10]; - gas2.helium = data[11]; - //Byte13-14: - //Gas 3 default (%O2=21, %He=0) - gas gas3; - gas3.oxygen = data[14]; - gas3.helium = data[15]; - //Byte17-18: - //Gas 4 default (%O2=21, %He=0) - gas gas4; - gas4.oxygen = data[18]; - gas4.helium = data[19]; - //Byte21-22: - //Gas 5 default (%O2=21, %He=0) - gas gas5; - gas5.oxygen = data[22]; - gas5.helium = data[23]; - //Byte25-26: - //Gas 6 current (%O2, %He) - m_deviceDetails->salinity = data[26]; - // Active Gas Flag Register - gas1.type = data[27] & 0x01; - gas2.type = (data[27] & 0x02) >> 1; - gas3.type = (data[27] & 0x04) >> 2; - gas4.type = (data[27] & 0x08) >> 3; - gas5.type = (data[27] & 0x10) >> 4; - - // Gas switch depths - gas1.depth = data[28]; - gas2.depth = data[29]; - gas3.depth = data[30]; - gas4.depth = data[31]; - gas5.depth = data[32]; - // 33 which gas is Fist gas - switch (data[33]) { - case 1: - gas1.type = 2; - break; - case 2: - gas2.type = 2; - break; - case 3: - gas3.type = 2; - break; - case 4: - gas4.type = 2; - break; - case 5: - gas5.type = 2; - break; - default: - //Error? - break; - } - // Data filled up, set the gases. - m_deviceDetails->gas1 = gas1; - m_deviceDetails->gas2 = gas2; - m_deviceDetails->gas3 = gas3; - m_deviceDetails->gas4 = gas4; - m_deviceDetails->gas5 = gas5; - m_deviceDetails->decoType = data[34]; - //Byte36: - //Use O2 Sensor Module in CC Modes (0= OFF, 1= ON) (Only available in old OSTC1 - unused for OSTC Mk.2/2N) - //m_deviceDetails->ccrMode = data[35]; - setpoint sp1; - sp1.sp = data[36]; - sp1.depth = 0; - setpoint sp2; - sp2.sp = data[37]; - sp2.depth = 0; - setpoint sp3; - sp3.sp = data[38]; - sp3.depth = 0; - m_deviceDetails->sp1 = sp1; - m_deviceDetails->sp2 = sp2; - m_deviceDetails->sp3 = sp3; - // Byte41-42: - // Lowest Battery voltage seen (in mV) - // Byte43: - // Lowest Battery voltage seen at (Month) - // Byte44: - // Lowest Battery voltage seen at (Day) - // Byte45: - // Lowest Battery voltage seen at (Year) - // Byte46-47: - // Lowest Battery voltage seen at (Temperature in 0.1 °C) - // Byte48: - // Last complete charge at (Month) - // Byte49: - // Last complete charge at (Day) - // Byte50: - // Last complete charge at (Year) - // Byte51-52: - // Total charge cycles - // Byte53-54: - // Total complete charge cycles - // Byte55-56: - // Temperature Extrema minimum (Temperature in 0.1 °C) - // Byte57: - // Temperature Extrema minimum at (Month) - // Byte58: - // Temperature Extrema minimum at (Day) - // Byte59: - // Temperature Extrema minimum at (Year) - // Byte60-61: - // Temperature Extrema maximum (Temperature in 0.1 °C) - // Byte62: - // Temperature Extrema maximum at (Month) - // Byte63: - // Temperature Extrema maximum at (Day) - // Byte64: - // Temperature Extrema maximum at (Year) - // Byte65: - // Custom Text active (=1), Custom Text Disabled (<>1) - // Byte66-90: - // TO FIX EDITOR SYNTAX/INDENT { - // (25Bytes): Custom Text for Surfacemode (Real text must end with "}") - // Example: OSTC Dive Computer} (19 Characters incl. "}") Bytes 85-90 will be ignored. - if (data[64] == 1) { - // Make shure the data is null-terminated - data[89] = 0; - // Find the internal termination and replace it with 0 - char *term = strchr((char *)data + 65, (int)'}'); - if (term) - *term = 0; - m_deviceDetails->customText = (const char *)data + 65; - } - // Byte91: - // Dim OLED in Divemode (>0), Normal mode (=0) - // Byte92: - // Date format for all outputs: - // =0: MM/DD/YY - // =1: DD/MM/YY - // =2: YY/MM/DD - m_deviceDetails->dateFormat = data[91]; -// Byte93: -// Total number of CF used in installed firmware -#ifdef DEBUG_OSTC_CF - max_CF = data[92]; -#endif - // Byte94: - // Last selected view for customview area in surface mode - // Byte95: - // Last selected view for customview area in dive mode - // Byte96-97: - // Diluent 1 Default (%O2,%He) - // Byte98-99: - // Diluent 1 Current (%O2,%He) - gas dil1(data[97], data[98]); - // Byte100-101: - // Gasuent 2 Default (%O2,%He) - // Byte102-103: - // Gasuent 2 Current (%O2,%He) - gas dil2(data[101], data[102]); - // Byte104-105: - // Gasuent 3 Default (%O2,%He) - // Byte106-107: - // Gasuent 3 Current (%O2,%He) - gas dil3(data[105], data[106]); - // Byte108-109: - // Gasuent 4 Default (%O2,%He) - // Byte110-111: - // Gasuent 4 Current (%O2,%He) - gas dil4(data[109], data[110]); - // Byte112-113: - // Gasuent 5 Default (%O2,%He) - // Byte114-115: - // Gasuent 5 Current (%O2,%He) - gas dil5(data[113], data[114]); - // Byte116: - // First Diluent (1-5) - switch (data[115]) { - case 1: - dil1.type = 2; - break; - case 2: - dil2.type = 2; - break; - case 3: - dil3.type = 2; - break; - case 4: - dil4.type = 2; - break; - case 5: - dil5.type = 2; - break; - default: - //Error? - break; - } - m_deviceDetails->dil1 = dil1; - m_deviceDetails->dil2 = dil2; - m_deviceDetails->dil3 = dil3; - m_deviceDetails->dil4 = dil4; - m_deviceDetails->dil5 = dil5; - // Byte117-128: - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF0-CF31) - - // Decode the relevant ones - // CF11: Factor for saturation processes - m_deviceDetails->saturation = read_ostc_cf(data, 11); - // CF12: Factor for desaturation processes - m_deviceDetails->desaturation = read_ostc_cf(data, 12); - // CF17: Lower threshold for ppO2 warning - m_deviceDetails->ppO2Min = read_ostc_cf(data, 17); - // CF18: Upper threshold for ppO2 warning - m_deviceDetails->ppO2Max = read_ostc_cf(data, 18); - // CF20: Depth sampling rate for Profile storage - m_deviceDetails->samplingRate = read_ostc_cf(data, 20); - // CF29: Depth of last decompression stop - m_deviceDetails->lastDeco = read_ostc_cf(data, 29); - -#ifdef DEBUG_OSTC_CF - for (int cf = 0; cf <= 31 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - - rc = hw_ostc_device_eeprom_read(device, 1, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - // Byte1: - // Logbook version indicator (Not writable!) - // Byte2-3: - // Last Firmware installed, 1st Byte.2nd Byte (e.g. „1.90“) (Not writable!) - m_deviceDetails->firmwareVersion = QString::number(data[1]) + "." + QString::number(data[2]); - // Byte4: - // OLED brightness (=0: Eco, =1 High) (Not writable!) - // Byte5-11: - // Time/Date vault during firmware updates - // Byte12-128 - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF 32-63) - - // Decode the relevant ones - // CF32: Gradient Factor low - m_deviceDetails->gfLow = read_ostc_cf(data, 32); - // CF33: Gradient Factor high - m_deviceDetails->gfHigh = read_ostc_cf(data, 33); - // CF56: Bottom gas consumption - m_deviceDetails->bottomGasConsumption = read_ostc_cf(data, 56); - // CF57: Ascent gas consumption - m_deviceDetails->decoGasConsumption = read_ostc_cf(data, 57); - // CF58: Future time to surface setFutureTTS - m_deviceDetails->futureTTS = read_ostc_cf(data, 58); - -#ifdef DEBUG_OSTC_CF - for (int cf = 32; cf <= 63 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - - rc = hw_ostc_device_eeprom_read(device, 2, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - // Byte1-4: - // not used/reserved (Not writable!) - // Byte5-128: - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF 64-95) - - // Decode the relevant ones - // CF60: Graphic velocity - m_deviceDetails->graphicalSpeedIndicator = read_ostc_cf(data, 60); - // CF65: Show safety stop - m_deviceDetails->safetyStop = read_ostc_cf(data, 65); - // CF67: Alternaitve Gradient Factor low - m_deviceDetails->aGFLow = read_ostc_cf(data, 67); - // CF68: Alternative Gradient Factor high - m_deviceDetails->aGFHigh = read_ostc_cf(data, 68); - // CF69: Allow Gradient Factor change - m_deviceDetails->aGFSelectable = read_ostc_cf(data, 69); - // CF70: Safety Stop Duration [s] - m_deviceDetails->safetyStopLength = read_ostc_cf(data, 70); - // CF71: Safety Stop Start Depth [m] - m_deviceDetails->safetyStopStartDepth = read_ostc_cf(data, 71); - // CF72: Safety Stop End Depth [m] - m_deviceDetails->safetyStopEndDepth = read_ostc_cf(data, 72); - // CF73: Safety Stop Reset Depth [m] - m_deviceDetails->safetyStopResetDepth = read_ostc_cf(data, 73); - // CF74: Battery Timeout [min] - -#ifdef DEBUG_OSTC_CF - for (int cf = 64; cf <= 95 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - - return rc; -} - -static dc_status_t write_ostc_settings(dc_device_t *device, DeviceDetails *m_deviceDetails, dc_event_callback_t progress_cb, void *userdata) -{ - dc_status_t rc; - dc_event_progress_t progress; - progress.current = 0; - progress.maximum = 7; - unsigned char data[256] = {}; - unsigned char max_CF = 0; - - // Because we write whole memory blocks, we read all the current - // values out and then change then ones we should change. - rc = hw_ostc_device_eeprom_read(device, 0, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - //Byte5-6: - //Gas 1 default (%O2=21, %He=0) - gas gas1 = m_deviceDetails->gas1; - data[6] = gas1.oxygen; - data[7] = gas1.helium; - //Byte9-10: - //Gas 2 default (%O2=21, %He=0) - gas gas2 = m_deviceDetails->gas2; - data[10] = gas2.oxygen; - data[11] = gas2.helium; - //Byte13-14: - //Gas 3 default (%O2=21, %He=0) - gas gas3 = m_deviceDetails->gas3; - data[14] = gas3.oxygen; - data[15] = gas3.helium; - //Byte17-18: - //Gas 4 default (%O2=21, %He=0) - gas gas4 = m_deviceDetails->gas4; - data[18] = gas4.oxygen; - data[19] = gas4.helium; - //Byte21-22: - //Gas 5 default (%O2=21, %He=0) - gas gas5 = m_deviceDetails->gas5; - data[22] = gas5.oxygen; - data[23] = gas5.helium; - //Byte25-26: - //Gas 6 current (%O2, %He) - data[26] = m_deviceDetails->salinity; - // Gas types, 0=Disabled, 1=Active, 2=Fist - // Active Gas Flag Register - data[27] = 0; - if (gas1.type) - data[27] ^= 0x01; - if (gas2.type) - data[27] ^= 0x02; - if (gas3.type) - data[27] ^= 0x04; - if (gas4.type) - data[27] ^= 0x08; - if (gas5.type) - data[27] ^= 0x10; - - // Gas switch depths - data[28] = gas1.depth; - data[29] = gas2.depth; - data[30] = gas3.depth; - data[31] = gas4.depth; - data[32] = gas5.depth; - // 33 which gas is Fist gas - if (gas1.type == 2) - data[33] = 1; - else if (gas2.type == 2) - data[33] = 2; - else if (gas3.type == 2) - data[33] = 3; - else if (gas4.type == 2) - data[33] = 4; - else if (gas5.type == 2) - data[33] = 5; - else - // FIXME: No gas was First? - // Set gas 1 to first - data[33] = 1; - - data[34] = m_deviceDetails->decoType; - //Byte36: - //Use O2 Sensor Module in CC Modes (0= OFF, 1= ON) (Only available in old OSTC1 - unused for OSTC Mk.2/2N) - //m_deviceDetails->ccrMode = data[35]; - data[36] = m_deviceDetails->sp1.sp; - data[37] = m_deviceDetails->sp2.sp; - data[38] = m_deviceDetails->sp3.sp; - // Byte41-42: - // Lowest Battery voltage seen (in mV) - // Byte43: - // Lowest Battery voltage seen at (Month) - // Byte44: - // Lowest Battery voltage seen at (Day) - // Byte45: - // Lowest Battery voltage seen at (Year) - // Byte46-47: - // Lowest Battery voltage seen at (Temperature in 0.1 °C) - // Byte48: - // Last complete charge at (Month) - // Byte49: - // Last complete charge at (Day) - // Byte50: - // Last complete charge at (Year) - // Byte51-52: - // Total charge cycles - // Byte53-54: - // Total complete charge cycles - // Byte55-56: - // Temperature Extrema minimum (Temperature in 0.1 °C) - // Byte57: - // Temperature Extrema minimum at (Month) - // Byte58: - // Temperature Extrema minimum at (Day) - // Byte59: - // Temperature Extrema minimum at (Year) - // Byte60-61: - // Temperature Extrema maximum (Temperature in 0.1 °C) - // Byte62: - // Temperature Extrema maximum at (Month) - // Byte63: - // Temperature Extrema maximum at (Day) - // Byte64: - // Temperature Extrema maximum at (Year) - // Byte65: - // Custom Text active (=1), Custom Text Disabled (<>1) - // Byte66-90: - // (25Bytes): Custom Text for Surfacemode (Real text must end with "}") - // Example: "OSTC Dive Computer}" (19 Characters incl. "}") Bytes 85-90 will be ignored. - if (m_deviceDetails->customText == "") { - data[64] = 0; - } else { - data[64] = 1; - // Copy the string to the right place in the memory, padded with 0x20 (" ") - strncpy((char *)data + 65, QString("%1").arg(m_deviceDetails->customText, -23, QChar(' ')).toUtf8().data(), 23); - // And terminate the string. - if (m_deviceDetails->customText.length() <= 23) - data[65 + m_deviceDetails->customText.length()] = '}'; - else - data[90] = '}'; - } - // Byte91: - // Dim OLED in Divemode (>0), Normal mode (=0) - // Byte92: - // Date format for all outputs: - // =0: MM/DD/YY - // =1: DD/MM/YY - // =2: YY/MM/DD - data[91] = m_deviceDetails->dateFormat; - // Byte93: - // Total number of CF used in installed firmware - max_CF = data[92]; - // Byte94: - // Last selected view for customview area in surface mode - // Byte95: - // Last selected view for customview area in dive mode - // Byte96-97: - // Diluent 1 Default (%O2,%He) - // Byte98-99: - // Diluent 1 Current (%O2,%He) - gas dil1 = m_deviceDetails->dil1; - data[97] = dil1.oxygen; - data[98] = dil1.helium; - // Byte100-101: - // Gasuent 2 Default (%O2,%He) - // Byte102-103: - // Gasuent 2 Current (%O2,%He) - gas dil2 = m_deviceDetails->dil2; - data[101] = dil2.oxygen; - data[102] = dil2.helium; - // Byte104-105: - // Gasuent 3 Default (%O2,%He) - // Byte106-107: - // Gasuent 3 Current (%O2,%He) - gas dil3 = m_deviceDetails->dil3; - data[105] = dil3.oxygen; - data[106] = dil3.helium; - // Byte108-109: - // Gasuent 4 Default (%O2,%He) - // Byte110-111: - // Gasuent 4 Current (%O2,%He) - gas dil4 = m_deviceDetails->dil4; - data[109] = dil4.oxygen; - data[110] = dil4.helium; - // Byte112-113: - // Gasuent 5 Default (%O2,%He) - // Byte114-115: - // Gasuent 5 Current (%O2,%He) - gas dil5 = m_deviceDetails->dil5; - data[113] = dil5.oxygen; - data[114] = dil5.helium; - // Byte116: - // First Diluent (1-5) - if (dil1.type == 2) - data[115] = 1; - else if (dil2.type == 2) - data[115] = 2; - else if (dil3.type == 2) - data[115] = 3; - else if (dil4.type == 2) - data[115] = 4; - else if (dil5.type == 2) - data[115] = 5; - else - // FIXME: No first diluent? - // Set gas 1 to fist - data[115] = 1; - - // Byte117-128: - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF0-CF31) - - // Write the relevant ones - // CF11: Factor for saturation processes - write_ostc_cf(data, 11, max_CF, m_deviceDetails->saturation); - // CF12: Factor for desaturation processes - write_ostc_cf(data, 12, max_CF, m_deviceDetails->desaturation); - // CF17: Lower threshold for ppO2 warning - write_ostc_cf(data, 17, max_CF, m_deviceDetails->ppO2Min); - // CF18: Upper threshold for ppO2 warning - write_ostc_cf(data, 18, max_CF, m_deviceDetails->ppO2Max); - // CF20: Depth sampling rate for Profile storage - write_ostc_cf(data, 20, max_CF, m_deviceDetails->samplingRate); - // CF29: Depth of last decompression stop - write_ostc_cf(data, 29, max_CF, m_deviceDetails->lastDeco); - -#ifdef DEBUG_OSTC_CF - for (int cf = 0; cf <= 31 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - rc = hw_ostc_device_eeprom_write(device, 0, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - rc = hw_ostc_device_eeprom_read(device, 1, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - // Byte1: - // Logbook version indicator (Not writable!) - // Byte2-3: - // Last Firmware installed, 1st Byte.2nd Byte (e.g. „1.90“) (Not writable!) - // Byte4: - // OLED brightness (=0: Eco, =1 High) (Not writable!) - // Byte5-11: - // Time/Date vault during firmware updates - // Byte12-128 - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF 32-63) - - // Decode the relevant ones - // CF32: Gradient Factor low - write_ostc_cf(data, 32, max_CF, m_deviceDetails->gfLow); - // CF33: Gradient Factor high - write_ostc_cf(data, 33, max_CF, m_deviceDetails->gfHigh); - // CF56: Bottom gas consumption - write_ostc_cf(data, 56, max_CF, m_deviceDetails->bottomGasConsumption); - // CF57: Ascent gas consumption - write_ostc_cf(data, 57, max_CF, m_deviceDetails->decoGasConsumption); - // CF58: Future time to surface setFutureTTS - write_ostc_cf(data, 58, max_CF, m_deviceDetails->futureTTS); -#ifdef DEBUG_OSTC_CF - for (int cf = 32; cf <= 63 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - rc = hw_ostc_device_eeprom_write(device, 1, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - rc = hw_ostc_device_eeprom_read(device, 2, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - // Byte1-4: - // not used/reserved (Not writable!) - // Byte5-128: - // not used/reserved - // Byte129-256: - // 32 custom Functions (CF 64-95) - - // Decode the relevant ones - // CF60: Graphic velocity - write_ostc_cf(data, 60, max_CF, m_deviceDetails->graphicalSpeedIndicator); - // CF65: Show safety stop - write_ostc_cf(data, 65, max_CF, m_deviceDetails->safetyStop); - // CF67: Alternaitve Gradient Factor low - write_ostc_cf(data, 67, max_CF, m_deviceDetails->aGFLow); - // CF68: Alternative Gradient Factor high - write_ostc_cf(data, 68, max_CF, m_deviceDetails->aGFHigh); - // CF69: Allow Gradient Factor change - write_ostc_cf(data, 69, max_CF, m_deviceDetails->aGFSelectable); - // CF70: Safety Stop Duration [s] - write_ostc_cf(data, 70, max_CF, m_deviceDetails->safetyStopLength); - // CF71: Safety Stop Start Depth [m] - write_ostc_cf(data, 71, max_CF, m_deviceDetails->safetyStopStartDepth); - // CF72: Safety Stop End Depth [m] - write_ostc_cf(data, 72, max_CF, m_deviceDetails->safetyStopEndDepth); - // CF73: Safety Stop Reset Depth [m] - write_ostc_cf(data, 73, max_CF, m_deviceDetails->safetyStopResetDepth); - // CF74: Battery Timeout [min] - -#ifdef DEBUG_OSTC_CF - for (int cf = 64; cf <= 95 && cf <= max_CF; cf++) - printf("CF %d: %d\n", cf, read_ostc_cf(data, cf)); -#endif - rc = hw_ostc_device_eeprom_write(device, 2, data, sizeof(data)); - if (rc != DC_STATUS_SUCCESS) - return rc; - EMIT_PROGRESS(); - - //sync date and time - if (m_deviceDetails->syncTime) { - QDateTime timeToSet = QDateTime::currentDateTime(); - dc_datetime_t time; - time.year = timeToSet.date().year(); - time.month = timeToSet.date().month(); - time.day = timeToSet.date().day(); - time.hour = timeToSet.time().hour(); - time.minute = timeToSet.time().minute(); - time.second = timeToSet.time().second(); - rc = hw_ostc_device_clock(device, &time); - } - EMIT_PROGRESS(); - return rc; -} - -#undef EMIT_PROGRESS - -DeviceThread::DeviceThread(QObject *parent, device_data_t *data) : QThread(parent), m_data(data) -{ -} - -void DeviceThread::progressCB(int percent) -{ - emit progress(percent); -} - -void DeviceThread::event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) -{ - Q_UNUSED(device); - - const dc_event_progress_t *progress = (dc_event_progress_t *) data; - DeviceThread *dt = static_cast(userdata); - - switch (event) { - case DC_EVENT_PROGRESS: - dt->progressCB(100.0 * (double)progress->current / (double)progress->maximum); - break; - default: - emit dt->error("Unexpected event recived"); - break; - } -} - -ReadSettingsThread::ReadSettingsThread(QObject *parent, device_data_t *data) : DeviceThread(parent, data) -{ -} - -void ReadSettingsThread::run() -{ - dc_status_t rc; - - DeviceDetails *m_deviceDetails = new DeviceDetails(0); - switch (dc_device_get_type(m_data->device)) { - case DC_FAMILY_SUUNTO_VYPER: - rc = read_suunto_vyper_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc == DC_STATUS_SUCCESS) { - emit devicedetails(m_deviceDetails); - } else if (rc == DC_STATUS_UNSUPPORTED) { - emit error(tr("This feature is not yet available for the selected dive computer.")); - } else { - emit error("Failed!"); - } - break; -#if DC_VERSION_CHECK(0, 5, 0) - case DC_FAMILY_HW_OSTC3: - rc = read_ostc3_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc == DC_STATUS_SUCCESS) - emit devicedetails(m_deviceDetails); - else - emit error("Failed!"); - break; -#endif // divecomputer 0.5.0 -#ifdef DEBUG_OSTC - case DC_FAMILY_NULL: -#endif - case DC_FAMILY_HW_OSTC: - rc = read_ostc_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc == DC_STATUS_SUCCESS) - emit devicedetails(m_deviceDetails); - else - emit error("Failed!"); - break; - default: - emit error(tr("This feature is not yet available for the selected dive computer.")); - break; - } -} - -WriteSettingsThread::WriteSettingsThread(QObject *parent, device_data_t *data) : - DeviceThread(parent, data), - m_deviceDetails(NULL) -{ -} - -void WriteSettingsThread::setDeviceDetails(DeviceDetails *details) -{ - m_deviceDetails = details; -} - -void WriteSettingsThread::run() -{ - dc_status_t rc; - - switch (dc_device_get_type(m_data->device)) { - case DC_FAMILY_SUUNTO_VYPER: - rc = write_suunto_vyper_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc == DC_STATUS_UNSUPPORTED) { - emit error(tr("This feature is not yet available for the selected dive computer.")); - } else if (rc != DC_STATUS_SUCCESS) { - emit error(tr("Failed!")); - } - break; -#if DC_VERSION_CHECK(0, 5, 0) - case DC_FAMILY_HW_OSTC3: - rc = write_ostc3_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc != DC_STATUS_SUCCESS) - emit error(tr("Failed!")); - break; -#endif // divecomputer 0.5.0 -#ifdef DEBUG_OSTC - case DC_FAMILY_NULL: -#endif - case DC_FAMILY_HW_OSTC: - rc = write_ostc_settings(m_data->device, m_deviceDetails, DeviceThread::event_cb, this); - if (rc != DC_STATUS_SUCCESS) - emit error(tr("Failed!")); - break; - default: - emit error(tr("This feature is not yet available for the selected dive computer.")); - break; - } -} - - -FirmwareUpdateThread::FirmwareUpdateThread(QObject *parent, device_data_t *data, QString fileName) : DeviceThread(parent, data), m_fileName(fileName) -{ -} - -void FirmwareUpdateThread::run() -{ - dc_status_t rc; - - rc = dc_device_set_events(m_data->device, DC_EVENT_PROGRESS, DeviceThread::event_cb, this); - if (rc != DC_STATUS_SUCCESS) { - emit error("Error registering the event handler."); - return; - } - switch (dc_device_get_type(m_data->device)) { -#if DC_VERSION_CHECK(0, 5, 0) - case DC_FAMILY_HW_OSTC3: - rc = hw_ostc3_device_fwupdate(m_data->device, m_fileName.toUtf8().data()); - break; - case DC_FAMILY_HW_OSTC: - rc = hw_ostc_device_fwupdate(m_data->device, m_fileName.toUtf8().data()); - break; -#endif // divecomputer 0.5.0 - default: - emit error(tr("This feature is not yet available for the selected dive computer.")); - return; - } - - if (rc != DC_STATUS_SUCCESS) { - emit error(tr("Firmware update failed!")); - } -} - - -ResetSettingsThread::ResetSettingsThread(QObject *parent, device_data_t *data) : DeviceThread(parent, data) -{ -} - -void ResetSettingsThread::run() -{ - dc_status_t rc = DC_STATUS_SUCCESS; - -#if DC_VERSION_CHECK(0, 5, 0) - if (dc_device_get_type(m_data->device) == DC_FAMILY_HW_OSTC3) { - rc = hw_ostc3_device_config_reset(m_data->device); - emit progress(100); - } -#endif // divecomputer 0.5.0 - if (rc != DC_STATUS_SUCCESS) { - emit error(tr("Reset settings failed!")); - } -} diff --git a/subsurface-core/configuredivecomputerthreads.h b/subsurface-core/configuredivecomputerthreads.h deleted file mode 100644 index 8817d848a..000000000 --- a/subsurface-core/configuredivecomputerthreads.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef CONFIGUREDIVECOMPUTERTHREADS_H -#define CONFIGUREDIVECOMPUTERTHREADS_H - -#include -#include -#include "libdivecomputer.h" -#include "devicedetails.h" - -class DeviceThread : public QThread { - Q_OBJECT -public: - DeviceThread(QObject *parent, device_data_t *data); - virtual void run() = 0; -signals: - void error(QString err); - void progress(int value); -protected: - device_data_t *m_data; - void progressCB(int value); - static void event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata); -}; - -class ReadSettingsThread : public DeviceThread { - Q_OBJECT -public: - ReadSettingsThread(QObject *parent, device_data_t *data); - void run(); -signals: - void devicedetails(DeviceDetails *newDeviceDetails); -}; - -class WriteSettingsThread : public DeviceThread { - Q_OBJECT -public: - WriteSettingsThread(QObject *parent, device_data_t *data); - void setDeviceDetails(DeviceDetails *details); - void run(); - -private: - DeviceDetails *m_deviceDetails; -}; - -class FirmwareUpdateThread : public DeviceThread { - Q_OBJECT -public: - FirmwareUpdateThread(QObject *parent, device_data_t *data, QString fileName); - void run(); - -private: - QString m_fileName; -}; - -class ResetSettingsThread : public DeviceThread { - Q_OBJECT -public: - ResetSettingsThread(QObject *parent, device_data_t *data); - void run(); -}; - -#endif // CONFIGUREDIVECOMPUTERTHREADS_H diff --git a/subsurface-core/datatrak.c b/subsurface-core/datatrak.c deleted file mode 100644 index 204ebd9b3..000000000 --- a/subsurface-core/datatrak.c +++ /dev/null @@ -1,698 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#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 subtracts - * (1970 - 1600) * 365,2425 = 135139,725 to our date variable, getting the - * days since Epoch. - */ -static time_t date_time_to_ssrfc(unsigned long date, int time) -{ - time_t tmp; - tmp = (date - 135140) * 86400 + time * 60; - return tmp; -} - -static unsigned char to_8859(unsigned char char_cp850) -{ - static const unsigned char char_8859[46] = { 0xc7, 0xfc, 0xe9, 0xe2, 0xe4, 0xe0, 0xe5, 0xe7, - 0xea, 0xeb, 0xe8, 0xef, 0xee, 0xec, 0xc4, 0xc5, - 0xc9, 0xe6, 0xc6, 0xf4, 0xf6, 0xf2, 0xfb, 0xf9, - 0xff, 0xd6, 0xdc, 0xf8, 0xa3, 0xd8, 0xd7, 0x66, - 0xe1, 0xed, 0xf3, 0xfa, 0xf1, 0xd1, 0xaa, 0xba, - 0xbf, 0xae, 0xac, 0xbd, 0xbc, 0xa1 }; - return char_8859[char_cp850 - 0x80]; -} - -static char *to_utf8(unsigned char *in_string) -{ - int outlen, inlen, i = 0, j = 0; - inlen = strlen((char *)in_string); - outlen = inlen * 2 + 1; - - char *out_string = calloc(outlen, 1); - for (i = 0; i < inlen; i++) { - if (in_string[i] < 127) - out_string[j] = in_string[i]; - else { - if (in_string[i] > 127 && in_string[i] <= 173) - in_string[i] = to_8859(in_string[i]); - out_string[j] = (in_string[i] >> 6) | 0xC0; - j++; - out_string[j] = (in_string[i] & 0x3F) | 0x80; - } - j++; - } - out_string[j + 1] = '\0'; - return out_string; -} - -/* - * Subsurface sample structure doesn't support the flags and alarms in the dt .log - * so will treat them as dc events. - */ -static struct sample *dtrak_profile(struct dive *dt_dive, FILE *archivo) -{ - int i, j = 1, interval, o2percent = dt_dive->cylinder[0].gasmix.o2.permille / 10; - struct sample *sample = dt_dive->dc.sample; - struct divecomputer *dc = &dt_dive->dc; - - for (i = 1; i <= dt_dive->dc.alloc_samples; i++) { - if (fread(&lector_bytes, 1, 2, archivo) != 2) - return sample; - interval= 20 * (i + 1); - sample = add_sample(sample, interval, dc); - sample->depth.mm = (two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0xFFC0) * 1000 / 410; - byte = byte_to_bits(two_bytes_to_int(lector_bytes[0], lector_bytes[1]) & 0x003F); - if (byte[0] != 0) - sample->in_deco = true; - else - sample->in_deco = false; - if (byte[1] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "rbt"); - if (byte[2] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "ascent"); - if (byte[3] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "ceiling"); - if (byte[4] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "workload"); - if (byte[5] != 0) - add_event(dc, sample->time.seconds, 0, 0, 0, "transmitter"); - if (j == 3) { - read_bytes(1); - if (is_O2) { - read_bytes(1); - o2percent = tmp_1byte; - } - j = 0; - } - free(byte); - - // In commit 5f44fdd setpoint replaced po2, so although this is not necessarily CCR dive ... - if (is_O2) - sample->setpoint.mbar = calculate_depth_to_mbar(sample->depth.mm, dt_dive->surface_pressure, 0) * o2percent / 100; - j++; - } -bail: - return sample; -} - -/* - * Reads the header of a file and returns the header struct - * If it's not a DATATRAK file returns header zero initalized - */ -static dtrakheader read_file_header(FILE *archivo) -{ - dtrakheader fileheader = { 0 }; - const short headerbytes = 12; - unsigned char *lector = (unsigned char *)malloc(headerbytes); - - if (fread(lector, 1, headerbytes, archivo) != headerbytes) { - free(lector); - return fileheader; - } - if (two_bytes_to_int(lector[0], lector[1]) != 0xA100) { - report_error(translate("gettextFromC", "Error: the file does not appear to be a DATATRAK divelog")); - free(lector); - return fileheader; - } - fileheader.header = (lector[0] << 8) + lector[1]; - fileheader.dc_serial_1 = two_bytes_to_int(lector[2], lector[3]); - fileheader.dc_serial_2 = two_bytes_to_int(lector[4], lector[5]); - fileheader.divesNum = two_bytes_to_int(lector[7], lector[6]); - free(lector); - return fileheader; -} - -#define CHECK(_func, _val) if ((_func) != (_val)) goto bail - -/* - * Parses the dive extracting its data and filling a subsurface's dive structure - */ -bool dt_dive_parser(FILE *archivo, struct dive *dt_dive) -{ - unsigned char n; - int profile_length; - char *tmp_notes_str = NULL; - unsigned char *tmp_string1 = NULL, - *locality = NULL, - *dive_point = NULL; - char buffer[1024]; - struct divecomputer *dc = &dt_dive->dc; - - is_nitrox = is_O2 = is_SCR = 0; - - /* - * Parse byte to byte till next dive entry - */ - n = 0; - CHECK(fread(&lector_bytes[n], 1, 1, archivo), 1); - while (lector_bytes[n] != 0xA0) - CHECK(fread(&lector_bytes[n], 1, 1, archivo), 1); - - /* - * Found dive header 0xA000, verify second byte - */ - CHECK(fread(&lector_bytes[n+1], 1, 1, archivo), 1); - if (two_bytes_to_int(lector_bytes[0], lector_bytes[1]) != 0xA000) { - printf("Error: byte = %4x\n", two_bytes_to_int(lector_bytes[0], lector_bytes[1])); - return false; - } - - /* - * Begin parsing - * First, Date of dive, 4 bytes - */ - read_bytes(4); - - - /* - * Next, Time in minutes since 00:00 - */ - read_bytes(2); - - dt_dive->dc.when = dt_dive->when = (timestamp_t)date_time_to_ssrfc(tmp_4bytes, tmp_2bytes); - - /* - * Now, Locality, 1st byte is long of string, rest is string - */ - read_bytes(1); - read_string(locality); - - /* - * Next, Dive point, defined as Locality - */ - read_bytes(1); - read_string(dive_point); - - /* - * Subsurface only have a location variable, so we have to merge DTrak's - * Locality and Dive points. - */ - snprintf(buffer, sizeof(buffer), "%s, %s", locality, dive_point); - dt_dive->dive_site_uuid = get_dive_site_uuid_by_name(buffer, NULL); - if (dt_dive->dive_site_uuid == 0) - dt_dive->dive_site_uuid = create_dive_site(buffer, dt_dive->when); - free(locality); - free(dive_point); - - /* - * Altitude. Don't exist in Subsurface, the equivalent would be - * surface air pressure which can, be calculated from altitude. - * As dtrak registers altitude intervals, we, arbitrarily, choose - * the lower altitude/pressure equivalence for each segment. So - * - * Datatrak table * Conversion formula: - * * - * byte = 1 0 - 700 m * P = P0 * exp(-(g * M * h ) / (R * T0)) - * byte = 2 700 - 1700m * P0 = sealevel pressure = 101325 Pa - * byte = 3 1700 - 2700 m * g = grav. acceleration = 9,80665 m/s² - * byte = 4 2700 - * m * M = molar mass (dry air) = 0,0289644 Kg/mol - * * h = altitude over sea level (m) - * * R = Universal gas constant = 8,31447 J/(mol*K) - * * T0 = sea level standard temperature = 288,15 K - */ - read_bytes(1); - switch (tmp_1byte) { - case 1: - dt_dive->dc.surface_pressure.mbar = 1013; - break; - case 2: - dt_dive->dc.surface_pressure.mbar = 932; - break; - case 3: - dt_dive->dc.surface_pressure.mbar = 828; - break; - case 4: - dt_dive->dc.surface_pressure.mbar = 735; - break; - default: - dt_dive->dc.surface_pressure.mbar = 1013; - } - - /* - * Interval (minutes) - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) - dt_dive->dc.surfacetime.seconds = (uint32_t) tmp_2bytes * 60; - - /* - * Weather, values table, 0 to 6 - * Subsurface don't have this record but we can use tags - */ - dt_dive->tag_list = NULL; - read_bytes(1); - switch (tmp_1byte) { - case 1: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "clear"))); - break; - case 2: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "misty"))); - break; - case 3: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fog"))); - break; - case 4: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "rain"))); - break; - case 5: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "storm"))); - break; - case 6: - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "snow"))); - break; - default: - // unknown, do nothing - break; - } - - /* - * Air Temperature - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) - dt_dive->dc.airtemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100)); - - /* - * Dive suit, values table, 0 to 6 - */ - read_bytes(1); - switch (tmp_1byte) { - case 1: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "No suit")); - break; - case 2: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Shorty")); - break; - case 3: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Combi")); - break; - case 4: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Wet suit")); - break; - case 5: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Semidry suit")); - break; - case 6: - dt_dive->suit = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Dry suit")); - break; - default: - // unknown, do nothing - break; - } - - /* - * Tank, volume size in liter*100. And initialize gasmix to air (default). - * Dtrak don't record init and end pressures, but consumed bar, so let's - * init a default pressure of 200 bar. - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) { - dt_dive->cylinder[0].type.size.mliter = tmp_2bytes * 10; - dt_dive->cylinder[0].type.description = strdup(""); - dt_dive->cylinder[0].start.mbar = 200000; - dt_dive->cylinder[0].gasmix.he.permille = 0; - dt_dive->cylinder[0].gasmix.o2.permille = 210; - dt_dive->cylinder[0].manually_added = true; - } - - /* - * Maximum depth, in cm. - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) - dt_dive->maxdepth.mm = dt_dive->dc.maxdepth.mm = (int32_t)tmp_2bytes * 10; - - /* - * Dive time in minutes. - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF) - dt_dive->duration.seconds = dt_dive->dc.duration.seconds = (uint32_t)tmp_2bytes * 60; - - /* - * Minimum water temperature in C*100. If unknown, set it to 0K which - * is subsurface's value for "unknown" - */ - read_bytes(2); - if (tmp_2bytes != 0x7fff) - dt_dive->watertemp.mkelvin = dt_dive->dc.watertemp.mkelvin = C_to_mkelvin((double)(tmp_2bytes / 100)); - else - dt_dive->watertemp.mkelvin = 0; - - /* - * Air used in bar*100. - */ - read_bytes(2); - if (tmp_2bytes != 0x7FFF && dt_dive->cylinder[0].type.size.mliter) - dt_dive->cylinder[0].gas_used.mliter = dt_dive->cylinder[0].type.size.mliter * (tmp_2bytes / 100.0); - - /* - * Dive Type 1 - Bit table. Subsurface don't have this record, but - * will use tags. Bits 0 and 1 are not used. Reuse coincident tags. - */ - read_bytes(1); - byte = byte_to_bits(tmp_1byte); - if (byte[2] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "no stop"))); - if (byte[3] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "deco"))); - if (byte[4] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "single ascent"))); - if (byte[5] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "multiple ascent"))); - if (byte[6] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "fresh"))); - if (byte[7] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "salt water"))); - free(byte); - - /* - * Dive Type 2 - Bit table, use tags again - */ - read_bytes(1); - byte = byte_to_bits(tmp_1byte); - if (byte[0] != 0) { - taglist_add_tag(&dt_dive->tag_list, strdup("nitrox")); - is_nitrox = 1; - } - if (byte[1] != 0) { - taglist_add_tag(&dt_dive->tag_list, strdup("rebreather")); - is_SCR = 1; - dt_dive->dc.divemode = PSCR; - } - free(byte); - - /* - * Dive Activity 1 - Bit table, use tags again - */ - read_bytes(1); - byte = byte_to_bits(tmp_1byte); - if (byte[0] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "sight seeing"))); - if (byte[1] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "club dive"))); - if (byte[2] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instructor"))); - if (byte[3] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "instruction"))); - if (byte[4] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "night"))); - if (byte[5] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "cave"))); - if (byte[6] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "ice"))); - if (byte[7] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "search"))); - free(byte); - - - /* - * Dive Activity 2 - Bit table, use tags again - */ - read_bytes(1); - byte = byte_to_bits(tmp_1byte); - if (byte[0] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "wreck"))); - if (byte[1] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "river"))); - if (byte[2] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "drift"))); - if (byte[3] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "photo"))); - if (byte[4] != 0) - taglist_add_tag(&dt_dive->tag_list, strdup(QT_TRANSLATE_NOOP("gettextFromC", "other"))); - free(byte); - - /* - * Other activities - String 1st byte = long - * Will put this in dive notes before the true notes - */ - read_bytes(1); - if (tmp_1byte != 0) { - read_string(tmp_string1); - snprintf(buffer, sizeof(buffer), "%s: %s\n", - QT_TRANSLATE_NOOP("gettextFromC", "Other activities"), - tmp_string1); - tmp_notes_str = strdup(buffer); - free(tmp_string1); - } - - /* - * Dive buddies - */ - read_bytes(1); - if (tmp_1byte != 0) { - read_string(tmp_string1); - dt_dive->buddy = strdup((char *)tmp_string1); - free(tmp_string1); - } - - /* - * Dive notes - */ - read_bytes(1); - if (tmp_1byte != 0) { - read_string(tmp_string1); - int len = snprintf(buffer, sizeof(buffer), "%s%s:\n%s", - tmp_notes_str ? tmp_notes_str : "", - QT_TRANSLATE_NOOP("gettextFromC", "Datatrak/Wlog notes"), - tmp_string1); - dt_dive->notes = calloc((len +1), 1); - dt_dive->notes = memcpy(dt_dive->notes, buffer, len); - free(tmp_string1); - if (tmp_notes_str != NULL) - free(tmp_notes_str); - } - - /* - * Alarms 1 - Bit table - Not in Subsurface, we use the profile - */ - read_bytes(1); - - /* - * Alarms 2 - Bit table - Not in Subsurface, we use the profile - */ - read_bytes(1); - - /* - * Dive number (in datatrak, after import user has to renumber) - */ - read_bytes(2); - dt_dive->number = tmp_2bytes; - - /* - * Computer timestamp - Useless for Subsurface - */ - read_bytes(4); - - /* - * Model - table - Not included 0x14, 0x24, 0x41, and 0x73 - * known to exist, but not its model name - To add in the future. - * Strangely 0x00 serves for manually added dives and a dc too, at - * least in EXAMPLE.LOG file, shipped with the software. - */ - read_bytes(1); - switch (tmp_1byte) { - case 0x00: - dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Manually entered dive")); - break; - case 0x1C: - dt_dive->dc.model = strdup("Aladin Air"); - break; - case 0x1D: - dt_dive->dc.model = strdup("Spiro Monitor 2 plus"); - break; - case 0x1E: - dt_dive->dc.model = strdup("Aladin Sport"); - break; - case 0x1F: - dt_dive->dc.model = strdup("Aladin Pro"); - break; - case 0x34: - dt_dive->dc.model = strdup("Aladin Air X"); - break; - case 0x3D: - dt_dive->dc.model = strdup("Spiro Monitor 2 plus"); - break; - case 0x3F: - dt_dive->dc.model = strdup("Mares Genius"); - break; - case 0x44: - dt_dive->dc.model = strdup("Aladin Air X"); - break; - case 0x48: - dt_dive->dc.model = strdup("Spiro Monitor 3 Air"); - break; - case 0xA4: - dt_dive->dc.model = strdup("Aladin Air X O2"); - break; - case 0xB1: - dt_dive->dc.model = strdup("Citizen Hyper Aqualand"); - break; - case 0xB2: - dt_dive->dc.model = strdup("Citizen ProMaster"); - break; - case 0xB3: - dt_dive->dc.model = strdup("Mares Guardian"); - break; - case 0xBC: - dt_dive->dc.model = strdup("Aladin Air X Nitrox"); - break; - case 0xF4: - dt_dive->dc.model = strdup("Aladin Air X Nitrox"); - break; - case 0xFF: - dt_dive->dc.model = strdup("Aladin Pro Nitrox"); - break; - default: - dt_dive->dc.model = strdup(QT_TRANSLATE_NOOP("gettextFromC", "Unknown")); - break; - } - if ((tmp_1byte & 0xF0) == 0xF0) - is_nitrox = 1; - if ((tmp_1byte & 0xF0) == 0xA0) - is_O2 = 1; - - /* - * Air usage, unknown use. Probably allows or deny manually entering gas - * comsumption based on dc model - Useless for Subsurface - */ - read_bytes(1); - if (fseek(archivo, 6, 1) != 0) // jump over 6 bytes whitout known use - goto bail; - /* - * Profile data length - */ - read_bytes(2); - profile_length = tmp_2bytes; - if (profile_length != 0) { - /* - * 8 x 2 bytes for the tissues saturation useless for subsurface - * and other 6 bytes without known use - */ - if (fseek(archivo, 22, 1) != 0) - goto bail; - if (is_nitrox || is_O2) { - - /* - * CNS % (unsure) values table (only nitrox computers) - */ - read_bytes(1); - - /* - * % O2 in nitrox mix - (only nitrox and O2 computers but differents) - */ - read_bytes(1); - if (is_nitrox) { - dt_dive->cylinder[0].gasmix.o2.permille = - (tmp_1byte & 0x0F ? 20.0 + 2 * (tmp_1byte & 0x0F) : 21.0) * 10; - } else { - dt_dive->cylinder[0].gasmix.o2.permille = tmp_1byte * 10; - read_bytes(1) // Jump over one byte, unknown use - } - } - /* - * profileLength = Nº bytes, need to know how many samples are there. - * 2bytes per sample plus another one each three samples. Also includes the - * bytes jumped over (22) and the nitrox (2) or O2 (3). - */ - int samplenum = is_O2 ? (profile_length - 25) * 3 / 8 : (profile_length - 24) * 3 / 7; - - dc->events = calloc(samplenum, sizeof(struct event)); - dc->alloc_samples = samplenum; - dc->samples = 0; - dc->sample = calloc(samplenum, sizeof(struct sample)); - - dtrak_profile(dt_dive, archivo); - } - /* - * Initialize some dive data not supported by Datatrak/WLog - */ - if (!strcmp(dt_dive->dc.model, "Manually entered dive")) - dt_dive->dc.deviceid = 0; - else - dt_dive->dc.deviceid = 0xffffffff; - create_device_node(dt_dive->dc.model, dt_dive->dc.deviceid, "", "", dt_dive->dc.model); - dt_dive->dc.next = NULL; - if (!is_SCR && dt_dive->cylinder[0].type.size.mliter) { - dt_dive->cylinder[0].end.mbar = dt_dive->cylinder[0].start.mbar - - ((dt_dive->cylinder[0].gas_used.mliter / dt_dive->cylinder[0].type.size.mliter) * 1000); - } - return true; - -bail: - return false; -} - -void datatrak_import(const char *file, struct dive_table *table) -{ - FILE *archivo; - dtrakheader *fileheader = (dtrakheader *)malloc(sizeof(dtrakheader)); - int i = 0; - - if ((archivo = subsurface_fopen(file, "rb")) == NULL) { - report_error(translate("gettextFromC", "Error: couldn't open the file %s"), file); - free(fileheader); - return; - } - - /* - * Verify fileheader, get number of dives in datatrak divelog - */ - *fileheader = read_file_header(archivo); - while (i < fileheader->divesNum) { - struct dive *ptdive = alloc_dive(); - - if (!dt_dive_parser(archivo, ptdive)) { - report_error(translate("gettextFromC", "Error: no dive")); - free(ptdive); - } else { - record_dive(ptdive); - } - i++; - } - taglist_cleanup(&g_tag_list); - fclose(archivo); - sort_table(table); - free(fileheader); -} diff --git a/subsurface-core/datatrak.h b/subsurface-core/datatrak.h deleted file mode 100644 index 3a37e0465..000000000 --- a/subsurface-core/datatrak.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef DATATRAK_HEADER_H -#define DATATRAK_HEADER_H - -#include - -typedef struct dtrakheader_ { - int header; //Must be 0xA100; - int divesNum; - int dc_serial_1; - int dc_serial_2; -} dtrakheader; - -#define read_bytes(_n) \ - switch (_n) { \ - case 1: \ - if (fread (&lector_bytes, sizeof(char), _n, archivo) != _n) \ - goto bail; \ - tmp_1byte = lector_bytes[0]; \ - break; \ - case 2: \ - if (fread (&lector_bytes, sizeof(char), _n, archivo) != _n) \ - goto bail; \ - tmp_2bytes = two_bytes_to_int (lector_bytes[1], lector_bytes[0]); \ - break; \ - default: \ - if (fread (&lector_word, sizeof(char), _n, archivo) != _n) \ - goto bail; \ - tmp_4bytes = four_bytes_to_long(lector_word[3], lector_word[2], lector_word[1], lector_word[0]); \ - break; \ - } - -#define read_string(_property) \ - unsigned char *_property##tmp = (unsigned char *)calloc(tmp_1byte + 1, 1); \ - if (fread((char *)_property##tmp, 1, tmp_1byte, archivo) != tmp_1byte) { \ - free(_property##tmp); \ - goto bail; \ - } \ - _property = (unsigned char *)strcat(to_utf8(_property##tmp), ""); \ - free(_property##tmp); - -#endif // DATATRAK_HEADER_H diff --git a/subsurface-core/deco.c b/subsurface-core/deco.c deleted file mode 100644 index 3cd8c4a16..000000000 --- a/subsurface-core/deco.c +++ /dev/null @@ -1,601 +0,0 @@ -/* calculate deco values - * based on Bühlmann ZHL-16b - * based on an implemention by heinrichs weikamp for the DR5 - * the original file was given to Subsurface under the GPLv2 - * by Matthias Heinrichs - * - * The implementation below is a fairly complete rewrite since then - * (C) Robert C. Helling 2013 and released under the GPLv2 - * - * add_segment() - add 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). - int last_deco_stop_in_mtr; //! depth of last_deco_stop. - double gf_high; //! gradient factor high (at surface). - double gf_low; //! gradient factor low (at bottom/start of deco calculation). - double gf_low_position_min; //! gf_low_position below surface_min_shallow. - bool gf_low_at_maxdepth; //! if true, gf_low applies at max depth instead of at deepest ceiling. -}; - -struct buehlmann_config buehlmann_config = { - .satmult = 1.0, - .desatmult = 1.01, - .last_deco_stop_in_mtr = 0, - .gf_high = 0.75, - .gf_low = 0.35, - .gf_low_position_min = 1.0, - .gf_low_at_maxdepth = false -}; - -//! Option structure for VPM-B decompression. -struct vpmb_config { - double crit_radius_N2; //! Critical radius of N2 nucleon (microns). - double crit_radius_He; //! Critical radius of He nucleon (microns). - double crit_volume_lambda; //! Constant corresponding to critical gas volume (bar * min). - double gradient_of_imperm; //! Gradient after which bubbles become impermeable (bar). - double surface_tension_gamma; //! Nucleons surface tension constant (N / bar = m2). - double skin_compression_gammaC; //! Skin compression gammaC (N / bar = m2). - double regeneration_time; //! Time needed for the bubble to regenerate to the start radius (min). - double other_gases_pressure; //! Always present pressure of other gasses in tissues (bar). -}; - -struct vpmb_config vpmb_config = { - .crit_radius_N2 = 0.55, - .crit_radius_He = 0.45, - .crit_volume_lambda = 199.58, - .gradient_of_imperm = 8.30865, // = 8.2 atm - .surface_tension_gamma = 0.18137175, // = 0.0179 N/msw - .skin_compression_gammaC = 2.6040525, // = 0.257 N/msw - .regeneration_time = 20160.0, - .other_gases_pressure = 0.1359888 -}; - -const double buehlmann_N2_a[] = { 1.1696, 1.0, 0.8618, 0.7562, - 0.62, 0.5043, 0.441, 0.4, - 0.375, 0.35, 0.3295, 0.3065, - 0.2835, 0.261, 0.248, 0.2327 }; - -const double buehlmann_N2_b[] = { 0.5578, 0.6514, 0.7222, 0.7825, - 0.8126, 0.8434, 0.8693, 0.8910, - 0.9092, 0.9222, 0.9319, 0.9403, - 0.9477, 0.9544, 0.9602, 0.9653 }; - -const double buehlmann_N2_t_halflife[] = { 5.0, 8.0, 12.5, 18.5, - 27.0, 38.3, 54.3, 77.0, - 109.0, 146.0, 187.0, 239.0, - 305.0, 390.0, 498.0, 635.0 }; - -const double buehlmann_N2_factor_expositon_one_second[] = { - 2.30782347297664E-003, 1.44301447809736E-003, 9.23769302935806E-004, 6.24261986779007E-004, - 4.27777107246730E-004, 3.01585140931371E-004, 2.12729727268379E-004, 1.50020603047807E-004, - 1.05980191127841E-004, 7.91232600646508E-005, 6.17759153688224E-005, 4.83354552742732E-005, - 3.78761777920511E-005, 2.96212356654113E-005, 2.31974277413727E-005, 1.81926738960225E-005 -}; - -const double buehlmann_He_a[] = { 1.6189, 1.383, 1.1919, 1.0458, - 0.922, 0.8205, 0.7305, 0.6502, - 0.595, 0.5545, 0.5333, 0.5189, - 0.5181, 0.5176, 0.5172, 0.5119 }; - -const double buehlmann_He_b[] = { 0.4770, 0.5747, 0.6527, 0.7223, - 0.7582, 0.7957, 0.8279, 0.8553, - 0.8757, 0.8903, 0.8997, 0.9073, - 0.9122, 0.9171, 0.9217, 0.9267 }; - -const double buehlmann_He_t_halflife[] = { 1.88, 3.02, 4.72, 6.99, - 10.21, 14.48, 20.53, 29.11, - 41.20, 55.19, 70.69, 90.34, - 115.29, 147.42, 188.24, 240.03 }; - -const double buehlmann_He_factor_expositon_one_second[] = { - 6.12608039419837E-003, 3.81800836683133E-003, 2.44456078654209E-003, 1.65134647076792E-003, - 1.13084424730725E-003, 7.97503165599123E-004, 5.62552521860549E-004, 3.96776399429366E-004, - 2.80360036664540E-004, 2.09299583354805E-004, 1.63410794820518E-004, 1.27869320250551E-004, - 1.00198406028040E-004, 7.83611475491108E-005, 6.13689891868496E-005, 4.81280465299827E-005 -}; - -const double conservatism_lvls[] = { 1.0, 1.05, 1.12, 1.22, 1.35 }; - -/* Inspired gas loading equations depend on the partial pressure of inert gas in the alveolar. - * P_alv = (P_amb - P_H2O + (1 - Rq) / Rq * P_CO2) * f - * where: - * P_alv alveolar partial pressure of inert gas - * P_amb ambient pressure - * P_H2O water vapour partial pressure = ~0.0627 bar - * P_CO2 carbon dioxide partial pressure = ~0.0534 bar - * Rq respiratory quotient (O2 consumption / CO2 production) - * f fraction of inert gas - * - * In our calculations, we simplify this to use an effective water vapour pressure - * WV = P_H20 - (1 - Rq) / Rq * P_CO2 - * - * Buhlmann ignored the contribution of CO2 (i.e. Rq = 1.0), whereas Schreiner adopted Rq = 0.8. - * WV_Buhlmann = PP_H2O = 0.0627 bar - * WV_Schreiner = 0.0627 - (1 - 0.8) / Rq * 0.0534 = 0.0493 bar - - * Buhlmann calculations use the Buhlmann value, VPM-B calculations use the Schreiner value. -*/ -#define WV_PRESSURE 0.0627 // water vapor pressure in bar, based on respiratory quotient Rq = 1.0 (Buhlmann value) -#define WV_PRESSURE_SCHREINER 0.0493 // water vapor pressure in bar, based on respiratory quotient Rq = 0.8 (Schreiner value) - -#define DECO_STOPS_MULTIPLIER_MM 3000.0 -#define NITROGEN_FRACTION 0.79 - -double tissue_n2_sat[16]; -double tissue_he_sat[16]; -int ci_pointing_to_guiding_tissue; -double gf_low_pressure_this_dive; -#define TISSUE_ARRAY_SZ sizeof(tissue_n2_sat) - -double tolerated_by_tissue[16]; -double tissue_inertgas_saturation[16]; -double buehlmann_inertgas_a[16], buehlmann_inertgas_b[16]; - -double max_n2_crushing_pressure[16]; -double max_he_crushing_pressure[16]; - -double crushing_onset_tension[16]; // total inert gas tension in the t* moment -double n2_regen_radius[16]; // rs -double he_regen_radius[16]; -double max_ambient_pressure; // last moment we were descending - -double bottom_n2_gradient[16]; -double bottom_he_gradient[16]; - -double initial_n2_gradient[16]; -double initial_he_gradient[16]; - -double get_crit_radius_He() -{ - if (prefs.conservatism_level <= 4) - return vpmb_config.crit_radius_He * conservatism_lvls[prefs.conservatism_level] * subsurface_conservatism_factor; - return vpmb_config.crit_radius_He; -} - -double get_crit_radius_N2() -{ - if (prefs.conservatism_level <= 4) - return vpmb_config.crit_radius_N2 * conservatism_lvls[prefs.conservatism_level] * subsurface_conservatism_factor; - return vpmb_config.crit_radius_N2; -} - -// Solve another cubic equation, this time -// x^3 - B x - C == 0 -// Use trigonometric formula for negative discriminants (see Wikipedia for details) - -double solve_cubic2(double B, double C) -{ - double discriminant = 27 * C * C - 4 * cube(B); - if (discriminant < 0.0) { - return 2.0 * sqrt(B / 3.0) * cos(acos(3.0 * C * sqrt(3.0 / B) / (2.0 * B)) / 3.0); - } - - double denominator = pow(9 * C + sqrt(3 * discriminant), 1 / 3.0); - - return pow(2.0 / 3.0, 1.0 / 3.0) * B / denominator + denominator / pow(18.0, 1.0 / 3.0); -} - -// This is a simplified formula avoiding radii. It uses the fact that Boyle's law says -// pV = (G + P_amb) / G^3 is constant to solve for the new gradient G. - -double update_gradient(double next_stop_pressure, double first_gradient) -{ - double B = cube(first_gradient) / (first_ceiling_pressure.mbar / 1000.0 + first_gradient); - double C = next_stop_pressure * B; - - double new_gradient = solve_cubic2(B, C); - - if (new_gradient < 0.0) - report_error("Negative gradient encountered!"); - return new_gradient; -} - -double vpmb_tolerated_ambient_pressure(double reference_pressure, int ci) -{ - double n2_gradient, he_gradient, total_gradient; - - if (reference_pressure >= first_ceiling_pressure.mbar / 1000.0 || !first_ceiling_pressure.mbar) { - n2_gradient = bottom_n2_gradient[ci]; - he_gradient = bottom_he_gradient[ci]; - } else { - n2_gradient = update_gradient(reference_pressure, bottom_n2_gradient[ci]); - he_gradient = update_gradient(reference_pressure, bottom_he_gradient[ci]); - } - - total_gradient = ((n2_gradient * tissue_n2_sat[ci]) + (he_gradient * tissue_he_sat[ci])) / (tissue_n2_sat[ci] + tissue_he_sat[ci]); - - return tissue_n2_sat[ci] + tissue_he_sat[ci] + vpmb_config.other_gases_pressure - total_gradient; -} - - -double tissue_tolerance_calc(const struct dive *dive, double pressure) -{ - int ci = -1; - double ret_tolerance_limit_ambient_pressure = 0.0; - double gf_high = buehlmann_config.gf_high; - double gf_low = buehlmann_config.gf_low; - double surface = get_surface_pressure_in_mbar(dive, true) / 1000.0; - double lowest_ceiling = 0.0; - double tissue_lowest_ceiling[16]; - - if (prefs.deco_mode != VPMB) { - for (ci = 0; ci < 16; ci++) { - tissue_inertgas_saturation[ci] = tissue_n2_sat[ci] + tissue_he_sat[ci]; - buehlmann_inertgas_a[ci] = ((buehlmann_N2_a[ci] * tissue_n2_sat[ci]) + (buehlmann_He_a[ci] * tissue_he_sat[ci])) / tissue_inertgas_saturation[ci]; - buehlmann_inertgas_b[ci] = ((buehlmann_N2_b[ci] * tissue_n2_sat[ci]) + (buehlmann_He_b[ci] * tissue_he_sat[ci])) / tissue_inertgas_saturation[ci]; - - - /* tolerated = (tissue_inertgas_saturation - buehlmann_inertgas_a) * buehlmann_inertgas_b; */ - - tissue_lowest_ceiling[ci] = (buehlmann_inertgas_b[ci] * tissue_inertgas_saturation[ci] - gf_low * buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci]) / - ((1.0 - buehlmann_inertgas_b[ci]) * gf_low + buehlmann_inertgas_b[ci]); - if (tissue_lowest_ceiling[ci] > lowest_ceiling) - lowest_ceiling = tissue_lowest_ceiling[ci]; - if (!buehlmann_config.gf_low_at_maxdepth) { - if (lowest_ceiling > gf_low_pressure_this_dive) - gf_low_pressure_this_dive = lowest_ceiling; - } - } - for (ci = 0; ci < 16; ci++) { - double tolerated; - - if ((surface / buehlmann_inertgas_b[ci] + buehlmann_inertgas_a[ci] - surface) * gf_high + surface < - (gf_low_pressure_this_dive / buehlmann_inertgas_b[ci] + buehlmann_inertgas_a[ci] - gf_low_pressure_this_dive) * gf_low + gf_low_pressure_this_dive) - tolerated = (-buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci] * (gf_high * gf_low_pressure_this_dive - gf_low * surface) - - (1.0 - buehlmann_inertgas_b[ci]) * (gf_high - gf_low) * gf_low_pressure_this_dive * surface + - buehlmann_inertgas_b[ci] * (gf_low_pressure_this_dive - surface) * tissue_inertgas_saturation[ci]) / - (-buehlmann_inertgas_a[ci] * buehlmann_inertgas_b[ci] * (gf_high - gf_low) + - (1.0 - buehlmann_inertgas_b[ci]) * (gf_low * gf_low_pressure_this_dive - gf_high * surface) + - buehlmann_inertgas_b[ci] * (gf_low_pressure_this_dive - surface)); - else - tolerated = ret_tolerance_limit_ambient_pressure; - - - tolerated_by_tissue[ci] = tolerated; - - if (tolerated >= ret_tolerance_limit_ambient_pressure) { - ci_pointing_to_guiding_tissue = ci; - ret_tolerance_limit_ambient_pressure = tolerated; - } - } - } else { - // VPM-B ceiling - double reference_pressure; - - ret_tolerance_limit_ambient_pressure = pressure; - // The Boyle compensated gradient depends on ambient pressure. For the ceiling, this should set the ambient pressure. - do { - reference_pressure = ret_tolerance_limit_ambient_pressure; - ret_tolerance_limit_ambient_pressure = 0.0; - for (ci = 0; ci < 16; ci++) { - double tolerated = vpmb_tolerated_ambient_pressure(reference_pressure, ci); - if (tolerated >= ret_tolerance_limit_ambient_pressure) { - ci_pointing_to_guiding_tissue = ci; - ret_tolerance_limit_ambient_pressure = tolerated; - } - tolerated_by_tissue[ci] = tolerated; - } - // We are doing ok if the gradient was computed within ten centimeters of the ceiling. - } while (fabs(ret_tolerance_limit_ambient_pressure - reference_pressure) > 0.01); - } - return ret_tolerance_limit_ambient_pressure; -} - -/* - * Return buelman factor for a particular period and tissue index. - * - * We cache the last factor, since we commonly call this with the - * same values... We have a special "fixed cache" for the one second - * case, although I wonder if that's even worth it considering the - * more general-purpose cache. - */ -struct factor_cache { - int last_period; - double last_factor; -}; - -double n2_factor(int period_in_seconds, int ci) -{ - static struct factor_cache cache[16]; - - if (period_in_seconds == 1) - return buehlmann_N2_factor_expositon_one_second[ci]; - - if (period_in_seconds != cache[ci].last_period) { - cache[ci].last_period = period_in_seconds; - cache[ci].last_factor = 1 - pow(2.0, -period_in_seconds / (buehlmann_N2_t_halflife[ci] * 60)); - } - - return cache[ci].last_factor; -} - -double he_factor(int period_in_seconds, int ci) -{ - static struct factor_cache cache[16]; - - if (period_in_seconds == 1) - return buehlmann_He_factor_expositon_one_second[ci]; - - if (period_in_seconds != cache[ci].last_period) { - cache[ci].last_period = period_in_seconds; - cache[ci].last_factor = 1 - pow(2.0, -period_in_seconds / (buehlmann_He_t_halflife[ci] * 60)); - } - - return cache[ci].last_factor; -} - -double calc_surface_phase(double surface_pressure, double he_pressure, double n2_pressure, double he_time_constant, double n2_time_constant) -{ - double inspired_n2 = (surface_pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE)) * NITROGEN_FRACTION; - - if (n2_pressure > inspired_n2) - return (he_pressure / he_time_constant + (n2_pressure - inspired_n2) / n2_time_constant) / (he_pressure + n2_pressure - inspired_n2); - - if (he_pressure + n2_pressure >= inspired_n2){ - double gradient_decay_time = 1.0 / (n2_time_constant - he_time_constant) * log ((inspired_n2 - n2_pressure) / he_pressure); - double gradients_integral = he_pressure / he_time_constant * (1.0 - exp(-he_time_constant * gradient_decay_time)) + (n2_pressure - inspired_n2) / n2_time_constant * (1.0 - exp(-n2_time_constant * gradient_decay_time)); - return gradients_integral / (he_pressure + n2_pressure - inspired_n2); - } - - return 0; -} - -void vpmb_start_gradient() -{ - int ci; - - for (ci = 0; ci < 16; ++ci) { - initial_n2_gradient[ci] = bottom_n2_gradient[ci] = 2.0 * (vpmb_config.surface_tension_gamma / vpmb_config.skin_compression_gammaC) * ((vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma) / n2_regen_radius[ci]); - initial_he_gradient[ci] = bottom_he_gradient[ci] = 2.0 * (vpmb_config.surface_tension_gamma / vpmb_config.skin_compression_gammaC) * ((vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma) / he_regen_radius[ci]); - } -} - -void vpmb_next_gradient(double deco_time, double surface_pressure) -{ - int ci; - double n2_b, n2_c; - double he_b, he_c; - double desat_time; - deco_time /= 60.0; - - for (ci = 0; ci < 16; ++ci) { - desat_time = deco_time + calc_surface_phase(surface_pressure, tissue_he_sat[ci], tissue_n2_sat[ci], log(2.0) / buehlmann_He_t_halflife[ci], log(2.0) / buehlmann_N2_t_halflife[ci]); - - n2_b = initial_n2_gradient[ci] + (vpmb_config.crit_volume_lambda * vpmb_config.surface_tension_gamma) / (vpmb_config.skin_compression_gammaC * desat_time); - he_b = initial_he_gradient[ci] + (vpmb_config.crit_volume_lambda * vpmb_config.surface_tension_gamma) / (vpmb_config.skin_compression_gammaC * desat_time); - - n2_c = vpmb_config.surface_tension_gamma * vpmb_config.surface_tension_gamma * vpmb_config.crit_volume_lambda * max_n2_crushing_pressure[ci]; - n2_c = n2_c / (vpmb_config.skin_compression_gammaC * vpmb_config.skin_compression_gammaC * desat_time); - he_c = vpmb_config.surface_tension_gamma * vpmb_config.surface_tension_gamma * vpmb_config.crit_volume_lambda * max_he_crushing_pressure[ci]; - he_c = he_c / (vpmb_config.skin_compression_gammaC * vpmb_config.skin_compression_gammaC * desat_time); - - bottom_n2_gradient[ci] = 0.5 * ( n2_b + sqrt(n2_b * n2_b - 4.0 * n2_c)); - bottom_he_gradient[ci] = 0.5 * ( he_b + sqrt(he_b * he_b - 4.0 * he_c)); - } -} - -// A*r^3 - B*r^2 - C == 0 -// Solved with the help of mathematica - -double solve_cubic(double A, double B, double C) -{ - double BA = B/A; - double CA = C/A; - - double discriminant = CA * (4 * cube(BA) + 27 * CA); - - // Let's make sure we have a real solution: - if (discriminant < 0.0) { - // This should better not happen - report_error("Complex solution for inner pressure encountered!\n A=%f\tB=%f\tC=%f\n", A, B, C); - return 0.0; - } - double denominator = pow(cube(BA) + 1.5 * (9 * CA + sqrt(3.0) * sqrt(discriminant)), 1/3.0); - return (BA + BA * BA / denominator + denominator) / 3.0; - -} - - -void nuclear_regeneration(double time) -{ - time /= 60.0; - int ci; - double crushing_radius_N2, crushing_radius_He; - for (ci = 0; ci < 16; ++ci) { - //rm - crushing_radius_N2 = 1.0 / (max_n2_crushing_pressure[ci] / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / get_crit_radius_N2()); - crushing_radius_He = 1.0 / (max_he_crushing_pressure[ci] / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / get_crit_radius_He()); - //rs - n2_regen_radius[ci] = crushing_radius_N2 + (get_crit_radius_N2() - crushing_radius_N2) * (1.0 - exp (-time / vpmb_config.regeneration_time)); - he_regen_radius[ci] = crushing_radius_He + (get_crit_radius_He() - crushing_radius_He) * (1.0 - exp (-time / vpmb_config.regeneration_time)); - } -} - - -// Calculates the nucleons inner pressure during the impermeable period -double calc_inner_pressure(double crit_radius, double onset_tension, double current_ambient_pressure) -{ - double onset_radius = 1.0 / (vpmb_config.gradient_of_imperm / (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) + 1.0 / crit_radius); - - - double A = current_ambient_pressure - vpmb_config.gradient_of_imperm + (2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma)) / onset_radius; - double B = 2.0 * (vpmb_config.skin_compression_gammaC - vpmb_config.surface_tension_gamma); - double C = onset_tension * pow(onset_radius, 3); - - double current_radius = solve_cubic(A, B, C); - - return onset_tension * onset_radius * onset_radius * onset_radius / (current_radius * current_radius * current_radius); -} - -// Calculates the crushing pressure in the given moment. Updates crushing_onset_tension and critical radius if needed -void calc_crushing_pressure(double pressure) -{ - int ci; - double gradient; - double gas_tension; - double n2_crushing_pressure, he_crushing_pressure; - double n2_inner_pressure, he_inner_pressure; - - for (ci = 0; ci < 16; ++ci) { - gas_tension = tissue_n2_sat[ci] + tissue_he_sat[ci] + vpmb_config.other_gases_pressure; - gradient = pressure - gas_tension; - - if (gradient <= vpmb_config.gradient_of_imperm) { // permeable situation - n2_crushing_pressure = he_crushing_pressure = gradient; - crushing_onset_tension[ci] = gas_tension; - } - else { // impermeable - if (max_ambient_pressure >= pressure) - return; - - n2_inner_pressure = calc_inner_pressure(get_crit_radius_N2(), crushing_onset_tension[ci], pressure); - he_inner_pressure = calc_inner_pressure(get_crit_radius_He(), crushing_onset_tension[ci], pressure); - - n2_crushing_pressure = pressure - n2_inner_pressure; - he_crushing_pressure = pressure - he_inner_pressure; - } - max_n2_crushing_pressure[ci] = MAX(max_n2_crushing_pressure[ci], n2_crushing_pressure); - max_he_crushing_pressure[ci] = MAX(max_he_crushing_pressure[ci], he_crushing_pressure); - } - max_ambient_pressure = MAX(pressure, max_ambient_pressure); -} - -/* add period_in_seconds at the given pressure and gas to the deco calculation */ -void add_segment(double pressure, const struct gasmix *gasmix, int period_in_seconds, int ccpo2, const struct dive *dive, int sac) -{ - (void) sac; - int ci; - struct gas_pressures pressures; - - fill_pressures(&pressures, pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE), - gasmix, (double) ccpo2 / 1000.0, dive->dc.divemode); - - if (buehlmann_config.gf_low_at_maxdepth && pressure > gf_low_pressure_this_dive) - gf_low_pressure_this_dive = pressure; - - for (ci = 0; ci < 16; ci++) { - double pn2_oversat = pressures.n2 - tissue_n2_sat[ci]; - double phe_oversat = pressures.he - tissue_he_sat[ci]; - double n2_f = n2_factor(period_in_seconds, ci); - double he_f = he_factor(period_in_seconds, ci); - double n2_satmult = pn2_oversat > 0 ? buehlmann_config.satmult : buehlmann_config.desatmult; - double he_satmult = phe_oversat > 0 ? buehlmann_config.satmult : buehlmann_config.desatmult; - - tissue_n2_sat[ci] += n2_satmult * pn2_oversat * n2_f; - tissue_he_sat[ci] += he_satmult * phe_oversat * he_f; - } - if(prefs.deco_mode == VPMB) - calc_crushing_pressure(pressure); - return; -} - -void dump_tissues() -{ - int ci; - printf("N2 tissues:"); - for (ci = 0; ci < 16; ci++) - printf(" %6.3e", tissue_n2_sat[ci]); - printf("\nHe tissues:"); - for (ci = 0; ci < 16; ci++) - printf(" %6.3e", tissue_he_sat[ci]); - printf("\n"); -} - -void clear_deco(double surface_pressure) -{ - int ci; - for (ci = 0; ci < 16; ci++) { - tissue_n2_sat[ci] = (surface_pressure - ((in_planner() && (prefs.deco_mode == VPMB)) ? WV_PRESSURE_SCHREINER : WV_PRESSURE)) * N2_IN_AIR / 1000; - tissue_he_sat[ci] = 0.0; - max_n2_crushing_pressure[ci] = 0.0; - max_he_crushing_pressure[ci] = 0.0; - n2_regen_radius[ci] = get_crit_radius_N2(); - he_regen_radius[ci] = get_crit_radius_He(); - } - gf_low_pressure_this_dive = surface_pressure; - if (!buehlmann_config.gf_low_at_maxdepth) - gf_low_pressure_this_dive += buehlmann_config.gf_low_position_min; - max_ambient_pressure = 0.0; -} - -void cache_deco_state(char **cached_datap) -{ - char *data = *cached_datap; - - if (!data) { - data = malloc(2 * TISSUE_ARRAY_SZ + sizeof(double) + sizeof(int)); - *cached_datap = data; - } - memcpy(data, tissue_n2_sat, TISSUE_ARRAY_SZ); - data += TISSUE_ARRAY_SZ; - memcpy(data, tissue_he_sat, TISSUE_ARRAY_SZ); - data += TISSUE_ARRAY_SZ; - memcpy(data, &gf_low_pressure_this_dive, sizeof(double)); - data += sizeof(double); - memcpy(data, &ci_pointing_to_guiding_tissue, sizeof(int)); -} - -void restore_deco_state(char *data) -{ - memcpy(tissue_n2_sat, data, TISSUE_ARRAY_SZ); - data += TISSUE_ARRAY_SZ; - memcpy(tissue_he_sat, data, TISSUE_ARRAY_SZ); - data += TISSUE_ARRAY_SZ; - memcpy(&gf_low_pressure_this_dive, data, sizeof(double)); - data += sizeof(double); - memcpy(&ci_pointing_to_guiding_tissue, data, sizeof(int)); -} - -int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth) -{ - int depth; - double pressure_delta; - - /* Avoid negative depths */ - pressure_delta = tissues_tolerance > surface_pressure ? tissues_tolerance - surface_pressure : 0.0; - - depth = rel_mbar_to_depth(pressure_delta * 1000, dive); - - if (!smooth) - depth = ceil(depth / DECO_STOPS_MULTIPLIER_MM) * DECO_STOPS_MULTIPLIER_MM; - - if (depth > 0 && depth < buehlmann_config.last_deco_stop_in_mtr * 1000) - depth = buehlmann_config.last_deco_stop_in_mtr * 1000; - - return depth; -} - -void set_gf(short gflow, short gfhigh, bool gf_low_at_maxdepth) -{ - if (gflow != -1) - buehlmann_config.gf_low = (double)gflow / 100.0; - if (gfhigh != -1) - buehlmann_config.gf_high = (double)gfhigh / 100.0; - buehlmann_config.gf_low_at_maxdepth = gf_low_at_maxdepth; -} diff --git a/subsurface-core/deco.h b/subsurface-core/deco.h deleted file mode 100644 index fd3b94a9f..000000000 --- a/subsurface-core/deco.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef DECO_H -#define DECO_H - -#ifdef __cplusplus -extern "C" { -#endif - -extern double tolerated_by_tissue[]; -extern double buehlmann_N2_t_halflife[]; -extern double tissue_inertgas_saturation[16]; -extern double buehlmann_inertgas_a[16], buehlmann_inertgas_b[16]; -extern double gf_low_pressure_this_dive; - -extern int deco_allowed_depth(double tissues_tolerance, double surface_pressure, struct dive *dive, bool smooth); - -#ifdef __cplusplus -} -#endif - -#endif // DECO_H diff --git a/subsurface-core/device.c b/subsurface-core/device.c deleted file mode 100644 index 6c4452f78..000000000 --- a/subsurface-core/device.c +++ /dev/null @@ -1,184 +0,0 @@ -#include -#include "dive.h" -#include "device.h" - -/* - * Good fake dive profiles are hard. - * - * "depthtime" is the integral of the dive depth over - * time ("area" of the dive profile). We want that - * area to match the average depth (avg_d*max_t). - * - * To do that, we generate a 6-point profile: - * - * (0, 0) - * (t1, max_d) - * (t2, max_d) - * (t3, d) - * (t4, d) - * (max_t, 0) - * - * with the same ascent/descent rates between the - * different depths. - * - * NOTE: avg_d, max_d and max_t are given constants. - * The rest we can/should play around with to get a - * good-looking profile. - * - * That six-point profile gives a total area of: - * - * (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) - * - * And the "same ascent/descent rates" requirement - * gives us (time per depth must be same): - * - * t1 / max_d = (t3-t2) / (max_d-d) - * t1 / max_d = (max_t-t4) / d - * - * We also obviously require: - * - * 0 <= t1 <= t2 <= t3 <= t4 <= max_t - * - * Let us call 'd_frac = d / max_d', and we get: - * - * Total area must match average depth-time: - * - * (max_d*max_t) - (max_d*t1) - (max_d-d)*(t4-t3) = avg_d*max_t - * max_d*(max_t-t1-(1-d_frac)*(t4-t3)) = avg_d*max_t - * max_t-t1-(1-d_frac)*(t4-t3) = avg_d*max_t/max_d - * t1+(1-d_frac)*(t4-t3) = max_t*(1-avg_d/max_d) - * - * and descent slope must match ascent slopes: - * - * t1 / max_d = (t3-t2) / (max_d*(1-d_frac)) - * t1 = (t3-t2)/(1-d_frac) - * - * and - * - * t1 / max_d = (max_t-t4) / (max_d*d_frac) - * t1 = (max_t-t4)/d_frac - * - * In general, we have more free variables than we have constraints, - * but we can aim for certain basics, like a good ascent slope. - */ -static int fill_samples(struct sample *s, int max_d, int avg_d, int max_t, double slope, double d_frac) -{ - double t_frac = max_t * (1 - avg_d / (double)max_d); - int t1 = max_d / slope; - int t4 = max_t - t1 * d_frac; - int t3 = t4 - (t_frac - t1) / (1 - d_frac); - int t2 = t3 - t1 * (1 - d_frac); - - if (t1 < 0 || t1 > t2 || t2 > t3 || t3 > t4 || t4 > max_t) - return 0; - - s[1].time.seconds = t1; - s[1].depth.mm = max_d; - s[2].time.seconds = t2; - s[2].depth.mm = max_d; - s[3].time.seconds = t3; - s[3].depth.mm = max_d * d_frac; - s[4].time.seconds = t4; - s[4].depth.mm = max_d * d_frac; - - return 1; -} - -/* we have no average depth; instead of making up a random average depth - * we should assume either a PADI rectangular profile (for short and/or - * shallow dives) or more reasonably a six point profile with a 3 minute - * safety stop at 5m */ -static void fill_samples_no_avg(struct sample *s, int max_d, int max_t, double slope) -{ - // shallow or short dives are just trapecoids based on the given slope - if (max_d < 10000 || max_t < 600) { - s[1].time.seconds = max_d / slope; - s[1].depth.mm = max_d; - s[2].time.seconds = max_t - max_d / slope; - s[2].depth.mm = max_d; - } else { - s[1].time.seconds = max_d / slope; - s[1].depth.mm = max_d; - s[2].time.seconds = max_t - max_d / slope - 180; - s[2].depth.mm = max_d; - s[3].time.seconds = max_t - 5000 / slope - 180; - s[3].depth.mm = 5000; - s[4].time.seconds = max_t - 5000 / slope; - s[4].depth.mm = 5000; - } -} - -struct divecomputer *fake_dc(struct divecomputer *dc, bool alloc) -{ - static struct sample fake_samples[6]; - static struct divecomputer fakedc; - struct sample *fake = fake_samples; - - fakedc = (*dc); - if (alloc) - fake = malloc(sizeof(fake_samples)); - - fakedc.sample = fake; - fakedc.samples = 6; - - /* The dive has no samples, so create a few fake ones */ - int max_t = dc->duration.seconds; - int max_d = dc->maxdepth.mm; - int avg_d = dc->meandepth.mm; - - memset(fake, 0, sizeof(fake_samples)); - fake[5].time.seconds = max_t; - if (!max_t || !max_d) - return &fakedc; - - /* - * We want to fake the profile so that the average - * depth ends up correct. However, in the absence of - * a reasonable average, let's just make something - * up. Note that 'avg_d == max_d' is _not_ a reasonable - * average. - * We explicitly treat avg_d == 0 differently */ - if (avg_d == 0) { - /* we try for a sane slope, but bow to the insanity of - * the user supplied data */ - fill_samples_no_avg(fake, max_d, max_t, MAX(2.0 * max_d / max_t, 5000.0 / 60)); - if (fake[3].time.seconds == 0) { // just a 4 point profile - fakedc.samples = 4; - fake[3].time.seconds = max_t; - } - return &fakedc; - } - if (avg_d < max_d / 10 || avg_d >= max_d) { - avg_d = (max_d + 10000) / 3; - if (avg_d > max_d) - avg_d = max_d * 2 / 3; - } - if (!avg_d) - avg_d = 1; - - /* - * Ok, first we try a basic profile with a specific ascent - * rate (5 meters per minute) and d_frac (1/3). - */ - if (fill_samples(fake, max_d, avg_d, max_t, 5000.0 / 60, 0.33)) - return &fakedc; - - /* - * Ok, assume that didn't work because we cannot make the - * average come out right because it was a quick deep dive - * followed by a much shallower region - */ - if (fill_samples(fake, max_d, avg_d, max_t, 10000.0 / 60, 0.10)) - return &fakedc; - - /* - * Uhhuh. That didn't work. We'd need to find a good combination that - * satisfies our constraints. Currently, we don't, we just give insane - * slopes. - */ - if (fill_samples(fake, max_d, avg_d, max_t, 10000.0, 0.01)) - return &fakedc; - - /* Even that didn't work? Give up, there's something wrong */ - return &fakedc; -} diff --git a/subsurface-core/device.h b/subsurface-core/device.h deleted file mode 100644 index 8a00b96d3..000000000 --- a/subsurface-core/device.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef DEVICE_H -#define DEVICE_H - -#ifdef __cplusplus -#include "dive.h" -extern "C" { -#endif - -extern struct divecomputer *fake_dc(struct divecomputer *dc, bool alloc); -extern void create_device_node(const char *model, uint32_t deviceid, const char *serial, const char *firmware, const char *nickname); -extern void call_for_each_dc(void *f, void (*callback)(void *, const char *, uint32_t, - const char *, const char *, const char *), bool select_only); - -#ifdef __cplusplus -} -#endif - -#endif // DEVICE_H diff --git a/subsurface-core/devicedetails.cpp b/subsurface-core/devicedetails.cpp deleted file mode 100644 index a917a4d0e..000000000 --- a/subsurface-core/devicedetails.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "devicedetails.h" - -gas::gas(unsigned char oxygen, unsigned char helium, unsigned char type, unsigned char depth) : - oxygen(oxygen), helium(helium), type(type), depth(depth) -{ -} - -setpoint::setpoint(unsigned char sp, unsigned char depth) : - sp(sp), depth(depth) -{ -} - -DeviceDetails::DeviceDetails(QObject *parent) : - QObject(parent), - data(0), - syncTime(false), - setPointFallback(0), - ccrMode(0), - calibrationGas(0), - diveMode(0), - decoType(0), - ppO2Max(0), - ppO2Min(0), - futureTTS(0), - gfLow(0), - gfHigh(0), - aGFLow(0), - aGFHigh(0), - aGFSelectable(0), - saturation(0), - desaturation(0), - lastDeco(0), - brightness(0), - units(0), - samplingRate(0), - salinity(0), - diveModeColor(0), - language(0), - dateFormat(0), - compassGain(0), - pressureSensorOffset(0), - flipScreen(0), - safetyStop(0), - maxDepth(0), - totalTime(0), - numberOfDives(0), - altitude(0), - personalSafety(0), - timeFormat(0), - lightEnabled(false), - light(0), - alarmTimeEnabled(false), - alarmTime(0), - alarmDepthEnabled(false), - alarmDepth(0), - leftButtonSensitivity(0), - rightButtonSensitivity(0), - bottomGasConsumption(0), - decoGasConsumption(0), - modWarning(false), - dynamicAscendRate(false), - graphicalSpeedIndicator(false), - alwaysShowppO2(false), - tempSensorOffset(0), - safetyStopLength(0), - safetyStopStartDepth(0), - safetyStopEndDepth(0), - safetyStopResetDepth(0) -{ -} diff --git a/subsurface-core/devicedetails.h b/subsurface-core/devicedetails.h deleted file mode 100644 index ff3009bc5..000000000 --- a/subsurface-core/devicedetails.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef DEVICEDETAILS_H -#define DEVICEDETAILS_H - -#include -#include -#include "libdivecomputer.h" - -struct gas { - unsigned char oxygen; - unsigned char helium; - unsigned char type; - unsigned char depth; - gas(unsigned char oxygen = 0, unsigned char helium = 0, unsigned char type = 0, unsigned char depth = 0); -}; - -struct setpoint { - unsigned char sp; - unsigned char depth; - setpoint(unsigned char sp = 0, unsigned char depth = 0); -}; - -class DeviceDetails : public QObject -{ - Q_OBJECT -public: - explicit DeviceDetails(QObject *parent = 0); - - device_data_t *data; - QString serialNo; - QString firmwareVersion; - QString customText; - QString model; - bool syncTime; - gas gas1; - gas gas2; - gas gas3; - gas gas4; - gas gas5; - gas dil1; - gas dil2; - gas dil3; - gas dil4; - gas dil5; - setpoint sp1; - setpoint sp2; - setpoint sp3; - setpoint sp4; - setpoint sp5; - bool setPointFallback; - int ccrMode; - int calibrationGas; - int diveMode; - int decoType; - int ppO2Max; - int ppO2Min; - int futureTTS; - int gfLow; - int gfHigh; - int aGFLow; - int aGFHigh; - int aGFSelectable; - int saturation; - int desaturation; - int lastDeco; - int brightness; - int units; - int samplingRate; - int salinity; - int diveModeColor; - int language; - int dateFormat; - int compassGain; - int pressureSensorOffset; - bool flipScreen; - bool safetyStop; - int maxDepth; - int totalTime; - int numberOfDives; - int altitude; - int personalSafety; - int timeFormat; - bool lightEnabled; - int light; - bool alarmTimeEnabled; - int alarmTime; - bool alarmDepthEnabled; - int alarmDepth; - int leftButtonSensitivity; - int rightButtonSensitivity; - int bottomGasConsumption; - int decoGasConsumption; - bool modWarning; - bool dynamicAscendRate; - bool graphicalSpeedIndicator; - bool alwaysShowppO2; - int tempSensorOffset; - unsigned safetyStopLength; - unsigned safetyStopStartDepth; - unsigned safetyStopEndDepth; - unsigned safetyStopResetDepth; -}; - - -#endif // DEVICEDETAILS_H diff --git a/subsurface-core/display.h b/subsurface-core/display.h deleted file mode 100644 index 9e3e1d159..000000000 --- a/subsurface-core/display.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef DISPLAY_H -#define DISPLAY_H - -#ifdef __cplusplus -extern "C" { -#endif - -struct membuffer; - -#define SCALE_SCREEN 1.0 -#define SCALE_PRINT (1.0 / get_screen_dpi()) - -extern double get_screen_dpi(void); - -/* Plot info with smoothing, velocity indication - * and one-, two- and three-minute minimums and maximums */ -struct plot_info { - int nr; - int maxtime; - int meandepth, maxdepth; - int minpressure, maxpressure; - int minhr, maxhr; - int mintemp, maxtemp; - enum {AIR, NITROX, TRIMIX, FREEDIVING} dive_type; - double endtempcoord; - double maxpp; - bool has_ndl; - struct plot_data *entry; -}; - -typedef enum { - SC_SCREEN, - SC_PRINT -} scale_mode_t; - -extern struct divecomputer *select_dc(struct dive *); - -extern unsigned int dc_number; - -extern unsigned int amount_selected; - -extern int is_default_dive_computer_device(const char *); -extern int is_default_dive_computer(const char *, const char *); - -typedef void (*device_callback_t)(const char *name, void *userdata); - -#define DC_TYPE_SERIAL 1 -#define DC_TYPE_UEMIS 2 -#define DC_TYPE_OTHER 3 - -int enumerate_devices(device_callback_t callback, void *userdata, int dc_type); - -extern const char *default_dive_computer_vendor; -extern const char *default_dive_computer_product; -extern const char *default_dive_computer_device; -extern int default_dive_computer_download_mode; -#define AMB_PERCENTAGE 50.0 - -#ifdef __cplusplus -} -#endif - -#endif // DISPLAY_H diff --git a/subsurface-core/dive.c b/subsurface-core/dive.c deleted file mode 100644 index ce730da28..000000000 --- a/subsurface-core/dive.c +++ /dev/null @@ -1,3561 +0,0 @@ -/* dive.c */ -/* maintains the internal dive list structure */ -#include -#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, unsigned int time, int type, int flags, int value, const char *name) -{ - int gas_index = -1; - struct event *ev, **p; - unsigned int size, len = strlen(name); - - size = sizeof(*ev) + len + 1; - ev = malloc(size); - if (!ev) - return NULL; - memset(ev, 0, size); - memcpy(ev->name, name, len); - ev->time.seconds = time; - ev->type = type; - ev->flags = flags; - ev->value = value; - - /* - * Expand the events into a sane format. Currently - * just gas switches - */ - switch (type) { - case SAMPLE_EVENT_GASCHANGE2: - /* High 16 bits are He percentage */ - ev->gas.mix.he.permille = (value >> 16) * 10; - - /* Extension to the GASCHANGE2 format: cylinder index in 'flags' */ - if (flags > 0 && flags <= MAX_CYLINDERS) - gas_index = flags-1; - /* Fallthrough */ - case SAMPLE_EVENT_GASCHANGE: - /* Low 16 bits are O2 percentage */ - ev->gas.mix.o2.permille = (value & 0xffff) * 10; - ev->gas.index = gas_index; - break; - } - - p = &dc->events; - - /* insert in the sorted list of events */ - while (*p && (*p)->time.seconds <= time) - p = &(*p)->next; - ev->next = *p; - *p = ev; - remember_event(name); - return ev; -} - -static int same_event(struct event *a, struct event *b) -{ - if (a->time.seconds != b->time.seconds) - return 0; - if (a->type != b->type) - return 0; - if (a->flags != b->flags) - return 0; - if (a->value != b->value) - return 0; - return !strcmp(a->name, b->name); -} - -void remove_event(struct event *event) -{ - struct event **ep = ¤t_dc->events; - while (ep && !same_event(*ep, event)) - ep = &(*ep)->next; - if (ep) { - /* we can't link directly with event->next - * because 'event' can be a copy from another - * dive (for instance the displayed_dive - * that we use on the interface to show things). */ - struct event *temp = (*ep)->next; - free(*ep); - *ep = temp; - } -} - -/* since the name is an array as part of the structure (how silly is that?) we - * have to actually remove the existing event and replace it with a new one. - * WARNING, WARNING... this may end up freeing event in case that event is indeed - * WARNING, WARNING... part of this divecomputer on this dive! */ -void update_event_name(struct dive *d, struct event *event, char *name) -{ - if (!d || !event) - return; - struct divecomputer *dc = get_dive_dc(d, dc_number); - if (!dc) - return; - struct event **removep = &dc->events; - struct event *remove; - while ((*removep)->next && !same_event(*removep, event)) - removep = &(*removep)->next; - if (!same_event(*removep, event)) - return; - remove = *removep; - *removep = (*removep)->next; - add_event(dc, event->time.seconds, event->type, event->flags, event->value, name); - free(remove); - invalidate_dive_cache(d); -} - -void add_extra_data(struct divecomputer *dc, const char *key, const char *value) -{ - struct extra_data **ed = &dc->extra_data; - - while (*ed) - ed = &(*ed)->next; - *ed = malloc(sizeof(struct extra_data)); - if (*ed) { - (*ed)->key = strdup(key); - (*ed)->value = strdup(value); - (*ed)->next = NULL; - } -} - -/* this returns a pointer to static variable - so use it right away after calling */ -struct gasmix *get_gasmix_from_event(struct event *ev) -{ - static struct gasmix dummy; - if (ev && event_is_gaschange(ev)) - return &ev->gas.mix; - - return &dummy; -} - -int get_pressure_units(int mb, const char **units) -{ - int pressure; - const char *unit; - struct units *units_p = get_units(); - - switch (units_p->pressure) { - case PASCAL: - pressure = mb * 100; - unit = translate("gettextFromC", "pascal"); - break; - case BAR: - default: - pressure = (mb + 500) / 1000; - unit = translate("gettextFromC", "bar"); - break; - case PSI: - pressure = mbar_to_PSI(mb); - unit = translate("gettextFromC", "psi"); - break; - } - if (units) - *units = unit; - return pressure; -} - -double get_temp_units(unsigned int mk, const char **units) -{ - double deg; - const char *unit; - struct units *units_p = get_units(); - - if (units_p->temperature == FAHRENHEIT) { - deg = mkelvin_to_F(mk); - unit = UTF8_DEGREE "F"; - } else { - deg = mkelvin_to_C(mk); - unit = UTF8_DEGREE "C"; - } - if (units) - *units = unit; - return deg; -} - -double get_volume_units(unsigned int ml, int *frac, const char **units) -{ - int decimals; - double vol; - const char *unit; - struct units *units_p = get_units(); - - switch (units_p->volume) { - case LITER: - default: - vol = ml / 1000.0; - unit = translate("gettextFromC", "ℓ"); - decimals = 1; - break; - case CUFT: - vol = ml_to_cuft(ml); - unit = translate("gettextFromC", "cuft"); - decimals = 2; - break; - } - if (frac) - *frac = decimals; - if (units) - *units = unit; - return vol; -} - -int units_to_sac(double volume) -{ - if (get_units()->volume == CUFT) - return rint(cuft_to_l(volume) * 1000.0); - else - return rint(volume * 1000); -} - -unsigned int units_to_depth(double depth) -{ - if (get_units()->length == METERS) - return rint(depth * 1000); - return feet_to_mm(depth); -} - -double get_depth_units(int mm, int *frac, const char **units) -{ - int decimals; - double d; - const char *unit; - struct units *units_p = get_units(); - - switch (units_p->length) { - case METERS: - default: - d = mm / 1000.0; - unit = translate("gettextFromC", "m"); - decimals = d < 20; - break; - case FEET: - d = mm_to_feet(mm); - unit = translate("gettextFromC", "ft"); - decimals = 0; - break; - } - if (frac) - *frac = decimals; - if (units) - *units = unit; - return d; -} - -double get_vertical_speed_units(unsigned int mms, int *frac, const char **units) -{ - double d; - const char *unit; - const struct units *units_p = get_units(); - const double time_factor = units_p->vertical_speed_time == MINUTES ? 60.0 : 1.0; - - switch (units_p->length) { - case METERS: - default: - d = mms / 1000.0 * time_factor; - if (units_p->vertical_speed_time == MINUTES) - unit = translate("gettextFromC", "m/min"); - else - unit = translate("gettextFromC", "m/s"); - break; - case FEET: - d = mm_to_feet(mms) * time_factor; - if (units_p->vertical_speed_time == MINUTES) - unit = translate("gettextFromC", "ft/min"); - else - unit = translate("gettextFromC", "ft/s"); - break; - } - if (frac) - *frac = d < 10; - if (units) - *units = unit; - return d; -} - -double get_weight_units(unsigned int grams, int *frac, const char **units) -{ - int decimals; - double value; - const char *unit; - struct units *units_p = get_units(); - - if (units_p->weight == LBS) { - value = grams_to_lbs(grams); - unit = translate("gettextFromC", "lbs"); - decimals = 0; - } else { - value = grams / 1000.0; - unit = translate("gettextFromC", "kg"); - decimals = 1; - } - if (frac) - *frac = decimals; - if (units) - *units = unit; - return value; -} - -bool has_hr_data(struct divecomputer *dc) -{ - int i; - struct sample *sample; - - if (!dc) - return false; - - sample = dc->sample; - for (i = 0; i < dc->samples; i++) - if (sample[i].heartbeat) - return true; - return false; -} - -struct dive *alloc_dive(void) -{ - struct dive *dive; - - dive = malloc(sizeof(*dive)); - if (!dive) - exit(1); - memset(dive, 0, sizeof(*dive)); - dive->id = dive_getUniqID(dive); - return dive; -} - -static void free_dc(struct divecomputer *dc); -static void free_pic(struct picture *picture); - -/* this is very different from the copy_divecomputer later in this file; - * this function actually makes full copies of the content */ -static void copy_dc(struct divecomputer *sdc, struct divecomputer *ddc) -{ - *ddc = *sdc; - ddc->model = copy_string(sdc->model); - copy_samples(sdc, ddc); - copy_events(sdc, ddc); -} - -/* copy an element in a list of pictures */ -static void copy_pl(struct picture *sp, struct picture *dp) -{ - *dp = *sp; - dp->filename = copy_string(sp->filename); - dp->hash = copy_string(sp->hash); -} - -/* copy an element in a list of tags */ -static void copy_tl(struct tag_entry *st, struct tag_entry *dt) -{ - dt->tag = malloc(sizeof(struct divetag)); - dt->tag->name = copy_string(st->tag->name); - dt->tag->source = copy_string(st->tag->source); -} - -/* Clear everything but the first element; - * this works for taglist, picturelist, even dive computers */ -#define STRUCTURED_LIST_FREE(_type, _start, _free) \ - { \ - _type *_ptr = _start; \ - while (_ptr) { \ - _type *_next = _ptr->next; \ - _free(_ptr); \ - _ptr = _next; \ - } \ - } - -#define STRUCTURED_LIST_COPY(_type, _first, _dest, _cpy) \ - { \ - _type *_sptr = _first; \ - _type **_dptr = &_dest; \ - while (_sptr) { \ - *_dptr = malloc(sizeof(_type)); \ - _cpy(_sptr, *_dptr); \ - _sptr = _sptr->next; \ - _dptr = &(*_dptr)->next; \ - } \ - *_dptr = 0; \ - } - -/* copy_dive makes duplicates of many components of a dive; - * in order not to leak memory, we need to free those . - * copy_dive doesn't play with the divetrip and forward/backward pointers - * so we can ignore those */ -void clear_dive(struct dive *d) -{ - if (!d) - return; - /* free the strings */ - free(d->buddy); - free(d->divemaster); - free(d->notes); - free(d->suit); - /* free tags, additional dive computers, and pictures */ - taglist_free(d->tag_list); - STRUCTURED_LIST_FREE(struct divecomputer, d->dc.next, free_dc); - STRUCTURED_LIST_FREE(struct picture, d->picture_list, free_pic); - for (int i = 0; i < MAX_CYLINDERS; i++) - free((void *)d->cylinder[i].type.description); - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) - free((void *)d->weightsystem[i].description); - memset(d, 0, sizeof(struct dive)); -} - -/* make a true copy that is independent of the source dive; - * all data structures are duplicated, so the copy can be modified without - * any impact on the source */ -void copy_dive(struct dive *s, struct dive *d) -{ - clear_dive(d); - /* simply copy things over, but then make actual copies of the - * relevant components that are referenced through pointers, - * so all the strings and the structured lists */ - *d = *s; - invalidate_dive_cache(d); - d->buddy = copy_string(s->buddy); - d->divemaster = copy_string(s->divemaster); - d->notes = copy_string(s->notes); - d->suit = copy_string(s->suit); - for (int i = 0; i < MAX_CYLINDERS; i++) - d->cylinder[i].type.description = copy_string(s->cylinder[i].type.description); - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) - d->weightsystem[i].description = copy_string(s->weightsystem[i].description); - STRUCTURED_LIST_COPY(struct picture, s->picture_list, d->picture_list, copy_pl); - STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl); - - // Copy the first dc explicitly, then the list of subsequent dc's - copy_dc(&s->dc, &d->dc); - STRUCTURED_LIST_COPY(struct divecomputer, s->dc.next, d->dc.next, copy_dc); -} - -/* make a clone of the source dive and clean out the source dive; - * this is specifically so we can create a dive in the displayed_dive and then - * add it to the divelist. - * Note the difference to copy_dive() / clean_dive() */ -struct dive *clone_dive(struct dive *s) -{ - struct dive *dive = alloc_dive(); - *dive = *s; // so all the pointers in dive point to the things s pointed to - memset(s, 0, sizeof(struct dive)); // and now the pointers in s are gone - return dive; -} - -#define CONDITIONAL_COPY_STRING(_component) \ - if (what._component) \ - d->_component = copy_string(s->_component) - -// copy elements, depending on bits in what that are set -void selective_copy_dive(struct dive *s, struct dive *d, struct dive_components what, bool clear) -{ - if (clear) - clear_dive(d); - CONDITIONAL_COPY_STRING(notes); - CONDITIONAL_COPY_STRING(divemaster); - CONDITIONAL_COPY_STRING(buddy); - CONDITIONAL_COPY_STRING(suit); - if (what.rating) - d->rating = s->rating; - if (what.visibility) - d->visibility = s->visibility; - if (what.divesite) - d->dive_site_uuid = s->dive_site_uuid; - if (what.tags) - STRUCTURED_LIST_COPY(struct tag_entry, s->tag_list, d->tag_list, copy_tl); - if (what.cylinders) - copy_cylinders(s, d, false); - if (what.weights) - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) { - free((void *)d->weightsystem[i].description); - d->weightsystem[i] = s->weightsystem[i]; - d->weightsystem[i].description = copy_string(s->weightsystem[i].description); - } -} -#undef CONDITIONAL_COPY_STRING - -struct event *clone_event(const struct event *src_ev) -{ - struct event *ev; - if (!src_ev) - return NULL; - - size_t size = sizeof(*src_ev) + strlen(src_ev->name) + 1; - ev = (struct event*) malloc(size); - if (!ev) - exit(1); - memcpy(ev, src_ev, size); - ev->next = NULL; - - return ev; -} - -/* copies all events in this dive computer */ -void copy_events(struct divecomputer *s, struct divecomputer *d) -{ - struct event *ev, **pev; - if (!s || !d) - return; - ev = s->events; - pev = &d->events; - while (ev != NULL) { - struct event *new_ev = clone_event(ev); - *pev = new_ev; - pev = &new_ev->next; - ev = ev->next; - } - *pev = NULL; -} - -int nr_cylinders(struct dive *dive) -{ - int nr; - - for (nr = MAX_CYLINDERS; nr; --nr) { - cylinder_t *cylinder = dive->cylinder + nr - 1; - if (!cylinder_nodata(cylinder)) - break; - } - return nr; -} - -int nr_weightsystems(struct dive *dive) -{ - int nr; - - for (nr = MAX_WEIGHTSYSTEMS; nr; --nr) { - weightsystem_t *ws = dive->weightsystem + nr - 1; - if (!weightsystem_none(ws)) - break; - } - return nr; -} - -/* copy the equipment data part of the cylinders */ -void copy_cylinders(struct dive *s, struct dive *d, bool used_only) -{ - int i,j; - if (!s || !d) - return; - for (i = 0; i < MAX_CYLINDERS; i++) { - free((void *)d->cylinder[i].type.description); - memset(&d->cylinder[i], 0, sizeof(cylinder_t)); - } - for (i = j = 0; i < MAX_CYLINDERS; i++) { - if (!used_only || is_cylinder_used(s, i)) { - d->cylinder[j].type = s->cylinder[i].type; - d->cylinder[j].type.description = copy_string(s->cylinder[i].type.description); - d->cylinder[j].gasmix = s->cylinder[i].gasmix; - d->cylinder[j].depth = s->cylinder[i].depth; - d->cylinder[j].cylinder_use = s->cylinder[i].cylinder_use; - d->cylinder[j].manually_added = true; - j++; - } - } -} - -int cylinderuse_from_text(const char *text) -{ - for (enum cylinderuse i = 0; i < NUM_GAS_USE; i++) { - if (same_string(text, cylinderuse_text[i]) || same_string(text, translate("gettextFromC", cylinderuse_text[i]))) - return i; - } - return -1; -} - -void copy_samples(struct divecomputer *s, struct divecomputer *d) -{ - /* instead of carefully copying them one by one and calling add_sample - * over and over again, let's just copy the whole blob */ - if (!s || !d) - return; - int nr = s->samples; - d->samples = nr; - d->alloc_samples = nr; - // We expect to be able to read the memory in the other end of the pointer - // if its a valid pointer, so don't expect malloc() to return NULL for - // zero-sized malloc, do it ourselves. - d->sample = NULL; - - if(!nr) - return; - - d->sample = malloc(nr * sizeof(struct sample)); - if (d->sample) - memcpy(d->sample, s->sample, nr * sizeof(struct sample)); -} - -struct sample *prepare_sample(struct divecomputer *dc) -{ - if (dc) { - int nr = dc->samples; - int alloc_samples = dc->alloc_samples; - struct sample *sample; - if (nr >= alloc_samples) { - struct sample *newsamples; - - alloc_samples = (alloc_samples * 3) / 2 + 10; - newsamples = realloc(dc->sample, alloc_samples * sizeof(struct sample)); - if (!newsamples) - return NULL; - dc->alloc_samples = alloc_samples; - dc->sample = newsamples; - } - sample = dc->sample + nr; - memset(sample, 0, sizeof(*sample)); - return sample; - } - return NULL; -} - -void finish_sample(struct divecomputer *dc) -{ - dc->samples++; -} - -/* - * So when we re-calculate maxdepth and meandepth, we will - * not override the old numbers if they are close to the - * new ones. - * - * Why? Because a dive computer may well actually track the - * max depth and mean depth at finer granularity than the - * samples it stores. So it's possible that the max and mean - * have been reported more correctly originally. - * - * Only if the values calculated from the samples are clearly - * different do we override the normal depth values. - * - * This considers 1m to be "clearly different". That's - * a totally random number. - */ -static void update_depth(depth_t *depth, int new) -{ - if (new) { - int old = depth->mm; - - if (abs(old - new) > 1000) - depth->mm = new; - } -} - -static void update_temperature(temperature_t *temperature, int new) -{ - if (new) { - int old = temperature->mkelvin; - - if (abs(old - new) > 1000) - temperature->mkelvin = new; - } -} - -/* - * Calculate how long we were actually under water, and the average - * depth while under water. - * - * This ignores any surface time in the middle of the dive. - */ -void fixup_dc_duration(struct divecomputer *dc) -{ - int duration, i; - int lasttime, lastdepth, depthtime; - - duration = 0; - lasttime = 0; - lastdepth = 0; - depthtime = 0; - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; - int depth = sample->depth.mm; - - /* We ignore segments at the surface */ - if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { - duration += time - lasttime; - depthtime += (time - lasttime) * (depth + lastdepth) / 2; - } - lastdepth = depth; - lasttime = time; - } - if (duration) { - dc->duration.seconds = duration; - dc->meandepth.mm = (depthtime + duration / 2) / duration; - } -} - -void per_cylinder_mean_depth(struct dive *dive, struct divecomputer *dc, int *mean, int *duration) -{ - int i; - int depthtime[MAX_CYLINDERS] = { 0, }; - uint32_t lasttime = 0; - int lastdepth = 0; - int idx = 0; - - for (i = 0; i < MAX_CYLINDERS; i++) - mean[i] = duration[i] = 0; - if (!dc) - return; - struct event *ev = get_next_event(dc->events, "gaschange"); - if (!ev || (dc && dc->sample && ev->time.seconds == dc->sample[0].time.seconds && get_next_event(ev->next, "gaschange") == NULL)) { - // we have either no gas change or only one gas change and that's setting an explicit first cylinder - mean[explicit_first_cylinder(dive, dc)] = dc->meandepth.mm; - duration[explicit_first_cylinder(dive, dc)] = dc->duration.seconds; - - if (dc->divemode == CCR) { - // Do the same for the O2 cylinder - int o2_cyl = get_cylinder_idx_by_use(dive, OXYGEN); - if (o2_cyl < 0) - return; - mean[o2_cyl] = dc->meandepth.mm; - duration[o2_cyl] = dc->duration.seconds; - } - return; - } - if (!dc->samples) - dc = fake_dc(dc, false); - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - uint32_t time = sample->time.seconds; - int depth = sample->depth.mm; - - /* Make sure to move the event past 'lasttime' */ - while (ev && lasttime >= ev->time.seconds) { - idx = get_cylinder_index(dive, ev); - ev = get_next_event(ev->next, "gaschange"); - } - - /* Do we need to fake a midway sample at an event? */ - if (ev && time > ev->time.seconds) { - int newtime = ev->time.seconds; - int newdepth = interpolate(lastdepth, depth, newtime - lasttime, time - lasttime); - - time = newtime; - depth = newdepth; - i--; - } - /* We ignore segments at the surface */ - if (depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) { - duration[idx] += time - lasttime; - depthtime[idx] += (time - lasttime) * (depth + lastdepth) / 2; - } - lastdepth = depth; - lasttime = time; - } - for (i = 0; i < MAX_CYLINDERS; i++) { - if (duration[i]) - mean[i] = (depthtime[i] + duration[i] / 2) / duration[i]; - } -} - -static void update_min_max_temperatures(struct dive *dive, temperature_t temperature) -{ - if (temperature.mkelvin) { - if (!dive->maxtemp.mkelvin || temperature.mkelvin > dive->maxtemp.mkelvin) - dive->maxtemp = temperature; - if (!dive->mintemp.mkelvin || temperature.mkelvin < dive->mintemp.mkelvin) - dive->mintemp = temperature; - } -} - -int gas_volume(cylinder_t *cyl, pressure_t p) -{ - double bar = p.mbar / 1000.0; - double z_factor = gas_compressibility_factor(&cyl->gasmix, bar); - return cyl->type.size.mliter * bar_to_atm(bar) / z_factor; -} - -/* - * If the cylinder tank pressures are within half a bar - * (about 8 PSI) of the sample pressures, we consider it - * to be a rounding error, and throw them away as redundant. - */ -static int same_rounded_pressure(pressure_t a, pressure_t b) -{ - return abs(a.mbar - b.mbar) <= 500; -} - -/* Some dive computers (Cobalt) don't start the dive with cylinder 0 but explicitly - * tell us what the first gas is with a gas change event in the first sample. - * Sneakily we'll use a return value of 0 (or FALSE) when there is no explicit - * first cylinder - in which case cylinder 0 is indeed the first cylinder */ -int explicit_first_cylinder(struct dive *dive, struct divecomputer *dc) -{ - if (dc) { - struct event *ev = get_next_event(dc->events, "gaschange"); - if (ev && dc->sample && ev->time.seconds == dc->sample[0].time.seconds) - return get_cylinder_index(dive, ev); - else if (dc->divemode == CCR) - return MAX(get_cylinder_idx_by_use(dive, DILUENT), 0); - } - return 0; -} - -/* this gets called when the dive mode has changed (so OC vs. CC) - * there are two places we might have setpoints... events or in the samples - */ -void update_setpoint_events(struct divecomputer *dc) -{ - struct event *ev; - int new_setpoint = 0; - - if (dc->divemode == CCR) - new_setpoint = prefs.defaultsetpoint; - - if (dc->divemode == OC && - (same_string(dc->model, "Shearwater Predator") || - same_string(dc->model, "Shearwater Petrel") || - same_string(dc->model, "Shearwater Nerd"))) { - // make sure there's no setpoint in the samples - // this is an irreversible change - so switching a dive to OC - // by mistake when it's actually CCR is _bad_ - // So we make sure, this comes from a Predator or Petrel and we only remove - // pO2 values we would have computed anyway. - struct event *ev = get_next_event(dc->events, "gaschange"); - struct gasmix *gasmix = get_gasmix_from_event(ev); - struct event *next = get_next_event(ev, "gaschange"); - - for (int i = 0; i < dc->samples; i++) { - struct gas_pressures pressures; - if (next && dc->sample[i].time.seconds >= next->time.seconds) { - ev = next; - gasmix = get_gasmix_from_event(ev); - next = get_next_event(ev, "gaschange"); - } - fill_pressures(&pressures, calculate_depth_to_mbar(dc->sample[i].depth.mm, dc->surface_pressure, 0), gasmix ,0, OC); - if (abs(dc->sample[i].setpoint.mbar - (int)(1000 * pressures.o2)) <= 50) - dc->sample[i].setpoint.mbar = 0; - } - } - - // an "SP change" event at t=0 is currently our marker for OC vs CCR - // this will need to change to a saner setup, but for now we can just - // check if such an event is there and adjust it, or add that event - ev = get_next_event(dc->events, "SP change"); - if (ev && ev->time.seconds == 0) { - ev->value = new_setpoint; - } else { - if (!add_event(dc, 0, SAMPLE_EVENT_PO2, 0, new_setpoint, "SP change")) - fprintf(stderr, "Could not add setpoint change event\n"); - } -} - -void sanitize_gasmix(struct gasmix *mix) -{ - unsigned int o2, he; - - o2 = mix->o2.permille; - he = mix->he.permille; - - /* Regular air: leave empty */ - if (!he) { - if (!o2) - return; - /* 20.8% to 21% O2 is just air */ - if (gasmix_is_air(mix)) { - mix->o2.permille = 0; - return; - } - } - - /* Sane mix? */ - if (o2 <= 1000 && he <= 1000 && o2 + he <= 1000) - return; - fprintf(stderr, "Odd gasmix: %u O2 %u He\n", o2, he); - memset(mix, 0, sizeof(*mix)); -} - -/* - * See if the size/workingpressure looks like some standard cylinder - * size, eg "AL80". - * - * NOTE! We don't take compressibility into account when naming - * cylinders. That makes a certain amount of sense, since the - * cylinder name is independent from the gasmix, and different - * gasmixes have different compressibility. - */ -static void match_standard_cylinder(cylinder_type_t *type) -{ - double cuft, bar; - int psi, len; - const char *fmt; - char buffer[40], *p; - - /* Do we already have a cylinder description? */ - if (type->description) - return; - - bar = type->workingpressure.mbar / 1000.0; - cuft = ml_to_cuft(type->size.mliter); - cuft *= bar_to_atm(bar); - psi = to_PSI(type->workingpressure); - - switch (psi) { - case 2300 ... 2500: /* 2400 psi: LP tank */ - fmt = "LP%d"; - break; - case 2600 ... 2700: /* 2640 psi: LP+10% */ - fmt = "LP%d"; - break; - case 2900 ... 3100: /* 3000 psi: ALx tank */ - fmt = "AL%d"; - break; - case 3400 ... 3500: /* 3442 psi: HP tank */ - fmt = "HP%d"; - break; - case 3700 ... 3850: /* HP+10% */ - fmt = "HP%d+"; - break; - default: - return; - } - len = snprintf(buffer, sizeof(buffer), fmt, (int)rint(cuft)); - p = malloc(len + 1); - if (!p) - return; - memcpy(p, buffer, len + 1); - type->description = p; -} - - -/* - * There are two ways to give cylinder size information: - * - total amount of gas in cuft (depends on working pressure and physical size) - * - physical size - * - * where "physical size" is the one that actually matters and is sane. - * - * We internally use physical size only. But we save the workingpressure - * so that we can do the conversion if required. - */ -static void sanitize_cylinder_type(cylinder_type_t *type) -{ - double volume_of_air, volume; - - /* If we have no working pressure, it had *better* be just a physical size! */ - if (!type->workingpressure.mbar) - return; - - /* No size either? Nothing to go on */ - if (!type->size.mliter) - return; - - if (xml_parsing_units.volume == CUFT) { - double bar = type->workingpressure.mbar / 1000.0; - /* confusing - we don't really start from ml but millicuft !*/ - volume_of_air = cuft_to_l(type->size.mliter); - /* milliliters at 1 atm: not corrected for compressibility! */ - volume = volume_of_air / bar_to_atm(bar); - type->size.mliter = rint(volume); - } - - /* Ok, we have both size and pressure: try to match a description */ - match_standard_cylinder(type); -} - -static void sanitize_cylinder_info(struct dive *dive) -{ - int i; - - for (i = 0; i < MAX_CYLINDERS; i++) { - sanitize_gasmix(&dive->cylinder[i].gasmix); - sanitize_cylinder_type(&dive->cylinder[i].type); - } -} - -/* some events should never be thrown away */ -static bool is_potentially_redundant(struct event *event) -{ - if (!strcmp(event->name, "gaschange")) - return false; - if (!strcmp(event->name, "bookmark")) - return false; - if (!strcmp(event->name, "heading")) - return false; - return true; -} - -/* match just by name - we compare the details in the code that uses this helper */ -static struct event *find_previous_event(struct divecomputer *dc, struct event *event) -{ - struct event *ev = dc->events; - struct event *previous = NULL; - - if (same_string(event->name, "")) - return NULL; - while (ev && ev != event) { - if (same_string(ev->name, event->name)) - previous = ev; - ev = ev->next; - } - return previous; -} - -static void fixup_surface_pressure(struct dive *dive) -{ - struct divecomputer *dc; - int sum = 0, nr = 0; - - for_each_dc (dive, dc) { - if (dc->surface_pressure.mbar) { - sum += dc->surface_pressure.mbar; - nr++; - } - } - if (nr) - dive->surface_pressure.mbar = (sum + nr / 2) / nr; -} - -static void fixup_water_salinity(struct dive *dive) -{ - struct divecomputer *dc; - int sum = 0, nr = 0; - - for_each_dc (dive, dc) { - if (dc->salinity) { - if (dc->salinity < 500) - dc->salinity += FRESHWATER_SALINITY; - sum += dc->salinity; - nr++; - } - } - if (nr) - dive->salinity = (sum + nr / 2) / nr; -} - -static void fixup_meandepth(struct dive *dive) -{ - struct divecomputer *dc; - int sum = 0, nr = 0; - - for_each_dc (dive, dc) { - if (dc->meandepth.mm) { - sum += dc->meandepth.mm; - nr++; - } - } - if (nr) - dive->meandepth.mm = (sum + nr / 2) / nr; -} - -static void fixup_duration(struct dive *dive) -{ - struct divecomputer *dc; - unsigned int duration = 0; - - for_each_dc (dive, dc) - duration = MAX(duration, dc->duration.seconds); - - dive->duration.seconds = duration; -} - -/* - * What do the dive computers say the water temperature is? - * (not in the samples, but as dc property for dcs that support that) - */ -unsigned int dc_watertemp(struct divecomputer *dc) -{ - int sum = 0, nr = 0; - - do { - if (dc->watertemp.mkelvin) { - sum += dc->watertemp.mkelvin; - nr++; - } - } while ((dc = dc->next) != NULL); - if (!nr) - return 0; - return (sum + nr / 2) / nr; -} - -static void fixup_watertemp(struct dive *dive) -{ - if (!dive->watertemp.mkelvin) - dive->watertemp.mkelvin = dc_watertemp(&dive->dc); -} - -/* - * What do the dive computers say the air temperature is? - */ -unsigned int dc_airtemp(struct divecomputer *dc) -{ - int sum = 0, nr = 0; - - do { - if (dc->airtemp.mkelvin) { - sum += dc->airtemp.mkelvin; - nr++; - } - } while ((dc = dc->next) != NULL); - if (!nr) - return 0; - return (sum + nr / 2) / nr; -} - -static void fixup_cylinder_use(struct dive *dive) // for CCR dives, store the indices -{ // of the oxygen and diluent cylinders - dive->oxygen_cylinder_index = get_cylinder_idx_by_use(dive, OXYGEN); - dive->diluent_cylinder_index = get_cylinder_idx_by_use(dive, DILUENT); -} - -static void fixup_airtemp(struct dive *dive) -{ - if (!dive->airtemp.mkelvin) - dive->airtemp.mkelvin = dc_airtemp(&dive->dc); -} - -/* zero out the airtemp in the dive structure if it was just created by - * running fixup on the dive. keep it if it had been edited by hand */ -static void un_fixup_airtemp(struct dive *a) -{ - if (a->airtemp.mkelvin && a->airtemp.mkelvin == dc_airtemp(&a->dc)) - a->airtemp.mkelvin = 0; -} - -/* - * events are stored as a linked list, so the concept of - * "consecutive, identical events" is somewhat hard to - * implement correctly (especially given that on some dive - * computers events are asynchronous, so they can come in - * between what would be the non-constant sample rate). - * - * So what we do is that we throw away clearly redundant - * events that are fewer than 61 seconds apart (assuming there - * is no dive computer with a sample rate of more than 60 - * seconds... that would be pretty pointless to plot the - * profile with) - * - * We first only mark the events for deletion so that we - * still know when the previous event happened. - */ -static void fixup_dc_events(struct divecomputer *dc) -{ - struct event *event; - - event = dc->events; - while (event) { - struct event *prev; - if (is_potentially_redundant(event)) { - prev = find_previous_event(dc, event); - if (prev && prev->value == event->value && - prev->flags == event->flags && - event->time.seconds - prev->time.seconds < 61) - event->deleted = true; - } - event = event->next; - } - event = dc->events; - while (event) { - if (event->next && event->next->deleted) { - struct event *nextnext = event->next->next; - free(event->next); - event->next = nextnext; - } else { - event = event->next; - } - } -} - -static int interpolate_depth(struct divecomputer *dc, int idx, int lastdepth, int lasttime, int now) -{ - int i; - int nextdepth = lastdepth; - int nexttime = now; - - for (i = idx+1; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - if (sample->depth.mm < 0) - continue; - nextdepth = sample->depth.mm; - nexttime = sample->time.seconds; - break; - } - return interpolate(lastdepth, nextdepth, now-lasttime, nexttime-lasttime); -} - -static void fixup_dc_depths(struct dive *dive, struct divecomputer *dc) -{ - int i; - int maxdepth = dc->maxdepth.mm; - int lasttime = 0, lastdepth = 0; - - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; - int depth = sample->depth.mm; - - if (depth < 0) { - depth = interpolate_depth(dc, i, lastdepth, lasttime, time); - sample->depth.mm = depth; - } - - if (depth > SURFACE_THRESHOLD) { - if (depth > maxdepth) - maxdepth = depth; - } - - lastdepth = depth; - lasttime = time; - if (sample->cns > dive->maxcns) - dive->maxcns = sample->cns; - } - - update_depth(&dc->maxdepth, maxdepth); - if (maxdepth > dive->maxdepth.mm) - dive->maxdepth.mm = maxdepth; -} - -static void fixup_dc_temp(struct dive *dive, struct divecomputer *dc) -{ - int i; - int mintemp = 0, lasttemp = 0; - - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int temp = sample->temperature.mkelvin; - - if (temp) { - /* - * If we have consecutive identical - * temperature readings, throw away - * the redundant ones. - */ - if (lasttemp == temp) - sample->temperature.mkelvin = 0; - else - lasttemp = temp; - - if (!mintemp || temp < mintemp) - mintemp = temp; - } - - update_min_max_temperatures(dive, sample->temperature); - } - update_temperature(&dc->watertemp, mintemp); - update_min_max_temperatures(dive, dc->watertemp); -} - -/* - * Fix up cylinder sensor information in the samples if we have - * an explicit first cylinder - */ -static void fixup_dc_cylinder_index(struct dive *dive, struct divecomputer *dc) -{ - int i; - int first_cylinder = explicit_first_cylinder(dive, dc); - - if (!first_cylinder) - return; - - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - - if (sample->sensor == 0) - sample->sensor = first_cylinder; - } -} - -/* - * Simplify dc pressure information: - * (a) Remove redundant pressure information - * (b) Remove linearly interpolated pressure data - */ -static void simplify_dc_pressures(struct dive *dive, struct divecomputer *dc) -{ - int i, j; - int lastindex = -1; - int lastpressure = 0, lasto2pressure = 0; - int pressure_delta[MAX_CYLINDERS] = { INT_MAX, }; - - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - int pressure = sample->cylinderpressure.mbar; - int o2_pressure = sample->o2cylinderpressure.mbar; - int index; - - index = sample->sensor; - - if (index == lastindex) { - /* Remove duplicate redundant pressure information */ - if (pressure == lastpressure) - sample->cylinderpressure.mbar = 0; - if (o2_pressure == lasto2pressure) - sample->o2cylinderpressure.mbar = 0; - /* check for simply linear data in the samples - +INT_MAX means uninitialized, -INT_MAX means not linear */ - if (pressure_delta[index] != -INT_MAX && lastpressure) { - if (pressure_delta[index] == INT_MAX) { - pressure_delta[index] = abs(pressure - lastpressure); - } else { - int cur_delta = abs(pressure - lastpressure); - if (cur_delta && abs(cur_delta - pressure_delta[index]) > 150) { - /* ok the samples aren't just a linearisation - * between start and end */ - pressure_delta[index] = -INT_MAX; - } - } - } - } - lastindex = index; - lastpressure = pressure; - lasto2pressure = o2_pressure; - } - - /* if all the samples for a cylinder have pressure data that - * is basically equidistant throw out the sample cylinder pressure - * information but make sure we still have a valid start and end - * pressure - * this happens when DivingLog decides to linearalize the - * pressure between beginning and end and for strange reasons - * decides to put that in the sample data as if it came from - * the dive computer; we don't want that (we'll visualize with - * constant SAC rate instead) - * WARNING WARNING - I have only seen this in single tank dives - * --- maybe I should try to create a multi tank dive and see what - * --- divinglog does there - but the code right now is only tested - * --- for the single tank case */ - for (j = 0; j < MAX_CYLINDERS; j++) { - if (abs(pressure_delta[j]) != INT_MAX) { - cylinder_t *cyl = dive->cylinder + j; - for (i = 0; i < dc->samples; i++) - if (dc->sample[i].sensor == j) - dc->sample[i].cylinderpressure.mbar = 0; - if (!cyl->start.mbar) - cyl->start.mbar = cyl->sample_start.mbar; - if (!cyl->end.mbar) - cyl->end.mbar = cyl->sample_end.mbar; - cyl->sample_start.mbar = 0; - cyl->sample_end.mbar = 0; - } - } -} - -/* FIXME! sensor -> cylinder mapping? */ -static void fixup_start_pressure(struct dive *dive, int idx, pressure_t p) -{ - if (idx >= 0 && idx < MAX_CYLINDERS) { - cylinder_t *cyl = dive->cylinder + idx; - if (p.mbar && !cyl->sample_start.mbar) - cyl->sample_start = p; - } -} - -static void fixup_end_pressure(struct dive *dive, int idx, pressure_t p) -{ - if (idx >= 0 && idx < MAX_CYLINDERS) { - cylinder_t *cyl = dive->cylinder + idx; - if (p.mbar && !cyl->sample_end.mbar) - cyl->sample_end = p; - } -} - -/* - * Check the cylinder pressure sample information and fill in the - * overall cylinder pressures from those. - * - * We ignore surface samples for tank pressure information. - * - * At the beginning of the dive, let the cylinder cool down - * if the diver starts off at the surface. And at the end - * of the dive, there may be surface pressures where the - * diver has already turned off the air supply (especially - * for computers like the Uemis Zurich that end up saving - * quite a bit of samples after the dive has ended). - */ -static void fixup_dive_pressures(struct dive *dive, struct divecomputer *dc) -{ - int i, o2index = -1; - - if (dive->dc.divemode == CCR) - o2index = get_cylinder_idx_by_use(dive, OXYGEN); - - /* Walk the samples from the beginning to find starting pressures.. */ - for (i = 0; i < dc->samples; i++) { - struct sample *sample = dc->sample + i; - - if (sample->depth.mm < SURFACE_THRESHOLD) - continue; - - fixup_start_pressure(dive, sample->sensor, sample->cylinderpressure); - fixup_start_pressure(dive, o2index, sample->o2cylinderpressure); - } - - /* ..and from the end for ending pressures */ - for (i = dc->samples; --i >= 0; ) { - struct sample *sample = dc->sample + i; - - if (sample->depth.mm < SURFACE_THRESHOLD) - continue; - - fixup_end_pressure(dive, sample->sensor, sample->cylinderpressure); - fixup_end_pressure(dive, o2index, sample->o2cylinderpressure); - } - - simplify_dc_pressures(dive, dc); -} - -static void fixup_dive_dc(struct dive *dive, struct divecomputer *dc) -{ - int i; - - /* Add device information to table */ - if (dc->deviceid && (dc->serial || dc->fw_version)) - create_device_node(dc->model, dc->deviceid, dc->serial, dc->fw_version, ""); - - /* Fixup duration and mean depth */ - fixup_dc_duration(dc); - - /* Fix up sample depth data */ - fixup_dc_depths(dive, dc); - - /* Fix up dive temperatures based on dive computer samples */ - fixup_dc_temp(dive, dc); - - /* Fix up cylinder sensor data */ - fixup_dc_cylinder_index(dive, dc); - - /* Fix up cylinder pressures based on DC info */ - fixup_dive_pressures(dive, dc); - - fixup_dc_events(dc); -} - -struct dive *fixup_dive(struct dive *dive) -{ - int i; - struct divecomputer *dc; - - sanitize_cylinder_info(dive); - dive->maxcns = dive->cns; - - /* - * Use the dive's temperatures for minimum and maximum in case - * we do not have temperatures recorded by DC. - */ - - update_min_max_temperatures(dive, dive->watertemp); - - for_each_dc (dive, dc) - fixup_dive_dc(dive, dc); - - fixup_water_salinity(dive); - fixup_surface_pressure(dive); - fixup_meandepth(dive); - fixup_duration(dive); - fixup_watertemp(dive); - fixup_airtemp(dive); - fixup_cylinder_use(dive); // store indices for CCR oxygen and diluent cylinders - for (i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = dive->cylinder + i; - add_cylinder_description(&cyl->type); - if (same_rounded_pressure(cyl->sample_start, cyl->start)) - cyl->start.mbar = 0; - if (same_rounded_pressure(cyl->sample_end, cyl->end)) - cyl->end.mbar = 0; - } - update_cylinder_related_info(dive); - for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) { - weightsystem_t *ws = dive->weightsystem + i; - add_weightsystem_description(ws); - } - /* we should always have a uniq ID as that gets assigned during alloc_dive(), - * but we want to make sure... */ - if (!dive->id) - dive->id = dive_getUniqID(dive); - - return dive; -} - -/* Don't pick a zero for MERGE_MIN() */ -#define MERGE_MAX(res, a, b, n) res->n = MAX(a->n, b->n) -#define MERGE_MIN(res, a, b, n) res->n = (a->n) ? (b->n) ? MIN(a->n, b->n) : (a->n) : (b->n) -#define MERGE_TXT(res, a, b, n) res->n = merge_text(a->n, b->n) -#define MERGE_NONZERO(res, a, b, n) res->n = a->n ? a->n : b->n - -struct sample *add_sample(struct sample *sample, int time, struct divecomputer *dc) -{ - struct sample *p = prepare_sample(dc); - - if (p) { - *p = *sample; - p->time.seconds = time; - finish_sample(dc); - } - return p; -} - -/* - * This is like add_sample(), but if the distance from the last sample - * is excessive, we add two surface samples in between. - * - * This is so that if you merge two non-overlapping dives, we make sure - * that the time in between the dives is at the surface, not some "last - * sample that happened to be at a depth of 1.2m". - */ -static void merge_one_sample(struct sample *sample, int time, struct divecomputer *dc) -{ - int last = dc->samples - 1; - if (last >= 0) { - static struct sample surface; - struct sample *prev = dc->sample + last; - int last_time = prev->time.seconds; - int last_depth = prev->depth.mm; - - /* - * Only do surface events if the samples are more than - * a minute apart, and shallower than 5m - */ - if (time > last_time + 60 && last_depth < 5000) { - add_sample(&surface, last_time + 20, dc); - add_sample(&surface, time - 20, dc); - } - } - add_sample(sample, time, dc); -} - - -/* - * Merge samples. Dive 'a' is "offset" seconds before Dive 'b' - */ -static void merge_samples(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int offset) -{ - int asamples = a->samples; - int bsamples = b->samples; - struct sample *as = a->sample; - struct sample *bs = b->sample; - - /* - * We want a positive sample offset, so that sample - * times are always positive. So if the samples for - * 'b' are before the samples for 'a' (so the offset - * is negative), we switch a and b around, and use - * the reverse offset. - */ - if (offset < 0) { - offset = -offset; - asamples = bsamples; - bsamples = a->samples; - as = bs; - bs = a->sample; - } - - for (;;) { - int at, bt; - struct sample sample; - - if (!res) - return; - - at = asamples ? as->time.seconds : -1; - bt = bsamples ? bs->time.seconds + offset : -1; - - /* No samples? All done! */ - if (at < 0 && bt < 0) - return; - - /* Only samples from a? */ - if (bt < 0) { - add_sample_a: - merge_one_sample(as, at, res); - as++; - asamples--; - continue; - } - - /* Only samples from b? */ - if (at < 0) { - add_sample_b: - merge_one_sample(bs, bt, res); - bs++; - bsamples--; - continue; - } - - if (at < bt) - goto add_sample_a; - if (at > bt) - goto add_sample_b; - - /* same-time sample: add a merged sample. Take the non-zero ones */ - sample = *bs; - if (as->depth.mm) - sample.depth = as->depth; - if (as->temperature.mkelvin) - sample.temperature = as->temperature; - if (as->cylinderpressure.mbar) - sample.cylinderpressure = as->cylinderpressure; - if (as->sensor) - sample.sensor = as->sensor; - if (as->cns) - sample.cns = as->cns; - if (as->setpoint.mbar) - sample.setpoint = as->setpoint; - if (as->ndl.seconds) - sample.ndl = as->ndl; - if (as->stoptime.seconds) - sample.stoptime = as->stoptime; - if (as->stopdepth.mm) - sample.stopdepth = as->stopdepth; - if (as->in_deco) - sample.in_deco = true; - - merge_one_sample(&sample, at, res); - - as++; - bs++; - asamples--; - bsamples--; - } -} - -static char *merge_text(const char *a, const char *b) -{ - char *res; - if (!a && !b) - return NULL; - if (!a || !*a) - return copy_string(b); - if (!b || !*b) - return strdup(a); - if (!strcmp(a, b)) - return copy_string(a); - res = malloc(strlen(a) + strlen(b) + 32); - if (!res) - return (char *)a; - sprintf(res, translate("gettextFromC", "(%s) or (%s)"), a, b); - return res; -} - -#define SORT(a, b, field) \ - if (a->field != b->field) \ - return a->field < b->field ? -1 : 1 - -static int sort_event(struct event *a, struct event *b) -{ - SORT(a, b, time.seconds); - SORT(a, b, type); - SORT(a, b, flags); - SORT(a, b, value); - return strcmp(a->name, b->name); -} - -static void merge_events(struct divecomputer *res, struct divecomputer *src1, struct divecomputer *src2, int offset) -{ - struct event *a, *b; - struct event **p = &res->events; - - /* Always use positive offsets */ - if (offset < 0) { - struct divecomputer *tmp; - - offset = -offset; - tmp = src1; - src1 = src2; - src2 = tmp; - } - - a = src1->events; - b = src2->events; - while (b) { - b->time.seconds += offset; - b = b->next; - } - b = src2->events; - - while (a || b) { - int s; - if (!b) { - *p = a; - break; - } - if (!a) { - *p = b; - break; - } - s = sort_event(a, b); - /* Pick b */ - if (s > 0) { - *p = b; - p = &b->next; - b = b->next; - continue; - } - /* Pick 'a' or neither */ - if (s < 0) { - *p = a; - p = &a->next; - } - a = a->next; - continue; - } -} - -/* Pick whichever has any info (if either). Prefer 'a' */ -static void merge_cylinder_type(cylinder_type_t *src, cylinder_type_t *dst) -{ - if (!dst->size.mliter) - dst->size.mliter = src->size.mliter; - if (!dst->workingpressure.mbar) - dst->workingpressure.mbar = src->workingpressure.mbar; - if (!dst->description) { - dst->description = src->description; - src->description = NULL; - } -} - -static void merge_cylinder_mix(struct gasmix *src, struct gasmix *dst) -{ - if (!dst->o2.permille) - *dst = *src; -} - -static void merge_cylinder_info(cylinder_t *src, cylinder_t *dst) -{ - merge_cylinder_type(&src->type, &dst->type); - merge_cylinder_mix(&src->gasmix, &dst->gasmix); - MERGE_MAX(dst, dst, src, start.mbar); - MERGE_MIN(dst, dst, src, end.mbar); -} - -static void merge_weightsystem_info(weightsystem_t *res, weightsystem_t *a, weightsystem_t *b) -{ - if (!a->weight.grams) - a = b; - *res = *a; -} - -/* get_cylinder_idx_by_use(): Find the index of the first cylinder with a particular CCR use type. - * The index returned corresponds to that of the first cylinder with a cylinder_use that - * equals the appropriate enum value [oxygen, diluent, bailout] given by cylinder_use_type. - * A negative number returned indicates that a match could not be found. - * Call parameters: dive = the dive being processed - * cylinder_use_type = an enum, one of {oxygen, diluent, bailout} */ -extern int get_cylinder_idx_by_use(struct dive *dive, enum cylinderuse cylinder_use_type) -{ - int cylinder_index; - for (cylinder_index = 0; cylinder_index < MAX_CYLINDERS; cylinder_index++) { - if (dive->cylinder[cylinder_index].cylinder_use == cylinder_use_type) - return cylinder_index; // return the index of the cylinder with that cylinder use type - } - return -1; // negative number means cylinder_use_type not found in list of cylinders -} - -int gasmix_distance(const struct gasmix *a, const struct gasmix *b) -{ - int a_o2 = get_o2(a), b_o2 = get_o2(b); - int a_he = get_he(a), b_he = get_he(b); - int delta_o2 = a_o2 - b_o2, delta_he = a_he - b_he; - - delta_he = delta_he * delta_he; - delta_o2 = delta_o2 * delta_o2; - return delta_he + delta_o2; -} - -/* fill_pressures(): Compute partial gas pressures in bar from gasmix and ambient pressures, possibly for OC or CCR, to be - * extended to PSCT. This function does the calculations of gas pressures applicable to a single point on the dive profile. - * The structure "pressures" is used to return calculated gas pressures to the calling software. - * Call parameters: po2 = po2 value applicable to the record in calling function - * amb_pressure = ambient pressure applicable to the record in calling function - * *pressures = structure for communicating o2 sensor values from and gas pressures to the calling function. - * *mix = structure containing cylinder gas mixture information. - * This function called by: calculate_gas_information_new() in profile.c; add_segment() in deco.c. - */ -extern void fill_pressures(struct gas_pressures *pressures, const double amb_pressure, const struct gasmix *mix, double po2, enum dive_comp_type divemode) -{ - if (po2) { // This is probably a CCR dive where pressures->o2 is defined - if (po2 >= amb_pressure) { - pressures->o2 = amb_pressure; - pressures->n2 = pressures->he = 0.0; - } else { - pressures->o2 = po2; - if (get_o2(mix) == 1000) { - pressures->he = pressures->n2 = 0; - } else { - pressures->he = (amb_pressure - pressures->o2) * (double)get_he(mix) / (1000 - get_o2(mix)); - pressures->n2 = amb_pressure - pressures->o2 - pressures->he; - } - } - } else { - if (divemode == PSCR) { /* The steady state approximation should be good enough */ - pressures->o2 = get_o2(mix) / 1000.0 * amb_pressure - (1.0 - get_o2(mix) / 1000.0) * prefs.o2consumption / (prefs.bottomsac * prefs.pscr_ratio / 1000.0); - if (pressures->o2 < 0) // He's dead, Jim. - pressures->o2 = 0; - if (get_o2(mix) != 1000) { - pressures->he = (amb_pressure - pressures->o2) * get_he(mix) / (1000.0 - get_o2(mix)); - pressures->n2 = (amb_pressure - pressures->o2) * (1000 - get_o2(mix) - get_he(mix)) / (1000.0 - get_o2(mix)); - } else { - pressures->he = pressures->n2 = 0; - } - } else { - // Open circuit dives: no gas pressure values available, they need to be calculated - pressures->o2 = get_o2(mix) / 1000.0 * amb_pressure; // These calculations are also used if the CCR calculation above.. - pressures->he = get_he(mix) / 1000.0 * amb_pressure; // ..returned a po2 of zero (i.e. o2 sensor data not resolvable) - pressures->n2 = (1000 - get_o2(mix) - get_he(mix)) / 1000.0 * amb_pressure; - } - } -} - -static int find_cylinder_match(cylinder_t *cyl, cylinder_t array[], unsigned int used) -{ - int i; - int best = -1, score = INT_MAX; - - if (cylinder_nodata(cyl)) - return -1; - for (i = 0; i < MAX_CYLINDERS; i++) { - const cylinder_t *match; - int distance; - - if (used & (1 << i)) - continue; - match = array + i; - distance = gasmix_distance(&cyl->gasmix, &match->gasmix); - if (distance >= score) - continue; - best = i; - score = distance; - } - return best; -} - -/* Force an initial gaschange event to the (old) gas #0 */ -static void add_initial_gaschange(struct dive *dive, struct divecomputer *dc) -{ - struct event *ev = get_next_event(dc->events, "gaschange"); - - if (ev && ev->time.seconds < 30) - return; - - /* Old starting gas mix */ - add_gas_switch_event(dive, dc, 0, 0); -} - -void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, int mapping[]) -{ - int i; - struct event *ev; - - /* Did the first gas get remapped? Add gas switch event */ - if (mapping[0] > 0) - add_initial_gaschange(dive, dc); - - /* Remap the sensor indexes */ - for (i = 0; i < dc->samples; i++) { - struct sample *s = dc->sample + i; - int sensor; - - if (!s->cylinderpressure.mbar) - continue; - sensor = mapping[s->sensor]; - if (sensor >= 0) - s->sensor = sensor; - } - - /* Remap the gas change indexes */ - for (ev = dc->events; ev; ev = ev->next) { - if (!event_is_gaschange(ev)) - continue; - if (ev->gas.index < 0) - continue; - ev->gas.index = mapping[ev->gas.index]; - } -} - -/* - * If the cylinder indexes change (due to merging dives or deleting - * cylinders in the middle), we need to change the indexes in the - * dive computer data for this dive. - * - * Also note that we assume that the initial cylinder is cylinder 0, - * so if that got renamed, we need to create a fake gas change event - */ -static void cylinder_renumber(struct dive *dive, int mapping[]) -{ - struct divecomputer *dc; - for_each_dc (dive, dc) - dc_cylinder_renumber(dive, dc, mapping); -} - -/* - * Merging cylinder information is non-trivial, because the two dive computers - * may have different ideas of what the different cylinder indexing is. - * - * Logic: take all the cylinder information from the preferred dive ('a'), and - * then try to match each of the cylinders in the other dive by the gasmix that - * is the best match and hasn't been used yet. - */ -static void merge_cylinders(struct dive *res, struct dive *a, struct dive *b) -{ - int i, renumber = 0; - int mapping[MAX_CYLINDERS]; - unsigned int used = 0; - - /* Copy the cylinder info raw from 'a' */ - memcpy(res->cylinder, a->cylinder, sizeof(res->cylinder)); - memset(a->cylinder, 0, sizeof(a->cylinder)); - - for (i = 0; i < MAX_CYLINDERS; i++) { - int j; - cylinder_t *cyl = b->cylinder + i; - - j = find_cylinder_match(cyl, res->cylinder, used); - mapping[i] = j; - if (j < 0) - continue; - used |= 1 << j; - merge_cylinder_info(cyl, res->cylinder + j); - - /* If that renumbered the cylinders, fix it up! */ - if (i != j) - renumber = 1; - } - if (renumber) - cylinder_renumber(b, mapping); -} - -static void merge_equipment(struct dive *res, struct dive *a, struct dive *b) -{ - int i; - - merge_cylinders(res, a, b); - for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) - merge_weightsystem_info(res->weightsystem + i, a->weightsystem + i, b->weightsystem + i); -} - -static void merge_airtemps(struct dive *res, struct dive *a, struct dive *b) -{ - un_fixup_airtemp(a); - un_fixup_airtemp(b); - MERGE_NONZERO(res, a, b, airtemp.mkelvin); -} - -/* - * When merging two dives, this picks the trip from one, and removes it - * from the other. - * - * The 'next' dive is not involved in the dive merging, but is the dive - * that will be the next dive after the merged dive. - */ -static void pick_trip(struct dive *res, struct dive *pick) -{ - tripflag_t tripflag = pick->tripflag; - dive_trip_t *trip = pick->divetrip; - - res->tripflag = tripflag; - add_dive_to_trip(res, trip); -} - -/* - * Pick a trip for a dive - */ -static void merge_trip(struct dive *res, struct dive *a, struct dive *b) -{ - dive_trip_t *atrip, *btrip; - - /* - * The larger tripflag is more relevant: we prefer - * take manually assigned trips over auto-generated - * ones. - */ - if (a->tripflag > b->tripflag) - goto pick_a; - - if (a->tripflag < b->tripflag) - goto pick_b; - - /* Otherwise, look at the trip data and pick the "better" one */ - atrip = a->divetrip; - btrip = b->divetrip; - if (!atrip) - goto pick_b; - if (!btrip) - goto pick_a; - if (!atrip->location) - goto pick_b; - if (!btrip->location) - goto pick_a; - if (!atrip->notes) - goto pick_b; - if (!btrip->notes) - goto pick_a; - - /* - * Ok, so both have location and notes. - * Pick the earlier one. - */ - if (a->when < b->when) - goto pick_a; - goto pick_b; - -pick_a: - b = a; -pick_b: - pick_trip(res, b); -} - -#if CURRENTLY_NOT_USED -/* - * Sample 's' is between samples 'a' and 'b'. It is 'offset' seconds before 'b'. - * - * If 's' and 'a' are at the same time, offset is 0, and b is NULL. - */ -static int compare_sample(struct sample *s, struct sample *a, struct sample *b, int offset) -{ - unsigned int depth = a->depth.mm; - int diff; - - if (offset) { - unsigned int interval = b->time.seconds - a->time.seconds; - unsigned int depth_a = a->depth.mm; - unsigned int depth_b = b->depth.mm; - - if (offset > interval) - return -1; - - /* pick the average depth, scaled by the offset from 'b' */ - depth = (depth_a * offset) + (depth_b * (interval - offset)); - depth /= interval; - } - diff = s->depth.mm - depth; - if (diff < 0) - diff = -diff; - /* cut off at one meter difference */ - if (diff > 1000) - diff = 1000; - return diff * diff; -} - -/* - * Calculate a "difference" in samples between the two dives, given - * the offset in seconds between them. Use this to find the best - * match of samples between two different dive computers. - */ -static unsigned long sample_difference(struct divecomputer *a, struct divecomputer *b, int offset) -{ - int asamples = a->samples; - int bsamples = b->samples; - struct sample *as = a->sample; - struct sample *bs = b->sample; - unsigned long error = 0; - int start = -1; - - if (!asamples || !bsamples) - return 0; - - /* - * skip the first sample - this way we know can always look at - * as/bs[-1] to look at the samples around it in the loop. - */ - as++; - bs++; - asamples--; - bsamples--; - - for (;;) { - int at, bt, diff; - - - /* If we run out of samples, punt */ - if (!asamples) - return INT_MAX; - if (!bsamples) - return INT_MAX; - - at = as->time.seconds; - bt = bs->time.seconds + offset; - - /* b hasn't started yet? Ignore it */ - if (bt < 0) { - bs++; - bsamples--; - continue; - } - - if (at < bt) { - diff = compare_sample(as, bs - 1, bs, bt - at); - as++; - asamples--; - } else if (at > bt) { - diff = compare_sample(bs, as - 1, as, at - bt); - bs++; - bsamples--; - } else { - diff = compare_sample(as, bs, NULL, 0); - as++; - bs++; - asamples--; - bsamples--; - } - - /* Invalid comparison point? */ - if (diff < 0) - continue; - - if (start < 0) - start = at; - - error += diff; - - if (at - start > 120) - break; - } - return error; -} - -/* - * Dive 'a' is 'offset' seconds before dive 'b' - * - * This is *not* because the dive computers clocks aren't in sync, - * it is because the dive computers may "start" the dive at different - * points in the dive, so the sample at time X in dive 'a' is the - * same as the sample at time X+offset in dive 'b'. - * - * For example, some dive computers take longer to "wake up" when - * they sense that you are under water (ie Uemis Zurich if it was off - * when the dive started). And other dive computers have different - * depths that they activate at, etc etc. - * - * If we cannot find a shared offset, don't try to merge. - */ -static int find_sample_offset(struct divecomputer *a, struct divecomputer *b) -{ - int offset, best; - unsigned long max; - - /* No samples? Merge at any time (0 offset) */ - if (!a->samples) - return 0; - if (!b->samples) - return 0; - - /* - * Common special-case: merging a dive that came from - * the same dive computer, so the samples are identical. - * Check this first, without wasting time trying to find - * some minimal offset case. - */ - best = 0; - max = sample_difference(a, b, 0); - if (!max) - return 0; - - /* - * Otherwise, look if we can find anything better within - * a thirty second window.. - */ - for (offset = -30; offset <= 30; offset++) { - unsigned long diff; - - diff = sample_difference(a, b, offset); - if (diff > max) - continue; - best = offset; - max = diff; - } - - return best; -} -#endif - -/* - * Are a and b "similar" values, when given a reasonable lower end expected - * difference? - * - * So for example, we'd expect different dive computers to give different - * max depth readings. You might have them on different arms, and they - * have different pressure sensors and possibly different ideas about - * water salinity etc. - * - * So have an expected minimum difference, but also allow a larger relative - * error value. - */ -static int similar(unsigned long a, unsigned long b, unsigned long expected) -{ - if (!a && !b) - return 1; - - if (a && b) { - unsigned long min, max, diff; - - min = a; - max = b; - if (a > b) { - min = b; - max = a; - } - diff = max - min; - - /* Smaller than expected difference? */ - if (diff < expected) - return 1; - /* Error less than 10% or the maximum */ - if (diff * 10 < max) - return 1; - } - return 0; -} - -/* - * Match two dive computer entries against each other, and - * tell if it's the same dive. Return 0 if "don't know", - * positive for "same dive" and negative for "definitely - * not the same dive" - */ -int match_one_dc(struct divecomputer *a, struct divecomputer *b) -{ - /* Not same model? Don't know if matching.. */ - if (!a->model || !b->model) - return 0; - if (strcasecmp(a->model, b->model)) - return 0; - - /* Different device ID's? Don't know */ - if (a->deviceid != b->deviceid) - return 0; - - /* Do we have dive IDs? */ - if (!a->diveid || !b->diveid) - return 0; - - /* - * If they have different dive ID's on the same - * dive computer, that's a definite "same or not" - */ - return a->diveid == b->diveid ? 1 : -1; -} - -/* - * Match every dive computer against each other to see if - * we have a matching dive. - * - * Return values: - * -1 for "is definitely *NOT* the same dive" - * 0 for "don't know" - * 1 for "is definitely the same dive" - */ -static int match_dc_dive(struct divecomputer *a, struct divecomputer *b) -{ - do { - struct divecomputer *tmp = b; - do { - int match = match_one_dc(a, tmp); - if (match) - return match; - tmp = tmp->next; - } while (tmp); - a = a->next; - } while (a); - return 0; -} - -static bool new_without_trip(struct dive *a) -{ - return a->downloaded && !a->divetrip; -} - -/* - * Do we want to automatically try to merge two dives that - * look like they are the same dive? - * - * This happens quite commonly because you download a dive - * that you already had, or perhaps because you maintained - * multiple dive logs and want to load them all together - * (possibly one of them was imported from another dive log - * application entirely). - * - * NOTE! We mainly look at the dive time, but it can differ - * between two dives due to a few issues: - * - * - rounding the dive date to the nearest minute in other dive - * applications - * - * - dive computers with "relative datestamps" (ie the dive - * computer doesn't actually record an absolute date at all, - * but instead at download-time synchronizes its internal - * time with real-time on the downloading computer) - * - * - using multiple dive computers with different real time on - * the same dive - * - * We do not merge dives that look radically different, and if - * the dates are *too* far off the user will have to join two - * dives together manually. But this tries to handle the sane - * cases. - */ -static int likely_same_dive(struct dive *a, struct dive *b) -{ - int match, fuzz = 20 * 60; - - /* don't merge manually added dives with anything */ - if (same_string(a->dc.model, "manually added dive") || - same_string(b->dc.model, "manually added dive")) - return 0; - - /* Don't try to merge dives with different trip information */ - if (a->divetrip != b->divetrip) { - /* - * Exception: if the dive is downloaded without any - * explicit trip information, we do want to merge it - * with existing old dives even if they have trips. - */ - if (!new_without_trip(a) && !new_without_trip(b)) - return 0; - } - - /* - * Do some basic sanity testing of the values we - * have filled in during 'fixup_dive()' - */ - if (!similar(a->maxdepth.mm, b->maxdepth.mm, 1000) || - (a->meandepth.mm && b->meandepth.mm && !similar(a->meandepth.mm, b->meandepth.mm, 1000)) || - !similar(a->duration.seconds, b->duration.seconds, 5 * 60)) - return 0; - - /* See if we can get an exact match on the dive computer */ - match = match_dc_dive(&a->dc, &b->dc); - if (match) - return match > 0; - - /* - * Allow a time difference due to dive computer time - * setting etc. Check if they overlap. - */ - fuzz = MAX(a->duration.seconds, b->duration.seconds) / 2; - if (fuzz < 60) - fuzz = 60; - - return ((a->when <= b->when + fuzz) && (a->when >= b->when - fuzz)); -} - -/* - * This could do a lot more merging. Right now it really only - * merges almost exact duplicates - something that happens easily - * with overlapping dive downloads. - */ -struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded) -{ - if (likely_same_dive(a, b)) - return merge_dives(a, b, 0, prefer_downloaded); - return NULL; -} - -void free_events(struct event *ev) -{ - while (ev) { - struct event *next = ev->next; - free(ev); - ev = next; - } -} - -static void free_dc(struct divecomputer *dc) -{ - free(dc->sample); - free((void *)dc->model); - free_events(dc->events); - free(dc); -} - -static void free_pic(struct picture *picture) -{ - if (picture) { - free(picture->filename); - free(picture); - } -} - -static int same_sample(struct sample *a, struct sample *b) -{ - if (a->time.seconds != b->time.seconds) - return 0; - if (a->depth.mm != b->depth.mm) - return 0; - if (a->temperature.mkelvin != b->temperature.mkelvin) - return 0; - if (a->cylinderpressure.mbar != b->cylinderpressure.mbar) - return 0; - return a->sensor == b->sensor; -} - -static int same_dc(struct divecomputer *a, struct divecomputer *b) -{ - int i; - struct event *eva, *evb; - - i = match_one_dc(a, b); - if (i) - return i > 0; - - if (a->when && b->when && a->when != b->when) - return 0; - if (a->samples != b->samples) - return 0; - for (i = 0; i < a->samples; i++) - if (!same_sample(a->sample + i, b->sample + i)) - return 0; - eva = a->events; - evb = b->events; - while (eva && evb) { - if (!same_event(eva, evb)) - return 0; - eva = eva->next; - evb = evb->next; - } - return eva == evb; -} - -static int might_be_same_device(struct divecomputer *a, struct divecomputer *b) -{ - /* No dive computer model? That matches anything */ - if (!a->model || !b->model) - return 1; - - /* Otherwise at least the model names have to match */ - if (strcasecmp(a->model, b->model)) - return 0; - - /* No device ID? Match */ - if (!a->deviceid || !b->deviceid) - return 1; - - return a->deviceid == b->deviceid; -} - -static void remove_redundant_dc(struct divecomputer *dc, int prefer_downloaded) -{ - do { - struct divecomputer **p = &dc->next; - - /* Check this dc against all the following ones.. */ - while (*p) { - struct divecomputer *check = *p; - if (same_dc(dc, check) || (prefer_downloaded && might_be_same_device(dc, check))) { - *p = check->next; - check->next = NULL; - free_dc(check); - continue; - } - p = &check->next; - } - - /* .. and then continue down the chain, but we */ - prefer_downloaded = 0; - dc = dc->next; - } while (dc); -} - -static void clear_dc(struct divecomputer *dc) -{ - memset(dc, 0, sizeof(*dc)); -} - -static struct divecomputer *find_matching_computer(struct divecomputer *match, struct divecomputer *list) -{ - struct divecomputer *p; - - while ((p = list) != NULL) { - list = list->next; - - if (might_be_same_device(match, p)) - break; - } - return p; -} - - -static void copy_dive_computer(struct divecomputer *res, struct divecomputer *a) -{ - *res = *a; - res->model = copy_string(a->model); - res->samples = res->alloc_samples = 0; - res->sample = NULL; - res->events = NULL; - res->next = NULL; -} - -/* - * Join dive computers with a specific time offset between - * them. - * - * Use the dive computer ID's (or names, if ID's are missing) - * to match them up. If we find a matching dive computer, we - * merge them. If not, we just take the data from 'a'. - */ -static void interleave_dive_computers(struct divecomputer *res, - struct divecomputer *a, struct divecomputer *b, int offset) -{ - do { - struct divecomputer *match; - - copy_dive_computer(res, a); - - match = find_matching_computer(a, b); - if (match) { - merge_events(res, a, match, offset); - merge_samples(res, a, match, offset); - /* Use the diveid of the later dive! */ - if (offset > 0) - res->diveid = match->diveid; - } else { - res->sample = a->sample; - res->samples = a->samples; - res->events = a->events; - a->sample = NULL; - a->samples = 0; - a->events = NULL; - } - a = a->next; - if (!a) - break; - res->next = calloc(1, sizeof(struct divecomputer)); - res = res->next; - } while (res); -} - - -/* - * Join dive computer information. - * - * If we have old-style dive computer information (no model - * name etc), we will prefer a new-style one and just throw - * away the old. We're assuming it's a re-download. - * - * Otherwise, we'll just try to keep all the information, - * unless the user has specified that they prefer the - * downloaded computer, in which case we'll aggressively - * try to throw out old information that *might* be from - * that one. - */ -static void join_dive_computers(struct divecomputer *res, struct divecomputer *a, struct divecomputer *b, int prefer_downloaded) -{ - struct divecomputer *tmp; - - if (a->model && !b->model) { - *res = *a; - clear_dc(a); - return; - } - if (b->model && !a->model) { - *res = *b; - clear_dc(b); - return; - } - - *res = *a; - clear_dc(a); - tmp = res; - while (tmp->next) - tmp = tmp->next; - - tmp->next = calloc(1, sizeof(*tmp)); - *tmp->next = *b; - clear_dc(b); - - remove_redundant_dc(res, prefer_downloaded); -} - -static bool tag_seen_before(struct tag_entry *start, struct tag_entry *before) -{ - while (start && start != before) { - if (same_string(start->tag->name, before->tag->name)) - return true; - start = start->next; - } - return false; -} - -/* remove duplicates and empty nodes */ -void taglist_cleanup(struct tag_entry **tag_list) -{ - struct tag_entry **tl = tag_list; - while (*tl) { - /* skip tags that are empty or that we have seen before */ - if (same_string((*tl)->tag->name, "") || tag_seen_before(*tag_list, *tl)) { - *tl = (*tl)->next; - continue; - } - tl = &(*tl)->next; - } -} - -int taglist_get_tagstring(struct tag_entry *tag_list, char *buffer, int len) -{ - int i = 0; - struct tag_entry *tmp; - tmp = tag_list; - memset(buffer, 0, len); - while (tmp != NULL) { - int newlength = strlen(tmp->tag->name); - if (i > 0) - newlength += 2; - if ((i + newlength) < len) { - if (i > 0) { - strcpy(buffer + i, ", "); - strcpy(buffer + i + 2, tmp->tag->name); - } else { - strcpy(buffer, tmp->tag->name); - } - } else { - return i; - } - i += newlength; - tmp = tmp->next; - } - return i; -} - -static inline void taglist_free_divetag(struct divetag *tag) -{ - if (tag->name != NULL) - free(tag->name); - if (tag->source != NULL) - free(tag->source); - free(tag); -} - -/* Add a tag to the tag_list, keep the list sorted */ -static struct divetag *taglist_add_divetag(struct tag_entry **tag_list, struct divetag *tag) -{ - struct tag_entry *next, *entry; - - while ((next = *tag_list) != NULL) { - int cmp = strcmp(next->tag->name, tag->name); - - /* Already have it? */ - if (!cmp) - return next->tag; - /* Is the entry larger? If so, insert here */ - if (cmp > 0) - break; - /* Continue traversing the list */ - tag_list = &next->next; - } - - /* Insert in front of it */ - entry = malloc(sizeof(struct tag_entry)); - entry->next = next; - entry->tag = tag; - *tag_list = entry; - return tag; -} - -struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag) -{ - size_t i = 0; - int is_default_tag = 0; - struct divetag *ret_tag, *new_tag; - const char *translation; - new_tag = malloc(sizeof(struct divetag)); - - for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++) { - if (strcmp(default_tags[i], tag) == 0) { - is_default_tag = 1; - break; - } - } - /* Only translate default tags */ - if (is_default_tag) { - translation = translate("gettextFromC", tag); - new_tag->name = malloc(strlen(translation) + 1); - memcpy(new_tag->name, translation, strlen(translation) + 1); - new_tag->source = malloc(strlen(tag) + 1); - memcpy(new_tag->source, tag, strlen(tag) + 1); - } else { - new_tag->source = NULL; - new_tag->name = malloc(strlen(tag) + 1); - memcpy(new_tag->name, tag, strlen(tag) + 1); - } - /* Try to insert new_tag into g_tag_list if we are not operating on it */ - if (tag_list != &g_tag_list) { - ret_tag = taglist_add_divetag(&g_tag_list, new_tag); - /* g_tag_list already contains new_tag, free the duplicate */ - if (ret_tag != new_tag) - taglist_free_divetag(new_tag); - ret_tag = taglist_add_divetag(tag_list, ret_tag); - } else { - ret_tag = taglist_add_divetag(tag_list, new_tag); - if (ret_tag != new_tag) - taglist_free_divetag(new_tag); - } - return ret_tag; -} - -void taglist_free(struct tag_entry *entry) -{ - STRUCTURED_LIST_FREE(struct tag_entry, entry, free) -} - -/* Merge src1 and src2, write to *dst */ -static void taglist_merge(struct tag_entry **dst, struct tag_entry *src1, struct tag_entry *src2) -{ - struct tag_entry *entry; - - for (entry = src1; entry; entry = entry->next) - taglist_add_divetag(dst, entry->tag); - for (entry = src2; entry; entry = entry->next) - taglist_add_divetag(dst, entry->tag); -} - -void taglist_init_global() -{ - size_t i; - - for (i = 0; i < sizeof(default_tags) / sizeof(char *); i++) - taglist_add_tag(&g_tag_list, default_tags[i]); -} - -bool taglist_contains(struct tag_entry *tag_list, const char *tag) -{ - while (tag_list) { - if (same_string(tag_list->tag->name, tag)) - return true; - tag_list = tag_list->next; - } - return false; -} - -// check if all tags in subtl are included in supertl (so subtl is a subset of supertl) -static bool taglist_contains_all(struct tag_entry *subtl, struct tag_entry *supertl) -{ - while (subtl) { - if (!taglist_contains(supertl, subtl->tag->name)) - return false; - subtl = subtl->next; - } - return true; -} - -struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list) -{ - struct tag_entry *added_list = NULL; - while (new_list) { - if (!taglist_contains(original_list, new_list->tag->name)) - taglist_add_tag(&added_list, new_list->tag->name); - new_list = new_list->next; - } - return added_list; -} - -void dump_taglist(const char *intro, struct tag_entry *tl) -{ - char *comma = ""; - fprintf(stderr, "%s", intro); - while(tl) { - fprintf(stderr, "%s %s", comma, tl->tag->name); - comma = ","; - tl = tl->next; - } - fprintf(stderr, "\n"); -} - -// if tl1 is both a subset and superset of tl2 they must be the same -bool taglist_equal(struct tag_entry *tl1, struct tag_entry *tl2) -{ - return taglist_contains_all(tl1, tl2) && taglist_contains_all(tl2, tl1); -} - -// count the dives where the tag list contains the given tag -int count_dives_with_tag(const char *tag) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(tag, "")) { - // count dives with no tags - if (d->tag_list == NULL) - counter++; - } else if (taglist_contains(d->tag_list, tag)) { - counter++; - } - } - return counter; -} - -extern bool string_sequence_contains(const char *string_sequence, const char *text); - -// count the dives where the person is included in the comma separated string sequences of buddies or divemasters -int count_dives_with_person(const char *person) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(person, "")) { - // solo dive - if (same_string(d->buddy, "") && same_string(d->divemaster, "")) - counter++; - } else if (string_sequence_contains(d->buddy, person) || string_sequence_contains(d->divemaster, person)) { - counter++; - } - } - return counter; -} - -// count the dives with exactly the location -int count_dives_with_location(const char *location) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(get_dive_location(d), location)) - counter++; - } - return counter; -} - -// count the dives with exactly the suit -int count_dives_with_suit(const char *suit) -{ - int i, counter = 0; - struct dive *d; - - for_each_dive (i, d) { - if (same_string(d->suit, suit)) - counter++; - } - return counter; -} - -/* - * Merging two dives can be subtle, because there's two different ways - * of merging: - * - * (a) two distinctly _different_ dives that have the same dive computer - * are merged into one longer dive, because the user asked for it - * in the divelist. - * - * Because this case is with the same dive computer, we *know* the - * two must have a different start time, and "offset" is the relative - * time difference between the two. - * - * (a) two different dive computers that we might want to merge into - * one single dive with multiple dive computers. - * - * This is the "try_to_merge()" case, which will have offset == 0, - * even if the dive times might be different. - */ -struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer_downloaded) -{ - struct dive *res = alloc_dive(); - struct dive *dl = NULL; - - if (offset) { - /* - * If "likely_same_dive()" returns true, that means that - * it is *not* the same dive computer, and we do not want - * to try to turn it into a single longer dive. So we'd - * join them as two separate dive computers at zero offset. - */ - if (likely_same_dive(a, b)) - offset = 0; - } else { - /* Aim for newly downloaded dives to be 'b' (keep old dive data first) */ - if (a->downloaded && !b->downloaded) { - struct dive *tmp = a; - a = b; - b = tmp; - } - if (prefer_downloaded && b->downloaded) - dl = b; - } - - res->when = dl ? dl->when : a->when; - res->selected = a->selected || b->selected; - merge_trip(res, a, b); - MERGE_TXT(res, a, b, notes); - MERGE_TXT(res, a, b, buddy); - MERGE_TXT(res, a, b, divemaster); - MERGE_MAX(res, a, b, rating); - MERGE_TXT(res, a, b, suit); - MERGE_MAX(res, a, b, number); - MERGE_NONZERO(res, a, b, cns); - MERGE_NONZERO(res, a, b, visibility); - MERGE_NONZERO(res, a, b, picture_list); - taglist_merge(&res->tag_list, a->tag_list, b->tag_list); - merge_equipment(res, a, b); - merge_airtemps(res, a, b); - if (dl) { - /* If we prefer downloaded, do those first, and get rid of "might be same" computers */ - join_dive_computers(&res->dc, &dl->dc, &a->dc, 1); - } else if (offset && might_be_same_device(&a->dc, &b->dc)) - interleave_dive_computers(&res->dc, &a->dc, &b->dc, offset); - else - join_dive_computers(&res->dc, &a->dc, &b->dc, 0); - res->dive_site_uuid = a->dive_site_uuid ?: b->dive_site_uuid; - fixup_dive(res); - return res; -} - -// copy_dive(), but retaining the new ID for the copied dive -static struct dive *create_new_copy(struct dive *from) -{ - struct dive *to = alloc_dive(); - int id; - - // alloc_dive() gave us a new ID, we just need to - // make sure it's not overwritten. - id = to->id; - copy_dive(from, to); - to->id = id; - return to; -} - -static void force_fixup_dive(struct dive *d) -{ - struct divecomputer *dc = &d->dc; - int old_temp = dc->watertemp.mkelvin; - int old_mintemp = d->mintemp.mkelvin; - int old_maxtemp = d->maxtemp.mkelvin; - duration_t old_duration = d->duration; - - d->maxdepth.mm = 0; - dc->maxdepth.mm = 0; - d->watertemp.mkelvin = 0; - dc->watertemp.mkelvin = 0; - d->duration.seconds = 0; - d->maxtemp.mkelvin = 0; - d->mintemp.mkelvin = 0; - - fixup_dive(d); - - if (!d->watertemp.mkelvin) - d->watertemp.mkelvin = old_temp; - - if (!dc->watertemp.mkelvin) - dc->watertemp.mkelvin = old_temp; - - if (!d->maxtemp.mkelvin) - d->maxtemp.mkelvin = old_maxtemp; - - if (!d->mintemp.mkelvin) - d->mintemp.mkelvin = old_mintemp; - - if (!d->duration.seconds) - d->duration = old_duration; - -} - -/* - * Split a dive that has a surface interval from samples 'a' to 'b' - * into two dives. - */ -static int split_dive_at(struct dive *dive, int a, int b) -{ - int i, nr; - uint32_t t; - struct dive *d1, *d2; - struct divecomputer *dc1, *dc2; - struct event *event, **evp; - - /* if we can't find the dive in the dive list, don't bother */ - if ((nr = get_divenr(dive)) < 0) - return 0; - - /* We're not trying to be efficient here.. */ - d1 = create_new_copy(dive); - d2 = create_new_copy(dive); - - /* now unselect the first first segment so we don't keep all - * dives selected by mistake. But do keep the second one selected - * so the algorithm keeps splitting the dive further */ - d1->selected = false; - - dc1 = &d1->dc; - dc2 = &d2->dc; - /* - * Cut off the samples of d1 at the beginning - * of the interval. - */ - dc1->samples = a; - - /* And get rid of the 'b' first samples of d2 */ - dc2->samples -= b; - memmove(dc2->sample, dc2->sample+b, dc2->samples * sizeof(struct sample)); - - /* - * This is where we cut off events from d1, - * and shift everything in d2 - */ - t = dc2->sample[0].time.seconds; - d2->when += t; - for (i = 0; i < dc2->samples; i++) - dc2->sample[i].time.seconds -= t; - - /* Remove the events past 't' from d1 */ - evp = &dc1->events; - while ((event = *evp) != NULL && event->time.seconds < t) - evp = &event->next; - *evp = NULL; - while (event) { - struct event *next = event->next; - free(event); - event = next; - } - - /* Remove the events before 't' from d2, and shift the rest */ - evp = &dc2->events; - while ((event = *evp) != NULL) { - if (event->time.seconds < t) { - *evp = event->next; - free(event); - } else { - event->time.seconds -= t; - } - } - - force_fixup_dive(d1); - force_fixup_dive(d2); - - if (dive->divetrip) { - d1->divetrip = d2->divetrip = 0; - add_dive_to_trip(d1, dive->divetrip); - add_dive_to_trip(d2, dive->divetrip); - } - - delete_single_dive(nr); - add_single_dive(nr, d1); - - /* - * Was the dive numbered? If it was the last dive, then we'll - * increment the dive number for the tail part that we split off. - * Otherwise the tail is unnumbered. - */ - if (d2->number) { - if (dive_table.nr == nr + 1) - d2->number++; - else - d2->number = 0; - } - add_single_dive(nr + 1, d2); - - mark_divelist_changed(true); - - return 1; -} - -/* in freedive mode we split for as little as 10 seconds on the surface, - * otherwise we use a minute */ -static bool should_split(struct divecomputer *dc, int t1, int t2) -{ - int threshold = dc->divemode == FREEDIVE ? 10 : 60; - - return t2 - t1 >= threshold; -} - -/* - * Try to split a dive into multiple dives at a surface interval point. - * - * NOTE! We will not split dives with multiple dive computers, and - * only split when there is at least one surface event that has - * non-surface events on both sides. - * - * In other words, this is a (simplified) reversal of the dive merging. - */ -int split_dive(struct dive *dive) -{ - int i; - int at_surface, surface_start; - struct divecomputer *dc; - - if (!dive || (dc = &dive->dc)->next) - return 0; - - surface_start = 0; - at_surface = 1; - for (i = 1; i < dc->samples; i++) { - struct sample *sample = dc->sample+i; - int surface_sample = sample->depth.mm < SURFACE_THRESHOLD; - - /* - * We care about the transition from and to depth 0, - * not about the depth staying similar. - */ - if (at_surface == surface_sample) - continue; - at_surface = surface_sample; - - // Did it become surface after having been non-surface? We found the start - if (at_surface) { - surface_start = i; - continue; - } - - // Going down again? We want at least a minute from - // the surface start. - if (!surface_start) - continue; - if (!should_split(dc, dc->sample[surface_start].time.seconds, sample[i - 1].time.seconds)) - continue; - - return split_dive_at(dive, surface_start, i-1); - } - return 0; -} - -/* - * "dc_maxtime()" is how much total time this dive computer - * has for this dive. Note that it can differ from "duration" - * if there are surface events in the middle. - * - * Still, we do ignore all but the last surface samples from the - * end, because some divecomputers just generate lots of them. - */ -static inline int dc_totaltime(const struct divecomputer *dc) -{ - int time = dc->duration.seconds; - int nr = dc->samples; - - while (nr--) { - struct sample *s = dc->sample + nr; - time = s->time.seconds; - if (s->depth.mm >= SURFACE_THRESHOLD) - break; - } - return time; -} - -/* - * The end of a dive is actually not trivial, because "duration" - * is not the duration until the end, but the time we spend under - * water, which can be very different if there are surface events - * during the dive. - * - * So walk the dive computers, looking for the longest actual - * time in the samples (and just default to the dive duration if - * there are no samples). - */ -static inline int dive_totaltime(const struct dive *dive) -{ - int time = dive->duration.seconds; - const struct divecomputer *dc; - - for_each_dc(dive, dc) { - int dc_time = dc_totaltime(dc); - if (dc_time > time) - time = dc_time; - } - return time; -} - -timestamp_t dive_endtime(const struct dive *dive) -{ - return dive->when + dive_totaltime(dive); -} - -struct dive *find_dive_including(timestamp_t when) -{ - int i; - struct dive *dive; - - /* binary search, anyone? Too lazy for now; - * also we always use the duration from the first divecomputer - * could this ever be a problem? */ - for_each_dive (i, dive) { - if (dive->when <= when && when <= dive_endtime(dive)) - return dive; - } - return NULL; -} - -bool time_during_dive_with_offset(struct dive *dive, timestamp_t when, timestamp_t offset) -{ - timestamp_t start = dive->when; - timestamp_t end = dive_endtime(dive); - return start - offset <= when && when <= end + offset; -} - -bool dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset) -{ - timestamp_t start = dive->when; - timestamp_t end = dive_endtime(dive); - return when - offset <= start && end <= when + offset; -} - -/* find the n-th dive that is part of a group of dives within the offset around 'when'. - * How is that for a vague definition of what this function should do... */ -struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset) -{ - int i, j = 0; - struct dive *dive; - - for_each_dive (i, dive) { - if (dive_within_time_range(dive, when, offset)) - if (++j == n) - return dive; - } - return NULL; -} - -void shift_times(const timestamp_t amount) -{ - int i; - struct dive *dive; - - for_each_dive (i, dive) { - if (!dive->selected) - continue; - dive->when += amount; - invalidate_dive_cache(dive); - } -} - -timestamp_t get_times() -{ - int i; - struct dive *dive; - - for_each_dive (i, dive) { - if (dive->selected) - break; - } - return dive->when; -} - -void set_save_userid_local(short value) -{ - prefs.save_userid_local = value; -} - -void set_userid(char *rUserId) -{ - if (prefs.userid) - free(prefs.userid); - prefs.userid = strdup(rUserId); - if (strlen(prefs.userid) > 30) - prefs.userid[30]='\0'; -} - -/* this sets a usually unused copy of the preferences with the units - * that were active the last time the dive list was saved to git storage - * (this isn't used in XML files); storing the unit preferences in the - * data file is usually pointless (that's a setting of the software, - * not a property of the data), but it's a great hint of what the user - * might expect to see when creating a backend service that visualizes - * the dive list without Subsurface running - so this is basically a - * functionality for the core library that Subsurface itself doesn't - * use but that another consumer of the library (like an HTML exporter) - * will need */ -void set_informational_units(char *units) -{ - if (strstr(units, "METRIC")) { - informational_prefs.unit_system = METRIC; - } else if (strstr(units, "IMPERIAL")) { - informational_prefs.unit_system = IMPERIAL; - } else if (strstr(units, "PERSONALIZE")) { - informational_prefs.unit_system = PERSONALIZE; - if (strstr(units, "METERS")) - informational_prefs.units.length = METERS; - if (strstr(units, "FEET")) - informational_prefs.units.length = FEET; - if (strstr(units, "LITER")) - informational_prefs.units.volume = LITER; - if (strstr(units, "CUFT")) - informational_prefs.units.volume = CUFT; - if (strstr(units, "BAR")) - informational_prefs.units.pressure = BAR; - if (strstr(units, "PSI")) - informational_prefs.units.pressure = PSI; - if (strstr(units, "PASCAL")) - informational_prefs.units.pressure = PASCAL; - if (strstr(units, "CELSIUS")) - informational_prefs.units.temperature = CELSIUS; - if (strstr(units, "FAHRENHEIT")) - informational_prefs.units.temperature = FAHRENHEIT; - if (strstr(units, "KG")) - informational_prefs.units.weight = KG; - if (strstr(units, "LBS")) - informational_prefs.units.weight = LBS; - if (strstr(units, "SECONDS")) - informational_prefs.units.vertical_speed_time = SECONDS; - if (strstr(units, "MINUTES")) - informational_prefs.units.vertical_speed_time = MINUTES; - } -} - -void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_depth) -{ - int integral = 0; - int last_time = 0; - int last_depth = 0; - struct divedatapoint *dp = dive->dp; - - *max_depth = 0; - - while (dp) { - if (dp->time) { - /* Ignore gas indication samples */ - integral += (dp->depth + last_depth) * (dp->time - last_time) / 2; - last_time = dp->time; - last_depth = dp->depth; - if (dp->depth > *max_depth) - *max_depth = dp->depth; - } - dp = dp->next; - } - if (last_time) - *avg_depth = integral / last_time; - else - *avg_depth = *max_depth = 0; -} - -struct picture *alloc_picture() -{ - struct picture *pic = malloc(sizeof(struct picture)); - if (!pic) - exit(1); - memset(pic, 0, sizeof(struct picture)); - return pic; -} - -static bool new_picture_for_dive(struct dive *d, char *filename) -{ - FOR_EACH_PICTURE (d) { - if (same_string(picture->filename, filename)) - return false; - } - return true; -} - -// only add pictures that have timestamps between 30 minutes before the dive and -// 30 minutes after the dive ends -#define D30MIN (30 * 60) -bool dive_check_picture_time(struct dive *d, int shift_time, timestamp_t timestamp) -{ - offset_t offset; - if (timestamp) { - offset.seconds = timestamp - d->when + shift_time; - if (offset.seconds > -D30MIN && offset.seconds < dive_totaltime(d) + D30MIN) { - // this picture belongs to this dive - return true; - } - } - return false; -} - -bool picture_check_valid(char *filename, int shift_time) -{ - int i; - struct dive *dive; - - timestamp_t timestamp = picture_get_timestamp(filename); - for_each_dive (i, dive) - if (dive->selected && dive_check_picture_time(dive, shift_time, timestamp)) - return true; - return false; -} - -void dive_create_picture(struct dive *dive, char *filename, int shift_time, bool match_all) -{ - timestamp_t timestamp = picture_get_timestamp(filename); - if (!new_picture_for_dive(dive, filename)) - return; - if (!match_all && !dive_check_picture_time(dive, shift_time, timestamp)) - return; - - struct picture *picture = alloc_picture(); - picture->filename = strdup(filename); - picture->offset.seconds = timestamp - dive->when + shift_time; - picture_load_exif_data(picture); - - dive_add_picture(dive, picture); - dive_set_geodata_from_picture(dive, picture); - invalidate_dive_cache(dive); -} - -void dive_add_picture(struct dive *dive, struct picture *newpic) -{ - struct picture **pic_ptr = &dive->picture_list; - /* let's keep the list sorted by time */ - while (*pic_ptr && (*pic_ptr)->offset.seconds <= newpic->offset.seconds) - pic_ptr = &(*pic_ptr)->next; - newpic->next = *pic_ptr; - *pic_ptr = newpic; - cache_picture(newpic); - return; -} - -unsigned int dive_get_picture_count(struct dive *dive) -{ - unsigned int i = 0; - FOR_EACH_PICTURE (dive) - i++; - return i; -} - -void dive_set_geodata_from_picture(struct dive *dive, struct picture *picture) -{ - struct dive_site *ds = get_dive_site_by_uuid(dive->dive_site_uuid); - if (!dive_site_has_gps_location(ds) && (picture->latitude.udeg || picture->longitude.udeg)) { - if (ds) { - ds->latitude = picture->latitude; - ds->longitude = picture->longitude; - } else { - dive->dive_site_uuid = create_dive_site_with_gps("", picture->latitude, picture->longitude, dive->when); - invalidate_dive_cache(dive); - } - } -} - -void picture_free(struct picture *picture) -{ - if (!picture) - return; - free(picture->filename); - free(picture->hash); - free(picture); -} - -// When handling pictures in different threads, we need to copy them so we don't -// run into problems when the main thread frees the picture. - -struct picture *clone_picture(struct picture *src) -{ - struct picture *dst; - - dst = alloc_picture(); - copy_pl(src, dst); - return dst; -} - -void dive_remove_picture(char *filename) -{ - struct picture **picture = ¤t_dive->picture_list; - while (picture && !same_string((*picture)->filename, filename)) - picture = &(*picture)->next; - if (picture) { - struct picture *temp = (*picture)->next; - picture_free(*picture); - *picture = temp; - invalidate_dive_cache(current_dive); - } -} - -/* this always acts on the current divecomputer of the current dive */ -void make_first_dc() -{ - struct divecomputer *dc = ¤t_dive->dc; - struct divecomputer *newdc = malloc(sizeof(*newdc)); - struct divecomputer *cur_dc = current_dc; /* needs to be in a local variable so the macro isn't re-executed */ - - /* skip the current DC in the linked list */ - while (dc && dc->next != cur_dc) - dc = dc->next; - if (!dc) { - free(newdc); - fprintf(stderr, "data inconsistent: can't find the current DC"); - return; - } - dc->next = cur_dc->next; - *newdc = current_dive->dc; - current_dive->dc = *cur_dc; - current_dive->dc.next = newdc; - free(cur_dc); - invalidate_dive_cache(current_dive); -} - -/* always acts on the current dive */ -unsigned int count_divecomputers(void) -{ - int ret = 1; - struct divecomputer *dc = current_dive->dc.next; - while (dc) { - ret++; - dc = dc->next; - } - return ret; -} - -/* always acts on the current dive */ -void delete_current_divecomputer(void) -{ - struct divecomputer *dc = current_dc; - - if (dc == ¤t_dive->dc) { - /* remove the first one, so copy the second one in place of the first and free the second one - * be careful about freeing the no longer needed structures - since we copy things around we can't use free_dc()*/ - struct divecomputer *fdc = dc->next; - free(dc->sample); - free((void *)dc->model); - free_events(dc->events); - memcpy(dc, fdc, sizeof(struct divecomputer)); - free(fdc); - } else { - struct divecomputer *pdc = ¤t_dive->dc; - while (pdc->next != dc && pdc->next) - pdc = pdc->next; - if (pdc->next == dc) { - pdc->next = dc->next; - free_dc(dc); - } - } - if (dc_number == count_divecomputers()) - dc_number--; - invalidate_dive_cache(current_dive); -} - -/* helper function to make it easier to work with our structures - * we don't interpolate here, just use the value from the last sample up to that time */ -int get_depth_at_time(struct divecomputer *dc, unsigned int time) -{ - int depth = 0; - if (dc && dc->sample) - for (int i = 0; i < dc->samples; i++) { - if (dc->sample[i].time.seconds > time) - break; - depth = dc->sample[i].depth.mm; - } - return depth; -} diff --git a/subsurface-core/dive.h b/subsurface-core/dive.h deleted file mode 100644 index ae24b7409..000000000 --- a/subsurface-core/dive.h +++ /dev/null @@ -1,913 +0,0 @@ -#ifndef DIVE_H -#define DIVE_H - -#include -#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 double gas_compressibility_factor(struct gasmix *gas, double bar); - - -static inline int get_o2(const struct gasmix *mix) -{ - return mix->o2.permille ?: O2_IN_AIR; -} - -static inline int get_he(const struct gasmix *mix) -{ - return mix->he.permille; -} - -struct gas_pressures { - double o2, n2, he; -}; - -extern void fill_pressures(struct gas_pressures *pressures, const double amb_pressure, const struct gasmix *mix, double po2, enum dive_comp_type dctype); - -extern void sanitize_gasmix(struct gasmix *mix); -extern int gasmix_distance(const struct gasmix *a, const struct gasmix *b); -extern struct gasmix *get_gasmix_from_event(struct event *ev); - -static inline bool gasmix_is_air(const struct gasmix *gasmix) -{ - int o2 = gasmix->o2.permille; - int he = gasmix->he.permille; - return (he == 0) && (o2 == 0 || ((o2 >= O2_IN_AIR - 1) && (o2 <= O2_IN_AIR + 1))); -} - -/* Linear interpolation between 'a' and 'b', when we are 'part'way into the 'whole' distance from a to b */ -static inline int interpolate(int a, int b, int part, int whole) -{ - /* It is doubtful that we actually need floating point for this, but whatever */ - double x = (double)a * (whole - part) + (double)b * part; - return rint(x / whole); -} - -void get_gas_string(const struct gasmix *gasmix, char *text, int len); -const char *gasname(const struct gasmix *gasmix); - -struct sample // BASE TYPE BYTES UNITS RANGE DESCRIPTION -{ // --------- ----- ----- ----- ----------- - duration_t time; // uint32_t 4 seconds (0-68 yrs) elapsed dive time up to this sample - duration_t stoptime; // uint32_t 4 seconds (0-18 h) time duration of next deco stop - duration_t ndl; // uint32_t 4 seconds (0-18 h) time duration before no-deco limit - duration_t tts; // uint32_t 4 seconds (0-18 h) time duration to reach the surface - duration_t rbt; // uint32_t 4 seconds (0-18 h) remaining bottom time - depth_t depth; // int32_t 4 mm (0-2000 km) dive depth of this sample - depth_t stopdepth; // int32_t 4 mm (0-2000 km) depth of next deco stop - temperature_t temperature; // int32_t 4 mdegrK (0-2 MdegK) ambient temperature - pressure_t cylinderpressure; // int32_t 4 mbar (0-2 Mbar) main cylinder pressure - pressure_t o2cylinderpressure; // int32_t 4 mbar (0-2 Mbar) CCR o2 cylinder pressure (rebreather) - o2pressure_t setpoint; // uint16_t 2 mbar (0-65 bar) O2 partial pressure (will be setpoint) - o2pressure_t o2sensor[3]; // uint16_t 6 mbar (0-65 bar) Up to 3 PO2 sensor values (rebreather) - bearing_t bearing; // int16_t 2 degrees (-32k to 32k deg) compass bearing - uint8_t sensor; // uint8_t 1 sensorID (0-255) ID of cylinder pressure sensor - uint8_t cns; // uint8_t 1 % (0-255 %) cns% accumulated - uint8_t heartbeat; // uint8_t 1 beats/m (0-255) heart rate measurement - volume_t sac; // 4 ml/min predefined SAC - bool in_deco; // bool 1 y/n y/n this sample is part of deco - bool manually_entered; // bool 1 y/n y/n this sample was entered by the user, - // not calculated when planning a dive -}; // Total size of structure: 57 bytes, excluding padding at end - -struct divetag { - /* - * The name of the divetag. If a translation is available, name contains - * the translated tag - */ - char *name; - /* - * If a translation is available, we write the original tag to source. - * This enables us to write a non-localized tag to the xml file. - */ - char *source; -}; - -struct tag_entry { - struct divetag *tag; - struct tag_entry *next; -}; - -/* - * divetags are only stored once, each dive only contains - * a list of tag_entries which then point to the divetags - * in the global g_tag_list - */ - -extern struct tag_entry *g_tag_list; - -struct divetag *taglist_add_tag(struct tag_entry **tag_list, const char *tag); -struct tag_entry *taglist_added(struct tag_entry *original_list, struct tag_entry *new_list); -void dump_taglist(const char *intro, struct tag_entry *tl); - -/* - * Writes all divetags in tag_list to buffer, limited by the buffer's (len)gth. - * Returns the characters written - */ -int taglist_get_tagstring(struct tag_entry *tag_list, char *buffer, int len); - -/* cleans up a list: removes empty tags and duplicates */ -void taglist_cleanup(struct tag_entry **tag_list); - -void taglist_init_global(); -void taglist_free(struct tag_entry *tag_list); - -bool taglist_contains(struct tag_entry *tag_list, const char *tag); -bool taglist_equal(struct tag_entry *tl1, struct tag_entry *tl2); -int count_dives_with_tag(const char *tag); -int count_dives_with_person(const char *person); -int count_dives_with_location(const char *location); -int count_dives_with_suit(const char *suit); - -struct extra_data { - const char *key; - const char *value; - struct extra_data *next; -}; - -/* - * NOTE! The deviceid and diveid are model-specific *hashes* of - * whatever device identification that model may have. Different - * dive computers will have different identifying data, it could - * be a firmware number or a serial ID (in either string or in - * numeric format), and we do not care. - * - * The only thing we care about is that subsurface will hash - * that information the same way. So then you can check the ID - * of a dive computer by comparing the hashes for equality. - * - * A deviceid or diveid of zero is assumed to be "no ID". - */ -struct divecomputer { - timestamp_t when; - duration_t duration, surfacetime; - depth_t maxdepth, meandepth; - temperature_t airtemp, watertemp; - pressure_t surface_pressure; - enum dive_comp_type divemode; // dive computer type: OC(default) or CCR - uint8_t no_o2sensors; // rebreathers: number of O2 sensors used - int salinity; // kg per 10000 l - const char *model, *serial, *fw_version; - uint32_t deviceid, diveid; - int samples, alloc_samples; - struct sample *sample; - struct event *events; - struct extra_data *extra_data; - struct divecomputer *next; -}; - -#define MAX_CYLINDERS (8) -#define MAX_WEIGHTSYSTEMS (6) -#define W_IDX_PRIMARY 0 -#define W_IDX_SECONDARY 1 - -typedef enum { - TF_NONE, - NO_TRIP, - IN_TRIP, - ASSIGNED_TRIP, - NUM_TRIPFLAGS -} tripflag_t; - -typedef struct dive_trip -{ - timestamp_t when; - char *location; - char *notes; - struct dive *dives; - int nrdives; - int index; - unsigned expanded : 1, selected : 1, autogen : 1, fixup : 1; - struct dive_trip *next; -} dive_trip_t; - -/* List of dive trips (sorted by date) */ -extern dive_trip_t *dive_trip_list; -struct picture; -struct dive { - int number; - tripflag_t tripflag; - dive_trip_t *divetrip; - struct dive *next, **pprev; - bool selected; - bool hidden_by_filter; - bool downloaded; - timestamp_t when; - uint32_t dive_site_uuid; - char *notes; - char *divemaster, *buddy; - int rating; - int visibility; /* 0 - 5 star rating */ - cylinder_t cylinder[MAX_CYLINDERS]; - weightsystem_t weightsystem[MAX_WEIGHTSYSTEMS]; - char *suit; - int sac, otu, cns, maxcns; - - /* Calculated based on dive computer data */ - temperature_t mintemp, maxtemp, watertemp, airtemp; - depth_t maxdepth, meandepth; - pressure_t surface_pressure; - duration_t duration; - int salinity; // kg per 10000 l - - struct tag_entry *tag_list; - struct divecomputer dc; - int id; // unique ID for this dive - struct picture *picture_list; - int oxygen_cylinder_index, diluent_cylinder_index; // CCR dive cylinder indices - unsigned char git_id[20]; -}; - -static inline void invalidate_dive_cache(struct dive *dive) -{ - memset(dive->git_id, 0, 20); -} - -static inline bool dive_cache_is_valid(const struct dive *dive) -{ - static const unsigned char null_id[20] = { 0, }; - return !!memcmp(dive->git_id, null_id, 20); -} - -extern int get_cylinder_idx_by_use(struct dive *dive, enum cylinderuse cylinder_use_type); -extern void dc_cylinder_renumber(struct dive *dive, struct divecomputer *dc, int mapping[]); - -/* when selectively copying dive information, which parts should be copied? */ -struct dive_components { - unsigned int divesite : 1; - unsigned int notes : 1; - unsigned int divemaster : 1; - unsigned int buddy : 1; - unsigned int suit : 1; - unsigned int rating : 1; - unsigned int visibility : 1; - unsigned int tags : 1; - unsigned int cylinders : 1; - unsigned int weights : 1; -}; - -/* picture list and methods related to dive picture handling */ -struct picture { - char *filename; - char *hash; - offset_t offset; - degrees_t latitude; - degrees_t longitude; - struct picture *next; -}; - -#define FOR_EACH_PICTURE(_dive) \ - if (_dive) \ - for (struct picture *picture = (_dive)->picture_list; picture; picture = picture->next) - -#define FOR_EACH_PICTURE_NON_PTR(_divestruct) \ - for (struct picture *picture = (_divestruct).picture_list; picture; picture = picture->next) - -extern struct picture *alloc_picture(); -extern struct picture *clone_picture(struct picture *src); -extern bool dive_check_picture_time(struct dive *d, int shift_time, timestamp_t timestamp); -extern void dive_create_picture(struct dive *d, char *filename, int shift_time, bool match_all); -extern void dive_add_picture(struct dive *d, struct picture *newpic); -extern void dive_remove_picture(char *filename); -extern unsigned int dive_get_picture_count(struct dive *d); -extern bool picture_check_valid(char *filename, int shift_time); -extern void picture_load_exif_data(struct picture *p); -extern timestamp_t picture_get_timestamp(char *filename); -extern void dive_set_geodata_from_picture(struct dive *d, struct picture *pic); -extern void picture_free(struct picture *picture); - -extern int explicit_first_cylinder(struct dive *dive, struct divecomputer *dc); -extern int get_depth_at_time(struct divecomputer *dc, unsigned int time); - -static inline int get_surface_pressure_in_mbar(const struct dive *dive, bool non_null) -{ - int mbar = dive->surface_pressure.mbar; - if (!mbar && non_null) - mbar = SURFACE_PRESSURE; - return mbar; -} - -/* Pa = N/m^2 - so we determine the weight (in N) of the mass of 10m - * of water (and use standard salt water at 1.03kg per liter if we don't know salinity) - * and add that to the surface pressure (or to 1013 if that's unknown) */ -static inline int calculate_depth_to_mbar(int depth, pressure_t surface_pressure, int salinity) -{ - double specific_weight; - int mbar = surface_pressure.mbar; - - if (!mbar) - mbar = SURFACE_PRESSURE; - if (!salinity) - salinity = SEAWATER_SALINITY; - if (salinity < 500) - salinity += FRESHWATER_SALINITY; - specific_weight = salinity / 10000.0 * 0.981; - mbar += rint(depth / 10.0 * specific_weight); - return mbar; -} - -static inline int depth_to_mbar(int depth, struct dive *dive) -{ - return calculate_depth_to_mbar(depth, dive->surface_pressure, dive->salinity); -} - -static inline double depth_to_bar(int depth, struct dive *dive) -{ - return depth_to_mbar(depth, dive) / 1000.0; -} - -static inline double depth_to_atm(int depth, struct dive *dive) -{ - return mbar_to_atm(depth_to_mbar(depth, dive)); -} - -/* for the inverse calculation we use just the relative pressure - * (that's the one that some dive computers like the Uemis Zurich - * provide - for the other models that do this libdivecomputer has to - * take care of this, but the Uemis we support natively */ -static inline int rel_mbar_to_depth(int mbar, struct dive *dive) -{ - int cm; - double specific_weight = 1.03 * 0.981; - if (dive->dc.salinity) - specific_weight = dive->dc.salinity / 10000.0 * 0.981; - /* whole mbar gives us cm precision */ - cm = rint(mbar / specific_weight); - return cm * 10; -} - -static inline int mbar_to_depth(int mbar, struct dive *dive) -{ - pressure_t surface_pressure; - if (dive->surface_pressure.mbar) - surface_pressure = dive->surface_pressure; - else - surface_pressure.mbar = SURFACE_PRESSURE; - return rel_mbar_to_depth(mbar - surface_pressure.mbar, dive); -} - -/* MOD rounded to multiples of roundto mm */ -static inline depth_t gas_mod(struct gasmix *mix, pressure_t po2_limit, struct dive *dive, int roundto) { - depth_t rounded_depth; - - double depth = (double) mbar_to_depth(po2_limit.mbar * 1000 / get_o2(mix), dive); - rounded_depth.mm = rint(depth / roundto) * roundto; - return rounded_depth; -} - -#define SURFACE_THRESHOLD 750 /* somewhat arbitrary: only below 75cm is it really diving */ - -/* this is a global spot for a temporary dive structure that we use to - * be able to edit a dive without unintended side effects */ -extern struct dive edit_dive; - -extern short autogroup; -/* random threashold: three days without diving -> new trip - * this works very well for people who usually dive as part of a trip and don't - * regularly dive at a local facility; this is why trips are an optional feature */ -#define TRIP_THRESHOLD 3600 * 24 * 3 - -#define UNGROUPED_DIVE(_dive) ((_dive)->tripflag == NO_TRIP) -#define DIVE_IN_TRIP(_dive) ((_dive)->tripflag == IN_TRIP || (_dive)->tripflag == ASSIGNED_TRIP) -#define DIVE_NEEDS_TRIP(_dive) ((_dive)->tripflag == TF_NONE) - -extern void add_dive_to_trip(struct dive *, dive_trip_t *); - -extern void delete_single_dive(int idx); -extern void add_single_dive(int idx, struct dive *dive); - -extern void insert_trip(dive_trip_t **trip); - - -extern const struct units SI_units, IMPERIAL_units; -extern struct units xml_parsing_units; - -extern struct units *get_units(void); -extern int run_survey, verbose, quit, force_root; - -struct dive_table { - int nr, allocated, preexisting; - struct dive **dives; -}; - -extern struct dive_table dive_table, downloadTable; -extern struct dive displayed_dive; -extern struct dive_site displayed_dive_site; -extern int selected_dive; -extern unsigned int dc_number; -#define current_dive (get_dive(selected_dive)) -#define current_dc (get_dive_dc(current_dive, dc_number)) - -static inline struct dive *get_dive(int nr) -{ - if (nr >= dive_table.nr || nr < 0) - return NULL; - return dive_table.dives[nr]; -} - -static inline struct dive *get_dive_from_table(int nr, struct dive_table *dt) -{ - if (nr >= dt->nr || nr < 0) - return NULL; - return dt->dives[nr]; -} - -static inline struct dive_site *get_dive_site_for_dive(struct dive *dive) -{ - if (dive) - return get_dive_site_by_uuid(dive->dive_site_uuid); - return NULL; -} - -static inline char *get_dive_location(struct dive *dive) -{ - struct dive_site *ds = get_dive_site_by_uuid(dive->dive_site_uuid); - if (ds && ds->name) - return ds->name; - return NULL; -} - -static inline unsigned int number_of_computers(struct dive *dive) -{ - unsigned int total_number = 0; - struct divecomputer *dc = &dive->dc; - - if (!dive) - return 1; - - do { - total_number++; - dc = dc->next; - } while (dc); - return total_number; -} - -static inline struct divecomputer *get_dive_dc(struct dive *dive, int nr) -{ - struct divecomputer *dc = &dive->dc; - - while (nr-- > 0) { - dc = dc->next; - if (!dc) - return &dive->dc; - } - return dc; -} - -extern timestamp_t dive_endtime(const struct dive *dive); - -extern void make_first_dc(void); -extern unsigned int count_divecomputers(void); -extern void delete_current_divecomputer(void); - -/* - * Iterate over each dive, with the first parameter being the index - * iterator variable, and the second one being the dive one. - * - * I don't think anybody really wants the index, and we could make - * it local to the for-loop, but that would make us requires C99. - */ -#define for_each_dive(_i, _x) \ - for ((_i) = 0; ((_x) = get_dive(_i)) != NULL; (_i)++) - -#define for_each_dc(_dive, _dc) \ - for (_dc = &_dive->dc; _dc; _dc = _dc->next) - -#define for_each_gps_location(_i, _x) \ - for ((_i) = 0; ((_x) = get_gps_location(_i, &gps_location_table)) != NULL; (_i)++) - -static inline struct dive *get_dive_by_uniq_id(int id) -{ - int i; - struct dive *dive = NULL; - - for_each_dive (i, dive) { - if (dive->id == id) - break; - } -#ifdef DEBUG - if (dive == NULL) { - fprintf(stderr, "Invalid id %x passed to get_dive_by_diveid, try to fix the code\n", id); - exit(1); - } -#endif - return dive; -} - -static inline int get_idx_by_uniq_id(int id) -{ - int i; - struct dive *dive = NULL; - - for_each_dive (i, dive) { - if (dive->id == id) - break; - } -#ifdef DEBUG - if (dive == NULL) { - fprintf(stderr, "Invalid id %x passed to get_dive_by_diveid, try to fix the code\n", id); - exit(1); - } -#endif - return i; -} - -static inline bool dive_site_has_gps_location(struct dive_site *ds) -{ - return ds && (ds->latitude.udeg || ds->longitude.udeg); -} - -static inline int dive_has_gps_location(struct dive *dive) -{ - if (!dive) - return false; - return dive_site_has_gps_location(get_dive_site_by_uuid(dive->dive_site_uuid)); -} - -#ifdef __cplusplus -extern "C" { -#endif - -extern int report_error(const char *fmt, ...); -extern void report_message(const char *msg); -extern const char *get_error_string(void); - -extern struct dive *find_dive_including(timestamp_t when); -extern bool dive_within_time_range(struct dive *dive, timestamp_t when, timestamp_t offset); -extern bool time_during_dive_with_offset(struct dive *dive, timestamp_t when, timestamp_t offset); -struct dive *find_dive_n_near(timestamp_t when, int n, timestamp_t offset); - -/* Check if two dive computer entries are the exact same dive (-1=no/0=maybe/1=yes) */ -extern int match_one_dc(struct divecomputer *a, struct divecomputer *b); - -extern void parse_xml_init(void); -extern int parse_xml_buffer(const char *url, const char *buf, int size, struct dive_table *table, const char **params); -extern void parse_xml_exit(void); -extern void set_filename(const char *filename, bool force); - -extern int parse_dm4_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buf, int size, struct dive_table *table); -extern int parse_dlf_buffer(unsigned char *buffer, size_t size); - -extern int check_git_sha(const char *filename); -extern int parse_file(const char *filename); -extern int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate); -extern int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate); -extern int parse_txt_file(const char *filename, const char *csv); -extern int parse_manual_file(const char *filename, char **params, int pnr); -extern int save_dives(const char *filename); -extern int save_dives_logic(const char *filename, bool select_only); -extern int save_dive(FILE *f, struct dive *dive); -extern int export_dives_xslt(const char *filename, const bool selected, const int units, const char *export_xslt); - -struct membuffer; -extern void save_one_dive_to_mb(struct membuffer *b, struct dive *dive); - -int cylinderuse_from_text(const char *text); - - -struct user_info { - const char *name; - const char *email; -}; - -extern void subsurface_user_info(struct user_info *); -extern int subsurface_rename(const char *path, const char *newpath); -extern int subsurface_dir_rename(const char *path, const char *newpath); -extern int subsurface_open(const char *path, int oflags, mode_t mode); -extern FILE *subsurface_fopen(const char *path, const char *mode); -extern void *subsurface_opendir(const char *path); -extern int subsurface_access(const char *path, int mode); -extern struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp); -extern int subsurface_zip_close(struct zip *zip); -extern void subsurface_console_init(bool dedicated); -extern void subsurface_console_exit(void); -extern bool subsurface_user_is_root(void); - -extern void shift_times(const timestamp_t amount); -extern timestamp_t get_times(); - -extern xsltStylesheetPtr get_stylesheet(const char *name); - -extern timestamp_t utc_mktime(struct tm *tm); -extern void utc_mkdate(timestamp_t, struct tm *tm); - -extern struct dive *alloc_dive(void); -extern void record_dive_to_table(struct dive *dive, struct dive_table *table); -extern void record_dive(struct dive *dive); -extern void clear_dive(struct dive *dive); -extern void copy_dive(struct dive *s, struct dive *d); -extern void selective_copy_dive(struct dive *s, struct dive *d, struct dive_components what, bool clear); -extern struct dive *clone_dive(struct dive *s); - -extern void clear_table(struct dive_table *table); - -extern struct sample *prepare_sample(struct divecomputer *dc); -extern void finish_sample(struct divecomputer *dc); - -extern bool has_hr_data(struct divecomputer *dc); - -extern void sort_table(struct dive_table *table); -extern struct dive *fixup_dive(struct dive *dive); -extern void fixup_dc_duration(struct divecomputer *dc); -extern int dive_getUniqID(struct dive *d); -extern unsigned int dc_airtemp(struct divecomputer *dc); -extern unsigned int dc_watertemp(struct divecomputer *dc); -extern int split_dive(struct dive *); -extern struct dive *merge_dives(struct dive *a, struct dive *b, int offset, bool prefer_downloaded); -extern struct dive *try_to_merge(struct dive *a, struct dive *b, bool prefer_downloaded); -extern struct event *clone_event(const struct event *src_ev); -extern void copy_events(struct divecomputer *s, struct divecomputer *d); -extern void free_events(struct event *ev); -extern void copy_cylinders(struct dive *s, struct dive *d, bool used_only); -extern void copy_samples(struct divecomputer *s, struct divecomputer *d); -extern bool is_cylinder_used(struct dive *dive, int idx); -extern void fill_default_cylinder(cylinder_t *cyl); -extern void add_gas_switch_event(struct dive *dive, struct divecomputer *dc, int time, int idx); -extern struct event *add_event(struct divecomputer *dc, unsigned int time, int type, int flags, int value, const char *name); -extern void remove_event(struct event *event); -extern void update_event_name(struct dive *d, struct event* event, char *name); -extern void add_extra_data(struct divecomputer *dc, const char *key, const char *value); -extern void per_cylinder_mean_depth(struct dive *dive, struct divecomputer *dc, int *mean, int *duration); -extern int get_cylinder_index(struct dive *dive, struct event *ev); -extern int nr_cylinders(struct dive *dive); -extern int nr_weightsystems(struct dive *dive); - -/* UI related protopypes */ - -// extern void report_error(GError* error); - -extern void add_cylinder_description(cylinder_type_t *); -extern void add_weightsystem_description(weightsystem_t *); -extern void remember_event(const char *eventname); -extern void invalidate_dive_cache(struct dive *dc); - -#if WE_DONT_USE_THIS /* this is a missing feature in Qt - selecting which events to display */ -extern int evn_foreach(void (*callback)(const char *, bool *, void *), void *data); -#endif /* WE_DONT_USE_THIS */ - -extern void clear_events(void); - -extern void set_dc_nickname(struct dive *dive); -extern void set_autogroup(bool value); -extern int total_weight(struct dive *); - -#ifdef __cplusplus -} -#endif - -#define DIVE_ERROR_PARSE 1 -#define DIVE_ERROR_PLAN 2 - -const char *weekday(int wday); -const char *monthname(int mon); - -#define UTF8_DEGREE "\xc2\xb0" -#define UTF8_DELTA "\xce\x94" -#define UTF8_UPWARDS_ARROW "\xE2\x86\x91" -#define UTF8_DOWNWARDS_ARROW "\xE2\x86\x93" -#define UTF8_AVERAGE "\xc3\xb8" -#define UTF8_SUBSCRIPT_2 "\xe2\x82\x82" -#define UTF8_WHITESTAR "\xe2\x98\x86" -#define UTF8_BLACKSTAR "\xe2\x98\x85" - -extern const char *existing_filename; -extern void subsurface_command_line_init(int *, char ***); -extern void subsurface_command_line_exit(int *, char ***); - -#define FRACTION(n, x) ((unsigned)(n) / (x)), ((unsigned)(n) % (x)) - -extern void add_segment(double pressure, const struct gasmix *gasmix, int period_in_seconds, int setpoint, const struct dive *dive, int sac); -extern void clear_deco(double surface_pressure); -extern void dump_tissues(void); -extern void set_gf(short gflow, short gfhigh, bool gf_low_at_maxdepth); -extern void cache_deco_state(char **datap); -extern void restore_deco_state(char *data); -extern void nuclear_regeneration(double time); -extern void vpmb_start_gradient(); -extern void vpmb_next_gradient(double deco_time, double surface_pressure); -extern double tissue_tolerance_calc(const struct dive *dive, double pressure); - -/* this should be converted to use our types */ -struct divedatapoint { - int time; - int depth; - struct gasmix gasmix; - int setpoint; - bool entered; - struct divedatapoint *next; -}; - -struct diveplan { - timestamp_t when; - int surface_pressure; /* mbar */ - int bottomsac; /* ml/min */ - int decosac; /* ml/min */ - int salinity; - short gflow; - short gfhigh; - struct divedatapoint *dp; -}; - -struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, struct gasmix gasmix, int po2, bool entered); -struct divedatapoint *create_dp(int time_incr, int depth, struct gasmix gasmix, int po2); -#if DEBUG_PLAN -void dump_plan(struct diveplan *diveplan); -#endif -bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer); -void calc_crushing_pressure(double pressure); -void vpmb_start_gradient(); - -void delete_single_dive(int idx); - -struct event *get_next_event(struct event *event, const char *name); - - -/* these structs holds the information that - * describes the cylinders / weight systems. - * they are global variables initialized in equipment.c - * used to fill the combobox in the add/edit cylinder - * dialog - */ - -struct tank_info_t { - const char *name; - int cuft, ml, psi, bar; -}; -extern struct tank_info_t tank_info[100]; - -struct ws_info_t { - const char *name; - int grams; -}; -extern struct ws_info_t ws_info[100]; - -extern bool cylinder_nodata(cylinder_t *cyl); -extern bool cylinder_none(void *_data); -extern bool weightsystem_none(void *_data); -extern bool no_weightsystems(weightsystem_t *ws); -extern bool weightsystems_equal(weightsystem_t *ws1, weightsystem_t *ws2); -extern void remove_cylinder(struct dive *dive, int idx); -extern void remove_weightsystem(struct dive *dive, int idx); -extern void reset_cylinders(struct dive *dive, bool track_gas); - -/* - * String handling. - */ -#define STRTOD_NO_SIGN 0x01 -#define STRTOD_NO_DOT 0x02 -#define STRTOD_NO_COMMA 0x04 -#define STRTOD_NO_EXPONENT 0x08 -extern double strtod_flags(const char *str, const char **ptr, unsigned int flags); - -#define STRTOD_ASCII (STRTOD_NO_COMMA) - -#define ascii_strtod(str, ptr) strtod_flags(str, ptr, STRTOD_ASCII) - -extern void set_save_userid_local(short value); -extern void set_userid(char *user_id); -extern void set_informational_units(char *units); - -extern const char *get_dive_date_c_string(timestamp_t when); -extern void update_setpoint_events(struct divecomputer *dc); -#ifdef __cplusplus -} -#endif - -extern weight_t string_to_weight(const char *str); -extern depth_t string_to_depth(const char *str); -extern pressure_t string_to_pressure(const char *str); -extern volume_t string_to_volume(const char *str, pressure_t workp); -extern fraction_t string_to_fraction(const char *str); -extern void average_max_depth(struct diveplan *dive, int *avg_depth, int *max_depth); - -#include "pref.h" - -#endif // DIVE_H diff --git a/subsurface-core/divecomputer.cpp b/subsurface-core/divecomputer.cpp deleted file mode 100644 index e4081e1cd..000000000 --- a/subsurface-core/divecomputer.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "divecomputer.h" -#include "dive.h" - -#include - -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 deleted file mode 100644 index 98d12ab8b..000000000 --- a/subsurface-core/divecomputer.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef DIVECOMPUTER_H -#define DIVECOMPUTER_H - -#include -#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 deleted file mode 100644 index 543d9e17b..000000000 --- a/subsurface-core/divelist.c +++ /dev/null @@ -1,1207 +0,0 @@ -/* divelist.c */ -/* core logic for the dive list - - * accessed through the following interfaces: - * - * dive_trip_t *dive_trip_list; - * unsigned int amount_selected; - * void dump_selection(void) - * dive_trip_t *find_trip_by_idx(int idx) - * int trip_has_selected_dives(dive_trip_t *trip) - * void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p) - * int total_weight(struct dive *dive) - * int get_divenr(struct dive *dive) - * double init_decompression(struct dive *dive) - * void update_cylinder_related_info(struct dive *dive) - * void dump_trip_list(void) - * dive_trip_t *find_matching_trip(timestamp_t when) - * void insert_trip(dive_trip_t **dive_trip_p) - * void remove_dive_from_trip(struct dive *dive) - * void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) - * dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) - * void autogroup_dives(void) - * void delete_single_dive(int idx) - * void add_single_dive(int idx, struct dive *dive) - * void merge_two_dives(struct dive *a, struct dive *b) - * void select_dive(int idx) - * void deselect_dive(int idx) - * void mark_divelist_changed(int changed) - * int unsaved_changes() - * void remove_autogen_trips() - */ -#include -#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" -#include "git-access.h" - -static short dive_list_changed = false; - -short autogroup = false; - -dive_trip_t *dive_trip_list; - -unsigned int amount_selected; - -#if DEBUG_SELECTION_TRACKING -void dump_selection(void) -{ - int i; - struct dive *dive; - - printf("currently selected are %u dives:", amount_selected); - for_each_dive(i, dive) { - if (dive->selected) - printf(" %d", i); - } - printf("\n"); -} -#endif - -void set_autogroup(bool value) -{ - /* if we keep the UI paradigm, this needs to toggle - * the checkbox on the autogroup menu item */ - autogroup = value; -} - -dive_trip_t *find_trip_by_idx(int idx) -{ - dive_trip_t *trip = dive_trip_list; - - if (idx >= 0) - return NULL; - idx = -idx; - while (trip) { - if (trip->index == idx) - return trip; - trip = trip->next; - } - return NULL; -} - -int trip_has_selected_dives(dive_trip_t *trip) -{ - struct dive *dive; - for (dive = trip->dives; dive; dive = dive->next) { - if (dive->selected) - return 1; - } - return 0; -} - -/* - * Get "maximal" dive gas for a dive. - * Rules: - * - Trimix trumps nitrox (highest He wins, O2 breaks ties) - * - Nitrox trumps air (even if hypoxic) - * These are the same rules as the inter-dive sorting rules. - */ -void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2max_p) -{ - int i; - int maxo2 = -1, maxhe = -1, mino2 = 1000; - - - for (i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = dive->cylinder + i; - int o2 = get_o2(&cyl->gasmix); - int he = get_he(&cyl->gasmix); - - if (!is_cylinder_used(dive, i)) - continue; - if (cylinder_none(cyl)) - continue; - if (o2 > maxo2) - maxo2 = o2; - if (he > maxhe) - goto newmax; - if (he < maxhe) - continue; - if (o2 <= maxo2) - continue; - newmax: - maxhe = he; - mino2 = o2; - } - /* All air? Show/sort as "air"/zero */ - if ((!maxhe && maxo2 == O2_IN_AIR && mino2 == maxo2) || - (maxo2 == -1 && maxhe == -1 && mino2 == 1000)) - maxo2 = mino2 = 0; - *o2_p = mino2; - *he_p = maxhe; - *o2max_p = maxo2; -} - -int total_weight(struct dive *dive) -{ - int i, total_grams = 0; - - if (dive) - for (i = 0; i < MAX_WEIGHTSYSTEMS; i++) - total_grams += dive->weightsystem[i].weight.grams; - return total_grams; -} - -static int active_o2(struct dive *dive, struct divecomputer *dc, duration_t time) -{ - struct gasmix gas; - get_gas_at_time(dive, dc, time, &gas); - return get_o2(&gas); -} - -/* calculate OTU for a dive - this only takes the first divecomputer into account */ -static int calculate_otu(struct dive *dive) -{ - int i; - double otu = 0.0; - struct divecomputer *dc = &dive->dc; - - for (i = 1; i < dc->samples; i++) { - int t; - int po2; - struct sample *sample = dc->sample + i; - struct sample *psample = sample - 1; - t = sample->time.seconds - psample->time.seconds; - if (sample->setpoint.mbar) { - po2 = sample->setpoint.mbar; - } else { - int o2 = active_o2(dive, dc, sample->time); - po2 = o2 * depth_to_atm(sample->depth.mm, dive); - } - if (po2 >= 500) - otu += pow((po2 - 500) / 1000.0, 0.83) * t / 30.0; - } - return rint(otu); -} -/* calculate CNS for a dive - this only takes the first divecomputer into account */ -int const cns_table[][3] = { - /* po2, Maximum Single Exposure, Maximum 24 hour Exposure */ - { 1600, 45 * 60, 150 * 60 }, - { 1500, 120 * 60, 180 * 60 }, - { 1400, 150 * 60, 180 * 60 }, - { 1300, 180 * 60, 210 * 60 }, - { 1200, 210 * 60, 240 * 60 }, - { 1100, 240 * 60, 270 * 60 }, - { 1000, 300 * 60, 300 * 60 }, - { 900, 360 * 60, 360 * 60 }, - { 800, 450 * 60, 450 * 60 }, - { 700, 570 * 60, 570 * 60 }, - { 600, 720 * 60, 720 * 60 } -}; - -/* this only gets called if dive->maxcns == 0 which means we know that - * none of the divecomputers has tracked any CNS for us - * so we calculated it "by hand" */ -static int calculate_cns(struct dive *dive) -{ - int i, divenr; - size_t j; - double cns = 0.0; - struct divecomputer *dc = &dive->dc; - struct dive *prev_dive; - timestamp_t endtime; - - /* shortcut */ - if (dive->cns) - return dive->cns; - /* - * Do we start with a cns loading from a previous dive? - * Check if we did a dive 12 hours prior, and what cns we had from that. - * Then apply ha 90min halftime to see whats left. - */ - divenr = get_divenr(dive); - if (divenr) { - prev_dive = get_dive(divenr - 1); - if (prev_dive) { - endtime = prev_dive->when + prev_dive->duration.seconds; - if (dive->when < (endtime + 3600 * 12)) { - cns = calculate_cns(prev_dive); - cns = cns * 1 / pow(2, (dive->when - endtime) / (90.0 * 60.0)); - } - } - } - /* Caclulate the cns for each sample in this dive and sum them */ - for (i = 1; i < dc->samples; i++) { - int t; - int po2; - struct sample *sample = dc->sample + i; - struct sample *psample = sample - 1; - t = sample->time.seconds - psample->time.seconds; - if (sample->setpoint.mbar) { - po2 = sample->setpoint.mbar; - } else { - int o2 = active_o2(dive, dc, sample->time); - po2 = o2 * depth_to_atm(sample->depth.mm, dive); - } - /* CNS don't increse when below 500 matm */ - if (po2 < 500) - continue; - /* Find what table-row we should calculate % for */ - for (j = 1; j < sizeof(cns_table) / (sizeof(int) * 3); j++) - if (po2 > cns_table[j][0]) - break; - j--; - cns += ((double)t) / ((double)cns_table[j][1]) * 100; - } - /* save calculated cns in dive struct */ - dive->cns = cns; - return dive->cns; -} -/* - * Return air usage (in liters). - */ -static double calculate_airuse(struct dive *dive) -{ - int airuse = 0; - int i; - - for (i = 0; i < MAX_CYLINDERS; i++) { - pressure_t start, end; - cylinder_t *cyl = dive->cylinder + i; - - start = cyl->start.mbar ? cyl->start : cyl->sample_start; - end = cyl->end.mbar ? cyl->end : cyl->sample_end; - if (!end.mbar || start.mbar <= end.mbar) - continue; - - airuse += gas_volume(cyl, start) - gas_volume(cyl, end); - } - return airuse / 1000.0; -} - -/* this only uses the first divecomputer to calculate the SAC rate */ -static int calculate_sac(struct dive *dive) -{ - struct divecomputer *dc = &dive->dc; - double airuse, pressure, sac; - int duration, meandepth; - - airuse = calculate_airuse(dive); - if (!airuse) - return 0; - - duration = dc->duration.seconds; - if (!duration) - return 0; - - meandepth = dc->meandepth.mm; - if (!meandepth) - return 0; - - /* Mean pressure in ATM (SAC calculations are in atm*l/min) */ - pressure = depth_to_atm(meandepth, dive); - sac = airuse / pressure * 60 / duration; - - /* milliliters per minute.. */ - return sac * 1000; -} - -/* for now we do this based on the first divecomputer */ -static void add_dive_to_deco(struct dive *dive) -{ - struct divecomputer *dc = &dive->dc; - int i; - - if (!dc) - return; - for (i = 1; i < dc->samples; i++) { - struct sample *psample = dc->sample + i - 1; - struct sample *sample = dc->sample + i; - int t0 = psample->time.seconds; - int t1 = sample->time.seconds; - int j; - - for (j = t0; j < t1; j++) { - int depth = interpolate(psample->depth.mm, sample->depth.mm, j - t0, t1 - t0); - add_segment(depth_to_bar(depth, dive), - &dive->cylinder[sample->sensor].gasmix, 1, sample->setpoint.mbar, dive, dive->sac); - } - } -} - -int get_divenr(struct dive *dive) -{ - int i; - struct dive *d; - // tempting as it may be, don't die when called with dive=NULL - if (dive) - for_each_dive(i, d) { - if (d->id == dive->id) // don't compare pointers, we could be passing in a copy of the dive - return i; - } - return -1; -} - -int get_divesite_idx(struct dive_site *ds) -{ - int i; - struct dive_site *d; - // tempting as it may be, don't die when called with dive=NULL - if (ds) - for_each_dive_site(i, d) { - if (d->uuid == ds->uuid) // don't compare pointers, we could be passing in a copy of the dive - return i; - } - return -1; -} - -static struct gasmix air = { .o2.permille = O2_IN_AIR, .he.permille = 0 }; - -/* take into account previous dives until there is a 48h gap between dives */ -double init_decompression(struct dive *dive) -{ - int i, divenr = -1; - unsigned int surface_time; - timestamp_t when, lasttime = 0, laststart = 0; - bool deco_init = false; - double surface_pressure; - - if (!dive) - return 0.0; - - surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; - divenr = get_divenr(dive); - when = dive->when; - i = divenr; - if (i < 0) { - i = dive_table.nr - 1; - while (i >= 0 && get_dive(i)->when > when) - --i; - i++; - } - while (i--) { - struct dive *pdive = get_dive(i); - /* we don't want to mix dives from different trips as we keep looking - * for how far back we need to go */ - if (dive->divetrip && pdive->divetrip != dive->divetrip) - continue; - if (!pdive || pdive->when >= when || pdive->when + pdive->duration.seconds + 48 * 60 * 60 < when) - break; - /* For simultaneous dives, only consider the first */ - if (pdive->when == laststart) - continue; - when = pdive->when; - lasttime = when + pdive->duration.seconds; - } - while (++i < (divenr >= 0 ? divenr : dive_table.nr)) { - struct dive *pdive = get_dive(i); - /* again skip dives from different trips */ - if (dive->divetrip && dive->divetrip != pdive->divetrip) - continue; - surface_pressure = get_surface_pressure_in_mbar(pdive, true) / 1000.0; - if (!deco_init) { - clear_deco(surface_pressure); - deco_init = true; -#if DECO_CALC_DEBUG & 2 - dump_tissues(); -#endif - } - add_dive_to_deco(pdive); - laststart = pdive->when; -#if DECO_CALC_DEBUG & 2 - printf("added dive #%d\n", pdive->number); - dump_tissues(); -#endif - if (pdive->when > lasttime) { - surface_time = pdive->when - lasttime; - lasttime = pdive->when + pdive->duration.seconds; - add_segment(surface_pressure, &air, surface_time, 0, dive, prefs.decosac); -#if DECO_CALC_DEBUG & 2 - printf("after surface intervall of %d:%02u\n", FRACTION(surface_time, 60)); - dump_tissues(); -#endif - } - } - /* add the final surface time */ - if (lasttime && dive->when > lasttime) { - surface_time = dive->when - lasttime; - surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; - add_segment(surface_pressure, &air, surface_time, 0, dive, prefs.decosac); -#if DECO_CALC_DEBUG & 2 - printf("after surface intervall of %d:%02u\n", FRACTION(surface_time, 60)); - dump_tissues(); -#endif - } - if (!deco_init) { - surface_pressure = get_surface_pressure_in_mbar(dive, true) / 1000.0; - clear_deco(surface_pressure); -#if DECO_CALC_DEBUG & 2 - printf("no previous dive\n"); - dump_tissues(); -#endif - } - return tissue_tolerance_calc(dive, surface_pressure); -} - -void update_cylinder_related_info(struct dive *dive) -{ - if (dive != NULL) { - dive->sac = calculate_sac(dive); - dive->otu = calculate_otu(dive); - if (dive->maxcns == 0) - dive->maxcns = calculate_cns(dive); - } -} - -#define MAX_GAS_STRING 80 -#define UTF8_ELLIPSIS "\xE2\x80\xA6" - -/* callers needs to free the string */ -char *get_dive_gas_string(struct dive *dive) -{ - int o2, he, o2max; - char *buffer = malloc(MAX_GAS_STRING); - - if (buffer) { - get_dive_gas(dive, &o2, &he, &o2max); - o2 = (o2 + 5) / 10; - he = (he + 5) / 10; - o2max = (o2max + 5) / 10; - - if (he) - if (o2 == o2max) - snprintf(buffer, MAX_GAS_STRING, "%d/%d", o2, he); - else - snprintf(buffer, MAX_GAS_STRING, "%d/%d" UTF8_ELLIPSIS "%d%%", o2, he, o2max); - else if (o2) - if (o2 == o2max) - snprintf(buffer, MAX_GAS_STRING, "%d%%", o2); - else - snprintf(buffer, MAX_GAS_STRING, "%d" UTF8_ELLIPSIS "%d%%", o2, o2max); - else - strcpy(buffer, translate("gettextFromC", "air")); - } - return buffer; -} - -/* - * helper functions for dive_trip handling - */ -#ifdef DEBUG_TRIP -void dump_trip_list(void) -{ - dive_trip_t *trip; - int i = 0; - timestamp_t last_time = 0; - - for (trip = dive_trip_list; trip; trip = trip->next) { - struct tm tm; - utc_mkdate(trip->when, &tm); - if (trip->when < last_time) - printf("\n\ndive_trip_list OUT OF ORDER!!!\n\n\n"); - printf("%s trip %d to \"%s\" on %04u-%02u-%02u %02u:%02u:%02u (%d dives - %p)\n", - trip->autogen ? "autogen " : "", - ++i, trip->location, - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, - trip->nrdives, trip); - last_time = trip->when; - } - printf("-----\n"); -} -#endif - -/* this finds the last trip that at or before the time given */ -dive_trip_t *find_matching_trip(timestamp_t when) -{ - dive_trip_t *trip = dive_trip_list; - - if (!trip || trip->when > when) { -#ifdef DEBUG_TRIP - printf("no matching trip\n"); -#endif - return NULL; - } - while (trip->next && trip->next->when <= when) - trip = trip->next; -#ifdef DEBUG_TRIP - { - struct tm tm; - utc_mkdate(trip->when, &tm); - printf("found trip %p @ %04d-%02d-%02d %02d:%02d:%02d\n", - trip, - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec); - } -#endif - return trip; -} - -/* insert the trip into the dive_trip_list - but ensure you don't have - * two trips for the same date; but if you have, make sure you don't - * keep the one with less information */ -void insert_trip(dive_trip_t **dive_trip_p) -{ - dive_trip_t *dive_trip = *dive_trip_p; - dive_trip_t **p = &dive_trip_list; - dive_trip_t *trip; - struct dive *divep; - - /* Walk the dive trip list looking for the right location.. */ - while ((trip = *p) != NULL && trip->when < dive_trip->when) - p = &trip->next; - - if (trip && trip->when == dive_trip->when) { - if (!trip->location) - trip->location = dive_trip->location; - if (!trip->notes) - trip->notes = dive_trip->notes; - divep = dive_trip->dives; - while (divep) { - add_dive_to_trip(divep, trip); - divep = divep->next; - } - *dive_trip_p = trip; - } else { - dive_trip->next = trip; - *p = dive_trip; - } -#ifdef DEBUG_TRIP - dump_trip_list(); -#endif -} - -static void delete_trip(dive_trip_t *trip) -{ - dive_trip_t **p, *tmp; - - assert(!trip->dives); - - /* Remove the trip from the list of trips */ - p = &dive_trip_list; - while ((tmp = *p) != NULL) { - if (tmp == trip) { - *p = trip->next; - break; - } - p = &tmp->next; - } - - /* .. and free it */ - free(trip->location); - free(trip->notes); - free(trip); -} - -void find_new_trip_start_time(dive_trip_t *trip) -{ - struct dive *dive = trip->dives; - timestamp_t when = dive->when; - - while ((dive = dive->next) != NULL) { - if (dive->when < when) - when = dive->when; - } - trip->when = when; -} - -/* check if we have a trip right before / after this dive */ -bool is_trip_before_after(struct dive *dive, bool before) -{ - int idx = get_idx_by_uniq_id(dive->id); - if (before) { - if (idx > 0 && get_dive(idx - 1)->divetrip) - return true; - } else { - if (idx < dive_table.nr - 1 && get_dive(idx + 1)->divetrip) - return true; - } - return false; -} - -struct dive *first_selected_dive() -{ - int idx; - struct dive *d; - - for_each_dive (idx, d) { - if (d->selected) - return d; - } - return NULL; -} - -struct dive *last_selected_dive() -{ - int idx; - struct dive *d, *ret = NULL; - - for_each_dive (idx, d) { - if (d->selected) - ret = d; - } - return ret; -} - -void remove_dive_from_trip(struct dive *dive, short was_autogen) -{ - struct dive *next, **pprev; - dive_trip_t *trip = dive->divetrip; - - if (!trip) - return; - - /* Remove the dive from the trip's list of dives */ - next = dive->next; - pprev = dive->pprev; - *pprev = next; - if (next) - next->pprev = pprev; - - dive->divetrip = NULL; - if (was_autogen) - dive->tripflag = TF_NONE; - else - dive->tripflag = NO_TRIP; - assert(trip->nrdives > 0); - if (!--trip->nrdives) - delete_trip(trip); - else if (trip->when == dive->when) - find_new_trip_start_time(trip); -} - -void add_dive_to_trip(struct dive *dive, dive_trip_t *trip) -{ - if (dive->divetrip == trip) - return; - remove_dive_from_trip(dive, false); - trip->nrdives++; - dive->divetrip = trip; - dive->tripflag = ASSIGNED_TRIP; - - /* Add it to the trip's list of dives*/ - dive->next = trip->dives; - if (dive->next) - dive->next->pprev = &dive->next; - trip->dives = dive; - dive->pprev = &trip->dives; - - if (dive->when && trip->when > dive->when) - trip->when = dive->when; -} - -dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive) -{ - dive_trip_t *dive_trip = calloc(1, sizeof(dive_trip_t)); - - dive_trip->when = dive->when; - dive_trip->location = copy_string(get_dive_location(dive)); - insert_trip(&dive_trip); - - dive->tripflag = IN_TRIP; - add_dive_to_trip(dive, dive_trip); - return dive_trip; -} - -/* - * Walk the dives from the oldest dive, and see if we can autogroup them - */ -void autogroup_dives(void) -{ - int i; - struct dive *dive, *lastdive = NULL; - - for_each_dive(i, dive) { - dive_trip_t *trip; - - if (dive->divetrip) { - lastdive = dive; - continue; - } - - if (!DIVE_NEEDS_TRIP(dive)) { - lastdive = NULL; - continue; - } - - /* Do we have a trip we can combine this into? */ - if (lastdive && dive->when < lastdive->when + TRIP_THRESHOLD) { - dive_trip_t *trip = lastdive->divetrip; - add_dive_to_trip(dive, trip); - if (get_dive_location(dive) && !trip->location) - trip->location = copy_string(get_dive_location(dive)); - lastdive = dive; - continue; - } - - lastdive = dive; - trip = create_and_hookup_trip_from_dive(dive); - trip->autogen = 1; - } - -#ifdef DEBUG_TRIP - dump_trip_list(); -#endif -} - -/* this implements the mechanics of removing the dive from the table, - * but doesn't deal with updating dive trips, etc */ -void delete_single_dive(int idx) -{ - int i; - struct dive *dive = get_dive(idx); - if (!dive) - return; /* this should never happen */ - remove_dive_from_trip(dive, false); - if (dive->selected) - deselect_dive(idx); - for (i = idx; i < dive_table.nr - 1; i++) - dive_table.dives[i] = dive_table.dives[i + 1]; - dive_table.dives[--dive_table.nr] = NULL; - /* free all allocations */ - free(dive->dc.sample); - free((void *)dive->notes); - free((void *)dive->divemaster); - free((void *)dive->buddy); - free((void *)dive->suit); - taglist_free(dive->tag_list); - free(dive); -} - -struct dive **grow_dive_table(struct dive_table *table) -{ - int nr = table->nr, allocated = table->allocated; - struct dive **dives = table->dives; - - if (nr >= allocated) { - allocated = (nr + 32) * 3 / 2; - dives = realloc(dives, allocated * sizeof(struct dive *)); - if (!dives) - exit(1); - table->dives = dives; - table->allocated = allocated; - } - return dives; -} - -void add_single_dive(int idx, struct dive *dive) -{ - int i; - grow_dive_table(&dive_table); - dive_table.nr++; - if (dive->selected) - amount_selected++; - - if (idx < 0) { - // convert an idx of -1 so we do insert-in-chronological-order - idx = dive_table.nr - 1; - for (int i = 0; i < dive_table.nr - 1; i++) { - if (dive->when <= dive_table.dives[i]->when) { - idx = i; - break; - } - } - } - - for (i = idx; i < dive_table.nr; i++) { - struct dive *tmp = dive_table.dives[i]; - dive_table.dives[i] = dive; - dive = tmp; - } -} - -bool consecutive_selected() -{ - struct dive *d; - int i; - bool consecutive = true; - bool firstfound = false; - bool lastfound = false; - - if (amount_selected == 0 || amount_selected == 1) - return true; - - for_each_dive(i, d) { - if (d->selected) { - if (!firstfound) - firstfound = true; - else if (lastfound) - consecutive = false; - } else if (firstfound) { - lastfound = true; - } - } - return consecutive; -} - -/* - * Merge two dives. 'a' is always before 'b' in the dive list - * (and thus in time). - */ -struct dive *merge_two_dives(struct dive *a, struct dive *b) -{ - struct dive *res; - int i, j, nr, nrdiff; - int id; - - if (!a || !b) - return NULL; - - id = a->id; - i = get_divenr(a); - j = get_divenr(b); - if (i < 0 || j < 0) - // something is wrong with those dives. Bail - return NULL; - res = merge_dives(a, b, b->when - a->when, false); - if (!res) - return NULL; - - /* - * If 'a' and 'b' were numbered, and in proper order, - * then the resulting dive will get the first number, - * and the subsequent dives will be renumbered by the - * difference. - * - * So if you had a dive list 1 3 6 7 8, and you - * merge 1 and 3, the resulting numbered list will - * be 1 4 5 6, because we assume that there were - * some missing dives (originally dives 4 and 5), - * that now will still be missing (dives 2 and 3 - * in the renumbered world). - * - * Obviously the normal case is that everything is - * consecutive, and the difference will be 1, so the - * above example is not supposed to be normal. - */ - nrdiff = 0; - nr = a->number; - if (a->number && b->number > a->number) { - res->number = nr; - nrdiff = b->number - nr; - } - - add_single_dive(i, res); - delete_single_dive(i + 1); - delete_single_dive(j); - // now make sure that we keep the id of the first dive. - // why? - // because this way one of the previously selected ids is still around - res->id = id; - - // renumber dives from merged one in advance by difference between - // merged dives numbers. Do not renumber if actual number is zero. - for (; j < dive_table.nr; j++) { - struct dive *dive = dive_table.dives[j]; - int newnr; - - if (!dive->number) - continue; - newnr = dive->number - nrdiff; - - /* - * Don't renumber stuff that isn't in order! - * - * So if the new dive number isn't larger than the - * previous dive number, just stop here. - */ - if (newnr <= nr) - break; - dive->number = newnr; - nr = newnr; - } - - mark_divelist_changed(true); - return res; -} - -void select_dive(int idx) -{ - struct dive *dive = get_dive(idx); - if (dive) { - /* never select an invalid dive that isn't displayed */ - if (!dive->selected) { - dive->selected = 1; - amount_selected++; - } - selected_dive = idx; - } -} - -void deselect_dive(int idx) -{ - struct dive *dive = get_dive(idx); - if (dive && dive->selected) { - dive->selected = 0; - if (amount_selected) - amount_selected--; - if (selected_dive == idx && amount_selected > 0) { - /* pick a different dive as selected */ - while (--selected_dive >= 0) { - dive = get_dive(selected_dive); - if (dive && dive->selected) - return; - } - selected_dive = idx; - while (++selected_dive < dive_table.nr) { - dive = get_dive(selected_dive); - if (dive && dive->selected) - return; - } - } - if (amount_selected == 0) - selected_dive = -1; - } -} - -void deselect_dives_in_trip(struct dive_trip *trip) -{ - struct dive *dive; - if (!trip) - return; - for (dive = trip->dives; dive; dive = dive->next) - deselect_dive(get_divenr(dive)); -} - -void select_dives_in_trip(struct dive_trip *trip) -{ - struct dive *dive; - if (!trip) - return; - for (dive = trip->dives; dive; dive = dive->next) - if (!dive->hidden_by_filter) - select_dive(get_divenr(dive)); -} - -void filter_dive(struct dive *d, bool shown) -{ - if (!d) - return; - d->hidden_by_filter = !shown; - if (!shown && d->selected) - deselect_dive(get_divenr(d)); -} - - -/* This only gets called with non-NULL trips. - * It does not combine notes or location, just picks the first one - * (or the second one if the first one is empty */ -void combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b) -{ - if (same_string(trip_a->location, "") && trip_b->location) { - free(trip_a->location); - trip_a->location = strdup(trip_b->location); - } - if (same_string(trip_a->notes, "") && trip_b->notes) { - free(trip_a->notes); - trip_a->notes = strdup(trip_b->notes); - } - /* this also removes the dives from trip_b and eventually - * calls delete_trip(trip_b) when the last dive has been moved */ - while (trip_b->dives) - add_dive_to_trip(trip_b->dives, trip_a); -} - -void mark_divelist_changed(int changed) -{ - dive_list_changed = changed; - updateWindowTitle(); -} - -int unsaved_changes() -{ - return dive_list_changed; -} - -void remove_autogen_trips() -{ - int i; - struct dive *dive; - - for_each_dive(i, dive) { - dive_trip_t *trip = dive->divetrip; - - if (trip && trip->autogen) - remove_dive_from_trip(dive, true); - } -} - -/* - * When adding dives to the dive table, we try to renumber - * the new dives based on any old dives in the dive table. - * - * But we only do it if: - * - * - there are no dives in the dive table - * - * OR - * - * - the last dive in the old dive table was numbered - * - * - all the new dives are strictly at the end (so the - * "last dive" is at the same location in the dive table - * after re-sorting the dives. - * - * - none of the new dives have any numbers - * - * This catches the common case of importing new dives from - * a dive computer, and gives them proper numbers based on - * your old dive list. But it tries to be very conservative - * and not give numbers if there is *any* question about - * what the numbers should be - in which case you need to do - * a manual re-numbering. - */ -static void try_to_renumber(struct dive *last, int preexisting) -{ - int i, nr; - - /* - * If the new dives aren't all strictly at the end, - * we're going to expect the user to do a manual - * renumbering. - */ - if (preexisting && get_dive(preexisting - 1) != last) - return; - - /* - * If any of the new dives already had a number, - * we'll have to do a manual renumbering. - */ - for (i = preexisting; i < dive_table.nr; i++) { - struct dive *dive = get_dive(i); - if (dive->number) - return; - } - - /* - * Ok, renumber.. - */ - if (last) - nr = last->number; - else - nr = 0; - for (i = preexisting; i < dive_table.nr; i++) { - struct dive *dive = get_dive(i); - dive->number = ++nr; - } -} - -void process_dives(bool is_imported, bool prefer_imported) -{ - int i; - int preexisting = dive_table.preexisting; - bool did_merge = false; - struct dive *last; - - /* check if we need a nickname for the divecomputer for newly downloaded dives; - * since we know they all came from the same divecomputer we just check for the - * first one */ - if (preexisting < dive_table.nr && dive_table.dives[preexisting]->downloaded) - set_dc_nickname(dive_table.dives[preexisting]); - else - /* they aren't downloaded, so record / check all new ones */ - for (i = preexisting; i < dive_table.nr; i++) - set_dc_nickname(dive_table.dives[i]); - - for (i = preexisting; i < dive_table.nr; i++) - dive_table.dives[i]->downloaded = true; - - /* This does the right thing for -1: NULL */ - last = get_dive(preexisting - 1); - - sort_table(&dive_table); - - for (i = 1; i < dive_table.nr; i++) { - struct dive **pp = &dive_table.dives[i - 1]; - struct dive *prev = pp[0]; - struct dive *dive = pp[1]; - struct dive *merged; - int id; - - /* only try to merge overlapping dives - or if one of the dives has - * zero duration (that might be a gps marker from the webservice) */ - if (prev->duration.seconds && dive->duration.seconds && - prev->when + prev->duration.seconds < dive->when) - continue; - - merged = try_to_merge(prev, dive, prefer_imported); - if (!merged) - continue; - - // remember the earlier dive's id - id = prev->id; - - /* careful - we might free the dive that last points to. Oops... */ - if (last == prev || last == dive) - last = merged; - - /* Redo the new 'i'th dive */ - i--; - add_single_dive(i, merged); - delete_single_dive(i + 1); - delete_single_dive(i + 1); - // keep the id or the first dive for the merged dive - merged->id = id; - - /* this means the table was changed */ - did_merge = true; - } - /* make sure no dives are still marked as downloaded */ - for (i = 1; i < dive_table.nr; i++) - dive_table.dives[i]->downloaded = false; - - if (is_imported) { - /* If there are dives in the table, are they numbered */ - if (!last || last->number) - try_to_renumber(last, preexisting); - - /* did we add dives or divecomputers to the dive table? */ - if (did_merge || preexisting < dive_table.nr) { - mark_divelist_changed(true); - } - } -} - -void set_dive_nr_for_current_dive() -{ - if (dive_table.nr == 1) - current_dive->number = 1; - else if (selected_dive == dive_table.nr - 1 && get_dive(dive_table.nr - 2)->number) - current_dive->number = get_dive(dive_table.nr - 2)->number + 1; -} - -static int min_datafile_version; - -int get_min_datafile_version() -{ - return min_datafile_version; -} - -void reset_min_datafile_version() -{ - min_datafile_version = 0; -} - -void report_datafile_version(int version) -{ - if (min_datafile_version == 0 || min_datafile_version > version) - min_datafile_version = version; -} - -void clear_dive_file_data() -{ - while (dive_table.nr) - delete_single_dive(0); - while (dive_site_table.nr) - delete_dive_site(get_dive_site(0)->uuid); - - clear_dive(&displayed_dive); - clear_dive_site(&displayed_dive_site); - - free((void *)existing_filename); - existing_filename = NULL; - - reset_min_datafile_version(); - saved_git_id = ""; -} diff --git a/subsurface-core/divelist.h b/subsurface-core/divelist.h deleted file mode 100644 index 5bae09cff..000000000 --- a/subsurface-core/divelist.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef DIVELIST_H -#define DIVELIST_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* this is used for both git and xml format */ -#define DATAFORMAT_VERSION 3 - -struct dive; - -extern void update_cylinder_related_info(struct dive *); -extern void mark_divelist_changed(int); -extern int unsaved_changes(void); -extern void remove_autogen_trips(void); -extern double init_decompression(struct dive *dive); - -/* divelist core logic functions */ -extern void process_dives(bool imported, bool prefer_imported); -extern char *get_dive_gas_string(struct dive *dive); - -extern dive_trip_t *find_trip_by_idx(int idx); - -struct dive **grow_dive_table(struct dive_table *table); -extern int trip_has_selected_dives(dive_trip_t *trip); -extern void get_dive_gas(struct dive *dive, int *o2_p, int *he_p, int *o2low_p); -extern int get_divenr(struct dive *dive); -extern int get_divesite_idx(struct dive_site *ds); -extern dive_trip_t *find_matching_trip(timestamp_t when); -extern void remove_dive_from_trip(struct dive *dive, short was_autogen); -extern dive_trip_t *create_and_hookup_trip_from_dive(struct dive *dive); -extern void autogroup_dives(void); -extern struct dive *merge_two_dives(struct dive *a, struct dive *b); -extern bool consecutive_selected(); -extern void select_dive(int idx); -extern void deselect_dive(int idx); -extern void select_dives_in_trip(struct dive_trip *trip); -extern void deselect_dives_in_trip(struct dive_trip *trip); -extern void filter_dive(struct dive *d, bool shown); -extern void combine_trips(struct dive_trip *trip_a, struct dive_trip *trip_b); -extern void find_new_trip_start_time(dive_trip_t *trip); -extern struct dive *first_selected_dive(); -extern struct dive *last_selected_dive(); -extern bool is_trip_before_after(struct dive *dive, bool before); -extern void set_dive_nr_for_current_dive(); - -int get_min_datafile_version(); -void reset_min_datafile_version(); -void report_datafile_version(int version); -void clear_dive_file_data(); - -#ifdef DEBUG_TRIP -extern void dump_selection(void); -extern void dump_trip_list(void); -#endif - -#ifdef __cplusplus -} -#endif - -#endif // DIVELIST_H diff --git a/subsurface-core/divelogexportlogic.cpp b/subsurface-core/divelogexportlogic.cpp deleted file mode 100644 index af5157f4a..000000000 --- a/subsurface-core/divelogexportlogic.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include -#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 deleted file mode 100644 index 84f09c362..000000000 --- a/subsurface-core/divelogexportlogic.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef DIVELOGEXPORTLOGIC_H -#define DIVELOGEXPORTLOGIC_H - -struct htmlExportSetting { - bool exportPhotos; - bool selectedOnly; - bool listOnly; - QString fontFamily; - QString fontSize; - int themeSelection; - bool subsurfaceNumbers; - bool yearlyStatistics; - QString themeFile; -}; - -void file_copy_and_overwrite(const QString &fileName, const QString &newName); -void exportHtmlInitLogic(const QString &filename, struct htmlExportSetting &hes); - -#endif // DIVELOGEXPORTLOGIC_H - diff --git a/subsurface-core/divesite.c b/subsurface-core/divesite.c deleted file mode 100644 index e9eed2a07..000000000 --- a/subsurface-core/divesite.c +++ /dev/null @@ -1,337 +0,0 @@ -/* divesite.c */ -#include "divesite.h" -#include "dive.h" -#include "divelist.h" - -#include - -struct dive_site_table dive_site_table; - -/* there could be multiple sites of the same name - return the first one */ -uint32_t get_dive_site_uuid_by_name(const char *name, struct dive_site **dsp) -{ - int i; - struct dive_site *ds; - for_each_dive_site (i, ds) { - if (same_string(ds->name, name)) { - if (dsp) - *dsp = ds; - return ds->uuid; - } - } - return 0; -} - -/* there could be multiple sites at the same GPS fix - return the first one */ -uint32_t get_dive_site_uuid_by_gps(degrees_t latitude, degrees_t longitude, struct dive_site **dsp) -{ - int i; - struct dive_site *ds; - for_each_dive_site (i, ds) { - if (ds->latitude.udeg == latitude.udeg && ds->longitude.udeg == longitude.udeg) { - if (dsp) - *dsp = ds; - return ds->uuid; - } - } - return 0; -} - - -/* to avoid a bug where we have two dive sites with different name and the same GPS coordinates - * and first get the gps coordinates (reading a V2 file) and happen to get back "the other" name, - * this function allows us to verify if a very specific name/GPS combination already exists */ -uint32_t get_dive_site_uuid_by_gps_and_name(char *name, degrees_t latitude, degrees_t longitude) -{ - int i; - struct dive_site *ds; - for_each_dive_site (i, ds) { - if (ds->latitude.udeg == latitude.udeg && ds->longitude.udeg == longitude.udeg && same_string(ds->name, name)) - return ds->uuid; - } - return 0; -} - -// Calculate the distance in meters between two coordinates. -unsigned int get_distance(degrees_t lat1, degrees_t lon1, degrees_t lat2, degrees_t lon2) -{ - double lat2_r = udeg_to_radians(lat2.udeg); - double lat_d_r = udeg_to_radians(lat2.udeg-lat1.udeg); - double lon_d_r = udeg_to_radians(lon2.udeg-lon1.udeg); - - double a = sin(lat_d_r/2) * sin(lat_d_r/2) + - cos(lat2_r) * cos(lat2_r) * sin(lon_d_r/2) * sin(lon_d_r/2); - double c = 2 * atan2(sqrt(a), sqrt(1.0 - a)); - - // Earth radious in metres - return 6371000 * c; -} - -/* find the closest one, no more than distance meters away - if more than one at same distance, pick the first */ -uint32_t get_dive_site_uuid_by_gps_proximity(degrees_t latitude, degrees_t longitude, int distance, struct dive_site **dsp) -{ - int i; - int uuid = 0; - struct dive_site *ds; - unsigned int cur_distance, min_distance = distance; - for_each_dive_site (i, ds) { - if (dive_site_has_gps_location(ds) && - (cur_distance = get_distance(ds->latitude, ds->longitude, latitude, longitude)) < min_distance) { - min_distance = cur_distance; - uuid = ds->uuid; - if (dsp) - *dsp = ds; - } - } - return uuid; -} - -/* try to create a uniqe ID - fingers crossed */ -static uint32_t dive_site_getUniqId() -{ - uint32_t id = 0; - - while (id == 0 || get_dive_site_by_uuid(id)) { - id = rand() & 0xff; - id |= (rand() & 0xff) << 8; - id |= (rand() & 0xff) << 16; - id |= (rand() & 0xff) << 24; - } - - return id; -} - -/* we never allow a second dive site with the same uuid */ -struct dive_site *alloc_or_get_dive_site(uint32_t uuid) -{ - struct dive_site *ds; - if (uuid) { - if ((ds = get_dive_site_by_uuid(uuid)) != NULL) { - fprintf(stderr, "PROBLEM: refusing to create dive site with the same uuid %08x\n", uuid); - return ds; - } - } - int nr = dive_site_table.nr; - int allocated = dive_site_table.allocated; - struct dive_site **sites = dive_site_table.dive_sites; - - if (nr >= allocated) { - allocated = (nr + 32) * 3 / 2; - sites = realloc(sites, allocated * sizeof(struct dive_site *)); - if (!sites) - exit(1); - dive_site_table.dive_sites = sites; - dive_site_table.allocated = allocated; - } - ds = calloc(1, sizeof(*ds)); - if (!ds) - exit(1); - sites[nr] = ds; - dive_site_table.nr = nr + 1; - // we should always be called with a valid uuid except in the special - // case where we want to copy a dive site into the memory we allocated - // here - then we need to pass in 0 and create a temporary uuid here - // (just so things are always consistent) - if (uuid) - ds->uuid = uuid; - else - ds->uuid = dive_site_getUniqId(); - return ds; -} - -int nr_of_dives_at_dive_site(uint32_t uuid, bool select_only) -{ - int j; - int nr = 0; - struct dive *d; - for_each_dive(j, d) { - if (d->dive_site_uuid == uuid && (!select_only || d->selected)) { - nr++; - } - } - return nr; -} - -bool is_dive_site_used(uint32_t uuid, bool select_only) -{ - int j; - bool found = false; - struct dive *d; - for_each_dive(j, d) { - if (d->dive_site_uuid == uuid && (!select_only || d->selected)) { - found = true; - break; - } - } - return found; -} - -void delete_dive_site(uint32_t id) -{ - int nr = dive_site_table.nr; - for (int i = 0; i < nr; i++) { - struct dive_site *ds = get_dive_site(i); - if (ds->uuid == id) { - free(ds->name); - free(ds->notes); - free(ds); - if (nr - 1 > i) - memmove(&dive_site_table.dive_sites[i], - &dive_site_table.dive_sites[i+1], - (nr - 1 - i) * sizeof(dive_site_table.dive_sites[0])); - dive_site_table.nr = nr - 1; - break; - } - } -} - -uint32_t create_divesite_uuid(const char *name, timestamp_t divetime) -{ - if (name == NULL) - name =""; - unsigned char hash[20]; - SHA_CTX ctx; - SHA1_Init(&ctx); - SHA1_Update(&ctx, &divetime, sizeof(timestamp_t)); - SHA1_Update(&ctx, name, strlen(name)); - SHA1_Final(hash, &ctx); - // now return the first 32 of the 160 bit hash - return *(uint32_t *)hash; -} - -/* allocate a new site and add it to the table */ -uint32_t create_dive_site(const char *name, timestamp_t divetime) -{ - uint32_t uuid = create_divesite_uuid(name, divetime); - struct dive_site *ds = alloc_or_get_dive_site(uuid); - ds->name = copy_string(name); - - return uuid; -} - -/* same as before, but with current time if no current_dive is present */ -uint32_t create_dive_site_from_current_dive(const char *name) -{ - if (current_dive != NULL) { - return create_dive_site(name, current_dive->when); - } else { - timestamp_t when; - time_t now = time(0); - when = utc_mktime(localtime(&now)); - return create_dive_site(name, when); - } -} - -/* same as before, but with GPS data */ -uint32_t create_dive_site_with_gps(const char *name, degrees_t latitude, degrees_t longitude, timestamp_t divetime) -{ - uint32_t uuid = create_divesite_uuid(name, divetime); - struct dive_site *ds = alloc_or_get_dive_site(uuid); - ds->name = copy_string(name); - ds->latitude = latitude; - ds->longitude = longitude; - - return ds->uuid; -} - -/* a uuid is always present - but if all the other fields are empty, the dive site is pointless */ -bool dive_site_is_empty(struct dive_site *ds) -{ - return same_string(ds->name, "") && - same_string(ds->description, "") && - same_string(ds->notes, "") && - ds->latitude.udeg == 0 && - ds->longitude.udeg == 0; -} - -void copy_dive_site(struct dive_site *orig, struct dive_site *copy) -{ - free(copy->name); - free(copy->notes); - free(copy->description); - - copy->latitude = orig->latitude; - copy->longitude = orig->longitude; - copy->name = copy_string(orig->name); - copy->notes = copy_string(orig->notes); - copy->description = copy_string(orig->description); - copy->uuid = orig->uuid; - if (orig->taxonomy.category == NULL) { - free_taxonomy(©->taxonomy); - } else { - if (copy->taxonomy.category == NULL) - copy->taxonomy.category = alloc_taxonomy(); - for (int i = 0; i < TC_NR_CATEGORIES; i++) { - if (i < copy->taxonomy.nr) - free((void *)copy->taxonomy.category[i].value); - if (i < orig->taxonomy.nr) { - copy->taxonomy.category[i] = orig->taxonomy.category[i]; - copy->taxonomy.category[i].value = copy_string(orig->taxonomy.category[i].value); - } - } - copy->taxonomy.nr = orig->taxonomy.nr; - } -} - -void clear_dive_site(struct dive_site *ds) -{ - free(ds->name); - free(ds->notes); - free(ds->description); - ds->name = 0; - ds->notes = 0; - ds->description = 0; - ds->latitude.udeg = 0; - ds->longitude.udeg = 0; - ds->uuid = 0; - ds->taxonomy.nr = 0; - free_taxonomy(&ds->taxonomy); -} - -void merge_dive_sites(uint32_t ref, uint32_t* uuids, int count) -{ - int curr_dive, i; - struct dive *d; - for(i = 0; i < count; i++){ - if (uuids[i] == ref) - continue; - - for_each_dive(curr_dive, d) { - if (d->dive_site_uuid != uuids[i] ) - continue; - d->dive_site_uuid = ref; - } - } - - for(i = 0; i < count; i++) { - if (uuids[i] == ref) - continue; - delete_dive_site(uuids[i]); - } - mark_divelist_changed(true); -} - -uint32_t find_or_create_dive_site_with_name(const char *name, timestamp_t divetime) -{ - int i; - struct dive_site *ds; - for_each_dive_site(i,ds) { - if (same_string(name, ds->name)) - break; - } - if (ds) - return ds->uuid; - return create_dive_site(name, divetime); -} - -static int compare_sites(const void *_a, const void *_b) -{ - const struct dive_site *a = (const struct dive_site *)*(void **)_a; - const struct dive_site *b = (const struct dive_site *)*(void **)_b; - return a->uuid > b->uuid ? 1 : a->uuid == b->uuid ? 0 : -1; -} - -void dive_site_table_sort() -{ - qsort(dive_site_table.dive_sites, dive_site_table.nr, sizeof(struct dive_site *), compare_sites); -} diff --git a/subsurface-core/divesite.cpp b/subsurface-core/divesite.cpp deleted file mode 100644 index ae102a14b..000000000 --- a/subsurface-core/divesite.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "divesite.h" -#include "pref.h" - -QString constructLocationTags(uint32_t ds_uuid) -{ - QString locationTag; - struct dive_site *ds = get_dive_site_by_uuid(ds_uuid); - - if (!ds || !ds->taxonomy.nr) - return locationTag; - - locationTag = "(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 deleted file mode 100644 index f18b2e8e8..000000000 --- a/subsurface-core/divesite.h +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef DIVESITE_H -#define DIVESITE_H - -#include "units.h" -#include "taxonomy.h" -#include - -#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 deleted file mode 100644 index 3542f96fa..000000000 --- a/subsurface-core/divesitehelpers.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// -// infrastructure to deal with dive sites -// - -#include "divesitehelpers.h" - -#include "divesite.h" -#include "helpers.h" -#include "membuffer.h" -#include -#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 deleted file mode 100644 index a08069bc0..000000000 --- a/subsurface-core/divesitehelpers.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef DIVESITEHELPERS_H -#define DIVESITEHELPERS_H - -#include "units.h" -#include - -class ReverseGeoLookupThread : public QThread { -Q_OBJECT -public: - static ReverseGeoLookupThread *instance(); - void lookup(struct dive_site *ds); - void run() Q_DECL_OVERRIDE; - -private: - ReverseGeoLookupThread(QObject *parent = 0); -}; - -#endif // DIVESITEHELPERS_H diff --git a/subsurface-core/equipment.c b/subsurface-core/equipment.c deleted file mode 100644 index 9f3e49039..000000000 --- a/subsurface-core/equipment.c +++ /dev/null @@ -1,238 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -/* equipment.c */ -#include -#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 deleted file mode 100644 index 0b1cda2bc..000000000 --- a/subsurface-core/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/subsurface-core/exif.h b/subsurface-core/exif.h deleted file mode 100644 index 0fb3a7d4a..000000000 --- a/subsurface-core/exif.h +++ /dev/null @@ -1,147 +0,0 @@ -/************************************************************************** - exif.h -- A simple ISO C++ library to parse basic EXIF - information from a JPEG file. - - Based on the description of the EXIF file format at: - -- http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html - -- http://www.media.mit.edu/pia/Research/deepview/exif.html - -- http://www.exif.org/Exif2-2.PDF - - Copyright (c) 2010-2013 Mayank Lahiri - mlahiri@gmail.com - All rights reserved. - - VERSION HISTORY: - ================ - - 2.1: Released July 2013 - -- fixed a bug where JPEGs without an EXIF SubIFD would not be parsed - -- fixed a bug in parsing GPS coordinate seconds - -- fixed makefile bug - -- added two pathological test images from Matt Galloway - http://www.galloway.me.uk/2012/01/uiimageorientation-exif-orientation-sample-images/ - -- split main parsing routine for easier integration into Firefox - - 2.0: Released February 2013 - -- complete rewrite - -- no new/delete - -- added GPS support - - 1.0: Released 2010 - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - -- Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -- Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESS - OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#ifndef EXIF_H -#define EXIF_H - -#include - -// -// Class responsible for storing and parsing EXIF information from a JPEG blob -// -class EXIFInfo { -public: - // Parsing function for an entire JPEG image buffer. - // - // PARAM 'data': A pointer to a JPEG image. - // PARAM 'length': The length of the JPEG image. - // RETURN: PARSE_EXIF_SUCCESS (0) on succes with 'result' filled out - // error code otherwise, as defined by the PARSE_EXIF_ERROR_* macros - int parseFrom(const unsigned char *data, unsigned length); - int parseFrom(const std::string &data); - - // Parsing function for an EXIF segment. This is used internally by parseFrom() - // but can be called for special cases where only the EXIF section is - // available (i.e., a blob starting with the bytes "Exif\0\0"). - int parseFromEXIFSegment(const unsigned char *buf, unsigned len); - - // Set all data members to default values. - void clear(); - - // Data fields filled out by parseFrom() - char ByteAlign; // 0 = Motorola byte alignment, 1 = Intel - std::string ImageDescription; // Image description - std::string Make; // Camera manufacturer's name - std::string Model; // Camera model - unsigned short Orientation; // Image orientation, start of data corresponds to - // 0: unspecified in EXIF data - // 1: upper left of image - // 3: lower right of image - // 6: upper right of image - // 8: lower left of image - // 9: undefined - unsigned short BitsPerSample; // Number of bits per component - std::string Software; // Software used - std::string DateTime; // File change date and time - std::string DateTimeOriginal; // Original file date and time (may not exist) - std::string DateTimeDigitized; // Digitization date and time (may not exist) - std::string SubSecTimeOriginal; // Sub-second time that original picture was taken - std::string Copyright; // File copyright information - double ExposureTime; // Exposure time in seconds - double FNumber; // F/stop - unsigned short ISOSpeedRatings; // ISO speed - double ShutterSpeedValue; // Shutter speed (reciprocal of exposure time) - double ExposureBiasValue; // Exposure bias value in EV - double SubjectDistance; // Distance to focus point in meters - double FocalLength; // Focal length of lens in millimeters - unsigned short FocalLengthIn35mm; // Focal length in 35mm film - char Flash; // 0 = no flash, 1 = flash used - unsigned short MeteringMode; // Metering mode - // 1: average - // 2: center weighted average - // 3: spot - // 4: multi-spot - // 5: multi-segment - unsigned ImageWidth; // Image width reported in EXIF data - unsigned ImageHeight; // Image height reported in EXIF data - struct Geolocation_t - { // GPS information embedded in file - double Latitude; // Image latitude expressed as decimal - double Longitude; // Image longitude expressed as decimal - double Altitude; // Altitude in meters, relative to sea level - char AltitudeRef; // 0 = above sea level, -1 = below sea level - struct Coord_t { - double degrees; - double minutes; - double seconds; - char direction; - } LatComponents, LonComponents; // Latitude, Longitude expressed in deg/min/sec - } GeoLocation; - EXIFInfo() - { - clear(); - } - - time_t epoch(); -}; - -// Parse was successful -#define PARSE_EXIF_SUCCESS 0 -// No JPEG markers found in buffer, possibly invalid JPEG file -#define PARSE_EXIF_ERROR_NO_JPEG 1982 -// No EXIF header found in JPEG file. -#define PARSE_EXIF_ERROR_NO_EXIF 1983 -// Byte alignment specified in EXIF file was unknown (not Motorola or Intel). -#define PARSE_EXIF_ERROR_UNKNOWN_BYTEALIGN 1984 -// EXIF header was found, but data was corrupted. -#define PARSE_EXIF_ERROR_CORRUPT 1985 - -#endif // EXIF_H diff --git a/subsurface-core/file.c b/subsurface-core/file.c deleted file mode 100644 index 1337da3a2..000000000 --- a/subsurface-core/file.c +++ /dev/null @@ -1,1115 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "gettext.h" -#include -#include - -#include "dive.h" -#include "divelist.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 == (int)mem->size) // converting to int loses a bit but size will never be that big - goto out; - errno = EIO; - ret = -1; -free: - free(mem->buffer); - mem->buffer = NULL; - mem->size = 0; -out: - close(fd); - return ret; -} - - -static void zip_read(struct zip_file *file, const char *filename) -{ - int size = 1024, n, read = 0; - char *mem = malloc(size); - - while ((n = zip_fread(file, mem + read, size - read)) > 0) { - read += n; - size = read * 3 / 2; - mem = realloc(mem, size); - } - mem[read] = 0; - (void) parse_xml_buffer(filename, mem, read, &dive_table, NULL); - free(mem); -} - -int try_to_open_zip(const char *filename) -{ - int success = 0; - /* Grr. libzip needs to re-open the file, it can't take a buffer */ - struct zip *zip = subsurface_zip_open_readonly(filename, ZIP_CHECKCONS, NULL); - - if (zip) { - int index; - for (index = 0;; index++) { - struct zip_file *file = zip_fopen_index(zip, index, 0); - if (!file) - break; - /* skip parsing the divelogs.de pictures */ - if (strstr(zip_get_name(zip, index, 0), "pictures/")) - continue; - zip_read(file, filename); - zip_fclose(file); - success++; - } - subsurface_zip_close(zip); - - if (!success) - return report_error(translate("gettextFromC", "No dives in the input file '%s'"), filename); - } - return success; -} - -static int try_to_xslt_open_csv(const char *filename, struct memblock *mem, const char *tag) -{ - char *buf; - - if (mem->size == 0 && readfile(filename, mem) < 0) - return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); - - /* Surround the CSV file content with XML tags to enable XSLT - * parsing - * - * Tag markers take: strlen("<>") = 5 - */ - buf = realloc(mem->buffer, mem->size + 7 + strlen(tag) * 2); - if (buf != NULL) { - char *starttag = NULL; - char *endtag = NULL; - - starttag = malloc(3 + strlen(tag)); - endtag = malloc(5 + strlen(tag)); - - if (starttag == NULL || endtag == NULL) { - /* this is fairly silly - so the malloc fails, but we strdup the error? - * let's complete the silliness by freeing the two pointers in case one malloc succeeded - * and the other one failed - this will make static analysis tools happy */ - free(starttag); - free(endtag); - free(buf); - return report_error("Memory allocation failed in %s", __func__); - } - - sprintf(starttag, "<%s>", tag); - sprintf(endtag, "\n", tag); - - memmove(buf + 2 + strlen(tag), buf, mem->size); - memcpy(buf, starttag, 2 + strlen(tag)); - memcpy(buf + mem->size + 2 + strlen(tag), endtag, 5 + strlen(tag)); - mem->size += (6 + 2 * strlen(tag)); - mem->buffer = buf; - - free(starttag); - free(endtag); - } else { - free(mem->buffer); - return report_error("realloc failed in %s", __func__); - } - - return 0; -} - -int db_test_func(void *param, int columns, char **data, char **column) -{ - (void) param; - (void) columns; - (void) column; - return *data[0] == '0'; -} - - -static int try_to_open_db(const char *filename, struct memblock *mem) -{ - sqlite3 *handle; - char dm4_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%ProfileBlob%'"; - char dm5_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%SampleBlob%'"; - char shearwater_test[] = "select count(*) from sqlite_master where type='table' and name='system' and sql like '%dbVersion%'"; - char cobalt_test[] = "select count(*) from sqlite_master where type='table' and name='TrackPoints' and sql like '%DepthPressure%'"; - char divinglog_test[] = "select count(*) from sqlite_master where type='table' and name='DBInfo' and sql like '%PrgName%'"; - int retval; - - retval = sqlite3_open(filename, &handle); - - if (retval) { - fprintf(stderr, "Database connection failed '%s'.\n", filename); - return 1; - } - - /* Testing if DB schema resembles Suunto DM5 database format */ - retval = sqlite3_exec(handle, dm5_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_dm5_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - /* Testing if DB schema resembles Suunto DM4 database format */ - retval = sqlite3_exec(handle, dm4_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_dm4_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - /* Testing if DB schema resembles Shearwater database format */ - retval = sqlite3_exec(handle, shearwater_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_shearwater_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - /* Testing if DB schema resembles Atomic Cobalt database format */ - retval = sqlite3_exec(handle, cobalt_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_cobalt_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - /* Testing if DB schema resembles Divinglog database format */ - retval = sqlite3_exec(handle, divinglog_test, &db_test_func, 0, NULL); - if (!retval) { - retval = parse_divinglog_buffer(handle, filename, mem->buffer, mem->size, &dive_table); - sqlite3_close(handle); - return retval; - } - - sqlite3_close(handle); - - return retval; -} - -timestamp_t parse_date(const char *date) -{ - int hour, min, sec; - struct tm tm; - char *p; - - memset(&tm, 0, sizeof(tm)); - tm.tm_mday = strtol(date, &p, 10); - if (tm.tm_mday < 1 || tm.tm_mday > 31) - return 0; - for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) { - if (!memcmp(p, monthname(tm.tm_mon), 3)) - break; - } - if (tm.tm_mon > 11) - return 0; - date = p + 3; - tm.tm_year = strtol(date, &p, 10); - if (date == p) - return 0; - if (tm.tm_year < 70) - tm.tm_year += 2000; - if (tm.tm_year < 100) - tm.tm_year += 1900; - if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3) - return 0; - tm.tm_hour = hour; - tm.tm_min = min; - tm.tm_sec = sec; - return utc_mktime(&tm); -} - -enum csv_format { - CSV_DEPTH, - CSV_TEMP, - CSV_PRESSURE, - POSEIDON_DEPTH, - POSEIDON_TEMP, - POSEIDON_SETPOINT, - POSEIDON_SENSOR1, - POSEIDON_SENSOR2, - POSEIDON_PRESSURE, - POSEIDON_O2CYLINDER, - POSEIDON_NDL, - POSEIDON_CEILING -}; - -static void add_sample_data(struct sample *sample, enum csv_format type, double val) -{ - switch (type) { - case CSV_DEPTH: - sample->depth.mm = feet_to_mm(val); - break; - case CSV_TEMP: - sample->temperature.mkelvin = F_to_mkelvin(val); - break; - case CSV_PRESSURE: - sample->cylinderpressure.mbar = psi_to_mbar(val * 4); - break; - case POSEIDON_DEPTH: - sample->depth.mm = val * 0.5 *1000; - break; - case POSEIDON_TEMP: - sample->temperature.mkelvin = C_to_mkelvin(val * 0.2); - break; - case POSEIDON_SETPOINT: - sample->setpoint.mbar = val * 10; - break; - case POSEIDON_SENSOR1: - sample->o2sensor[0].mbar = val * 10; - break; - case POSEIDON_SENSOR2: - sample->o2sensor[1].mbar = val * 10; - break; - case POSEIDON_PRESSURE: - sample->cylinderpressure.mbar = val * 1000; - break; - case POSEIDON_O2CYLINDER: - sample->o2cylinderpressure.mbar = val * 1000; - break; - case POSEIDON_NDL: - sample->ndl.seconds = val * 60; - break; - case POSEIDON_CEILING: - sample->stopdepth.mm = val * 1000; - break; - } -} - -/* - * Cochran comma-separated values: depth in feet, temperature in F, pressure in psi. - * - * They start with eight comma-separated fields like: - * - * filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can} - * divenr: %d - * datetime: {03Sep11 16:37:22},{15Dec11 18:27:02} - * ??: 1 - * serialnr??: {CCI134},{CCI207} - * computer??: {GeminiII},{CommanderIII} - * computer??: {GeminiII},{CommanderIII} - * ??: 1 - * - * Followed by the data values (all comma-separated, all one long line). - */ -static int try_to_open_csv(struct memblock *mem, enum csv_format type) -{ - char *p = mem->buffer; - char *header[8]; - int i, time; - timestamp_t date; - struct dive *dive; - struct divecomputer *dc; - - for (i = 0; i < 8; i++) { - header[i] = p; - p = strchr(p, ','); - if (!p) - return 0; - p++; - } - - date = parse_date(header[2]); - if (!date) - return 0; - - dive = alloc_dive(); - dive->when = date; - dive->number = atoi(header[1]); - dc = &dive->dc; - - time = 0; - for (;;) { - char *end; - double val; - struct sample *sample; - - errno = 0; - val = strtod(p, &end); // FIXME == localization issue - if (end == p) - break; - if (errno) - break; - - sample = prepare_sample(dc); - sample->time.seconds = time; - add_sample_data(sample, type, val); - finish_sample(dc); - - time++; - dc->duration.seconds = time; - if (*end != ',') - break; - p = end + 1; - } - record_dive(dive); - return 1; -} - -static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem) -{ - // hack to be able to provide a comment for the translated string - static char *csv_warning = QT_TRANSLATE_NOOP3("gettextFromC", - "Cannot open CSV file %s; please use Import log file dialog", - "'Import log file' should be the same text as corresponding label in Import menu"); - - /* Suunto Dive Manager files: SDE, ZIP; divelogs.de files: DLD */ - if (!strcasecmp(fmt, "SDE") || !strcasecmp(fmt, "ZIP") || !strcasecmp(fmt, "DLD")) - return try_to_open_zip(filename); - - /* CSV files */ - if (!strcasecmp(fmt, "CSV")) - return report_error(translate("gettextFromC", csv_warning), filename); - /* Truly nasty intentionally obfuscated Cochran Anal software */ - if (!strcasecmp(fmt, "CAN")) - return try_to_open_cochran(filename, mem); - /* Cochran export comma-separated-value files */ - if (!strcasecmp(fmt, "DPT")) - return try_to_open_csv(mem, CSV_DEPTH); - if (!strcasecmp(fmt, "LVD")) - return try_to_open_liquivision(filename, mem); - if (!strcasecmp(fmt, "TMP")) - return try_to_open_csv(mem, CSV_TEMP); - if (!strcasecmp(fmt, "HP1")) - return try_to_open_csv(mem, CSV_PRESSURE); - - return 0; -} - -static int parse_file_buffer(const char *filename, struct memblock *mem) -{ - int ret; - char *fmt = strrchr(filename, '.'); - if (fmt && (ret = open_by_filename(filename, fmt + 1, mem)) != 0) - return ret; - - if (!mem->size || !mem->buffer) - return report_error("Out of memory parsing file %s\n", filename); - - return parse_xml_buffer(filename, mem->buffer, mem->size, &dive_table, NULL); -} - -int check_git_sha(const char *filename) -{ - struct git_repository *git; - const char *branch = NULL; - - git = is_git_repository(filename, &branch, NULL, false); - if (prefs.cloud_git_url && - strstr(filename, prefs.cloud_git_url) - && git == dummy_git_repository) - /* opening the cloud storage repository failed for some reason, - * so we don't know if there is additional data in the remote */ - return 1; - - /* if this is a git repository, do we already have this exact state loaded ? - * get the SHA and compare with what we currently have */ - if (git && git != dummy_git_repository) { - const char *sha = get_sha(git, branch); - if (!same_string(sha, "") && - same_string(sha, saved_git_id)) { - fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha); - return 0; - } - } - return 1; -} - -int parse_file(const char *filename) -{ - struct git_repository *git; - const char *branch = NULL; - char *current_sha = copy_string(saved_git_id); - struct memblock mem; - char *fmt; - int ret; - - git = is_git_repository(filename, &branch, NULL, false); - if (prefs.cloud_git_url && - strstr(filename, prefs.cloud_git_url) - && git == dummy_git_repository) { - /* opening the cloud storage repository failed for some reason - * give up here and don't send errors about git repositories */ - free(current_sha); - return 0; - } - /* if this is a git repository, do we already have this exact state loaded ? - * get the SHA and compare with what we currently have */ - if (git && git != dummy_git_repository) { - const char *sha = get_sha(git, branch); - if (!same_string(sha, "") && - same_string(sha, current_sha) && - !unsaved_changes()) { - fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha); - free(current_sha); - return 0; - } - } - free(current_sha); - if (git) - return git_load_dives(git, branch); - - if ((ret = readfile(filename, &mem)) < 0) { - /* we don't want to display an error if this was the default file or the cloud storage */ - if ((prefs.default_filename && !strcmp(filename, prefs.default_filename)) || - isCloudUrl(filename)) - return 0; - - return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); - } else if (ret == 0) { - return report_error(translate("gettextFromC", "Empty file '%s'"), filename); - } - - fmt = strrchr(filename, '.'); - if (fmt && (!strcasecmp(fmt + 1, "DB") || !strcasecmp(fmt + 1, "BAK") || !strcasecmp(fmt + 1, "SQL"))) { - if (!try_to_open_db(filename, &mem)) { - free(mem.buffer); - return 0; - } - } - - /* Divesoft Freedom */ - if (fmt && (!strcasecmp(fmt + 1, "DLF"))) { - if (!parse_dlf_buffer(mem.buffer, mem.size)) { - free(mem.buffer); - return 0; - } - return -1; - } - - /* DataTrak/Wlog */ - if (fmt && !strcasecmp(fmt + 1, "LOG")) { - datatrak_import(filename, &dive_table); - return 0; - } - - /* OSTCtools */ - if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) { - ostctools_import(filename, &dive_table); - return 0; - } - - ret = parse_file_buffer(filename, &mem); - free(mem.buffer); - return ret; -} - -#define MATCH(buffer, pattern) \ - memcmp(buffer, pattern, strlen(pattern)) - -char *parse_mkvi_value(const char *haystack, const char *needle) -{ - char *lineptr, *valueptr, *endptr, *ret = NULL; - - if ((lineptr = strstr(haystack, needle)) != NULL) { - if ((valueptr = strstr(lineptr, ": ")) != NULL) { - valueptr += 2; - } - if ((endptr = strstr(lineptr, "\n")) != NULL) { - char terminator = '\n'; - if (*(endptr - 1) == '\r') { - --endptr; - terminator = '\r'; - } - *endptr = 0; - ret = copy_string(valueptr); - *endptr = terminator; - - } - } - return ret; -} - -char *next_mkvi_key(const char *haystack) -{ - char *valueptr, *endptr, *ret = NULL; - - if ((valueptr = strstr(haystack, "\n")) != NULL) { - valueptr += 1; - if ((endptr = strstr(valueptr, ": ")) != NULL) { - *endptr = 0; - ret = strdup(valueptr); - *endptr = ':'; - } - } - return ret; -} - -int parse_txt_file(const char *filename, const char *csv) -{ - struct memblock memtxt, memcsv; - - if (readfile(filename, &memtxt) < 0) { - return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); - } - - /* - * MkVI stores some information in .txt file but the whole profile and events are stored in .csv file. First - * make sure the input .txt looks like proper MkVI file, then start parsing the .csv. - */ - if (MATCH(memtxt.buffer, "MkVI_Config") == 0) { - int d, m, y, he; - int hh = 0, mm = 0, ss = 0; - int prev_depth = 0, cur_sampletime = 0, prev_setpoint = -1, prev_ndl = -1; - bool has_depth = false, has_setpoint = false, has_ndl = false; - char *lineptr, *key, *value; - int o2cylinder_pressure = 0, cylinder_pressure = 0, cur_cylinder_index = 0; - unsigned int prev_time = 0; - - struct dive *dive; - struct divecomputer *dc; - struct tm cur_tm; - - value = parse_mkvi_value(memtxt.buffer, "Dive started at"); - if (sscanf(value, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) != 6) { - free(value); - return -1; - } - free(value); - cur_tm.tm_year = y; - cur_tm.tm_mon = m - 1; - cur_tm.tm_mday = d; - cur_tm.tm_hour = hh; - cur_tm.tm_min = mm; - cur_tm.tm_sec = ss; - - dive = alloc_dive(); - dive->when = utc_mktime(&cur_tm);; - dive->dc.model = strdup("Poseidon MkVI Discovery"); - value = parse_mkvi_value(memtxt.buffer, "Rig Serial number"); - dive->dc.deviceid = atoi(value); - free(value); - dive->dc.divemode = CCR; - dive->dc.no_o2sensors = 2; - - dive->cylinder[cur_cylinder_index].cylinder_use = OXYGEN; - dive->cylinder[cur_cylinder_index].type.size.mliter = 3000; - dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000; - dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6"); - dive->cylinder[cur_cylinder_index].gasmix.o2.permille = 1000; - cur_cylinder_index++; - - dive->cylinder[cur_cylinder_index].cylinder_use = DILUENT; - dive->cylinder[cur_cylinder_index].type.size.mliter = 3000; - dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000; - dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6"); - value = parse_mkvi_value(memtxt.buffer, "Helium percentage"); - he = atoi(value); - free(value); - value = parse_mkvi_value(memtxt.buffer, "Nitrogen percentage"); - dive->cylinder[cur_cylinder_index].gasmix.o2.permille = (100 - atoi(value) - he) * 10; - free(value); - dive->cylinder[cur_cylinder_index].gasmix.he.permille = he * 10; - cur_cylinder_index++; - - lineptr = strstr(memtxt.buffer, "Dive started at"); - while (lineptr && *lineptr && (lineptr = strchr(lineptr, '\n')) && ++lineptr) { - key = next_mkvi_key(lineptr); - if (!key) - break; - value = parse_mkvi_value(lineptr, key); - if (!value) { - free(key); - break; - } - add_extra_data(&dive->dc, key, value); - free(key); - free(value); - } - dc = &dive->dc; - - /* - * Read samples from the CSV file. A sample contains all the lines with same timestamp. The CSV file has - * the following format: - * - * timestamp, type, value - * - * And following fields are of interest to us: - * - * 6 sensor1 - * 7 sensor2 - * 8 depth - * 13 o2 tank pressure - * 14 diluent tank pressure - * 20 o2 setpoint - * 39 water temp - */ - - if (readfile(csv, &memcsv) < 0) { - free(dive); - return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv); - } - lineptr = memcsv.buffer; - for (;;) { - struct sample *sample; - int type; - int value; - int sampletime; - int gaschange = 0; - - /* Collect all the information for one sample */ - sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value); - - has_depth = false; - has_setpoint = false; - has_ndl = false; - sample = prepare_sample(dc); - - /* - * There was a bug in MKVI download tool that resulted in erroneous sample - * times. This fix should work similarly as the vendor's own. - */ - - sample->time.seconds = cur_sampletime < 0xFFFF * 3 / 4 ? cur_sampletime : prev_time; - prev_time = sample->time.seconds; - - do { - int i = sscanf(lineptr, "%d,%d,%d", &sampletime, &type, &value); - switch (i) { - case 3: - switch (type) { - case 0: - //Mouth piece position event: 0=OC, 1=CC, 2=UN, 3=NC - switch (value) { - case 0: - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position OC")); - break; - case 1: - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position CC")); - break; - case 2: - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position unknown")); - break; - case 3: - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position not connected")); - break; - } - break; - case 3: - //Power Off event - add_event(dc, cur_sampletime, 0, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "Power off")); - break; - case 4: - //Battery State of Charge in % -#ifdef SAMPLE_EVENT_BATTERY - add_event(dc, cur_sampletime, SAMPLE_EVENT_BATTERY, 0, - value, QT_TRANSLATE_NOOP("gettextFromC", "battery")); -#endif - break; - case 6: - //PO2 Cell 1 Average - add_sample_data(sample, POSEIDON_SENSOR1, value); - break; - case 7: - //PO2 Cell 2 Average - add_sample_data(sample, POSEIDON_SENSOR2, value); - break; - case 8: - //Depth * 2 - has_depth = true; - prev_depth = value; - add_sample_data(sample, POSEIDON_DEPTH, value); - break; - //9 Max Depth * 2 - //10 Ascent/Descent Rate * 2 - case 11: - //Ascent Rate Alert >10 m/s - add_event(dc, cur_sampletime, SAMPLE_EVENT_ASCENT, 0, 0, - QT_TRANSLATE_NOOP("gettextFromC", "ascent")); - break; - case 13: - //O2 Tank Pressure - add_sample_data(sample, POSEIDON_O2CYLINDER, value); - if (!o2cylinder_pressure) { - dive->cylinder[0].sample_start.mbar = value * 1000; - o2cylinder_pressure = value; - } else - o2cylinder_pressure = value; - break; - case 14: - //Diluent Tank Pressure - add_sample_data(sample, POSEIDON_PRESSURE, value); - if (!cylinder_pressure) { - dive->cylinder[1].sample_start.mbar = value * 1000; - cylinder_pressure = value; - } else - cylinder_pressure = value; - break; - //16 Remaining dive time #1? - //17 related to O2 injection - case 20: - //PO2 Setpoint - has_setpoint = true; - prev_setpoint = value; - add_sample_data(sample, POSEIDON_SETPOINT, value); - break; - case 22: - //End of O2 calibration Event: 0 = OK, 2 = Failed, rest of dive setpoint 1.0 - if (value == 2) - add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0, - QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration failed")); - add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0, - QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration")); - break; - case 25: - //25 Max Ascent depth - add_sample_data(sample, POSEIDON_CEILING, value); - break; - case 31: - //Start of O2 calibration Event - add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_BEGIN, 0, - QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration")); - break; - case 37: - //Remaining dive time #2? - has_ndl = true; - prev_ndl = value; - add_sample_data(sample, POSEIDON_NDL, value); - break; - case 39: - // Water Temperature in Celcius - add_sample_data(sample, POSEIDON_TEMP, value); - break; - case 85: - //He diluent part in % - gaschange += value << 16; - break; - case 86: - //O2 diluent part in % - gaschange += value; - break; - //239 Unknown, maybe PO2 at sensor validation? - //240 Unknown, maybe PO2 at sensor validation? - //247 Unknown, maybe PO2 Cell 1 during pressure test - //248 Unknown, maybe PO2 Cell 2 during pressure test - //250 PO2 Cell 1 - //251 PO2 Cell 2 - default: - break; - } /* sample types */ - break; - case EOF: - break; - default: - printf("Unable to parse input: %s\n", lineptr); - break; - } - - lineptr = strchr(lineptr, '\n'); - if (!lineptr || !*lineptr) - break; - lineptr++; - - /* Grabbing next sample time */ - sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value); - } while (sampletime == cur_sampletime); - - if (gaschange) - add_event(dc, cur_sampletime, SAMPLE_EVENT_GASCHANGE2, 0, gaschange, - QT_TRANSLATE_NOOP("gettextFromC", "gaschange")); - if (!has_depth) - add_sample_data(sample, POSEIDON_DEPTH, prev_depth); - if (!has_setpoint && prev_setpoint >= 0) - add_sample_data(sample, POSEIDON_SETPOINT, prev_setpoint); - if (!has_ndl && prev_ndl >= 0) - add_sample_data(sample, POSEIDON_NDL, prev_ndl); - if (cylinder_pressure) - dive->cylinder[1].sample_end.mbar = cylinder_pressure * 1000; - if (o2cylinder_pressure) - dive->cylinder[0].sample_end.mbar = o2cylinder_pressure * 1000; - finish_sample(dc); - - if (!lineptr || !*lineptr) - break; - } - record_dive(dive); - return 1; - } else { - return report_error(translate("gettextFromC", "No matching DC found for file '%s'"), csv); - } - - return 0; -} - -#define MAXCOLDIGITS 10 -#define DATESTR 9 -#define TIMESTR 6 - -int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate) -{ - int ret, i; - struct memblock mem; - time_t now; - struct tm *timep = NULL; - char tmpbuf[MAXCOLDIGITS]; - - /* Increase the limits for recursion and variables on XSLT - * parsing */ - xsltMaxDepth = 30000; -#if LIBXSLT_VERSION > 10126 - xsltMaxVars = 150000; -#endif - - if (filename == NULL) - return report_error("No CSV filename"); - - time(&now); - timep = localtime(&now); - - strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep); - params[pnr++] = "date"; - params[pnr++] = strdup(tmpbuf); - - /* As the parameter is numeric, we need to ensure that the leading zero - * is not discarded during the transform, thus prepend time with 1 */ - - strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep); - params[pnr++] = "time"; - params[pnr++] = strdup(tmpbuf); - params[pnr++] = NULL; - - mem.size = 0; - if (try_to_xslt_open_csv(filename, &mem, csvtemplate)) - return -1; - - /* - * Lets print command line for manual testing with xsltproc if - * verbosity level is high enough. The printed line needs the - * input file added as last parameter. - */ - -#ifndef SUBSURFACE_MOBILE - if (verbose >= 2) { - fprintf(stderr, "(echo ''; 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); - } -#endif - ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params); - - free(mem.buffer); - for (i = 0; params[i]; i += 2) - free(params[i + 1]); - - return ret; -} - -#define SBPARAMS 40 -int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate) -{ - int ret, i; - struct memblock mem; - time_t now; - struct tm *timep = NULL; - char *ptr, *ptr_old = NULL; - char *NL = NULL; - char tmpbuf[MAXCOLDIGITS]; - - /* Increase the limits for recursion and variables on XSLT - * parsing */ - xsltMaxDepth = 30000; -#if LIBXSLT_VERSION > 10126 - xsltMaxVars = 150000; -#endif - - time(&now); - timep = localtime(&now); - - strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep); - params[pnr++] = "date"; - params[pnr++] = strdup(tmpbuf); - - /* As the parameter is numeric, we need to ensure that the leading zero - * is not discarded during the transform, thus prepend time with 1 */ - strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep); - params[pnr++] = "time"; - params[pnr++] = strdup(tmpbuf); - - - if (filename == NULL) - return report_error("No CSV filename"); - - if (readfile(filename, &mem) < 0) - return report_error(translate("gettextFromC", "Failed to read '%s'"), filename); - - /* Determine NL (new line) character and the start of CSV data */ - ptr = mem.buffer; - while ((ptr = strstr(ptr, "\r\n\r\n")) != NULL) { - ptr_old = ptr; - ptr += 1; - NL = "\r\n"; - } - - if (!ptr_old) { - ptr = mem.buffer; - while ((ptr = strstr(ptr, "\n\n")) != NULL) { - ptr_old = ptr; - ptr += 1; - NL = "\n"; - } - ptr_old += 2; - } else - ptr_old += 4; - - /* - * If file does not contain empty lines, it is not a valid - * Seabear CSV file. - */ - if (NL == NULL) - return -1; - - /* - * On my current sample of Seabear DC log file, the date is - * without any identifier. Thus we must search for the previous - * line and step through from there. That is the line after - * Serial number. - */ - ptr = strstr(mem.buffer, "Serial number:"); - if (ptr) - ptr = strstr(ptr, NL); - - /* - * Write date and time values to params array, if available in - * the CSV header - */ - - if (ptr) { - ptr += strlen(NL) + 2; - /* - * pnr is the index of NULL on the params as filled by - * the init function. The two last entries should be - * date and time. Here we overwrite them with the data - * from the CSV header. - */ - - memcpy(params[pnr - 3], ptr, 4); - memcpy(params[pnr - 3] + 4, ptr + 5, 2); - memcpy(params[pnr - 3] + 6, ptr + 8, 2); - params[pnr - 3][8] = 0; - - memcpy(params[pnr - 1] + 1, ptr + 11, 2); - memcpy(params[pnr - 1] + 3, ptr + 14, 2); - params[pnr - 1][5] = 0; - } - - params[pnr++] = NULL; - - /* Move the CSV data to the start of mem buffer */ - memmove(mem.buffer, ptr_old, mem.size - (ptr_old - (char*)mem.buffer)); - mem.size = (int)mem.size - (ptr_old - (char*)mem.buffer); - - if (try_to_xslt_open_csv(filename, &mem, csvtemplate)) - return -1; - - /* - * Lets print command line for manual testing with xsltproc if - * verbosity level is high enough. The printed line needs the - * input file added as last parameter. - */ - - if (verbose >= 2) { - fprintf(stderr, "xsltproc "); - for (i=0; params[i]; i+=2) - fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]); - fprintf(stderr, "xslt/csv2xml.xslt\n"); - } - - ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params); - free(mem.buffer); - for (i = 0; params[i]; i += 2) - free(params[i + 1]); - - return ret; -} - -int parse_manual_file(const char *filename, char **params, int pnr) -{ - struct memblock mem; - time_t now; - struct tm *timep; - char curdate[9]; - char curtime[6]; - int ret, i; - - - time(&now); - timep = localtime(&now); - strftime(curdate, DATESTR, "%Y%m%d", timep); - - /* As the parameter is numeric, we need to ensure that the leading zero - * is not discarded during the transform, thus prepend time with 1 */ - strftime(curtime, TIMESTR, "1%H%M", timep); - - - params[pnr++] = strdup("date"); - params[pnr++] = strdup(curdate); - params[pnr++] = strdup("time"); - params[pnr++] = strdup(curtime); - params[pnr++] = NULL; - - if (filename == NULL) - return report_error("No manual CSV filename"); - - mem.size = 0; - if (try_to_xslt_open_csv(filename, &mem, "manualCSV")) - return -1; - - ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params); - - free(mem.buffer); - for (i = 0; i < pnr - 2; ++i) - free(params[i]); - return ret; -} diff --git a/subsurface-core/file.h b/subsurface-core/file.h deleted file mode 100644 index 1c1dfc116..000000000 --- a/subsurface-core/file.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef FILE_H -#define FILE_H - -struct memblock { - void *buffer; - size_t size; -}; - -extern int try_to_open_cochran(const char *filename, struct memblock *mem); -extern int try_to_open_liquivision(const char *filename, struct memblock *mem); -extern void datatrak_import(const char *file, struct dive_table *table); -extern void ostctools_import(const char *file, struct dive_table *table); - -#ifdef __cplusplus -extern "C" { -#endif -extern int readfile(const char *filename, struct memblock *mem); -extern timestamp_t parse_date(const char *date); -extern int try_to_open_zip(const char *filename); -#ifdef __cplusplus -} -#endif - -#endif // FILE_H diff --git a/subsurface-core/gas-model.c b/subsurface-core/gas-model.c deleted file mode 100644 index ad1160f3b..000000000 --- a/subsurface-core/gas-model.c +++ /dev/null @@ -1,64 +0,0 @@ -/* gas-model.c */ -/* gas compressibility model */ -#include -#include -#include "dive.h" - -/* "Virial minus one" - the virial cubic form without the initial 1.0 */ -#define virial_m1(C, x1, x2, x3) (C[0]*x1+C[1]*x2+C[2]*x3) - -/* - * Cubic virial least-square coefficients for O2/N2/He based on data from - * - * PERRY’S CHEMICAL ENGINEERS’ HANDBOOK SEVENTH EDITION - * - * with the lookup and curve fitting by Lubomir. - * - * The "virial" form of the compression factor polynomial is - * - * Z = 1.0 + C[0]*P + C[1]*P^2 + C[2]*P^3 ... - * - * and these tables do not contain the initial 1.0 term. - * - * NOTE! Helium coefficients are a linear mix operation between the - * 323K and one for 273K isotherms, to make everything be at 300K. - */ -double gas_compressibility_factor(struct gasmix *gas, double bar) -{ - static const double o2_coefficients[3] = { - -7.18092073703e-04, - +2.81852572808e-06, - -1.50290620492e-09 - }; - static const double n2_coefficients[3] = { - -2.19260353292e-04, - +2.92844845532e-06, - -2.07613482075e-09 - }; - static const double he_coefficients[3] = { - +4.87320026468e-04, - -8.83632921053e-08, - +5.33304543646e-11 - }; - int o2, he; - double x1, x2, x3; - double Z; - - o2 = get_o2(gas); - he = get_he(gas); - - x1 = bar; x2 = x1*x1; x3 = x2*x1; - - Z = virial_m1(o2_coefficients, x1, x2, x3) * o2 + - virial_m1(he_coefficients, x1, x2, x3) * he + - virial_m1(n2_coefficients, x1, x2, x3) * (1000 - o2 - he); - - /* - * We add the 1.0 at the very end - the linear mixing of the - * three 1.0 terms is still 1.0 regardless of the gas mix. - * - * The * 0.001 is because we did the linear mixing using the - * raw permille gas values. - */ - return Z * 0.001 + 1.0; -} diff --git a/subsurface-core/gaspressures.c b/subsurface-core/gaspressures.c deleted file mode 100644 index 5d3fc9791..000000000 --- a/subsurface-core/gaspressures.c +++ /dev/null @@ -1,430 +0,0 @@ -/* gaspressures.c - * --------------- - * This file contains the routines to calculate the gas pressures in the cylinders. - * The functions below support the code in profile.c. - * The high-level function is populate_pressure_information(), called by function - * create_plot_info_new() in profile.c. The other functions below are, in turn, - * called by populate_pressure_information(). The calling sequence is as follows: - * - * populate_pressure_information() -> calc_pressure_time() - * -> fill_missing_tank_pressures() -> fill_missing_segment_pressures() - * -> get_pr_interpolate_data() - * - * The pr_track_t related functions below implement a linked list that is used by - * the majority of the functions below. The linked list covers a part of the dive profile - * for which there are no cylinder pressure data. Each element in the linked list - * represents a segment between two consecutive points on the dive profile. - * pr_track_t is defined in gaspressures.h - */ - -#include "dive.h" -#include "display.h" -#include "profile.h" -#include "gaspressures.h" - -static pr_track_t *pr_track_alloc(int start, int t_start) -{ - pr_track_t *pt = malloc(sizeof(pr_track_t)); - pt->start = start; - pt->end = 0; - pt->t_start = pt->t_end = t_start; - pt->pressure_time = 0; - pt->next = NULL; - return pt; -} - -/* poor man's linked list */ -static pr_track_t *list_last(pr_track_t *list) -{ - pr_track_t *tail = list; - if (!tail) - return NULL; - while (tail->next) { - tail = tail->next; - } - return tail; -} - -static pr_track_t *list_add(pr_track_t *list, pr_track_t *element) -{ - pr_track_t *tail = list_last(list); - if (!tail) - return element; - tail->next = element; - return list; -} - -static void list_free(pr_track_t *list) -{ - if (!list) - return; - list_free(list->next); - free(list); -} - -#ifdef DEBUG_PR_TRACK -static void dump_pr_track(pr_track_t **track_pr) -{ - int cyl; - pr_track_t *list; - - for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { - list = track_pr[cyl]; - while (list) { - printf("cyl%d: start %d end %d t_start %d t_end %d pt %d\n", cyl, - list->start, list->end, list->t_start, list->t_end, list->pressure_time); - list = list->next; - } - } -} -#endif - -/* - * This looks at the pressures for one cylinder, and - * calculates any missing beginning/end pressures for - * each segment by taking the over-all SAC-rate into - * account for that cylinder. - * - * NOTE! Many segments have full pressure information - * (both beginning and ending pressure). But if we have - * switched away from a cylinder, we will have the - * beginning pressure for the first segment with a - * missing end pressure. We may then have one or more - * segments without beginning or end pressures, until - * we finally have a segment with an end pressure. - * - * We want to spread out the pressure over these missing - * segments according to how big of a time_pressure area - * they have. - */ -static void fill_missing_segment_pressures(pr_track_t *list, enum interpolation_strategy strategy) -{ - double magic; - - while (list) { - int start = list->start, end; - pr_track_t *tmp = list; - int pt_sum = 0, pt = 0; - - for (;;) { - pt_sum += tmp->pressure_time; - end = tmp->end; - if (end) - break; - end = start; - if (!tmp->next) - break; - tmp = tmp->next; - } - - if (!start) - start = end; - - /* - * Now 'start' and 'end' contain the pressure values - * for the set of segments described by 'list'..'tmp'. - * pt_sum is the sum of all the pressure-times of the - * segments. - * - * Now dole out the pressures relative to pressure-time. - */ - list->start = start; - tmp->end = end; - switch (strategy) { - case SAC: - for (;;) { - int pressure; - pt += list->pressure_time; - pressure = start; - if (pt_sum) - pressure -= (start - end) * (double)pt / pt_sum; - list->end = pressure; - if (list == tmp) - break; - list = list->next; - list->start = pressure; - } - break; - case TIME: - if (list->t_end && (tmp->t_start - tmp->t_end)) { - magic = (list->t_start - tmp->t_end) / (tmp->t_start - tmp->t_end); - list->end = rint(start - (start - end) * magic); - } else { - list->end = start; - } - break; - case CONSTANT: - list->end = start; - } - - /* Ok, we've done that set of segments */ - list = list->next; - } -} - -#ifdef DEBUG_PR_INTERPOLATE -void dump_pr_interpolate(int i, pr_interpolate_t interpolate_pr) -{ - printf("Interpolate for entry %d: start %d - end %d - pt %d - acc_pt %d\n", i, - interpolate_pr.start, interpolate_pr.end, interpolate_pr.pressure_time, interpolate_pr.acc_pressure_time); -} -#endif - - -static struct pr_interpolate_struct get_pr_interpolate_data(pr_track_t *segment, struct plot_info *pi, int cur) -{ // cur = index to pi->entry corresponding to t_end of segment; - struct pr_interpolate_struct interpolate; - int i; - struct plot_data *entry; - - interpolate.start = segment->start; - interpolate.end = segment->end; - interpolate.acc_pressure_time = 0; - interpolate.pressure_time = 0; - - for (i = 0; i < pi->nr; i++) { - entry = pi->entry + i; - - if (entry->sec < segment->t_start) - continue; - interpolate.pressure_time += entry->pressure_time; - if (entry->sec >= segment->t_end) - break; - if (i <= cur) - interpolate.acc_pressure_time += entry->pressure_time; - } - return interpolate; -} - -static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, pr_track_t **track_pr, bool o2_flag) -{ - int cyl, i; - struct plot_data *entry; - pr_interpolate_t interpolate = { 0, 0, 0, 0 }; - pr_track_t *last_segment = NULL; - int cur_pr[MAX_CYLINDERS]; // cur_pr[MAX_CYLINDERS] is the CCR diluent cylinder - - for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { - enum interpolation_strategy strategy; - if (!track_pr[cyl]) { - /* no segment where this cylinder is used */ - cur_pr[cyl] = -1; - continue; - } - if (dive->cylinder[cyl].cylinder_use == OC_GAS) - strategy = SAC; - else - strategy = TIME; - fill_missing_segment_pressures(track_pr[cyl], strategy); // Interpolate the missing tank pressure values .. - cur_pr[cyl] = track_pr[cyl]->start; // in the pr_track_t lists of structures - } // and keep the starting pressure for each cylinder. - -#ifdef DEBUG_PR_TRACK - /* another great debugging tool */ - dump_pr_track(track_pr); -#endif - - /* Transfer interpolated cylinder pressures from pr_track strucktures to plotdata - * Go down the list of tank pressures in plot_info. Align them with the start & - * end times of each profile segment represented by a pr_track_t structure. Get - * the accumulated pressure_depths from the pr_track_t structures and then - * interpolate the pressure where these do not exist in the plot_info pressure - * variables. Pressure values are transferred from the pr_track_t structures - * to the plot_info structure, allowing us to plot the tank pressure. - * - * The first two pi structures are "fillers", but in case we don't have a sample - * at time 0 we need to process the second of them here, therefore i=1 */ - for (i = 1; i < pi->nr; i++) { // For each point on the profile: - double magic; - pr_track_t *segment; - int pressure; - int *save_pressure, *save_interpolated; - - entry = pi->entry + i; - - if (o2_flag) { - // Find the cylinder index (cyl) and pressure - cyl = dive->oxygen_cylinder_index; - if (cyl < 0) - return; // Can we do this?!? - pressure = O2CYLINDER_PRESSURE(entry); - save_pressure = &(entry->o2cylinderpressure[SENSOR_PR]); - save_interpolated = &(entry->o2cylinderpressure[INTERPOLATED_PR]); - } else { - pressure = SENSOR_PRESSURE(entry); - save_pressure = &(entry->pressure[SENSOR_PR]); - save_interpolated = &(entry->pressure[INTERPOLATED_PR]); - cyl = entry->cylinderindex; - } - - if (pressure) { // If there is a valid pressure value, - last_segment = NULL; // get rid of interpolation data, - cur_pr[cyl] = pressure; // set current pressure - continue; // and skip to next point. - } - // If there is NO valid pressure value.. - // Find the pressure segment corresponding to this entry.. - segment = track_pr[cyl]; - while (segment && segment->t_end < entry->sec) // Find the track_pr with end time.. - segment = segment->next; // ..that matches the plot_info time (entry->sec) - - if (!segment || !segment->pressure_time) { // No (or empty) segment? - *save_pressure = cur_pr[cyl]; // Just use our current pressure - continue; // and skip to next point. - } - - // If there is a valid segment but no tank pressure .. - if (segment == last_segment) { - interpolate.acc_pressure_time += entry->pressure_time; - } else { - // Set up an interpolation structure - interpolate = get_pr_interpolate_data(segment, pi, i); - last_segment = segment; - } - - if(dive->cylinder[cyl].cylinder_use == OC_GAS) { - - /* if this segment has pressure_time, then calculate a new interpolated pressure */ - if (interpolate.pressure_time) { - /* Overall pressure change over total pressure-time for this segment*/ - magic = (interpolate.end - interpolate.start) / (double)interpolate.pressure_time; - - /* Use that overall pressure change to update the current pressure */ - cur_pr[cyl] = rint(interpolate.start + magic * interpolate.acc_pressure_time); - } - } else { - magic = (interpolate.end - interpolate.start) / (segment->t_end - segment->t_start); - cur_pr[cyl] = rint(segment->start + magic * (entry->sec - segment->t_start)); - } - *save_interpolated = cur_pr[cyl]; // and store the interpolated data in plot_info - } -} - - -/* - * What's the pressure-time between two plot data entries? - * We're calculating the integral of pressure over time by - * adding these up. - * - * The units won't matter as long as everybody agrees about - * them, since they'll cancel out - we use this to calculate - * a constant SAC-rate-equivalent, but we only use it to - * scale pressures, so it ends up being a unitless scaling - * factor. - */ -static inline int calc_pressure_time(struct dive *dive, struct plot_data *a, struct plot_data *b) -{ - int time = b->sec - a->sec; - int depth = (a->depth + b->depth) / 2; - - if (depth <= SURFACE_THRESHOLD) - return 0; - - return depth_to_mbar(depth, dive) * time; -} - -#ifdef PRINT_PRESSURES_DEBUG -// A CCR debugging tool that prints the gas pressures in cylinder 0 and in the diluent cylinder, used in populate_pressure_information(): -static void debug_print_pressures(struct plot_info *pi) -{ - int i; - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - printf("%5d |%9d | %9d || %9d | %9d |\n", i, SENSOR_PRESSURE(entry), INTERPOLATED_PRESSURE(entry), DILUENT_PRESSURE(entry), INTERPOLATED_DILUENT_PRESSURE(entry)); - } -} -#endif - -/* This function goes through the list of tank pressures, either SENSOR_PRESSURE(entry) or O2CYLINDER_PRESSURE(entry), - * of structure plot_info for the dive profile where each item in the list corresponds to one point (node) of the - * profile. It finds values for which there are no tank pressures (pressure==0). For each missing item (node) of - * tank pressure it creates a pr_track_alloc structure that represents a segment on the dive profile and that - * contains tank pressures. There is a linked list of pr_track_alloc structures for each cylinder. These pr_track_alloc - * structures ultimately allow for filling the missing tank pressure values on the dive profile using the depth_pressure - * of the dive. To do this, it calculates the summed pressure-time value for the duration of the dive and stores these - * in the pr_track_alloc structures. If diluent_flag = 1, then DILUENT_PRESSURE(entry) is used instead of SENSOR_PRESSURE. - * This function is called by create_plot_info_new() in profile.c - */ -void populate_pressure_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, int o2_flag) -{ - (void) dc; - int i, cylinderid, cylinderindex = -1; - pr_track_t *track_pr[MAX_CYLINDERS] = { NULL, }; - pr_track_t *current = NULL; - bool missing_pr = false; - bool found_any_pr_data = false; - - /* if we have no pressure data whatsoever, this is pointless, so let's just return */ - for (i = 0; i < MAX_CYLINDERS; i++) { - if (dive->cylinder[i].start.mbar || dive->cylinder[i].sample_start.mbar || - dive->cylinder[i].end.mbar || dive->cylinder[i].sample_end.mbar) { - found_any_pr_data = true; - break; - } - } - if (!found_any_pr_data) - return; - - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - unsigned pressure; - if (o2_flag) { // if this is a diluent cylinder: - pressure = O2CYLINDER_PRESSURE(entry); - cylinderid = dive->oxygen_cylinder_index; - if (cylinderid < 0) - goto GIVE_UP; - } else { - pressure = SENSOR_PRESSURE(entry); - cylinderid = entry->cylinderindex; - } - /* If track_pr structure already exists, then update it: */ - /* discrete integration of pressure over time to get the SAC rate equivalent */ - if (current) { - entry->pressure_time = calc_pressure_time(dive, entry - 1, entry); - current->pressure_time += entry->pressure_time; - current->t_end = entry->sec; - } - - /* If 1st record or different cylinder: Create a new track_pr structure: */ - /* track the segments per cylinder and their pressure/time integral */ - if (cylinderid != cylinderindex) { - if (o2_flag) // For CCR dives: - cylinderindex = dive->oxygen_cylinder_index; // indicate o2 cylinder - else - cylinderindex = entry->cylinderindex; - current = pr_track_alloc(pressure, entry->sec); - track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current); - continue; - } - - if (!pressure) { - missing_pr = 1; - continue; - } - if (current) - current->end = pressure; - - /* Was it continuous? */ - if ((o2_flag) && (O2CYLINDER_PRESSURE(entry - 1))) // in the case of CCR o2 pressure - continue; - else if (SENSOR_PRESSURE(entry - 1)) // for all other cylinders - continue; - - /* transmitter stopped transmitting cylinder pressure data */ - current = pr_track_alloc(pressure, entry->sec); - if (cylinderindex >= 0) - track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current); - } - - if (missing_pr) { - fill_missing_tank_pressures(dive, pi, track_pr, o2_flag); - } - -#ifdef PRINT_PRESSURES_DEBUG - debug_print_pressures(pi); -#endif - -GIVE_UP: - for (i = 0; i < MAX_CYLINDERS; i++) - list_free(track_pr[i]); -} diff --git a/subsurface-core/gaspressures.h b/subsurface-core/gaspressures.h deleted file mode 100644 index 420c117a2..000000000 --- a/subsurface-core/gaspressures.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef GASPRESSURES_H -#define GASPRESSURES_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * simple structure to track the beginning and end tank pressure as - * well as the integral of depth over time spent while we have no - * pressure reading from the tank */ -typedef struct pr_track_struct pr_track_t; -struct pr_track_struct { - int start; - int end; - int t_start; - int t_end; - int pressure_time; - pr_track_t *next; -}; - -typedef struct pr_interpolate_struct pr_interpolate_t; -struct pr_interpolate_struct { - int start; - int end; - int pressure_time; - int acc_pressure_time; -}; - -enum interpolation_strategy {SAC, TIME, CONSTANT}; - -#ifdef __cplusplus -} -#endif -#endif // GASPRESSURES_H diff --git a/subsurface-core/gettext.h b/subsurface-core/gettext.h deleted file mode 100644 index 43ff023c7..000000000 --- a/subsurface-core/gettext.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef MYGETTEXT_H -#define MYGETTEXT_H - -/* this is for the Qt based translations */ -extern const char *trGettext(const char *); -#define translate(_context, arg) trGettext(arg) -#define QT_TRANSLATE_NOOP(_context, arg) arg -#define QT_TRANSLATE_NOOP3(_context, arg, _comment) arg - -#endif // MYGETTEXT_H diff --git a/subsurface-core/gettextfromc.cpp b/subsurface-core/gettextfromc.cpp deleted file mode 100644 index c579e3c3c..000000000 --- a/subsurface-core/gettextfromc.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include -#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 deleted file mode 100644 index 53df18365..000000000 --- a/subsurface-core/gettextfromc.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef GETTEXTFROMC_H -#define GETTEXTFROMC_H - -#include -#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 deleted file mode 100644 index d10139d3d..000000000 --- a/subsurface-core/git-access.c +++ /dev/null @@ -1,929 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#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" - -bool is_subsurface_cloud = false; - -int (*update_progress_cb)(int, const char *) = NULL; - -void set_git_update_cb(int(*cb)(int, const char *)) -{ - update_progress_cb = cb; -} - -// total overkill, but this allows us to get good timing in various scenarios; -// the various parts of interacting with the local and remote git repositories send -// us updates which indicate progress (and no, this is not smooth and definitely not -// proportional - some parts are based on compute performance, some on network speed) -// they also provide information where in the process we are so we can analyze the log -// to understand which parts of the process take how much time. - -// last_git_storage_update_val is used to detect when we suddenly go back to smaller -// "percentage" value because we are back to executing earlier code a second (or third -// time) in that case a negative percentage value is sent to the callback function as a -// special case to mark that situation. Overall this ensures monotonous percentage values -int last_git_storage_update_val; - -int git_storage_update_progress(int percent, const char *text) -{ - static int delta = 0; - - if (percent == 0) { - delta = 0; - } else if (percent > 0 && percent < last_git_storage_update_val) { - delta = last_git_storage_update_val + delta; - if (update_progress_cb) - (*update_progress_cb)(-delta, "DELTA"); - if (verbose) - fprintf(stderr, "set git storage percentage delta to %d\n", delta); - } - - last_git_storage_update_val = percent; - - percent += delta; - - int ret = 0; - if (update_progress_cb) - ret = (*update_progress_cb)(percent, text); - return ret; -} - -// the checkout_progress_cb doesn't allow canceling of the operation -// map the git progress to 70..90% of overall progress -static void progress_cb(const char *path, size_t completed_steps, size_t total_steps, void *payload) -{ - (void) path; - (void) payload; - - int percent = 0; - if (total_steps) - percent = 70 + 20 * completed_steps / total_steps; - (void)git_storage_update_progress(percent, "checkout_progress_cb"); -} - -// this randomly assumes that 80% of the time is spent on the objects and 20% on the deltas -// map the git progress to 70..90% of overall progress -// if the user cancels the dialog this is passed back to libgit2 -static int transfer_progress_cb(const git_transfer_progress *stats, void *payload) -{ - (void) payload; - - int percent = 0; - if (stats->total_objects) - percent = 70 + 16 * stats->received_objects / stats->total_objects; - if (stats->total_deltas) - percent += 4 * stats->indexed_deltas / stats->total_deltas; - /* for debugging this is useful - char buf[100]; - snprintf(buf, 100, "transfer cb rec_obj %d tot_obj %d idx_delta %d total_delta %d local obj %d", stats->received_objects, stats->total_objects, stats->indexed_deltas, stats->total_deltas, stats->local_objects); - return git_storage_update_progress(percent, buf); - */ - return git_storage_update_progress(percent, "transfer cb"); -} - -// the initial push to sync the repos is mapped to 10..15% of overall progress -static int push_transfer_progress_cb(unsigned int current, unsigned int total, size_t bytes, void *payload) -{ - (void) bytes; - (void) payload; - - int percent = 0; - if (total != 0) - percent = 12 + 5 * current / total; - return git_storage_update_progress(percent, "push trasfer cb"); -} - -char *get_local_dir(const char *remote, const char *branch) -{ - SHA_CTX ctx; - unsigned char hash[20]; - - // That zero-byte update is so that we don't get hash - // collisions for "repo1 branch" vs "repo 1branch". - SHA1_Init(&ctx); - SHA1_Update(&ctx, remote, strlen(remote)); - SHA1_Update(&ctx, "", 1); - SHA1_Update(&ctx, branch, strlen(branch)); - SHA1_Final(hash, &ctx); - - return format_string("%s/cloudstorage/%02x%02x%02x%02x%02x%02x%02x%02x", - system_default_directory(), - hash[0], hash[1], hash[2], hash[3], - hash[4], hash[5], hash[6], hash[7]); -} - -static char *move_local_cache(const char *remote, const char *branch) -{ - char *old_path = get_local_dir(remote, branch); - return move_away(old_path); -} - -static int check_clean(const char *path, unsigned int status, void *payload) -{ - (void) payload; - status &= ~GIT_STATUS_CURRENT | GIT_STATUS_IGNORED; - if (!status) - return 0; - if (is_subsurface_cloud) - report_error(translate("gettextFromC", "Local cache directory %s corrupted - can't sync with Subsurface cloud storage"), path); - else - report_error("WARNING: Git cache directory modified (path %s) status %0x", path, status); - return 1; -} - -/* - * The remote is strictly newer than the local branch. - */ -static int reset_to_remote(git_repository *repo, git_reference *local, const git_oid *new_id) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - opts.progress_cb = &progress_cb; - git_object *target; - - if (verbose) - fprintf(stderr, "git storage: reset to remote\n"); - - // If it's not checked out (bare or not HEAD), just update the reference */ - if (git_repository_is_bare(repo) || git_branch_is_head(local) != 1) { - git_reference *out; - - if (git_reference_set_target(&out, local, new_id, "Update to remote")) - return report_error(translate("gettextFromC", "Could not update local cache to newer remote data")); - - git_reference_free(out); - -#ifdef DEBUG - // Not really an error, just informational - report_error("Updated local branch from remote"); -#endif - return 0; - } - - if (git_object_lookup(&target, repo, new_id, GIT_OBJ_COMMIT)) { - if (is_subsurface_cloud) - return report_error(translate("gettextFromC", "Subsurface cloud storage corrupted")); - else - return report_error("Could not look up remote commit"); - } - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - if (git_reset(repo, target, GIT_RESET_HARD, &opts)) { - if (is_subsurface_cloud) - return report_error(translate("gettextFromC", "Could not update local cache to newer remote data")); - else - return report_error("Local head checkout failed after update"); - } - // Not really an error, just informational -#ifdef DEBUG - report_error("Updated local information from remote"); -#endif - return 0; -} - -int credential_ssh_cb(git_cred **out, - const char *url, - const char *username_from_url, - unsigned int allowed_types, - void *payload) -{ - (void) url; - (void) allowed_types; - (void) payload; - - const char *priv_key = format_string("%s/%s", system_default_directory(), "ssrf_remote.key"); - const char *passphrase = prefs.cloud_storage_password ? strdup(prefs.cloud_storage_password) : strdup(""); - return git_cred_ssh_key_new(out, username_from_url, NULL, priv_key, passphrase); -} - -int credential_https_cb(git_cred **out, - const char *url, - const char *username_from_url, - unsigned int allowed_types, - void *payload) -{ - (void) url; - (void) username_from_url; - (void) payload; - (void) allowed_types; - const char *username = prefs.cloud_storage_email_encoded; - const char *password = prefs.cloud_storage_password ? strdup(prefs.cloud_storage_password) : strdup(""); - return git_cred_userpass_plaintext_new(out, username, password); -} - -#define KNOWN_CERT "\xfd\xb8\xf7\x73\x76\xe2\x75\x53\x93\x37\xdc\xfe\x1e\x55\x43\x3d\xf2\x2c\x18\x2c" -int certificate_check_cb(git_cert *cert, int valid, const char *host, void *payload) -{ - (void) payload; - if (same_string(host, "cloud.subsurface-divelog.org") && cert->cert_type == GIT_CERT_X509) { - SHA_CTX ctx; - unsigned char hash[21]; - git_cert_x509 *cert509 = (git_cert_x509 *)cert; - SHA1_Init(&ctx); - SHA1_Update(&ctx, cert509->data, cert509->len); - SHA1_Final(hash, &ctx); - hash[20] = 0; - if (verbose > 1) - if (same_string((char *)hash, KNOWN_CERT)) { - fprintf(stderr, "cloud certificate considered %s, forcing it valid\n", - valid ? "valid" : "not valid"); - return 1; - } - } - return valid; -} - -static int update_remote(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, enum remote_transport rt) -{ - (void) repo; - (void) remote; - - git_push_options opts = GIT_PUSH_OPTIONS_INIT; - git_strarray refspec; - const char *name = git_reference_name(local); - - if (verbose) - fprintf(stderr, "git storage: update remote\n"); - - refspec.count = 1; - refspec.strings = (char **)&name; - - opts.callbacks.push_transfer_progress = &push_transfer_progress_cb; - if (rt == RT_SSH) - opts.callbacks.credentials = credential_ssh_cb; - else if (rt == RT_HTTPS) - opts.callbacks.credentials = credential_https_cb; - opts.callbacks.certificate_check = certificate_check_cb; - - if (git_remote_push(origin, &refspec, &opts)) { - if (is_subsurface_cloud) - return report_error(translate("gettextFromC", "Could not update Subsurface cloud storage, try again later")); - else - return report_error("Unable to update remote with current local cache state (%s)", giterr_last()->message); - } - return 0; -} - -extern int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree); - -static int try_to_git_merge(git_repository *repo, git_reference **local_p, git_reference *remote, git_oid *base, const git_oid *local_id, const git_oid *remote_id) -{ - (void) remote; - git_tree *local_tree, *remote_tree, *base_tree; - git_commit *local_commit, *remote_commit, *base_commit; - git_index *merged_index; - git_merge_options merge_options; - - if (verbose) { - char outlocal[41], outremote[41]; - outlocal[40] = outremote[40] = 0; - git_oid_fmt(outlocal, local_id); - git_oid_fmt(outremote, remote_id); - fprintf(stderr, "trying to merge local SHA %s remote SHA %s\n", outlocal, outremote); - } - - git_merge_init_options(&merge_options, GIT_MERGE_OPTIONS_VERSION); - merge_options.tree_flags = GIT_MERGE_TREE_FIND_RENAMES; - merge_options.file_favor = GIT_MERGE_FILE_FAVOR_UNION; - merge_options.rename_threshold = 100; - if (git_commit_lookup(&local_commit, repo, local_id)) { - fprintf(stderr, "Remote storage and local data diverged. Error: can't get commit (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_tree(&local_tree, local_commit)) { - fprintf(stderr, "Remote storage and local data diverged. Error: failed local tree lookup (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_lookup(&remote_commit, repo, remote_id)) { - fprintf(stderr, "Remote storage and local data diverged. Error: can't get commit (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_tree(&remote_tree, remote_commit)) { - fprintf(stderr, "Remote storage and local data diverged. Error: failed local tree lookup (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_lookup(&base_commit, repo, base)) { - fprintf(stderr, "Remote storage and local data diverged. Error: can't get commit (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_commit_tree(&base_tree, base_commit)) { - fprintf(stderr, "Remote storage and local data diverged. Error: failed base tree lookup (%s)", giterr_last()->message); - goto diverged_error; - } - if (git_merge_trees(&merged_index, repo, base_tree, local_tree, remote_tree, &merge_options)) { - fprintf(stderr, "Remote storage and local data diverged. Error: merge failed (%s)", giterr_last()->message); - // this is the one where I want to report more detail to the user - can't quite explain why - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: merge failed (%s)"), giterr_last()->message); - } - if (git_index_has_conflicts(merged_index)) { - int error; - const git_index_entry *ancestor = NULL, - *ours = NULL, - *theirs = NULL; - git_index_conflict_iterator *iter = NULL; - error = git_index_conflict_iterator_new(&iter, merged_index); - while (git_index_conflict_next(&ancestor, &ours, &theirs, iter) - != GIT_ITEROVER) { - /* Mark this conflict as resolved */ - fprintf(stderr, "conflict in %s / %s / %s -- ", - ours ? ours->path : "-", - theirs ? theirs->path : "-", - ancestor ? ancestor->path : "-"); - if ((!ours && theirs && ancestor) || - (ours && !theirs && ancestor)) { - // the file was removed on one side or the other - just remove it - fprintf(stderr, "looks like a delete on one side; removing the file from the index\n"); - error = git_index_remove(merged_index, ours ? ours->path : theirs->path, GIT_INDEX_STAGE_ANY); - } else if (ancestor) { - error = git_index_conflict_remove(merged_index, ours ? ours->path : theirs ? theirs->path : ancestor->path); - } - if (error) { - fprintf(stderr, "error at conflict resplution (%s)", giterr_last()->message); - } - } - git_index_conflict_cleanup(merged_index); - git_index_conflict_iterator_free(iter); - report_error(translate("gettextFromC", "Remote storage and local data diverged. Cannot combine local and remote changes")); - } - git_oid merge_oid, commit_oid; - git_tree *merged_tree; - git_signature *author; - git_commit *commit; - - if (git_index_write_tree_to(&merge_oid, merged_index, repo)) - goto write_error; - if (git_tree_lookup(&merged_tree, repo, &merge_oid)) - goto write_error; - if (git_signature_default(&author, repo) < 0) - if (git_signature_now(&author, "Subsurface", "noemail@given") < 0) - goto write_error; - if (git_commit_create_v(&commit_oid, repo, NULL, author, author, NULL, "automatic merge", merged_tree, 2, local_commit, remote_commit)) - goto write_error; - if (git_commit_lookup(&commit, repo, &commit_oid)) - goto write_error; - if (git_branch_is_head(*local_p) && !git_repository_is_bare(repo)) { - git_object *parent; - git_reference_peel(&parent, *local_p, GIT_OBJ_COMMIT); - if (update_git_checkout(repo, parent, merged_tree)) { - goto write_error; - } - } - if (git_reference_set_target(local_p, *local_p, &commit_oid, "Subsurface merge event")) - goto write_error; - set_git_id(&commit_oid); - git_signature_free(author); - if (verbose) - fprintf(stderr, "Successfully merged repositories"); - return 0; - -diverged_error: - return report_error(translate("gettextFromC", "Remote storage and local data diverged")); - -write_error: - return report_error(translate("gettextFromC", "Remote storage and local data diverged. Error: writing the data failed (%s)"), giterr_last()->message); -} - -// if accessing the local cache of Subsurface cloud storage fails, we simplify things -// for the user and simply move the cache away (in case they want to try and extract data) -// and ask them to retry the operation (which will then refresh the data from the cloud server) -static int cleanup_local_cache(const char *remote_url, const char *branch) -{ - char *backup_path = move_local_cache(remote_url, branch); - report_error(translate("gettextFromC", "Problems with local cache of Subsurface cloud data")); - report_error(translate("gettextFromC", "Moved cache data to %s. Please try the operation again."), backup_path); - free(backup_path); - return -1; -} -static int try_to_update(git_repository *repo, git_remote *origin, git_reference *local, git_reference *remote, - const char *remote_url, const char *branch, enum remote_transport rt) -{ - git_oid base; - const git_oid *local_id, *remote_id; - int ret = 0; - - if (verbose) - fprintf(stderr, "git storage: try to update\n"); - git_storage_update_progress(9, "try to update"); - if (!git_reference_cmp(local, remote)) - return 0; - - // Dirty modified state in the working tree? We're not going - // to update either way - if (git_status_foreach(repo, check_clean, NULL)) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("local cached copy is dirty, skipping update"); - } - local_id = git_reference_target(local); - remote_id = git_reference_target(remote); - - if (!local_id || !remote_id) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("Unable to get local or remote SHA1"); - } - if (git_merge_base(&base, repo, local_id, remote_id)) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("Unable to find common commit of local and remote branches"); - } - /* Is the remote strictly newer? Use it */ - if (git_oid_equal(&base, local_id)) - return reset_to_remote(repo, local, remote_id); - - /* Is the local repo the more recent one? See if we can update upstream */ - if (git_oid_equal(&base, remote_id)) { - if (verbose) - fprintf(stderr, "local is newer than remote, update remote\n"); - git_storage_update_progress(10, "git_update_remote, local was newer"); - return update_remote(repo, origin, local, remote, rt); - } - /* Merging a bare repository always needs user action */ - if (git_repository_is_bare(repo)) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("Local and remote have diverged, merge of bare branch needed"); - } - /* Merging will definitely need the head branch too */ - if (git_branch_is_head(local) != 1) { - if (is_subsurface_cloud) - goto cloud_data_error; - else - return report_error("Local and remote do not match, local branch not HEAD - cannot update"); - } - /* Ok, let's try to merge these */ - git_storage_update_progress(11, "try to merge"); - ret = try_to_git_merge(repo, &local, remote, &base, local_id, remote_id); - if (ret == 0) - return update_remote(repo, origin, local, remote, rt); - else - return ret; - -cloud_data_error: - // since we are working with Subsurface cloud storage we want to make the user interaction - // as painless as possible. So if something went wrong with the local cache, tell the user - // about it an move it away - return cleanup_local_cache(remote_url, branch); -} - -static int check_remote_status(git_repository *repo, git_remote *origin, const char *remote, const char *branch, enum remote_transport rt) -{ - int error = 0; - - git_reference *local_ref, *remote_ref; - - if (verbose) - fprintf(stderr, "git storage: check remote status\n"); - git_storage_update_progress(7, "git check remote status"); - - if (git_branch_lookup(&local_ref, repo, branch, GIT_BRANCH_LOCAL)) { - if (is_subsurface_cloud) - return cleanup_local_cache(remote, branch); - else - return report_error("Git cache branch %s no longer exists", branch); - } - if (git_branch_upstream(&remote_ref, local_ref)) { - /* so there is no upstream branch for our branch; that's a problem. - * let's push our branch */ - git_strarray refspec; - git_reference_list(&refspec, repo); - git_push_options opts = GIT_PUSH_OPTIONS_INIT; - opts.callbacks.transfer_progress = &transfer_progress_cb; - if (rt == RT_SSH) - opts.callbacks.credentials = credential_ssh_cb; - else if (rt == RT_HTTPS) - opts.callbacks.credentials = credential_https_cb; - opts.callbacks.certificate_check = certificate_check_cb; - git_storage_update_progress(8, "git remote push (no remote existed)"); - error = git_remote_push(origin, &refspec, &opts); - } else { - error = try_to_update(repo, origin, local_ref, remote_ref, remote, branch, rt); - git_reference_free(remote_ref); - } - git_reference_free(local_ref); - return error; -} - -int sync_with_remote(git_repository *repo, const char *remote, const char *branch, enum remote_transport rt) -{ - int error; - git_remote *origin; - char *proxy_string; - git_config *conf; - - if (prefs.git_local_only) { - if (verbose) - fprintf(stderr, "don't sync with remote - read from cache only\n"); - return 0; - } - if (verbose) - fprintf(stderr, "sync with remote %s[%s]\n", remote, branch); - git_storage_update_progress(2, "sync with remote"); - git_repository_config(&conf, repo); - if (rt == RT_HTTPS && getProxyString(&proxy_string)) { - if (verbose) - fprintf(stderr, "set proxy to \"%s\"\n", proxy_string); - git_config_set_string(conf, "http.proxy", proxy_string); - free(proxy_string); - } else { - if (verbose) - fprintf(stderr, "delete proxy setting\n"); - git_config_delete_entry(conf, "http.proxy"); - } - - /* - * NOTE! Remote errors are reported, but are nonfatal: - * we still successfully return the local repository. - */ - error = git_remote_lookup(&origin, repo, "origin"); - if (error) { - if (!is_subsurface_cloud) - report_error("Repository '%s' origin lookup failed (%s)", remote, giterr_last()->message); - return 0; - } - - if (rt == RT_HTTPS && !canReachCloudServer()) { - // this is not an error, just a warning message, so return 0 - report_error("Cannot connect to cloud server, working with local copy"); - git_storage_update_progress(18, "can't reach cloud server, working with local copy"); - return 0; - } - if (verbose) - fprintf(stderr, "git storage: fetch remote\n"); - git_fetch_options opts = GIT_FETCH_OPTIONS_INIT; - opts.callbacks.transfer_progress = &transfer_progress_cb; - if (rt == RT_SSH) - opts.callbacks.credentials = credential_ssh_cb; - else if (rt == RT_HTTPS) - opts.callbacks.credentials = credential_https_cb; - opts.callbacks.certificate_check = certificate_check_cb; - git_storage_update_progress(6, "git fetch remote"); - error = git_remote_fetch(origin, NULL, &opts, NULL); - // NOTE! A fetch error is not fatal, we just report it - if (error) { - if (is_subsurface_cloud) - report_error("Cannot sync with cloud server, working with offline copy"); - else - report_error("Unable to fetch remote '%s'", remote); - if (verbose) - fprintf(stderr, "remote fetch failed (%s)\n", giterr_last()->message); - error = 0; - } else { - error = check_remote_status(repo, origin, remote, branch, rt); - } - git_remote_free(origin); - git_storage_update_progress(18, "done with sync with remote"); - return error; -} - -static git_repository *update_local_repo(const char *localdir, const char *remote, const char *branch, enum remote_transport rt) -{ - int error; - git_repository *repo = NULL; - - if (verbose) - fprintf(stderr, "git storage: update local repo\n"); - - error = git_repository_open(&repo, localdir); - if (error) { - if (is_subsurface_cloud) - (void)cleanup_local_cache(remote, branch); - else - report_error("Unable to open git cache repository at %s: %s", localdir, giterr_last()->message); - return NULL; - } - sync_with_remote(repo, remote, branch, rt); - return repo; -} - -static int repository_create_cb(git_repository **out, const char *path, int bare, void *payload) -{ - (void) payload; - char *proxy_string; - git_config *conf; - - int ret = git_repository_init(out, path, bare); - - git_repository_config(&conf, *out); - if (getProxyString(&proxy_string)) { - if (verbose) - fprintf(stderr, "set proxy to \"%s\"\n", proxy_string); - git_config_set_string(conf, "http.proxy", proxy_string); - free(proxy_string); - } else { - if (verbose) - fprintf(stderr, "delete proxy setting\n"); - git_config_delete_entry(conf, "http.proxy"); - } - return ret; -} - -/* this should correctly initialize both the local and remote - * repository for the Subsurface cloud storage */ -static git_repository *create_and_push_remote(const char *localdir, const char *remote, const char *branch) -{ - git_repository *repo; - git_config *conf; - int len; - char *variable_name, *merge_head; - - if (verbose) - fprintf(stderr, "git storage: create and push remote\n"); - - /* first make sure the directory for the local cache exists */ - subsurface_mkdir(localdir); - - /* set up the origin to point to our remote */ - git_repository_init_options init_opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; - init_opts.origin_url = remote; - - /* now initialize the repository with */ - git_repository_init_ext(&repo, localdir, &init_opts); - - /* create a config so we can set the remote tracking branch */ - git_repository_config(&conf, repo); - len = sizeof("branch..remote") + strlen(branch); - variable_name = malloc(len); - snprintf(variable_name, len, "branch.%s.remote", branch); - git_config_set_string(conf, variable_name, "origin"); - /* we know this is shorter than the previous one, so we reuse the variable*/ - snprintf(variable_name, len, "branch.%s.merge", branch); - len = sizeof("refs/heads/") + strlen(branch); - merge_head = malloc(len); - snprintf(merge_head, len, "refs/heads/%s", branch); - git_config_set_string(conf, variable_name, merge_head); - - /* finally create an empty commit and push it to the remote */ - if (do_git_save(repo, branch, remote, false, true)) - return NULL; - return(repo); -} - -static git_repository *create_local_repo(const char *localdir, const char *remote, const char *branch, enum remote_transport rt) -{ - int error; - git_repository *cloned_repo = NULL; - git_clone_options opts = GIT_CLONE_OPTIONS_INIT; - - if (verbose) - fprintf(stderr, "git storage: create_local_repo\n"); - - opts.fetch_opts.callbacks.transfer_progress = &transfer_progress_cb; - if (rt == RT_SSH) - opts.fetch_opts.callbacks.credentials = credential_ssh_cb; - else if (rt == RT_HTTPS) - opts.fetch_opts.callbacks.credentials = credential_https_cb; - opts.repository_cb = repository_create_cb; - opts.fetch_opts.callbacks.certificate_check = certificate_check_cb; - - opts.checkout_branch = branch; - if (rt == RT_HTTPS && !canReachCloudServer()) - return 0; - if (verbose > 1) - fprintf(stderr, "git storage: calling git_clone()\n"); - error = git_clone(&cloned_repo, remote, localdir, &opts); - if (verbose > 1) - fprintf(stderr, "git storage: returned from git_clone() with error %d\n", error); - if (error) { - char *msg = giterr_last()->message; - int len = sizeof("Reference 'refs/remotes/origin/' not found") + strlen(branch); - char *pattern = malloc(len); - snprintf(pattern, len, "Reference 'refs/remotes/origin/%s' not found", branch); - if (strstr(remote, prefs.cloud_git_url) && strstr(msg, pattern)) { - /* we're trying to open the remote branch that corresponds - * to our cloud storage and the branch doesn't exist. - * So we need to create the branch and push it to the remote */ - cloned_repo = create_and_push_remote(localdir, remote, branch); -#if !defined(DEBUG) && !defined(SUBSURFACE_MOBILE) - } else if (is_subsurface_cloud) { - report_error(translate("gettextFromC", "Error connecting to Subsurface cloud storage")); -#endif - } else { - report_error(translate("gettextFromC", "git clone of %s failed (%s)"), remote, msg); - } - free(pattern); - } - return cloned_repo; -} - -static struct git_repository *get_remote_repo(const char *localdir, const char *remote, const char *branch) -{ - struct stat st; - enum remote_transport rt; - - /* figure out the remote transport */ - if (strncmp(remote, "ssh://", 6) == 0) - rt = RT_SSH; - else if (strncmp(remote, "https://", 8) == 0) - rt = RT_HTTPS; - else - rt = RT_OTHER; - - if (verbose > 1) { - fprintf(stderr, "git_remote_repo: accessing %s\n", remote); - } - git_storage_update_progress(1, "start git interaction"); - /* Do we already have a local cache? */ - if (!stat(localdir, &st)) { - if (!S_ISDIR(st.st_mode)) { - if (is_subsurface_cloud) - (void)cleanup_local_cache(remote, branch); - else - report_error("local git cache at '%s' is corrupt"); - return NULL; - } - return update_local_repo(localdir, remote, branch, rt); - } - if (!prefs.git_local_only) - return create_local_repo(localdir, remote, branch, rt); - else - return 0; - -} - -/* - * This turns a remote repository into a local one if possible. - * - * The recognized formats are - * git://host/repo[branch] - * ssh://host/repo[branch] - * http://host/repo[branch] - * https://host/repo[branch] - * file://repo[branch] - */ -static struct git_repository *is_remote_git_repository(char *remote, const char *branch) -{ - char c, *localdir; - const char *p = remote; - - while ((c = *p++) >= 'a' && c <= 'z') - /* nothing */; - if (c != ':') - return NULL; - if (*p++ != '/' || *p++ != '/') - return NULL; - - /* Special-case "file://", since it's already local */ - if (!strncmp(remote, "file://", 7)) - remote += 7; - - /* - * Ok, we found "[a-z]*://", we've simplified the - * local repo case (because libgit2 is insanely slow - * for that), and we think we have a real "remote - * git" format. - * - * We now create the SHA1 hash of the whole thing, - * including the branch name. That will be our unique - * unique local repository name. - * - * NOTE! We will create a local repository per branch, - * because - * - * (a) libgit2 remote tracking branch support seems to - * be a bit lacking - * (b) we'll actually check the branch out so that we - * can do merges etc too. - * - * so even if you have a single remote git repo with - * multiple branches for different people, the local - * caches will sadly force that to split into multiple - * individual repositories. - */ - - /* - * next we need to make sure that any encoded username - * has been extracted from an https:// based URL - */ - if (!strncmp(remote, "https://", 8)) { - char *at = strchr(remote, '@'); - if (at) { - /* was this the @ that denotes an account? that means it was before the - * first '/' after the https:// - so let's find a '/' after that and compare */ - char *slash = strchr(remote + 8, '/'); - if (slash && slash > at) { - /* grab the part between "https://" and "@" as encoded email address - * (that's our username) and move the rest of the URL forward, remembering - * to copy the closing NUL as well */ - prefs.cloud_storage_email_encoded = strndup(remote + 8, at - remote - 8); - memmove(remote + 8, at + 1, strlen(at + 1) + 1); - } - } - } - localdir = get_local_dir(remote, branch); - if (!localdir) - return NULL; - - /* remember if the current git storage we are working on is our cloud storage - * this is used to create more user friendly error message and warnings */ - is_subsurface_cloud = strstr(remote, prefs.cloud_git_url) != NULL; - - return get_remote_repo(localdir, remote, branch); -} - -/* - * If it's not a git repo, return NULL. Be very conservative. - */ -struct git_repository *is_git_repository(const char *filename, const char **branchp, const char **remote, bool dry_run) -{ - int flen, blen, ret; - int offset = 1; - struct stat st; - git_repository *repo; - char *loc, *branch; - - flen = strlen(filename); - if (!flen || filename[--flen] != ']') - return NULL; - - /* Find the matching '[' */ - blen = 0; - while (flen && filename[--flen] != '[') - blen++; - - /* Ignore slashes at the end of the repo name */ - while (flen && filename[flen-1] == '/') { - flen--; - offset++; - } - - if (!flen) - return NULL; - - /* - * This is the "point of no return": the name matches - * the git repository name rules, and we will no longer - * return NULL. - * - * We will either return "dummy_git_repository" and the - * branch pointer will have the _whole_ filename in it, - * or we will return a real git repository with the - * branch pointer being filled in with just the branch - * name. - * - * The actual git reading/writing routines can use this - * to generate proper error messages. - */ - *branchp = filename; - loc = format_string("%.*s", flen, filename); - if (!loc) - return dummy_git_repository; - - branch = format_string("%.*s", blen, filename + flen + offset); - if (!branch) { - free(loc); - return dummy_git_repository; - } - - if (dry_run) { - *branchp = branch; - *remote = loc; - return dummy_git_repository; - } - repo = is_remote_git_repository(loc, branch); - if (repo) { - if (remote) - *remote = loc; - else - free(loc); - *branchp = branch; - return repo; - } - - if (stat(loc, &st) < 0 || !S_ISDIR(st.st_mode)) { - free(loc); - free(branch); - return dummy_git_repository; - } - - ret = git_repository_open(&repo, loc); - free(loc); - if (ret < 0) { - free(branch); - return dummy_git_repository; - } - if (remote) - *remote = NULL; - *branchp = branch; - return repo; -} diff --git a/subsurface-core/git-access.h b/subsurface-core/git-access.h deleted file mode 100644 index 3a0c5160a..000000000 --- a/subsurface-core/git-access.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef GITACCESS_H -#define GITACCESS_H - -#include "git2.h" - -#ifdef __cplusplus -extern "C" { -#else -#include -#endif - -enum remote_transport { RT_OTHER, RT_HTTPS, RT_SSH }; - -struct git_oid; -struct git_repository; -#define dummy_git_repository ((git_repository *)3ul) /* Random bogus pointer, not NULL */ -extern struct git_repository *is_git_repository(const char *filename, const char **branchp, const char **remote, bool dry_run); -extern int sync_with_remote(struct git_repository *repo, const char *remote, const char *branch, enum remote_transport rt); -extern int git_save_dives(struct git_repository *, const char *, const char *remote, bool select_only); -extern int git_load_dives(struct git_repository *, const char *); -extern const char *get_sha(git_repository *repo, const char *branch); -extern int do_git_save(git_repository *repo, const char *branch, const char *remote, bool select_only, bool create_empty); -extern const char *saved_git_id; -extern void clear_git_id(void); -extern void set_git_id(const struct git_oid *); -void set_git_update_cb(int (*)(int, const char *)); -int git_storage_update_progress(int percent, const char *text); -char *get_local_dir(const char *remote, const char *branch); - -extern int last_git_storage_update_val; - -#ifdef __cplusplus -} -#endif -#endif // GITACCESS_H - diff --git a/subsurface-core/gpslocation.cpp b/subsurface-core/gpslocation.cpp deleted file mode 100644 index 075b1c046..000000000 --- a/subsurface-core/gpslocation.cpp +++ /dev/null @@ -1,606 +0,0 @@ -#include "gpslocation.h" -#include "gpslistmodel.h" -#include "pref.h" -#include "dive.h" -#include "helpers.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define GPS_FIX_ADD_URL "http://api.subsurface-divelog.org/api/dive/add/" -#define GPS_FIX_DELETE_URL "http://api.subsurface-divelog.org/api/dive/delete/" -#define GPS_FIX_DOWNLOAD_URL "http://api.subsurface-divelog.org/api/dive/get/" -#define GET_WEBSERVICE_UID_URL "https://cloud.subsurface-divelog.org/webuserid/" - -GpsLocation *GpsLocation::m_Instance = NULL; - -GpsLocation::GpsLocation(void (*showMsgCB)(const char *), QObject *parent) : QObject(parent) -{ - Q_ASSERT_X(m_Instance == NULL, "GpsLocation", "GpsLocation recreated"); - m_Instance = this; - m_GpsSource = 0; - waitingForPosition = false; - showMessageCB = showMsgCB; - // create a QSettings object that's separate from the main application settings - geoSettings = new QSettings(QSettings::NativeFormat, QSettings::UserScope, - QString("org.subsurfacedivelog"), QString("subsurfacelocation"), this); -#ifdef SUBSURFACE_MOBILE - if (hasLocationsSource()) - status(QString("Found GPS with positioning methods %1").arg(QString::number(m_GpsSource->supportedPositioningMethods(), 16))); -#endif - userAgent = getUserAgent(); - loadFromStorage(); -} - -GpsLocation *GpsLocation::instance() -{ - Q_ASSERT(m_Instance != NULL); - - return m_Instance; -} - -GpsLocation::~GpsLocation() -{ - m_Instance = NULL; -} - -QGeoPositionInfoSource *GpsLocation::getGpsSource() -{ - if (!m_GpsSource) { - m_GpsSource = QGeoPositionInfoSource::createDefaultSource(this); - if (m_GpsSource != 0) { -#ifndef SUBSURFACE_MOBILE - if (verbose) -#endif - status(QString("Created position source %1").arg(m_GpsSource->sourceName())); - connect(m_GpsSource, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(newPosition(QGeoPositionInfo))); - connect(m_GpsSource, SIGNAL(updateTimeout()), this, SLOT(updateTimeout())); - m_GpsSource->setUpdateInterval(5 * 60 * 1000); // 5 minutes so the device doesn't drain the battery - } else { -#ifdef SUBSURFACE_MOBILE - status("don't have GPS source"); -#endif - } - } - return m_GpsSource; -} - -bool GpsLocation::hasLocationsSource() -{ - QGeoPositionInfoSource *gpsSource = getGpsSource(); - return gpsSource != 0 && (gpsSource->supportedPositioningMethods() & QGeoPositionInfoSource::SatellitePositioningMethods); -} - -void GpsLocation::serviceEnable(bool toggle) -{ - QGeoPositionInfoSource *gpsSource = getGpsSource(); - if (!gpsSource) { - if (toggle) - status("Can't start location service, no location source available"); - return; - } - if (toggle) { - gpsSource->startUpdates(); - status(QString("Starting Subsurface GPS service with update interval %1").arg(gpsSource->updateInterval())); - } else { - gpsSource->stopUpdates(); - status("Stopping Subsurface GPS service"); - } -} - -QString GpsLocation::currentPosition() -{ - if (!hasLocationsSource()) - return tr("Unknown GPS location"); - if (m_trackers.count() && m_trackers.lastKey() + 300 >= QDateTime::currentMSecsSinceEpoch() / 1000) { - // we can simply use the last position that we tracked - gpsTracker gt = m_trackers.last(); - QString gpsString = printGPSCoords(gt.latitude.udeg, gt.longitude.udeg); - qDebug() << "returning last position" << gpsString; - return gpsString; - } - qDebug() << "requesting new GPS position"; - m_GpsSource->requestUpdate(); - // ok, we need to get the current position and somehow in the callback update the location in the QML UI - // punting right now - waitingForPosition = true; - return QString("waiting for the next gps location"); -} - -void GpsLocation::newPosition(QGeoPositionInfo pos) -{ - int64_t lastTime = 0; - QGeoCoordinate lastCoord; - int nr = m_trackers.count(); - if (nr) { - gpsTracker gt = m_trackers.last(); - lastCoord.setLatitude(gt.latitude.udeg / 1000000.0); - lastCoord.setLongitude(gt.longitude.udeg / 1000000.0); - lastTime = gt.when; - } - // if we are waiting for a position update or - // if we have no record stored or if at least the configured minimum - // time has passed or we moved at least the configured minimum distance - int64_t delta = (int64_t)pos.timestamp().toTime_t() + gettimezoneoffset() - lastTime; - if (!nr || waitingForPosition || delta > prefs.time_threshold || - lastCoord.distanceTo(pos.coordinate()) > prefs.distance_threshold) { - QString msg("received new position %1 after delta %2 threshold %3"); - status(qPrintable(msg.arg(pos.coordinate().toString()).arg(delta).arg(prefs.time_threshold))); - waitingForPosition = false; - gpsTracker gt; - gt.when = pos.timestamp().toTime_t(); - gt.when += gettimezoneoffset(gt.when); - gt.latitude.udeg = rint(pos.coordinate().latitude() * 1000000); - gt.longitude.udeg = rint(pos.coordinate().longitude() * 1000000); - addFixToStorage(gt); - } -} - -void GpsLocation::updateTimeout() -{ - status("request to get new position timed out"); -} - -void GpsLocation::status(QString msg) -{ - qDebug() << msg; - if (showMessageCB) - (*showMessageCB)(qPrintable(msg)); -} - -QString GpsLocation::getUserid(QString user, QString passwd) -{ - qDebug() << "called getUserid"; - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - - QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - QUrl url(GET_WEBSERVICE_UID_URL); - QString data; - data = user + " " + passwd; - QNetworkRequest request; - request.setUrl(url); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Accept", "text/html"); - request.setRawHeader("Content-type", "application/x-www-form-urlencoded"); - reply = manager->post(request, data.toUtf8()); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(getUseridError(QNetworkReply::NetworkError))); - timer.start(10000); - loop.exec(); - if (timer.isActive()) { - timer.stop(); - if (reply->error() == QNetworkReply::NoError) { - QString result = reply->readAll(); - status(QString("received ") + result); - result.remove("WebserviceID:"); - reply->deleteLater(); - return result; - } - } else { - status("Getting Web service ID timed out"); - } - reply->deleteLater(); - return QString(); -} - -int GpsLocation::getGpsNum() const -{ - return m_trackers.count(); -} - -static void copy_gps_location(struct gpsTracker &gps, struct dive *d) -{ - struct dive_site *ds = get_dive_site_by_uuid(d->dive_site_uuid); - if (!ds) { - d->dive_site_uuid = create_dive_site(qPrintable(gps.name), gps.when); - ds = get_dive_site_by_uuid(d->dive_site_uuid); - } - ds->latitude = gps.latitude; - ds->longitude = gps.longitude; -} - -#define SAME_GROUP 6 * 3600 /* six hours */ -bool GpsLocation::applyLocations() -{ - int i; - bool changed = false; - int last = 0; - int cnt = m_trackers.count(); - if (cnt == 0) - return false; - - // create a table with the GPS information - QList gpsTable = m_trackers.values(); - - // now walk the dive table and see if we can fill in missing gps data - struct dive *d; - for_each_dive(i, d) { - if (dive_has_gps_location(d)) - continue; - for (int j = last; j < cnt; j++) { - if (time_during_dive_with_offset(d, gpsTable[j].when, SAME_GROUP)) { - if (verbose) - qDebug() << "processing gpsFix @" << get_dive_date_string(gpsTable[j].when) << - "which is withing six hours of dive from" << - get_dive_date_string(d->when) << "until" << - get_dive_date_string(d->when + d->duration.seconds); - /* - * If position is fixed during dive. This is the good one. - * Asign and mark position, and end gps_location loop - */ - if (time_during_dive_with_offset(d, gpsTable[j].when, 0)) { - if (verbose) - qDebug() << "gpsFix is during the dive, pick that one"; - copy_gps_location(gpsTable[j], d); - changed = true; - last = j; - break; - } else { - /* - * If it is not, check if there are more position fixes in SAME_GROUP range - */ - if (j + 1 < cnt && time_during_dive_with_offset(d, gpsTable[j+1].when, SAME_GROUP)) { - if (verbose) - qDebug() << "look at the next gps fix @" << get_dive_date_string(gpsTable[j+1].when); - /* first let's test if this one is during the dive */ - if (time_during_dive_with_offset(d, gpsTable[j+1].when, 0)) { - if (verbose) - qDebug() << "which is during the dive, pick that one"; - copy_gps_location(gpsTable[j+1], d); - changed = true; - last = j + 1; - break; - } - /* we know the gps fixes are sorted; if they are both before the dive, ignore the first, - * if theay are both after the dive, take the first, - * if the first is before and the second is after, take the closer one */ - if (gpsTable[j+1].when < d->when) { - if (verbose) - qDebug() << "which is closer to the start of the dive, do continue with that"; - continue; - } else if (gpsTable[j].when > d->when + d->duration.seconds) { - if (verbose) - qDebug() << "which is even later after the end of the dive, so pick the previous one"; - copy_gps_location(gpsTable[j], d); - changed = true; - last = j; - break; - } else { - /* ok, gpsFix is before, nextgpsFix is after */ - if (d->when - gpsTable[j].when <= gpsTable[j+1].when - (d->when + d->duration.seconds)) { - if (verbose) - qDebug() << "pick the one before as it's closer to the start"; - copy_gps_location(gpsTable[j], d); - changed = true; - last = j; - break; - } else { - if (verbose) - qDebug() << "pick the one after as it's closer to the start"; - copy_gps_location(gpsTable[j+1], d); - changed = true; - last = j + 1; - break; - } - } - /* - * If no more positions in range, the actual is the one. Asign, mark and end loop. - */ - } else { - if (verbose) - qDebug() << "which seems to be the best one for this dive, so pick it"; - copy_gps_location(gpsTable[j], d); - changed = true; - last = j; - break; - } - } - } else { - /* If position is out of SAME_GROUP range and in the future, mark position for - * next dive iteration and end the gps_location loop - */ - if (gpsTable[j].when >= d->when + d->duration.seconds + SAME_GROUP) { - last = j; - break; - } - } - - } - } - if (changed) - mark_divelist_changed(true); - return changed; -} - -QMap GpsLocation::currentGPSInfo() const -{ - return m_trackers; -} - -void GpsLocation::loadFromStorage() -{ - int nr = geoSettings->value(QString("count")).toInt(); - for (int i = 0; i < nr; i++) { - struct gpsTracker gt; - gt.when = geoSettings->value(QString("gpsFix%1_time").arg(i)).toLongLong(); - gt.latitude.udeg = geoSettings->value(QString("gpsFix%1_lat").arg(i)).toInt(); - gt.longitude.udeg = geoSettings->value(QString("gpsFix%1_lon").arg(i)).toInt(); - gt.name = geoSettings->value(QString("gpsFix%1_name").arg(i)).toString(); - gt.idx = i; - m_trackers.insert(gt.when, gt); - } -} - -void GpsLocation::replaceFixToStorage(gpsTracker >) -{ - if (!m_trackers.keys().contains(gt.when)) { - addFixToStorage(gt); - return; - } - gpsTracker replacedTracker = m_trackers.value(gt.when); - geoSettings->setValue(QString("gpsFix%1_time").arg(replacedTracker.idx), gt.when); - geoSettings->setValue(QString("gpsFix%1_lat").arg(replacedTracker.idx), gt.latitude.udeg); - geoSettings->setValue(QString("gpsFix%1_lon").arg(replacedTracker.idx), gt.longitude.udeg); - geoSettings->setValue(QString("gpsFix%1_name").arg(replacedTracker.idx), gt.name); - replacedTracker.latitude = gt.latitude; - replacedTracker.longitude = gt.longitude; - replacedTracker.name = gt.name; -} - -void GpsLocation::addFixToStorage(gpsTracker >) -{ - int nr = m_trackers.count(); - geoSettings->setValue("count", nr + 1); - geoSettings->setValue(QString("gpsFix%1_time").arg(nr), gt.when); - geoSettings->setValue(QString("gpsFix%1_lat").arg(nr), gt.latitude.udeg); - geoSettings->setValue(QString("gpsFix%1_lon").arg(nr), gt.longitude.udeg); - geoSettings->setValue(QString("gpsFix%1_name").arg(nr), gt.name); - gt.idx = nr; - geoSettings->sync(); - m_trackers.insert(gt.when, gt); -} - -void GpsLocation::deleteFixFromStorage(gpsTracker >) -{ - qint64 when = gt.when; - int cnt = m_trackers.count(); - if (cnt == 0 || !m_trackers.keys().contains(when)) { - qDebug() << "no gps fix with timestamp" << when; - return; - } - if (geoSettings->value(QString("gpsFix%1_time").arg(gt.idx)).toLongLong() != when) { - qDebug() << "uh oh - index for tracker has gotten out of sync..."; - return; - } - if (cnt > 1) { - // now we move the last tracker into that spot (so our settings stay consecutive) - // and delete the last settings entry - when = geoSettings->value(QString("gpsFix%1_time").arg(cnt - 1)).toLongLong(); - struct gpsTracker movedTracker = m_trackers.value(when); - movedTracker.idx = gt.idx; - m_trackers.remove(movedTracker.when); - m_trackers.insert(movedTracker.when, movedTracker); - geoSettings->setValue(QString("gpsFix%1_time").arg(movedTracker.idx), when); - geoSettings->setValue(QString("gpsFix%1_lat").arg(movedTracker.idx), movedTracker.latitude.udeg); - geoSettings->setValue(QString("gpsFix%1_lon").arg(movedTracker.idx), movedTracker.longitude.udeg); - geoSettings->setValue(QString("gpsFix%1_name").arg(movedTracker.idx), movedTracker.name); - geoSettings->remove(QString("gpsFix%1_lat").arg(cnt - 1)); - geoSettings->remove(QString("gpsFix%1_lon").arg(cnt - 1)); - geoSettings->remove(QString("gpsFix%1_time").arg(cnt - 1)); - geoSettings->remove(QString("gpsFix%1_name").arg(cnt - 1)); - } - geoSettings->setValue("count", cnt - 1); - geoSettings->sync(); - m_trackers.remove(gt.when); -} - -void GpsLocation::deleteGpsFix(qint64 when) -{ - struct gpsTracker defaultTracker; - defaultTracker.when = 0; - struct gpsTracker deletedTracker = m_trackers.value(when, defaultTracker); - if (deletedTracker.when != when) { - qDebug() << "can't find tracker for timestamp" << when; - return; - } - deleteFixFromStorage(deletedTracker); - m_deletedTrackers.append(deletedTracker); -} - -void GpsLocation::clearGpsData() -{ - m_trackers.clear(); - geoSettings->clear(); - geoSettings->sync(); -} - -void GpsLocation::postError(QNetworkReply::NetworkError error) -{ - Q_UNUSED(error); - status(QString("error when sending a GPS fix: %1").arg(reply->errorString())); -} - -void GpsLocation::getUseridError(QNetworkReply::NetworkError error) -{ - Q_UNUSED(error); - status(QString("error when retrieving Subsurface webservice user id: %1").arg(reply->errorString())); -} - -void GpsLocation::deleteFixesFromServer() -{ - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - - QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - QUrl url(GPS_FIX_DELETE_URL); - QList keys = m_trackers.keys(); - while (!m_deletedTrackers.isEmpty()) { - gpsTracker gt = m_deletedTrackers.takeFirst(); - QDateTime dt; - QUrlQuery data; - dt.setTime_t(gt.when - gettimezoneoffset(gt.when)); - data.addQueryItem("login", prefs.userid); - data.addQueryItem("dive_date", dt.toString("yyyy-MM-dd")); - data.addQueryItem("dive_time", dt.toString("hh:mm")); - status(data.toString(QUrl::FullyEncoded).toUtf8()); - QNetworkRequest request; - request.setUrl(url); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Accept", "text/json"); - request.setRawHeader("Content-type", "application/x-www-form-urlencoded"); - reply = manager->post(request, data.toString(QUrl::FullyEncoded).toUtf8()); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(postError(QNetworkReply::NetworkError))); - timer.start(10000); - loop.exec(); - if (timer.isActive()) { - timer.stop(); - if (reply->error() != QNetworkReply::NoError) { - QString response = reply->readAll(); - status(QString("Server response:") + reply->readAll()); - } - } else { - status("Deleting on the server timed out - try again later"); - m_deletedTrackers.prepend(gt); - break; - } - reply->deleteLater(); - status(QString("completed deleting gps fix %1 - response: ").arg(gt.idx) + reply->readAll()); - } -} - -void GpsLocation::uploadToServer() -{ - // we want to do this one at a time (the server prefers that) - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - - QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - QUrl url(GPS_FIX_ADD_URL); - Q_FOREACH(qint64 key, m_trackers.keys()) { - struct gpsTracker gt = m_trackers.value(key); - QDateTime dt; - QUrlQuery data; - dt.setTime_t(gt.when - gettimezoneoffset(gt.when)); - data.addQueryItem("login", prefs.userid); - data.addQueryItem("dive_date", dt.toString("yyyy-MM-dd")); - data.addQueryItem("dive_time", dt.toString("hh:mm")); - data.addQueryItem("dive_latitude", QString::number(gt.latitude.udeg / 1000000.0, 'f', 9)); - data.addQueryItem("dive_longitude", QString::number(gt.longitude.udeg / 1000000.0, 'f', 9)); - if (gt.name.isEmpty()) - gt.name = "Auto-created dive"; - data.addQueryItem("dive_name", gt.name); - status(data.toString(QUrl::FullyEncoded).toUtf8()); - QNetworkRequest request; - request.setUrl(url); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Accept", "text/json"); - request.setRawHeader("Content-type", "application/x-www-form-urlencoded"); - reply = manager->post(request, data.toString(QUrl::FullyEncoded).toUtf8()); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - // somehoe I cannot get this to work with the new connect syntax: - // connect(reply, &QNetworkReply::error, this, &GpsLocation::postError); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(postError(QNetworkReply::NetworkError))); - timer.start(10000); - loop.exec(); - if (timer.isActive()) { - timer.stop(); - if (reply->error() != QNetworkReply::NoError) { - QString response = reply->readAll(); - if (!response.contains("Duplicate entry")) { - status(QString("Server response:") + reply->readAll()); - break; - } - } - } else { - status("Uploading to server timed out"); - break; - } - reply->deleteLater(); - status(QString("completed sending gps fix %1 - response: ").arg(gt.idx) + reply->readAll()); - } - // and now remove the ones that were locally deleted - deleteFixesFromServer(); -} - -void GpsLocation::downloadFromServer() -{ - QEventLoop loop; - QTimer timer; - timer.setSingleShot(true); - QNetworkAccessManager *manager = new QNetworkAccessManager(qApp); - QUrl url(QString(GPS_FIX_DOWNLOAD_URL "?login=%1").arg(prefs.userid)); - QNetworkRequest request; - request.setUrl(url); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - request.setRawHeader("Accept", "text/json"); - request.setRawHeader("Content-type", "text/html"); - qDebug() << "downloadFromServer accessing" << url; - reply = manager->get(request); - connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit); - connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(getUseridError(QNetworkReply::NetworkError))); - timer.start(10000); - loop.exec(); - if (timer.isActive()) { - timer.stop(); - if (!reply->error()) { - QString response = reply->readAll(); - QJsonDocument json = QJsonDocument::fromJson(response.toLocal8Bit()); - QJsonObject object = json.object(); - if (object.value("download").toString() != "ok") { - qDebug() << "problems downloading GPS fixes"; - return; - } - qDebug() << "already have" << m_trackers.count() << "GPS fixes"; - QJsonArray downloadedFixes = object.value("dives").toArray(); - qDebug() << downloadedFixes.count() << "GPS fixes downloaded"; - for (int i = 0; i < downloadedFixes.count(); i++) { - QJsonObject fix = downloadedFixes[i].toObject(); - QString date = fix.value("date").toString(); - QString time = fix.value("time").toString(); - QString name = fix.value("name").toString(); - QString latitude = fix.value("latitude").toString(); - QString longitude = fix.value("longitude").toString(); - QDateTime timestamp = QDateTime::fromString(date + " " + time, "yyyy-M-d hh:m:s"); - - struct gpsTracker gt; - gt.when = timestamp.toMSecsSinceEpoch() / 1000 + gettimezoneoffset(timestamp.toMSecsSinceEpoch() / 1000); - gt.latitude.udeg = latitude.toDouble() * 1000000; - gt.longitude.udeg = longitude.toDouble() * 1000000; - gt.name = name; - // add this GPS fix to the QMap and the settings (remove existing fix at the same timestamp first) - if (m_trackers.keys().contains(gt.when)) { - qDebug() << "already have a fix at time stamp" << gt.when; - replaceFixToStorage(gt); - } else { - addFixToStorage(gt); - } - } - } else { - qDebug() << "network error" << reply->error() << reply->errorString() << reply->readAll(); - } - } else { - qDebug() << "download timed out"; - status("Download from server timed out"); - } - reply->deleteLater(); -} diff --git a/subsurface-core/gpslocation.h b/subsurface-core/gpslocation.h deleted file mode 100644 index 82b51a291..000000000 --- a/subsurface-core/gpslocation.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef GPSLOCATION_H -#define GPSLOCATION_H - -#include "units.h" -#include -#include -#include -#include -#include -#include -#include - -struct gpsTracker { - degrees_t latitude; - degrees_t longitude; - qint64 when; - QString name; - int idx; -}; - -class GpsLocation : QObject { - Q_OBJECT -public: - GpsLocation(void (*showMsgCB)(const char *msg), QObject *parent); - ~GpsLocation(); - static GpsLocation *instance(); - bool applyLocations(); - int getGpsNum() const; - QString getUserid(QString user, QString passwd); - bool hasLocationsSource(); - QString currentPosition(); - - QMap currentGPSInfo() const; - -private: - QGeoPositionInfo lastPos; - QGeoPositionInfoSource *getGpsSource(); - QGeoPositionInfoSource *m_GpsSource; - void status(QString msg); - QSettings *geoSettings; - QNetworkReply *reply; - QString userAgent; - void (*showMessageCB)(const char *msg); - static GpsLocation *m_Instance; - bool waitingForPosition; - QMap m_trackers; - QList m_deletedTrackers; - void loadFromStorage(); - void addFixToStorage(gpsTracker >); - void replaceFixToStorage(gpsTracker >); - void deleteFixFromStorage(gpsTracker >); - void deleteFixesFromServer(); - -public slots: - void serviceEnable(bool toggle); - void newPosition(QGeoPositionInfo pos); - void updateTimeout(); - void uploadToServer(); - void downloadFromServer(); - void postError(QNetworkReply::NetworkError error); - void getUseridError(QNetworkReply::NetworkError error); - void clearGpsData(); - void deleteGpsFix(qint64 when); -}; - -#endif // GPSLOCATION_H diff --git a/subsurface-core/helpers.h b/subsurface-core/helpers.h deleted file mode 100644 index f88da015c..000000000 --- a/subsurface-core/helpers.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * helpers.h - * - * header file for random helper functions of Subsurface - * - */ -#ifndef HELPERS_H -#define HELPERS_H - -#include -#include "dive.h" -#include "qthelper.h" - -QString get_depth_string(depth_t depth, bool showunit = false, bool showdecimal = true); -QString get_depth_string(int mm, bool showunit = false, bool showdecimal = true); -QString get_depth_unit(); -QString get_weight_string(weight_t weight, bool showunit = false); -QString get_weight_unit(); -QString get_cylinder_used_gas_string(cylinder_t *cyl, bool showunit = false); -QString get_temperature_string(temperature_t temp, bool showunit = false); -QString get_temp_unit(); -QString get_volume_string(volume_t volume, bool showunit = false); -QString get_volume_unit(); -QString get_pressure_string(pressure_t pressure, bool showunit = false); -QString get_pressure_unit(); -void set_default_dive_computer(const char *vendor, const char *product); -void set_default_dive_computer_device(const char *name); -void set_default_dive_computer_download_mode(int downloadMode); -QString getSubsurfaceDataPath(QString folderToFind); -QString getPrintingTemplatePathUser(); -QString getPrintingTemplatePathBundle(); -void copyPath(QString src, QString dst); -extern const QString get_dc_nickname(const char *model, uint32_t deviceid); -int gettimezoneoffset(timestamp_t when = 0); -int parseLengthToMm(const QString &text); -int parseTemperatureToMkelvin(const QString &text); -int parseWeightToGrams(const QString &text); -int parsePressureToMbar(const QString &text); -int parseGasMixO2(const QString &text); -int parseGasMixHE(const QString &text); -QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText); -QString get_dive_date_string(timestamp_t when); -QString get_short_dive_date_string(timestamp_t when); -bool is_same_day (timestamp_t trip_when, timestamp_t dive_when); -QString get_trip_date_string(timestamp_t when, int nr, bool getday); -QString uiLanguage(QLocale *callerLoc); -QLocale getLocale(); -void selectedDivesGasUsed(QVector > &gasUsed); -QString getUserAgent(); - -#if defined __APPLE__ -#define TITLE_OR_TEXT(_t, _m) "", _t + "\n" + _m -#else -#define TITLE_OR_TEXT(_t, _m) _t, _m -#endif -#endif // HELPERS_H diff --git a/subsurface-core/imagedownloader.cpp b/subsurface-core/imagedownloader.cpp deleted file mode 100644 index daa49eadf..000000000 --- a/subsurface-core/imagedownloader.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "dive.h" -#include "metrics.h" -#include "divelist.h" -#include "qthelper.h" -#include "imagedownloader.h" -#include - -#include - -QUrl cloudImageURL(const char *hash) -{ - return QUrl::fromUserInput(QString("https://cloud.subsurface-divelog.org/images/").append(hash)); -} - -ImageDownloader::ImageDownloader(struct picture *pic) -{ - picture = pic; -} - -ImageDownloader::~ImageDownloader() -{ - picture_free(picture); -} - -void ImageDownloader::load(bool fromHash){ - QUrl url; - loadFromHash = fromHash; - if(fromHash) - url = cloudImageURL(picture->hash); - else - url = QUrl::fromUserInput(QString(picture->filename)); - if (url.isValid()) { - QEventLoop loop; - QNetworkRequest request(url); - connect(&manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(saveImage(QNetworkReply *))); - QNetworkReply *reply = manager.get(request); - while (reply->isRunning()) { - loop.processEvents(); - sleep(1); - } - } -} - -void ImageDownloader::saveImage(QNetworkReply *reply) -{ - QByteArray imageData = reply->readAll(); - QImage image = QImage(); - image.loadFromData(imageData); - if (image.isNull()) { - if (loadFromHash) - load(false); - return; - } - QCryptographicHash hash(QCryptographicHash::Sha1); - hash.addData(imageData); - QString path = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first(); - QDir dir(path); - if (!dir.exists()) - dir.mkpath(path); - QFile imageFile(path.append("/").append(hash.result().toHex())); - if (imageFile.open(QIODevice::WriteOnly)) { - QDataStream stream(&imageFile); - stream.writeRawData(imageData.data(), imageData.length()); - imageFile.waitForBytesWritten(-1); - imageFile.close(); - add_hash(imageFile.fileName(), hash.result()); - learnHash(picture, hash.result()); - } - reply->manager()->deleteLater(); - reply->deleteLater(); - // This should be called to make the picture actually show. - // Problem is DivePictureModel is not in subsurface-core. - // Nevertheless, the image shows when the dive is selected the next time. - // DivePictureModel::instance()->updateDivePictures(); - -} - -void loadPicture(struct picture *picture, bool fromHash) -{ - ImageDownloader download(picture); - download.load(fromHash); -} - -SHashedImage::SHashedImage(struct picture *picture) : QImage() -{ - QUrl url = QUrl::fromUserInput(localFilePath(QString(picture->filename))); - if(url.isLocalFile()) - load(url.toLocalFile()); - if (isNull()) { - // This did not load anything. Let's try to get the image from other sources - // Let's try to load it locally via its hash - QString filename = fileFromHash(picture->hash); - if (filename.isNull()) { - // That didn't produce a local filename. - // Try the cloud server - QtConcurrent::run(loadPicture, clone_picture(picture), true); - } else { - // Load locally from translated file name - load(filename); - if (!isNull()) { - // Make sure the hash still matches the image file - QtConcurrent::run(updateHash, clone_picture(picture)); - } else { - // Interpret filename as URL - QtConcurrent::run(loadPicture, clone_picture(picture), false); - } - } - } else { - // We loaded successfully. Now, make sure hash is up to date. - QtConcurrent::run(hashPicture, clone_picture(picture)); - } -} - diff --git a/subsurface-core/imagedownloader.h b/subsurface-core/imagedownloader.h deleted file mode 100644 index f4e3df875..000000000 --- a/subsurface-core/imagedownloader.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef IMAGEDOWNLOADER_H -#define IMAGEDOWNLOADER_H - -#include -#include -#include - -typedef QPair SHashedFilename; - -extern QUrl cloudImageURL(const char *hash); - - -class ImageDownloader : public QObject { - Q_OBJECT; -public: - ImageDownloader(struct picture *picture); - ~ImageDownloader(); - void load(bool fromHash); - -private: - struct picture *picture; - QNetworkAccessManager manager; - bool loadFromHash; - -private slots: - void saveImage(QNetworkReply *reply); -}; - -class SHashedImage : public QImage { -public: - SHashedImage(struct picture *picture); -}; - -#endif // IMAGEDOWNLOADER_H diff --git a/subsurface-core/isocialnetworkintegration.cpp b/subsurface-core/isocialnetworkintegration.cpp deleted file mode 100644 index eb1e82a49..000000000 --- a/subsurface-core/isocialnetworkintegration.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "isocialnetworkintegration.h" - -//Hack for moc. -ISocialNetworkIntegration::ISocialNetworkIntegration(QObject* parent) : QObject(parent) -{ -} diff --git a/subsurface-core/isocialnetworkintegration.h b/subsurface-core/isocialnetworkintegration.h deleted file mode 100644 index 70ea3d9ab..000000000 --- a/subsurface-core/isocialnetworkintegration.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef ISOCIALNETWORKINTEGRATION_H -#define ISOCIALNETWORKINTEGRATION_H - -#include - -/* This Interface represents a Plugin for Social Network integration, - * with it you may be able to create plugins for facebook, instagram, - * twitpic, google plus and any other thing you may imagine. - * - * We bundle facebook integration as an example. - */ - -class ISocialNetworkIntegration : public QObject { - Q_OBJECT -public: - ISocialNetworkIntegration(QObject* parent = 0); - - /*! - * @name socialNetworkName - * @brief The name of this social network - * @return The name of this social network - * - * The name of this social network will be used to populate the Menu to toggle states - * between connected/disconnected, and also submit stuff to it. - */ - virtual QString socialNetworkName() const = 0; - - /*! - * @name socialNetworkIcon - * @brief The icon of this social network - * @return The icon of this social network - * - * The icon of this social network will be used to populate the menu, and can also be - * used on a toolbar if requested. - */ - virtual QString socialNetworkIcon() const = 0; - - /*! - * @name isConnected - * @brief returns true if connected to this social network, false otherwise - * @return true if connected to this social network, false otherwise - */ - virtual bool isConnected() = 0; - - /*! - * @name requestLogin - * @brief try to login on this social network. - * - * Try to login on this social network. All widget implementation that - * manages login should be done inside this function. - */ - virtual void requestLogin() = 0; - - /*! - * @name requestLogoff - * @brief tries to logoff from this social network - * - * Try to logoff from this social network. - */ - virtual void requestLogoff() = 0; - - /*! - * @name uploadCurrentDive - * @brief send the current dive info to the Social Network - * - * Should format all the options and pixmaps from the current dive - * to update to the social network. All widget stuff related to sendint - * dive information should be executed inside this function. - */ - virtual void requestUpload() = 0; -}; - -#endif \ No newline at end of file diff --git a/subsurface-core/libdivecomputer.c b/subsurface-core/libdivecomputer.c deleted file mode 100644 index 549b894ce..000000000 --- a/subsurface-core/libdivecomputer.c +++ /dev/null @@ -1,1081 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#include -#include -#include -#include "gettext.h" -#include "dive.h" -#include "device.h" -#include "divelist.h" -#include "display.h" - -#include -#include -#include -#include "libdivecomputer.h" - -/* Christ. Libdivecomputer has the worst configuration system ever. */ -#ifdef HW_FROG_H -#define NOT_FROG , 0 -#define LIBDIVECOMPUTER_SUPPORTS_FROG -#else -#define NOT_FROG -#endif - -char *dumpfile_name; -char *logfile_name; -const char *progress_bar_text = ""; -double progress_bar_fraction = 0.0; - -static int stoptime, stopdepth, ndl, po2, cns; -static bool in_deco, first_temp_is_air; - -/* - * Directly taken from libdivecomputer's examples/common.c to improve - * the error messages resulting from libdc's return codes - */ -const char *errmsg (dc_status_t rc) -{ - switch (rc) { - case DC_STATUS_SUCCESS: - return "Success"; - case DC_STATUS_UNSUPPORTED: - return "Unsupported operation"; - case DC_STATUS_INVALIDARGS: - return "Invalid arguments"; - case DC_STATUS_NOMEMORY: - return "Out of memory"; - case DC_STATUS_NODEVICE: - return "No device found"; - case DC_STATUS_NOACCESS: - return "Access denied"; - case DC_STATUS_IO: - return "Input/output error"; - case DC_STATUS_TIMEOUT: - return "Timeout"; - case DC_STATUS_PROTOCOL: - return "Protocol error"; - case DC_STATUS_DATAFORMAT: - return "Data format error"; - case DC_STATUS_CANCELLED: - return "Cancelled"; - default: - return "Unknown error"; - } -} - -static dc_status_t create_parser(device_data_t *devdata, dc_parser_t **parser) -{ - return dc_parser_new(parser, devdata->device); -} - -static int parse_gasmixes(device_data_t *devdata, struct dive *dive, dc_parser_t *parser, unsigned int ngases) -{ - static bool shown_warning = false; - unsigned int i; - int rc; - -#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) - unsigned int ntanks = 0; - rc = dc_parser_get_field(parser, DC_FIELD_TANK_COUNT, 0, &ntanks); - if (rc == DC_STATUS_SUCCESS) { - if (ntanks && ntanks != ngases) { - shown_warning = true; - report_error("different number of gases (%d) and tanks (%d)", ngases, ntanks); - } - } - dc_tank_t tank = { 0 }; -#endif - - for (i = 0; i < ngases; i++) { - dc_gasmix_t gasmix = { 0 }; - int o2, he; - bool no_volume = true; - - rc = dc_parser_get_field(parser, DC_FIELD_GASMIX, i, &gasmix); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) - return rc; - - if (i >= MAX_CYLINDERS) - continue; - - o2 = rint(gasmix.oxygen * 1000); - he = rint(gasmix.helium * 1000); - - /* Ignore bogus data - libdivecomputer does some crazy stuff */ - if (o2 + he <= O2_IN_AIR || o2 > 1000) { - if (!shown_warning) { - shown_warning = true; - report_error("unlikely dive gas data from libdivecomputer: o2 = %d he = %d", o2, he); - } - o2 = 0; - } - if (he < 0 || o2 + he > 1000) { - if (!shown_warning) { - shown_warning = true; - report_error("unlikely dive gas data from libdivecomputer: o2 = %d he = %d", o2, he); - } - he = 0; - } - dive->cylinder[i].gasmix.o2.permille = o2; - dive->cylinder[i].gasmix.he.permille = he; - -#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) - tank.volume = 0.0; - if (i < ntanks) { - rc = dc_parser_get_field(parser, DC_FIELD_TANK, i, &tank); - if (rc == DC_STATUS_SUCCESS) { - if (tank.type == DC_TANKVOLUME_IMPERIAL) { - dive->cylinder[i].type.size.mliter = rint(tank.volume * 1000); - dive->cylinder[i].type.workingpressure.mbar = rint(tank.workpressure * 1000); - if (same_string(devdata->model, "Suunto EON Steel")) { - /* Suunto EON Steele gets this wrong. Badly. - * but on the plus side it only supports a few imperial sizes, - * so let's try and guess at least the most common ones. - * First, the pressures are off by a constant factor. WTF? - * Then we can round the wet sizes so we get to multiples of 10 - * for cuft sizes (as that's all that you can enter) */ - dive->cylinder[i].type.workingpressure.mbar *= 206.843 / 206.7; - char name_buffer[9]; - int rounded_size = ml_to_cuft(gas_volume(&dive->cylinder[i], - dive->cylinder[i].type.workingpressure)); - rounded_size = (int)((rounded_size + 5) / 10) * 10; - switch (dive->cylinder[i].type.workingpressure.mbar) { - case 206843: - snprintf(name_buffer, 9, "AL%d", rounded_size); - break; - case 234422: /* this is wrong - HP tanks tend to be 3440, but Suunto only allows 3400 */ - snprintf(name_buffer, 9, "HP%d", rounded_size); - break; - case 179263: - snprintf(name_buffer, 9, "LP+%d", rounded_size); - break; - case 165474: - snprintf(name_buffer, 9, "LP%d", rounded_size); - break; - default: - snprintf(name_buffer, 9, "%d cuft", rounded_size); - break; - } - dive->cylinder[i].type.description = copy_string(name_buffer); - dive->cylinder[i].type.size.mliter = cuft_to_l(rounded_size) * 1000 / - mbar_to_atm(dive->cylinder[i].type.workingpressure.mbar); - } - } else if (tank.type == DC_TANKVOLUME_METRIC) { - dive->cylinder[i].type.size.mliter = rint(tank.volume * 1000); - } - if (tank.gasmix != i) { // we don't handle this, yet - shown_warning = true; - report_error("gasmix %d for tank %d doesn't match", tank.gasmix, i); - } - } - } - if (!IS_FP_SAME(tank.volume, 0.0)) - no_volume = false; - - // this new API also gives us the beginning and end pressure for the tank - if (!IS_FP_SAME(tank.beginpressure, 0.0) && !IS_FP_SAME(tank.endpressure, 0.0)) { - dive->cylinder[i].start.mbar = tank.beginpressure * 1000; - dive->cylinder[i].end.mbar = tank.endpressure * 1000; - } -#endif - if (no_volume) { - /* for the first tank, if there is no tanksize available from the - * dive computer, fill in the default tank information (if set) */ - fill_default_cylinder(&dive->cylinder[i]); - } - /* whatever happens, make sure there is a name for the cylinder */ - if (same_string(dive->cylinder[i].type.description, "")) - dive->cylinder[i].type.description = strdup(translate("gettextFromC", "unknown")); - } - return DC_STATUS_SUCCESS; -} - -static void handle_event(struct divecomputer *dc, struct sample *sample, dc_sample_value_t value) -{ - int type, time; - /* we mark these for translation here, but we store the untranslated strings - * and only translate them when they are displayed on screen */ - static const char *events[] = { - QT_TRANSLATE_NOOP("gettextFromC", "none"), QT_TRANSLATE_NOOP("gettextFromC", "deco stop"), QT_TRANSLATE_NOOP("gettextFromC", "rbt"), QT_TRANSLATE_NOOP("gettextFromC", "ascent"), QT_TRANSLATE_NOOP("gettextFromC", "ceiling"), QT_TRANSLATE_NOOP("gettextFromC", "workload"), - QT_TRANSLATE_NOOP("gettextFromC", "transmitter"), QT_TRANSLATE_NOOP("gettextFromC", "violation"), QT_TRANSLATE_NOOP("gettextFromC", "bookmark"), QT_TRANSLATE_NOOP("gettextFromC", "surface"), QT_TRANSLATE_NOOP("gettextFromC", "safety stop"), - QT_TRANSLATE_NOOP("gettextFromC", "gaschange"), QT_TRANSLATE_NOOP("gettextFromC", "safety stop (voluntary)"), QT_TRANSLATE_NOOP("gettextFromC", "safety stop (mandatory)"), - QT_TRANSLATE_NOOP("gettextFromC", "deepstop"), QT_TRANSLATE_NOOP("gettextFromC", "ceiling (safety stop)"), QT_TRANSLATE_NOOP3("gettextFromC", "below floor", "event showing dive is below deco floor and adding deco time"), QT_TRANSLATE_NOOP("gettextFromC", "divetime"), - QT_TRANSLATE_NOOP("gettextFromC", "maxdepth"), QT_TRANSLATE_NOOP("gettextFromC", "OLF"), QT_TRANSLATE_NOOP("gettextFromC", "pO₂"), QT_TRANSLATE_NOOP("gettextFromC", "airtime"), QT_TRANSLATE_NOOP("gettextFromC", "rgbm"), QT_TRANSLATE_NOOP("gettextFromC", "heading"), - QT_TRANSLATE_NOOP("gettextFromC", "tissue level warning"), QT_TRANSLATE_NOOP("gettextFromC", "gaschange"), QT_TRANSLATE_NOOP("gettextFromC", "non stop time") - }; - const int nr_events = sizeof(events) / sizeof(const char *); - const char *name; - /* - * Just ignore surface events. They are pointless. What "surface" - * means depends on the dive computer (and possibly even settings - * in the dive computer). It does *not* necessarily mean "depth 0", - * so don't even turn it into that. - */ - if (value.event.type == SAMPLE_EVENT_SURFACE) - return; - - /* - * Other evens might be more interesting, but for now we just print them out. - */ - type = value.event.type; - name = QT_TRANSLATE_NOOP("gettextFromC", "invalid event number"); - if (type < nr_events) - name = events[type]; - - time = value.event.time; - if (sample) - time += sample->time.seconds; - - add_event(dc, time, type, value.event.flags, value.event.value, name); -} - -void -sample_cb(dc_sample_type_t type, dc_sample_value_t value, void *userdata) -{ - static unsigned int nsensor = 0; - struct divecomputer *dc = userdata; - struct sample *sample; - - /* - * We fill in the "previous" sample - except for DC_SAMPLE_TIME, - * which creates a new one. - */ - sample = dc->samples ? dc->sample + dc->samples - 1 : NULL; - - /* - * Ok, sanity check. - * If first sample is not a DC_SAMPLE_TIME, Allocate a sample for us - */ - if (sample == NULL && type != DC_SAMPLE_TIME) - sample = prepare_sample(dc); - - switch (type) { - case DC_SAMPLE_TIME: - nsensor = 0; - - // The previous sample gets some sticky values - // that may have been around from before, even - // if there was no new data - if (sample) { - sample->in_deco = in_deco; - sample->ndl.seconds = ndl; - sample->stoptime.seconds = stoptime; - sample->stopdepth.mm = stopdepth; - sample->setpoint.mbar = po2; - sample->cns = cns; - } - // Create a new sample. - // Mark depth as negative - sample = prepare_sample(dc); - sample->time.seconds = value.time; - sample->depth.mm = -1; - finish_sample(dc); - break; - case DC_SAMPLE_DEPTH: - sample->depth.mm = rint(value.depth * 1000); - break; - case DC_SAMPLE_PRESSURE: - sample->sensor = value.pressure.tank; - sample->cylinderpressure.mbar = rint(value.pressure.value * 1000); - break; - case DC_SAMPLE_TEMPERATURE: - sample->temperature.mkelvin = C_to_mkelvin(value.temperature); - break; - case DC_SAMPLE_EVENT: - handle_event(dc, sample, value); - break; - case DC_SAMPLE_RBT: - sample->rbt.seconds = (!strncasecmp(dc->model, "suunto", 6)) ? value.rbt : value.rbt * 60; - break; - case DC_SAMPLE_HEARTBEAT: - sample->heartbeat = value.heartbeat; - break; - case DC_SAMPLE_BEARING: - sample->bearing.degrees = value.bearing; - break; -#ifdef DEBUG_DC_VENDOR - case DC_SAMPLE_VENDOR: - printf(" ", 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, ...) -{ - (void) devdata; - static char buffer[1024]; - va_list ap; - - va_start(ap, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, ap); - va_end(ap); - progress_bar_text = buffer; -} - -static int import_dive_number = 0; - -static int parse_samples(device_data_t *devdata, struct divecomputer *dc, dc_parser_t *parser) -{ - (void) devdata; - // Parse the sample data. - return dc_parser_samples_foreach(parser, sample_cb, dc); -} - -static int might_be_same_dc(struct divecomputer *a, struct divecomputer *b) -{ - if (!a->model || !b->model) - return 1; - if (strcasecmp(a->model, b->model)) - return 0; - if (!a->deviceid || !b->deviceid) - return 1; - return a->deviceid == b->deviceid; -} - -static int match_one_dive(struct divecomputer *a, struct dive *dive) -{ - struct divecomputer *b = &dive->dc; - - /* - * Walk the existing dive computer data, - * see if we have a match (or an anti-match: - * the same dive computer but a different - * dive ID). - */ - do { - int match = match_one_dc(a, b); - if (match) - return match > 0; - b = b->next; - } while (b); - - /* Ok, no exact dive computer match. Does the date match? */ - b = &dive->dc; - do { - if (a->when == b->when && might_be_same_dc(a, b)) - return 1; - b = b->next; - } while (b); - - return 0; -} - -/* - * Check if this dive already existed before the import - */ -static int find_dive(struct divecomputer *match) -{ - int i; - - for (i = 0; i < dive_table.preexisting; i++) { - struct dive *old = dive_table.dives[i]; - - if (match_one_dive(match, old)) - return 1; - } - return 0; -} - -/* - * Like g_strdup_printf(), but without the stupid g_malloc/g_free confusion. - * And we limit the string to some arbitrary size. - */ -static char *str_printf(const char *fmt, ...) -{ - va_list args; - char buf[1024]; - - va_start(args, fmt); - vsnprintf(buf, sizeof(buf) - 1, fmt, args); - va_end(args); - buf[sizeof(buf) - 1] = 0; - return strdup(buf); -} - -/* - * The dive ID for libdivecomputer dives is the first word of the - * SHA1 of the fingerprint, if it exists. - * - * NOTE! This is byte-order dependent, and I don't care. - */ -static uint32_t calculate_diveid(const unsigned char *fingerprint, unsigned int fsize) -{ - uint32_t csum[5]; - - if (!fingerprint || !fsize) - return 0; - - SHA1(fingerprint, fsize, (unsigned char *)csum); - return csum[0]; -} - -#ifdef DC_FIELD_STRING -static uint32_t calculate_string_hash(const char *str) -{ - return calculate_diveid((const unsigned char *)str, strlen(str)); -} - -static void parse_string_field(struct dive *dive, dc_field_string_t *str) -{ - // Our dive ID is the string hash of the "Dive ID" string - if (!strcmp(str->desc, "Dive ID")) { - if (!dive->dc.diveid) - dive->dc.diveid = calculate_string_hash(str->value); - return; - } - add_extra_data(&dive->dc, str->desc, str->value); - if (!strcmp(str->desc, "Serial")) { - dive->dc.serial = strdup(str->value); - /* should we just overwrite this whenever we have the "Serial" field? - * It's a much better deviceid then what we have so far... for now I'm leaving it as is */ - if (!dive->dc.deviceid) - dive->dc.deviceid = calculate_string_hash(str->value); - return; - } - if (!strcmp(str->desc, "FW Version")) { - dive->dc.fw_version = strdup(str->value); - return; - } -} -#endif - -static dc_status_t libdc_header_parser(dc_parser_t *parser, struct device_data_t *devdata, struct dive *dive) -{ - dc_status_t rc = 0; - dc_datetime_t dt = { 0 }; - struct tm tm; - - rc = dc_parser_get_datetime(parser, &dt); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the datetime")); - return rc; - } - - dive->dc.deviceid = devdata->deviceid; - - if (rc == DC_STATUS_SUCCESS) { - tm.tm_year = dt.year; - tm.tm_mon = dt.month - 1; - tm.tm_mday = dt.day; - tm.tm_hour = dt.hour; - tm.tm_min = dt.minute; - tm.tm_sec = dt.second; - dive->when = dive->dc.when = utc_mktime(&tm); - } - - // Parse the divetime. - const char *date_string = get_dive_date_c_string(dive->when); - dev_info(devdata, translate("gettextFromC", "Dive %d: %s"), import_dive_number, date_string); - free((void *)date_string); - - unsigned int divetime = 0; - rc = dc_parser_get_field(parser, DC_FIELD_DIVETIME, 0, &divetime); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the divetime")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - dive->dc.duration.seconds = divetime; - - // Parse the maxdepth. - double maxdepth = 0.0; - rc = dc_parser_get_field(parser, DC_FIELD_MAXDEPTH, 0, &maxdepth); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the maxdepth")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - dive->dc.maxdepth.mm = rint(maxdepth * 1000); - -#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) - // if this is defined then we have a fairly late version of libdivecomputer - // from the 0.5 development cylcle - most likely temperatures and tank sizes - // are supported - - // Parse temperatures - double temperature; - dc_field_type_t temp_fields[] = {DC_FIELD_TEMPERATURE_SURFACE, - DC_FIELD_TEMPERATURE_MAXIMUM, - DC_FIELD_TEMPERATURE_MINIMUM}; - for (int i = 0; i < 3; i++) { - rc = dc_parser_get_field(parser, temp_fields[i], 0, &temperature); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing temperature")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - switch(i) { - case 0: - dive->dc.airtemp.mkelvin = C_to_mkelvin(temperature); - break; - case 1: // we don't distinguish min and max water temp here, so take min if given, max otherwise - case 2: - dive->dc.watertemp.mkelvin = C_to_mkelvin(temperature); - break; - } - } -#endif - - // Parse the gas mixes. - unsigned int ngases = 0; - rc = dc_parser_get_field(parser, DC_FIELD_GASMIX_COUNT, 0, &ngases); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the gas mix count")); - return rc; - } - -#if DC_VERSION_CHECK(0, 3, 0) - // Check if the libdivecomputer version already supports salinity & atmospheric - dc_salinity_t salinity = { - .type = DC_WATER_SALT, - .density = SEAWATER_SALINITY / 10.0 - }; - rc = dc_parser_get_field(parser, DC_FIELD_SALINITY, 0, &salinity); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error obtaining water salinity")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - dive->dc.salinity = rint(salinity.density * 10.0); - - double surface_pressure = 0; - rc = dc_parser_get_field(parser, DC_FIELD_ATMOSPHERIC, 0, &surface_pressure); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error obtaining surface pressure")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - dive->dc.surface_pressure.mbar = rint(surface_pressure * 1000.0); -#endif - -#ifdef DC_FIELD_STRING - // The dive parsing may give us more device information - int idx; - for (idx = 0; idx < 100; idx++) { - dc_field_string_t str = { NULL }; - rc = dc_parser_get_field(parser, DC_FIELD_STRING, idx, &str); - if (rc != DC_STATUS_SUCCESS) - break; - if (!str.desc || !str.value) - break; - parse_string_field(dive, &str); - } -#endif - -#if DC_VERSION_CHECK(0, 5, 0) && defined(DC_GASMIX_UNKNOWN) - dc_divemode_t divemode; - rc = dc_parser_get_field(parser, DC_FIELD_DIVEMODE, 0, &divemode); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error obtaining divemode")); - return rc; - } - if (rc == DC_STATUS_SUCCESS) - switch(divemode) { - case DC_DIVEMODE_FREEDIVE: - dive->dc.divemode = FREEDIVE; - break; - case DC_DIVEMODE_GAUGE: - case DC_DIVEMODE_OC: /* Open circuit */ - dive->dc.divemode = OC; - break; - case DC_DIVEMODE_CC: /* Closed circuit */ - dive->dc.divemode = CCR; - break; - } -#endif - - rc = parse_gasmixes(devdata, dive, parser, ngases); - if (rc != DC_STATUS_SUCCESS && rc != DC_STATUS_UNSUPPORTED) { - dev_info(devdata, translate("gettextFromC", "Error parsing the gas mix")); - return rc; - } - - return DC_STATUS_SUCCESS; -} - -/* returns true if we want libdivecomputer's dc_device_foreach() to continue, - * false otherwise */ -static int dive_cb(const unsigned char *data, unsigned int size, - const unsigned char *fingerprint, unsigned int fsize, - void *userdata) -{ - int rc; - dc_parser_t *parser = NULL; - device_data_t *devdata = userdata; - struct dive *dive = NULL; - - /* reset the deco / ndl data */ - ndl = stoptime = stopdepth = 0; - in_deco = false; - - rc = create_parser(devdata, &parser); - if (rc != DC_STATUS_SUCCESS) { - dev_info(devdata, translate("gettextFromC", "Unable to create parser for %s %s"), devdata->vendor, devdata->product); - return false; - } - - rc = dc_parser_set_data(parser, data, size); - if (rc != DC_STATUS_SUCCESS) { - dev_info(devdata, translate("gettextFromC", "Error registering the data")); - goto error_exit; - } - - import_dive_number++; - dive = alloc_dive(); - - // Parse the dive's header data - rc = libdc_header_parser (parser, devdata, dive); - if (rc != DC_STATUS_SUCCESS) { - dev_info(devdata, translate("getextFromC", "Error parsing the header")); - goto error_exit; - } - - dive->dc.model = strdup(devdata->model); - dive->dc.diveid = calculate_diveid(fingerprint, fsize); - - // Initialize the sample data. - rc = parse_samples(devdata, &dive->dc, parser); - if (rc != DC_STATUS_SUCCESS) { - dev_info(devdata, translate("gettextFromC", "Error parsing the samples")); - goto error_exit; - } - - /* If we already saw this dive, abort. */ - if (!devdata->force_download && find_dive(&dive->dc)) - goto error_exit; - - dc_parser_destroy(parser); - - /* Various libdivecomputer interface fixups */ - if (dive->dc.airtemp.mkelvin == 0 && first_temp_is_air && dive->dc.samples) { - dive->dc.airtemp = dive->dc.sample[0].temperature; - dive->dc.sample[0].temperature.mkelvin = 0; - } - - if (devdata->create_new_trip) { - if (!devdata->trip) - devdata->trip = create_and_hookup_trip_from_dive(dive); - else - add_dive_to_trip(dive, devdata->trip); - } - - dive->downloaded = true; - record_dive_to_table(dive, devdata->download_table); - mark_divelist_changed(true); - return true; - -error_exit: - dc_parser_destroy(parser); - free(dive); - return false; - -} - -/* - * The device ID for libdivecomputer devices is the first 32-bit word - * of the SHA1 hash of the model/firmware/serial numbers. - * - * NOTE! This is byte-order-dependent. And I can't find it in myself to - * care. - */ -static uint32_t calculate_sha1(unsigned int model, unsigned int firmware, unsigned int serial) -{ - SHA_CTX ctx; - uint32_t csum[5]; - - SHA1_Init(&ctx); - SHA1_Update(&ctx, &model, sizeof(model)); - SHA1_Update(&ctx, &firmware, sizeof(firmware)); - SHA1_Update(&ctx, &serial, sizeof(serial)); - SHA1_Final((unsigned char *)csum, &ctx); - return csum[0]; -} - -/* - * libdivecomputer has returned two different serial numbers for the - * same device in different versions. First it used to just do the four - * bytes as one 32-bit number, then it turned it into a decimal number - * with each byte giving two digits (0-99). - * - * The only way we can tell is by looking at the format of the number, - * so we'll just fix it to the first format. - */ -static unsigned int undo_libdivecomputer_suunto_nr_changes(unsigned int serial) -{ - unsigned char b0, b1, b2, b3; - - /* - * The second format will never have more than 8 decimal - * digits, so do a cheap check first - */ - if (serial >= 100000000) - return serial; - - /* The original format seems to be four bytes of values 00-99 */ - b0 = (serial >> 0) & 0xff; - b1 = (serial >> 8) & 0xff; - b2 = (serial >> 16) & 0xff; - b3 = (serial >> 24) & 0xff; - - /* Looks like an old-style libdivecomputer serial number */ - if ((b0 < 100) && (b1 < 100) && (b2 < 100) && (b3 < 100)) - return serial; - - /* Nope, it was converted. */ - b0 = serial % 100; - serial /= 100; - b1 = serial % 100; - serial /= 100; - b2 = serial % 100; - serial /= 100; - b3 = serial % 100; - - serial = b0 + (b1 << 8) + (b2 << 16) + (b3 << 24); - return serial; -} - -static unsigned int fixup_suunto_versions(device_data_t *devdata, const dc_event_devinfo_t *devinfo) -{ - unsigned int serial = devinfo->serial; - char serial_nr[13] = ""; - char firmware[13] = ""; - - first_temp_is_air = 1; - - serial = undo_libdivecomputer_suunto_nr_changes(serial); - - if (serial) { - snprintf(serial_nr, sizeof(serial_nr), "%02d%02d%02d%02d", - (devinfo->serial >> 24) & 0xff, - (devinfo->serial >> 16) & 0xff, - (devinfo->serial >> 8) & 0xff, - (devinfo->serial >> 0) & 0xff); - } - if (devinfo->firmware) { - snprintf(firmware, sizeof(firmware), "%d.%d.%d", - (devinfo->firmware >> 16) & 0xff, - (devinfo->firmware >> 8) & 0xff, - (devinfo->firmware >> 0) & 0xff); - } - create_device_node(devdata->model, devdata->deviceid, serial_nr, firmware, ""); - - return serial; -} - -static void event_cb(dc_device_t *device, dc_event_type_t event, const void *data, void *userdata) -{ - (void) device; - const dc_event_progress_t *progress = data; - const dc_event_devinfo_t *devinfo = data; - const dc_event_clock_t *clock = data; - const dc_event_vendor_t *vendor = data; - device_data_t *devdata = userdata; - unsigned int serial; - - switch (event) { - case DC_EVENT_WAITING: - dev_info(devdata, translate("gettextFromC", "Event: waiting for user action")); - break; - case DC_EVENT_PROGRESS: - if (!progress->maximum) - break; - progress_bar_fraction = (double)progress->current / (double)progress->maximum; - break; - case DC_EVENT_DEVINFO: - dev_info(devdata, translate("gettextFromC", "model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)"), - devinfo->model, devinfo->model, - devinfo->firmware, devinfo->firmware, - devinfo->serial, devinfo->serial); - if (devdata->libdc_logfile) { - fprintf(devdata->libdc_logfile, "Event: model=%u (0x%08x), firmware=%u (0x%08x), serial=%u (0x%08x)\n", - devinfo->model, devinfo->model, - devinfo->firmware, devinfo->firmware, - devinfo->serial, devinfo->serial); - } - /* - * libdivecomputer doesn't give serial numbers in the proper string form, - * so we have to see if we can do some vendor-specific munging. - */ - serial = devinfo->serial; - if (!strcmp(devdata->vendor, "Suunto")) - serial = fixup_suunto_versions(devdata, devinfo); - devdata->deviceid = calculate_sha1(devinfo->model, devinfo->firmware, serial); - /* really, serial and firmware version are NOT numbers. We'll try to save them here - * in something that might work, but this really needs to be handled with the - * DC_FIELD_STRING interface instead */ - devdata->libdc_serial = devinfo->serial; - devdata->libdc_firmware = devinfo->firmware; - break; - case DC_EVENT_CLOCK: - dev_info(devdata, translate("gettextFromC", "Event: systime=%" PRId64 ", devtime=%u\n"), - (uint64_t)clock->systime, clock->devtime); - if (devdata->libdc_logfile) { - fprintf(devdata->libdc_logfile, "Event: systime=%" PRId64 ", devtime=%u\n", - (uint64_t)clock->systime, clock->devtime); - } - break; - case DC_EVENT_VENDOR: - if (devdata->libdc_logfile) { - fprintf(devdata->libdc_logfile, "Event: vendor="); - for (unsigned int i = 0; i < vendor->size; ++i) - fprintf(devdata->libdc_logfile, "%02X", vendor->data[i]); - fprintf(devdata->libdc_logfile, "\n"); - } - break; - default: - break; - } -} - -int import_thread_cancelled; - -static int cancel_cb(void *userdata) -{ - (void) userdata; - return import_thread_cancelled; -} - -static const char *do_device_import(device_data_t *data) -{ - dc_status_t rc; - dc_device_t *device = data->device; - - data->model = str_printf("%s %s", data->vendor, data->product); - - // Register the event handler. - int events = DC_EVENT_WAITING | DC_EVENT_PROGRESS | DC_EVENT_DEVINFO | DC_EVENT_CLOCK | DC_EVENT_VENDOR; - rc = dc_device_set_events(device, events, event_cb, data); - if (rc != DC_STATUS_SUCCESS) - return translate("gettextFromC", "Error registering the event handler."); - - // Register the cancellation handler. - rc = dc_device_set_cancel(device, cancel_cb, data); - if (rc != DC_STATUS_SUCCESS) - return translate("gettextFromC", "Error registering the cancellation handler."); - - if (data->libdc_dump) { - dc_buffer_t *buffer = dc_buffer_new(0); - - rc = dc_device_dump(device, buffer); - if (rc == DC_STATUS_SUCCESS && dumpfile_name) { - FILE *fp = subsurface_fopen(dumpfile_name, "wb"); - if (fp != NULL) { - fwrite(dc_buffer_get_data(buffer), 1, dc_buffer_get_size(buffer), fp); - fclose(fp); - } - } - - dc_buffer_free(buffer); - } else { - rc = dc_device_foreach(device, dive_cb, data); - } - - if (rc != DC_STATUS_SUCCESS) { - progress_bar_fraction = 0.0; - return translate("gettextFromC", "Dive data import error"); - } - - /* All good */ - return NULL; -} - -void logfunc(dc_context_t *context, dc_loglevel_t loglevel, const char *file, unsigned int line, const char *function, const char *msg, void *userdata) -{ - (void) context; - const char *loglevels[] = { "NONE", "ERROR", "WARNING", "INFO", "DEBUG", "ALL" }; - - FILE *fp = (FILE *)userdata; - - if (loglevel == DC_LOGLEVEL_ERROR || loglevel == DC_LOGLEVEL_WARNING) { - fprintf(fp, "%s: %s [in %s:%d (%s)]\n", loglevels[loglevel], msg, file, line, function); - } else { - fprintf(fp, "%s: %s\n", loglevels[loglevel], msg); - } -} - -const char *do_libdivecomputer_import(device_data_t *data) -{ - dc_status_t rc; - const char *err; - FILE *fp = NULL; - - import_dive_number = 0; - first_temp_is_air = 0; - data->device = NULL; - data->context = NULL; - - if (data->libdc_log && logfile_name) - fp = subsurface_fopen(logfile_name, "w"); - - data->libdc_logfile = fp; - - rc = dc_context_new(&data->context); - if (rc != DC_STATUS_SUCCESS) - return translate("gettextFromC", "Unable to create libdivecomputer context"); - - if (fp) { - dc_context_set_loglevel(data->context, DC_LOGLEVEL_ALL); - dc_context_set_logfunc(data->context, logfunc, fp); - } - - err = translate("gettextFromC", "Unable to open %s %s (%s)"); - -#if defined(SSRF_CUSTOM_SERIAL) - dc_serial_t *serial_device = NULL; - - if (data->bluetooth_mode) { -#if defined(BT_SUPPORT) && defined(SSRF_CUSTOM_SERIAL) - rc = dc_serial_qt_open(&serial_device, data->context, data->devname); -#endif -#ifdef SERIAL_FTDI - } else if (!strcmp(data->devname, "ftdi")) { - rc = dc_serial_ftdi_open(&serial_device, data->context); -#endif - } - - if (rc != DC_STATUS_SUCCESS) { - report_error(errmsg(rc)); - } else if (serial_device) { - rc = dc_device_custom_open(&data->device, data->context, data->descriptor, serial_device); - } else { -#else - { -#endif - rc = dc_device_open(&data->device, data->context, data->descriptor, data->devname); - - if (rc != DC_STATUS_SUCCESS && subsurface_access(data->devname, R_OK | W_OK) != 0) - err = translate("gettextFromC", "Insufficient privileges to open the device %s %s (%s)"); - } - - if (rc == DC_STATUS_SUCCESS) { - err = do_device_import(data); - /* TODO: Show the logfile to the user on error. */ - dc_device_close(data->device); - data->device = NULL; - } - - dc_context_free(data->context); - data->context = NULL; - - if (fp) { - fclose(fp); - } - - return err; -} - -/* - * Parse data buffers instead of dc devices downloaded data. - * Intended to be used to parse profile data from binary files during import tasks. - * Actually included Uwatec families because of works on datatrak and smartrak logs - * and OSTC families for OSTCTools logs import. - * For others, simply include them in the switch (check parameters). - * Note that dc_descriptor_t in data *must* have been filled using dc_descriptor_iterator() - * calls. - */ -dc_status_t libdc_buffer_parser(struct dive *dive, device_data_t *data, unsigned char *buffer, int size) -{ - dc_status_t rc; - dc_parser_t *parser = NULL; - - switch (data->descriptor->type) { - case DC_FAMILY_UWATEC_ALADIN: - case DC_FAMILY_UWATEC_MEMOMOUSE: - rc = uwatec_memomouse_parser_create(&parser, data->context, 0, 0); - break; - case DC_FAMILY_UWATEC_SMART: - case DC_FAMILY_UWATEC_MERIDIAN: - rc = uwatec_smart_parser_create (&parser, data->context, data->descriptor->model, 0, 0); - break; - case DC_FAMILY_HW_OSTC: -#if defined(SSRF_CUSTOM_SERIAL) - rc = hw_ostc_parser_create (&parser, data->context, data->deviceid, 0); -#else - rc = hw_ostc_parser_create (&parser, data->context, data->deviceid); -#endif - break; - case DC_FAMILY_HW_FROG: - case DC_FAMILY_HW_OSTC3: -#if defined(SSRF_CUSTOM_SERIAL) - rc = hw_ostc_parser_create (&parser, data->context, data->deviceid, 1); -#else - rc = hw_ostc_parser_create (&parser, data->context, data->deviceid); -#endif - break; - default: - report_error("Device type not handled!"); - return DC_STATUS_UNSUPPORTED; - } - if (rc != DC_STATUS_SUCCESS) { - report_error("Error creating parser."); - dc_parser_destroy (parser); - return rc; - } - rc = dc_parser_set_data(parser, buffer, size); - if (rc != DC_STATUS_SUCCESS) { - report_error("Error registering the data."); - dc_parser_destroy (parser); - return rc; - } - // Do not parse Aladin/Memomouse headers as they are fakes - // Do not return on error, we can still parse the samples - if (data->descriptor->type != DC_FAMILY_UWATEC_ALADIN && data->descriptor->type != DC_FAMILY_UWATEC_MEMOMOUSE) { - rc = libdc_header_parser (parser, data, dive); - if (rc != DC_STATUS_SUCCESS) { - report_error("Error parsing the dive header data. Dive # %d\nStatus = %s", dive->number, errmsg(rc)); - } - } - rc = dc_parser_samples_foreach (parser, sample_cb, &dive->dc); - if (rc != DC_STATUS_SUCCESS) { - report_error("Error parsing the sample data. Dive # %d\nStatus = %s", dive->number, errmsg(rc)); - dc_parser_destroy (parser); - return rc; - } - dc_parser_destroy(parser); - return(DC_STATUS_SUCCESS); -} diff --git a/subsurface-core/libdivecomputer.h b/subsurface-core/libdivecomputer.h deleted file mode 100644 index 99f1c2490..000000000 --- a/subsurface-core/libdivecomputer.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef LIBDIVECOMPUTER_H -#define LIBDIVECOMPUTER_H - - -/* libdivecomputer */ - -#ifdef DC_VERSION /* prevent a warning with wingdi.h */ -#undef DC_VERSION -#endif -#ifdef HAVE_LIBDIVECOMPUTER -#include -#endif -#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 deleted file mode 100644 index b81f6bf53..000000000 --- a/subsurface-core/linux.c +++ /dev/null @@ -1,232 +0,0 @@ -/* linux.c */ -/* implements Linux specific functions */ -#include "dive.h" -#include "display.h" -#include "membuffer.h" -#include -#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 - (void)font; - return false; -} - -void subsurface_user_info(struct user_info *user) -{ - struct passwd *pwd = getpwuid(getuid()); - const char *username = getenv("USER"); - - if (pwd) { - if (pwd->pw_gecos && *pwd->pw_gecos) - user->name = pwd->pw_gecos; - if (!username) - username = pwd->pw_name; - } - if (username && *username) { - char hostname[64]; - struct membuffer mb = {}; - gethostname(hostname, sizeof(hostname)); - put_format(&mb, "%s@%s", username, hostname); - user->email = mb_cstring(&mb); - } -} - -static const char *system_default_path_append(const char *append) -{ - const char *home = getenv("HOME"); - if (!home) - home = "~"; - const char *path = "/.subsurface"; - - int len = strlen(home) + strlen(path) + 1; - if (append) - len += strlen(append) + 1; - - char *buffer = (char *)malloc(len); - memset(buffer, 0, len); - strcat(buffer, home); - strcat(buffer, path); - if (append) { - strcat(buffer, "/"); - strcat(buffer, append); - } - - return buffer; -} - -const char *system_default_directory(void) -{ - static const char *path = NULL; - if (!path) - path = system_default_path_append(NULL); - return path; -} - -const char *system_default_filename(void) -{ - static char *filename = NULL; - if (!filename) { - const char *user = getenv("LOGNAME"); - if (same_string(user, "")) - user = "username"; - filename = calloc(strlen(user) + 5, 1); - strcat(filename, user); - strcat(filename, ".xml"); - } - static const char *path = NULL; - if (!path) - path = system_default_path_append(filename); - return path; -} - -int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) -{ - int index = -1, entries = 0; - DIR *dp = NULL; - struct dirent *ep = NULL; - size_t i; - FILE *file; - char *line = NULL; - char *fname; - size_t len; - if (dc_type != DC_TYPE_UEMIS) { - const char *dirname = "/dev"; - const char *patterns[] = { - "ttyUSB*", - "ttyS*", - "ttyACM*", - "rfcomm*", - NULL - }; - - dp = opendir(dirname); - if (dp == NULL) { - return -1; - } - - while ((ep = readdir(dp)) != NULL) { - for (i = 0; patterns[i] != NULL; ++i) { - if (fnmatch(patterns[i], ep->d_name, 0) == 0) { - char filename[1024]; - int n = snprintf(filename, sizeof(filename), "%s/%s", dirname, ep->d_name); - if (n >= (int)sizeof(filename)) { - closedir(dp); - return -1; - } - callback(filename, userdata); - if (is_default_dive_computer_device(filename)) - index = entries; - entries++; - break; - } - } - } - closedir(dp); - } - if (dc_type != DC_TYPE_SERIAL) { - int num_uemis = 0; - file = fopen("/proc/mounts", "r"); - if (file == NULL) - return index; - - while ((getline(&line, &len, file)) != -1) { - char *ptr = strstr(line, "UEMISSDA"); - if (ptr) { - char *end = ptr, *start = ptr; - while (start > line && *start != ' ') - start--; - if (*start == ' ') - start++; - while (*end != ' ' && *end != '\0') - end++; - - *end = '\0'; - fname = strdup(start); - - callback(fname, userdata); - - if (is_default_dive_computer_device(fname)) - index = entries; - entries++; - num_uemis++; - free((void *)fname); - } - } - free(line); - fclose(file); - if (num_uemis == 1 && entries == 1) /* if we found only one and it's a mounted Uemis, pick it */ - index = 0; - } - return index; -} - -/* NOP wrappers to comform with windows.c */ -int subsurface_rename(const char *path, const char *newpath) -{ - return rename(path, newpath); -} - -int subsurface_open(const char *path, int oflags, mode_t mode) -{ - return open(path, oflags, mode); -} - -FILE *subsurface_fopen(const char *path, const char *mode) -{ - return fopen(path, mode); -} - -void *subsurface_opendir(const char *path) -{ - return (void *)opendir(path); -} - -int subsurface_access(const char *path, int mode) -{ - return access(path, mode); -} - -struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp) -{ - return zip_open(path, flags, errorp); -} - -int subsurface_zip_close(struct zip *zip) -{ - return zip_close(zip); -} - -/* win32 console */ -void subsurface_console_init(bool dedicated) -{ - (void)dedicated; - /* NOP */ -} - -void subsurface_console_exit(void) -{ - /* NOP */ -} - -bool subsurface_user_is_root() -{ - return (geteuid() == 0); -} diff --git a/subsurface-core/liquivision.c b/subsurface-core/liquivision.c deleted file mode 100644 index 9347a724a..000000000 --- a/subsurface-core/liquivision.c +++ /dev/null @@ -1,420 +0,0 @@ -#include - -#include "dive.h" -#include "divelist.h" -#include "file.h" -#include "strndup.h" - -// Convert bytes into an INT -#define array_uint16_le(p) ((unsigned int) (p)[0] \ - + ((p)[1]<<8) ) -#define array_uint32_le(p) ((unsigned int) (p)[0] \ - + ((p)[1]<<8) + ((p)[2]<<16) \ - + ((p)[3]<<24)) - -struct lv_event { - time_t time; - struct pressure { - int sensor; - int mbar; - } pressure; -}; - -uint16_t primary_sensor; - -static int handle_event_ver2(int code, const unsigned char *ps, unsigned int ps_ptr, struct lv_event *event) -{ - (void) code; - (void) ps; - (void) ps_ptr; - (void) event; - - // Skip 4 bytes - return 4; -} - - -static int handle_event_ver3(int code, const unsigned char *ps, unsigned int ps_ptr, struct lv_event *event) -{ - int skip = 4; - uint16_t current_sensor; - - switch (code) { - case 0x0002: // Unknown - case 0x0004: // Unknown - skip = 4; - break; - case 0x0005: // Unknown - skip = 6; - break; - case 0x0007: // Gas - // 4 byte time - // 1 byte O2, 1 bye He - skip = 6; - break; - case 0x0008: - // 4 byte time - // 2 byte gas set point 2 - skip = 6; - break; - case 0x000f: - // Tank pressure - event->time = array_uint32_le(ps + ps_ptr); - - /* As far as I know, Liquivision supports 2 sensors, own and buddie's. This is my - * best guess how it is represented. */ - - current_sensor = array_uint16_le(ps + ps_ptr + 4); - if (primary_sensor == 0) { - primary_sensor = current_sensor; - } - if (current_sensor == primary_sensor) { - event->pressure.sensor = 0; - event->pressure.mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb - } else { - /* Ignoring the buddy sensor for no as we cannot draw it on the profile. - event->pressure.sensor = 1; - event->pressure.mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb - */ - } - // 1 byte PSR - // 1 byte ST - skip = 10; - break; - case 0x0010: - skip = 26; - break; - case 0x0015: // Unknown - skip = 2; - break; - default: - skip = 4; - break; - } - - return skip; -} - -static void parse_dives (int log_version, const unsigned char *buf, unsigned int buf_size) -{ - unsigned int ptr = 0; - unsigned char model; - - struct dive *dive; - struct divecomputer *dc; - struct sample *sample; - - while (ptr < buf_size) { - int i; - bool found_divesite = false; - dive = alloc_dive(); - primary_sensor = 0; - dc = &dive->dc; - - /* Just the main cylinder until we can handle the buddy cylinder porperly */ - for (i = 0; i < 1; i++) - fill_default_cylinder(&dive->cylinder[i]); - - // Model 0=Xen, 1,2=Xeo, 4=Lynx, other=Liquivision - model = *(buf + ptr); - switch (model) { - case 0: - dc->model = strdup("Xen"); - break; - case 1: - case 2: - dc->model = strdup("Xeo"); - break; - case 4: - dc->model = strdup("Lynx"); - break; - default: - dc->model = strdup("Liquivision"); - break; - } - ptr++; - - // Dive location, assemble Location and Place - unsigned int len, place_len; - char *location; - len = array_uint32_le(buf + ptr); - ptr += 4; - place_len = array_uint32_le(buf + ptr + len); - - if (len && place_len) { - location = malloc(len + place_len + 4); - memset(location, 0, len + place_len + 4); - memcpy(location, buf + ptr, len); - memcpy(location + len, ", ", 2); - memcpy(location + len + 2, buf + ptr + len + 4, place_len); - } else if (len) { - location = strndup((char *)buf + ptr, len); - } else if (place_len) { - location = strndup((char *)buf + ptr + len + 4, place_len); - } - - /* Store the location only if we have one */ - if (len || place_len) - found_divesite = true; - - ptr += len + 4 + place_len; - - // Dive comment - len = array_uint32_le(buf + ptr); - ptr += 4; - - // Blank notes are better than the default text - if (len && strncmp((char *)buf + ptr, "Comment ...", 11)) { - dive->notes = strndup((char *)buf + ptr, len); - } - ptr += len; - - dive->id = array_uint32_le(buf + ptr); - ptr += 4; - - dive->number = array_uint16_le(buf + ptr) + 1; - ptr += 2; - - dive->duration.seconds = array_uint32_le(buf + ptr); // seconds - ptr += 4; - - dive->maxdepth.mm = array_uint16_le(buf + ptr) * 10; // cm->mm - ptr += 2; - - dive->meandepth.mm = array_uint16_le(buf + ptr) * 10; // cm->mm - ptr += 2; - - dive->when = array_uint32_le(buf + ptr); - ptr += 4; - - // now that we have the dive time we can store the divesite - // (we need the dive time to create deterministic uuids) - if (found_divesite) { - dive->dive_site_uuid = find_or_create_dive_site_with_name(location, dive->when); - free(location); - } - //unsigned int end_time = array_uint32_le(buf + ptr); - ptr += 4; - - //unsigned int sit = array_uint32_le(buf + ptr); - ptr += 4; - //if (sit == 0xffffffff) { - //} - - dive->surface_pressure.mbar = array_uint16_le(buf + ptr); // ??? - ptr += 2; - - //unsigned int rep_dive = array_uint16_le(buf + ptr); - ptr += 2; - - dive->mintemp.mkelvin = C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK - ptr += 2; - - dive->maxtemp.mkelvin = C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK - ptr += 2; - - dive->salinity = *(buf + ptr); // ??? - ptr += 1; - - unsigned int sample_count = array_uint32_le(buf + ptr); - ptr += 4; - - // Sample interval - unsigned char sample_interval; - sample_interval = 1; - - unsigned char intervals[6] = {1,2,5,10,30,60}; - if (*(buf + ptr) < 6) - sample_interval = intervals[*(buf + ptr)]; - ptr += 1; - - float start_cns = 0; - unsigned char dive_mode = 0, algorithm = 0; - if (array_uint32_le(buf + ptr) != sample_count) { - // Xeo, with CNS and OTU - start_cns = *(float *) (buf + ptr); - ptr += 4; - dive->cns = *(float *) (buf + ptr); // end cns - ptr += 4; - dive->otu = *(float *) (buf + ptr); - ptr += 4; - dive_mode = *(buf + ptr++); // 0=Deco, 1=Gauge, 2=None - algorithm = *(buf + ptr++); // 0=ZH-L16C+GF - sample_count = array_uint32_le(buf + ptr); - } - // we aren't using the start_cns, dive_mode, and algorithm, yet - (void)start_cns; - (void)dive_mode; - (void)algorithm; - - ptr += 4; - - // Parse dive samples - const unsigned char *ds = buf + ptr; - const unsigned char *ts = buf + ptr + sample_count * 2 + 4; - const unsigned char *ps = buf + ptr + sample_count * 4 + 4; - unsigned int ps_count = array_uint32_le(ps); - ps += 4; - - // Bump ptr - ptr += sample_count * 4 + 4; - - // Handle events - unsigned int ps_ptr; - ps_ptr = 0; - - unsigned int event_code, d = 0, e; - struct lv_event event; - - // Loop through events - for (e = 0; e < ps_count; e++) { - // Get event - event_code = array_uint16_le(ps + ps_ptr); - ps_ptr += 2; - - if (log_version == 3) { - ps_ptr += handle_event_ver3(event_code, ps, ps_ptr, &event); - if (event_code != 0xf) - continue; // ignore all by pressure sensor event - } else { // version 2 - ps_ptr += handle_event_ver2(event_code, ps, ps_ptr, &event); - continue; // ignore all events - } - int sample_time, last_time; - int depth_mm, last_depth, temp_mk, last_temp; - - while (true) { - sample = prepare_sample(dc); - - // Get sample times - sample_time = d * sample_interval; - depth_mm = array_uint16_le(ds + d * 2) * 10; // cm->mm - temp_mk = C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10); // dC->mK - last_time = (d ? (d - 1) * sample_interval : 0); - - if (d == sample_count) { - // We still have events to record - sample->time.seconds = event.time; - sample->depth.mm = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm - sample->temperature.mkelvin = C_to_mkelvin((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK - sample->sensor = event.pressure.sensor; - sample->cylinderpressure.mbar = event.pressure.mbar; - finish_sample(dc); - - break; - } else if (event.time > sample_time) { - // Record sample and loop - sample->time.seconds = sample_time; - sample->depth.mm = depth_mm; - sample->temperature.mkelvin = temp_mk; - finish_sample(dc); - d++; - - continue; - } else if (event.time == sample_time) { - sample->time.seconds = sample_time; - sample->depth.mm = depth_mm; - sample->temperature.mkelvin = temp_mk; - sample->sensor = event.pressure.sensor; - sample->cylinderpressure.mbar = event.pressure.mbar; - finish_sample(dc); - - break; - } else { // Event is prior to sample - sample->time.seconds = event.time; - sample->sensor = event.pressure.sensor; - sample->cylinderpressure.mbar = event.pressure.mbar; - if (last_time == sample_time) { - sample->depth.mm = depth_mm; - sample->temperature.mkelvin = temp_mk; - } else { - // Extrapolate - last_depth = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm - last_temp = C_to_mkelvin((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK - sample->depth.mm = last_depth + (depth_mm - last_depth) - * (event.time - last_time) / sample_interval; - sample->temperature.mkelvin = last_temp + (temp_mk - last_temp) - * (event.time - last_time) / sample_interval; - } - finish_sample(dc); - - break; - } - } // while (true); - } // for each event sample - - // record trailing depth samples - for ( ;d < sample_count; d++) { - sample = prepare_sample(dc); - sample->time.seconds = d * sample_interval; - - sample->depth.mm = array_uint16_le(ds + d * 2) * 10; // cm->mm - sample->temperature.mkelvin = - C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10); - finish_sample(dc); - } - - if (log_version == 3 && model == 4) { - // Advance to begin of next dive - switch (array_uint16_le(ps + ps_ptr)) { - case 0x0000: - ps_ptr += 5; - break; - case 0x0100: - ps_ptr += 7; - break; - case 0x0200: - ps_ptr += 9; - break; - case 0x0300: - ps_ptr += 11; - break; - case 0x0b0b: - ps_ptr += 27; - break; - } - - while (*(ps + ps_ptr) != 0x04) - ps_ptr++; - } - - // End dive - dive->downloaded = true; - record_dive(dive); - mark_divelist_changed(true); - - // Advance ptr for next dive - ptr += ps_ptr + 4; - } // while - - //DEBUG save_dives("/tmp/test.xml"); -} - -int try_to_open_liquivision(const char *filename, struct memblock *mem) -{ - (void) filename; - const unsigned char *buf = mem->buffer; - unsigned int buf_size = mem->size; - unsigned int ptr; - int log_version; - - // Get name length - unsigned int len = array_uint32_le(buf); - // Ignore length field and the name - ptr = 4 + len; - - unsigned int dive_count = array_uint32_le(buf + ptr); - if (dive_count == 0xffffffff) { - // File version 3.0 - log_version = 3; - ptr += 6; - dive_count = array_uint32_le(buf + ptr); - } else { - log_version = 2; - } - ptr += 4; - - parse_dives(log_version, buf + ptr, buf_size - ptr); - - return 1; -} diff --git a/subsurface-core/load-git.c b/subsurface-core/load-git.c deleted file mode 100644 index a1f2d2031..000000000 --- a/subsurface-core/load-git.c +++ /dev/null @@ -1,1709 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#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) -{ - (void) str; - uint32_t uuid; - degrees_t latitude = parse_degrees(line, &line); - degrees_t longitude = parse_degrees(line, &line); - struct dive *dive = _dive; - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) { - uuid = get_dive_site_uuid_by_gps(latitude, longitude, NULL); - if (!uuid) - uuid = create_dive_site_with_gps("", latitude, longitude, dive->when); - dive->dive_site_uuid = uuid; - } else { - if (dive_site_has_gps_location(ds) && - (ds->latitude.udeg != latitude.udeg || ds->longitude.udeg != longitude.udeg)) { - const char *coords = printGPSCoords(latitude.udeg, longitude.udeg); - // we have a dive site that already has GPS coordinates - ds->notes = add_to_string(ds->notes, translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords); - free((void *)coords); - } - ds->latitude = latitude; - ds->longitude = longitude; - } - -} - -static void parse_dive_location(char *line, struct membuffer *str, void *_dive) -{ - (void) line; - uint32_t uuid; - char *name = get_utf8(str); - struct dive *dive = _dive; - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) { - uuid = get_dive_site_uuid_by_name(name, NULL); - if (!uuid) - uuid = create_dive_site(name, dive->when); - dive->dive_site_uuid = uuid; - } else { - // we already had a dive site linked to the dive - if (same_string(ds->name, "")) { - ds->name = strdup(name); - } else { - // and that dive site had a name. that's weird - if our name is different, add it to the notes - if (!same_string(ds->name, name)) - ds->notes = add_to_string(ds->notes, translate("gettextFromC", "additional name for site: %s\n"), name); - } - } - free(name); -} - -static void parse_dive_divemaster(char *line, struct membuffer *str, void *_dive) -{ (void) line; struct dive *dive = _dive; dive->divemaster = get_utf8(str); } - -static void parse_dive_buddy(char *line, struct membuffer *str, void *_dive) -{ (void) line; struct dive *dive = _dive; dive->buddy = get_utf8(str); } - -static void parse_dive_suit(char *line, struct membuffer *str, void *_dive) -{ (void) line; struct dive *dive = _dive; dive->suit = get_utf8(str); } - -static void parse_dive_notes(char *line, struct membuffer *str, void *_dive) -{ (void) line; struct dive *dive = _dive; dive->notes = get_utf8(str); } - -static void parse_dive_divesiteid(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->dive_site_uuid = get_hex(line); } - -/* - * We can have multiple tags in the membuffer. They are separated by - * NUL bytes. - */ -static void parse_dive_tags(char *line, struct membuffer *str, void *_dive) -{ - (void) line; - struct dive *dive = _dive; - const char *tag; - int len = str->len; - - if (!len) - return; - - /* Make sure there is a NUL at the end too */ - tag = mb_cstring(str); - for (;;) { - int taglen = strlen(tag); - if (taglen) - taglist_add_tag(&dive->tag_list, tag); - len -= taglen; - if (!len) - return; - tag += taglen+1; - len--; - } -} - -static void parse_dive_airtemp(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->airtemp = get_temperature(line); } - -static void parse_dive_watertemp(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->watertemp = get_temperature(line); } - -static void parse_dive_duration(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->duration = get_duration(line); } - -static void parse_dive_rating(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->rating = get_index(line); } - -static void parse_dive_visibility(char *line, struct membuffer *str, void *_dive) -{ (void) str; struct dive *dive = _dive; dive->visibility = get_index(line); } - -static void parse_dive_notrip(char *line, struct membuffer *str, void *_dive) -{ - (void) str; - (void) line; - struct dive *dive = _dive; dive->tripflag = NO_TRIP; -} - -static void parse_site_description(char *line, struct membuffer *str, void *_ds) -{ (void) line; struct dive_site *ds = _ds; ds->description = strdup(mb_cstring(str)); } - -static void parse_site_name(char *line, struct membuffer *str, void *_ds) -{ (void) line; struct dive_site *ds = _ds; ds->name = strdup(mb_cstring(str)); } - -static void parse_site_notes(char *line, struct membuffer *str, void *_ds) -{ (void) line; struct dive_site *ds = _ds; ds->notes = strdup(mb_cstring(str)); } - -extern degrees_t parse_degrees(char *buf, char **end); -static void parse_site_gps(char *line, struct membuffer *str, void *_ds) -{ - (void) str; - struct dive_site *ds = _ds; - - ds->latitude = parse_degrees(line, &line); - ds->longitude = parse_degrees(line, &line); -} - -static void parse_site_geo(char *line, struct membuffer *str, void *_ds) -{ - struct dive_site *ds = _ds; - if (ds->taxonomy.category == NULL) - ds->taxonomy.category = alloc_taxonomy(); - int nr = ds->taxonomy.nr; - if (nr < TC_NR_CATEGORIES) { - struct taxonomy *t = &ds->taxonomy.category[nr]; - t->value = strdup(mb_cstring(str)); - sscanf(line, "cat %d origin %d \"", &t->category, (int *)&t->origin); - ds->taxonomy.nr++; - } -} - -/* Parse key=val parts of samples and cylinders etc */ -static char *parse_keyvalue_entry(void (*fn)(void *, const char *, const char *), void *fndata, char *line) -{ - char *key = line, *val, c; - - while ((c = *line) != 0) { - if (isspace(c) || c == '=') - break; - line++; - } - - if (c == '=') - *line++ = 0; - val = line; - - while ((c = *line) != 0) { - if (isspace(c)) - break; - line++; - } - if (c) - *line++ = 0; - - fn(fndata, key, val); - return line; -} - -static int cylinder_index, weightsystem_index; - -static void parse_cylinder_keyvalue(void *_cylinder, const char *key, const char *value) -{ - cylinder_t *cylinder = _cylinder; - if (!strcmp(key, "vol")) { - cylinder->type.size = get_volume(value); - return; - } - if (!strcmp(key, "workpressure")) { - cylinder->type.workingpressure = get_pressure(value); - return; - } - /* This is handled by the "get_utf8()" */ - if (!strcmp(key, "description")) - return; - if (!strcmp(key, "o2")) { - cylinder->gasmix.o2 = get_fraction(value); - return; - } - if (!strcmp(key, "he")) { - cylinder->gasmix.he = get_fraction(value); - return; - } - if (!strcmp(key, "start")) { - cylinder->start = get_pressure(value); - return; - } - if (!strcmp(key, "end")) { - cylinder->end = get_pressure(value); - return; - } - if (!strcmp(key, "use")) { - cylinder->cylinder_use = cylinderuse_from_text(value); - return; - } - report_error("Unknown cylinder key/value pair (%s/%s)", key, value); -} - -static void parse_dive_cylinder(char *line, struct membuffer *str, void *_dive) -{ - struct dive *dive = _dive; - cylinder_t *cylinder = dive->cylinder + cylinder_index; - - cylinder_index++; - cylinder->type.description = get_utf8(str); - for (;;) { - char c; - while (isspace(c = *line)) - line++; - if (!c) - break; - line = parse_keyvalue_entry(parse_cylinder_keyvalue, cylinder, line); - } -} - -static void parse_weightsystem_keyvalue(void *_ws, const char *key, const char *value) -{ - weightsystem_t *ws = _ws; - if (!strcmp(key, "weight")) { - ws->weight = get_weight(value); - return; - } - /* This is handled by the "get_utf8()" */ - if (!strcmp(key, "description")) - return; - report_error("Unknown weightsystem key/value pair (%s/%s)", key, value); -} - -static void parse_dive_weightsystem(char *line, struct membuffer *str, void *_dive) -{ - struct dive *dive = _dive; - weightsystem_t *ws = dive->weightsystem + weightsystem_index; - - weightsystem_index++; - ws->description = get_utf8(str); - for (;;) { - char c; - while (isspace(c = *line)) - line++; - if (!c) - break; - line = parse_keyvalue_entry(parse_weightsystem_keyvalue, ws, line); - } -} - -static int match_action(char *line, struct membuffer *str, void *data, - struct keyword_action *action, unsigned nr_action) -{ - char *p = line, c; - unsigned low, high; - - while ((c = *p) >= 'a' && c <= 'z') // skip over 1st word - p++; // Extract the second word from the line: - if (p == line) - return -1; - switch (c) { - case 0: // if 2nd word is C-terminated - break; - case ' ': // =end of 2nd word? - *p++ = 0; // then C-terminate that word - break; - default: - return -1; - } - - /* Standard binary search in a table */ - low = 0; - high = nr_action; - while (low < high) { - unsigned mid = (low+high)/2; - struct keyword_action *a = action + mid; - int cmp = strcmp(line, a->keyword); - if (!cmp) { // attribute found: - a->fn(p, str, data); // Execute appropriate function, - return 0; // .. passing 2n word from above - } // (p) as a function argument. - if (cmp < 0) - high = mid; - else - low = mid+1; - } -report_error("Unmatched action '%s'", line); - return -1; -} - -/* FIXME! We should do the array thing here too. */ -static void parse_sample_keyvalue(void *_sample, const char *key, const char *value) -{ - struct sample *sample = _sample; - - if (!strcmp(key, "sensor")) { - sample->sensor = atoi(value); - return; - } - if (!strcmp(key, "ndl")) { - sample->ndl = get_duration(value); - return; - } - if (!strcmp(key, "tts")) { - sample->tts = get_duration(value); - return; - } - if (!strcmp(key, "in_deco")) { - sample->in_deco = atoi(value); - return; - } - if (!strcmp(key, "stoptime")) { - sample->stoptime = get_duration(value); - return; - } - if (!strcmp(key, "stopdepth")) { - sample->stopdepth = get_depth(value); - return; - } - if (!strcmp(key, "cns")) { - sample->cns = atoi(value); - return; - } - - if (!strcmp(key, "rbt")) { - sample->rbt = get_duration(value); - return; - } - - if (!strcmp(key, "po2")) { - pressure_t p = get_pressure(value); - sample->setpoint.mbar = p.mbar; - return; - } - if (!strcmp(key, "sensor1")) { - pressure_t p = get_pressure(value); - sample->o2sensor[0].mbar = p.mbar; - return; - } - if (!strcmp(key, "sensor2")) { - pressure_t p = get_pressure(value); - sample->o2sensor[1].mbar = p.mbar; - return; - } - if (!strcmp(key, "sensor3")) { - pressure_t p = get_pressure(value); - sample->o2sensor[2].mbar = p.mbar; - return; - } - if (!strcmp(key, "o2pressure")) { - pressure_t p = get_pressure(value); - sample->o2cylinderpressure.mbar = p.mbar; - return; - } - if (!strcmp(key, "heartbeat")) { - sample->heartbeat = atoi(value); - return; - } - if (!strcmp(key, "bearing")) { - sample->bearing.degrees = atoi(value); - return; - } - - report_error("Unexpected sample key/value pair (%s/%s)", key, value); -} - -static char *parse_sample_unit(struct sample *sample, double val, char *unit) -{ - char *end = unit, c; - - /* Skip over the unit */ - while ((c = *end) != 0) { - if (isspace(c)) { - *end++ = 0; - break; - } - end++; - } - - /* The units are "°C", "m" or "bar", so let's just look at the first character */ - switch (*unit) { - case 'm': - sample->depth.mm = rint(1000*val); - break; - case 'b': - sample->cylinderpressure.mbar = rint(1000*val); - break; - default: - sample->temperature.mkelvin = C_to_mkelvin(val); - break; - } - - return end; -} - -/* - * By default the sample data does not change unless the - * save-file gives an explicit new value. So we copy the - * data from the previous sample if one exists, and then - * the parsing will update it as necessary. - * - * There are a few exceptions, like the sample pressure: - * missing sample pressure doesn't mean "same as last - * time", but "interpolate". We clear those ones - * explicitly. - */ -static struct sample *new_sample(struct divecomputer *dc) -{ - struct sample *sample = prepare_sample(dc); - if (sample != dc->sample) { - memcpy(sample, sample-1, sizeof(struct sample)); - sample->cylinderpressure.mbar = 0; - } - return sample; -} - -static void sample_parser(char *line, struct divecomputer *dc) -{ - int m, s = 0; - struct sample *sample = new_sample(dc); - - m = strtol(line, &line, 10); - if (*line == ':') - s = strtol(line+1, &line, 10); - sample->time.seconds = m*60+s; - - for (;;) { - char c; - - while (isspace(c = *line)) - line++; - if (!c) - break; - /* Less common sample entries have a name */ - if (c >= 'a' && c <= 'z') { - line = parse_keyvalue_entry(parse_sample_keyvalue, sample, line); - } else { - const char *end; - double val = ascii_strtod(line, &end); - if (end == line) { - report_error("Odd sample data: %s", line); - break; - } - line = (char *)end; - line = parse_sample_unit(sample, val, line); - } - } - finish_sample(dc); -} - -static void parse_dc_airtemp(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->airtemp = get_temperature(line); } - -static void parse_dc_date(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; update_date(&dc->when, line); } - -static void parse_dc_deviceid(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->deviceid = get_hex(line); } - -static void parse_dc_diveid(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->diveid = get_hex(line); } - -static void parse_dc_duration(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->duration = get_duration(line); } - -static void parse_dc_dctype(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->divemode = get_dctype(line); } - -static void parse_dc_maxdepth(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->maxdepth = get_depth(line); } - -static void parse_dc_meandepth(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->meandepth = get_depth(line); } - -static void parse_dc_model(char *line, struct membuffer *str, void *_dc) -{ (void) line; struct divecomputer *dc = _dc; dc->model = get_utf8(str); } - -static void parse_dc_numberofoxygensensors(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->no_o2sensors = get_index(line); } - -static void parse_dc_surfacepressure(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->surface_pressure = get_pressure(line); } - -static void parse_dc_salinity(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->salinity = get_salinity(line); } - -static void parse_dc_surfacetime(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->surfacetime = get_duration(line); } - -static void parse_dc_time(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; update_time(&dc->when, line); } - -static void parse_dc_watertemp(char *line, struct membuffer *str, void *_dc) -{ (void) str; struct divecomputer *dc = _dc; dc->watertemp = get_temperature(line); } - -static void parse_event_keyvalue(void *_event, const char *key, const char *value) -{ - struct event *event = _event; - int val = atoi(value); - - if (!strcmp(key, "type")) { - event->type = val; - } else if (!strcmp(key, "flags")) { - event->flags = val; - } else if (!strcmp(key, "value")) { - event->value = val; - } else if (!strcmp(key, "name")) { - /* We get the name from the string handling */ - } else if (!strcmp(key, "cylinder")) { - /* NOTE! We add one here as a marker that "yes, we got a cylinder index" */ - event->gas.index = 1+get_index(value); - } else if (!strcmp(key, "o2")) { - event->gas.mix.o2 = get_fraction(value); - } else if (!strcmp(key, "he")) { - event->gas.mix.he = get_fraction(value); - } else - report_error("Unexpected event key/value pair (%s/%s)", key, value); -} - -/* keyvalue "key" "value" - * so we have two strings (possibly empty) in the membuffer, separated by a '\0' */ -static void parse_dc_keyvalue(char *line, struct membuffer *str, void *_dc) -{ - const char *key, *value; - struct divecomputer *dc = _dc; - - // Let's make sure we have two strings... - int string_counter = 0; - while(*line) { - if (*line == '"') - string_counter++; - line++; - } - if (string_counter != 2) - return; - - // stupidly the second string in the membuffer isn't NUL terminated; - // asking for a cstring fixes that; interestingly enough, given that there are two - // strings in the mb, the next command at the same time assigns a pointer to the - // first string to 'key' and NUL terminates the second string (which then goes to 'value') - key = mb_cstring(str); - value = key + strlen(key) + 1; - add_extra_data(dc, key, value); -} - -static void parse_dc_event(char *line, struct membuffer *str, void *_dc) -{ - int m, s = 0; - const char *name; - struct divecomputer *dc = _dc; - struct event event = { 0 }, *ev; - - m = strtol(line, &line, 10); - if (*line == ':') - s = strtol(line+1, &line, 10); - event.time.seconds = m*60+s; - - for (;;) { - char c; - while (isspace(c = *line)) - line++; - if (!c) - break; - line = parse_keyvalue_entry(parse_event_keyvalue, &event, line); - } - - name = ""; - if (str->len) - name = mb_cstring(str); - ev = add_event(dc, event.time.seconds, event.type, event.flags, event.value, name); - - /* - * Older logs might mark the dive to be CCR by having an "SP change" event at time 0:00. - * Better to mark them being CCR on import so no need for special treatments elsewhere on - * the code. - */ - if (ev && event.time.seconds == 0 && event.type == SAMPLE_EVENT_PO2 && dc->divemode==OC) { - dc->divemode = CCR; - } - - if (ev && event_is_gaschange(ev)) { - /* - * We subtract one here because "0" is "no index", - * and the parsing will add one for actual cylinder - * index data (see parse_event_keyvalue) - */ - ev->gas.index = event.gas.index-1; - if (event.gas.mix.o2.permille || event.gas.mix.he.permille) - ev->gas.mix = event.gas.mix; - } -} - -static void parse_trip_date(char *line, struct membuffer *str, void *_trip) -{ (void) str; dive_trip_t *trip = _trip; update_date(&trip->when, line); } - -static void parse_trip_time(char *line, struct membuffer *str, void *_trip) -{ (void) str; dive_trip_t *trip = _trip; update_time(&trip->when, line); } - -static void parse_trip_location(char *line, struct membuffer *str, void *_trip) -{ (void) line; dive_trip_t *trip = _trip; trip->location = get_utf8(str); } - -static void parse_trip_notes(char *line, struct membuffer *str, void *_trip) -{ (void) line; dive_trip_t *trip = _trip; trip->notes = get_utf8(str); } - -static void parse_settings_autogroup(char *line, struct membuffer *str, void *_unused) -{ - (void) line; - (void) str; - (void) _unused; - set_autogroup(1); -} - -static void parse_settings_units(char *line, struct membuffer *str, void *unused) -{ - (void) str; - (void) unused; - if (line) - set_informational_units(line); -} - -static void parse_settings_userid(char *line, struct membuffer *str, void *_unused) -{ - (void) str; - (void) _unused; - if (line) { - set_save_userid_local(true); - set_userid(line); - } -} - -/* - * Our versioning is a joke right now, but this is more of an example of what we - * *can* do some day. And if we do change the version, this warning will show if - * you read with a version of subsurface that doesn't know about it. - * We MUST keep this in sync with the XML version (so we can report a consistent - * minimum datafile version) - */ -static void parse_settings_version(char *line, struct membuffer *str, void *_unused) -{ - (void) str; - (void) _unused; - int version = atoi(line); - report_datafile_version(version); - if (version > DATAFORMAT_VERSION) - report_error("Git save file version %d is newer than version %d I know about", version, DATAFORMAT_VERSION); -} - -/* The string in the membuffer is the version string of subsurface that saved things, just FYI */ -static void parse_settings_subsurface(char *line, struct membuffer *str, void *_unused) -{ - (void) line; - (void) str; - (void) _unused; -} - -struct divecomputerid { - const char *model; - const char *nickname; - const char *firmware; - const char *serial; - const char *cstr; - unsigned int deviceid; -}; - -static void parse_divecomputerid_keyvalue(void *_cid, const char *key, const char *value) -{ - struct divecomputerid *cid = _cid; - - if (*value == '"') { - value = cid->cstr; - cid->cstr += strlen(cid->cstr)+1; - } - if (!strcmp(key, "deviceid")) { - cid->deviceid = get_hex(value); - return; - } - if (!strcmp(key, "serial")) { - cid->serial = value; - return; - } - if (!strcmp(key, "firmware")) { - cid->firmware = value; - return; - } - if (!strcmp(key, "nickname")) { - cid->nickname = value; - return; - } - report_error("Unknow divecomputerid key/value pair (%s/%s)", key, value); -} - -/* - * The 'divecomputerid' is a bit harder to parse than some other things, because - * it can have multiple strings (but see the tag parsing for another example of - * that) in addition to the non-string entries. - * - * We keep the "next" string in "id.cstr" and update it as we use it. - */ -static void parse_settings_divecomputerid(char *line, struct membuffer *str, void *_unused) -{ - (void) _unused; - struct divecomputerid id = { mb_cstring(str) }; - - id.cstr = id.model + strlen(id.model) + 1; - - /* Skip the '"' that stood for the model string */ - line++; - - for (;;) { - char c; - while (isspace(c = *line)) - line++; - if (!c) - break; - line = parse_keyvalue_entry(parse_divecomputerid_keyvalue, &id, line); - } - create_device_node(id.model, id.deviceid, id.serial, id.firmware, id.nickname); -} - -static void parse_picture_filename(char *line, struct membuffer *str, void *_pic) -{ - (void) line; - struct picture *pic = _pic; - pic->filename = get_utf8(str); -} - -static void parse_picture_gps(char *line, struct membuffer *str, void *_pic) -{ - (void) str; - struct picture *pic = _pic; - - pic->latitude = parse_degrees(line, &line); - pic->longitude = parse_degrees(line, &line); -} - -static void parse_picture_hash(char *line, struct membuffer *str, void *_pic) -{ - (void) line; - struct picture *pic = _pic; - pic->hash = get_utf8(str); -} - -/* These need to be sorted! */ -struct keyword_action dc_action[] = { -#undef D -#define D(x) { #x, parse_dc_ ## x } - D(airtemp), D(date), D(dctype), D(deviceid), D(diveid), D(duration), - D(event), D(keyvalue), D(maxdepth), D(meandepth), D(model), D(numberofoxygensensors), - D(salinity), D(surfacepressure), D(surfacetime), D(time), D(watertemp) -}; - -/* Sample lines start with a space or a number */ -static void divecomputer_parser(char *line, struct membuffer *str, void *_dc) -{ - char c = *line; - if (c < 'a' || c > 'z') - sample_parser(line, _dc); - match_action(line, str, _dc, dc_action, ARRAY_SIZE(dc_action)); -} - -/* These need to be sorted! */ -struct keyword_action dive_action[] = { -#undef D -#define D(x) { #x, parse_dive_ ## x } - D(airtemp), D(buddy), D(cylinder), D(divemaster), D(divesiteid), D(duration), - D(gps), D(location), D(notes), D(notrip), D(rating), D(suit), - D(tags), D(visibility), D(watertemp), D(weightsystem) -}; - -static void dive_parser(char *line, struct membuffer *str, void *_dive) -{ - match_action(line, str, _dive, dive_action, ARRAY_SIZE(dive_action)); -} - -/* These need to be sorted! */ -struct keyword_action site_action[] = { -#undef D -#define D(x) { #x, parse_site_ ## x } - D(description), D(geo), D(gps), D(name), D(notes) -}; - -static void site_parser(char *line, struct membuffer *str, void *_ds) -{ - match_action(line, str, _ds, site_action, ARRAY_SIZE(site_action)); -} - -/* These need to be sorted! */ -struct keyword_action trip_action[] = { -#undef D -#define D(x) { #x, parse_trip_ ## x } - D(date), D(location), D(notes), D(time), -}; - -static void trip_parser(char *line, struct membuffer *str, void *_trip) -{ - match_action(line, str, _trip, trip_action, ARRAY_SIZE(trip_action)); -} - -/* These need to be sorted! */ -static struct keyword_action settings_action[] = { -#undef D -#define D(x) { #x, parse_settings_ ## x } - D(autogroup), D(divecomputerid), D(subsurface), D(units), D(userid), D(version), -}; - -static void settings_parser(char *line, struct membuffer *str, void *_unused) -{ - (void) _unused; - match_action(line, str, NULL, settings_action, ARRAY_SIZE(settings_action)); -} - -/* These need to be sorted! */ -static struct keyword_action picture_action[] = { -#undef D -#define D(x) { #x, parse_picture_ ## x } - D(filename), D(gps), D(hash) -}; - -static void picture_parser(char *line, struct membuffer *str, void *_pic) -{ - match_action(line, str, _pic, picture_action, ARRAY_SIZE(picture_action)); -} - -/* - * We have a very simple line-based interface, with the small - * complication that lines can have strings in the middle, and - * a string can be multiple lines. - * - * The UTF-8 string escaping is *very* simple, though: - * - * - a string starts and ends with double quotes (") - * - * - inside the string we escape: - * (a) double quotes with '\"' - * (b) backslash (\) with '\\' - * - * - additionally, for human readability, we escape - * newlines with '\n\t', with the exception that - * consecutive newlines are left unescaped (so an - * empty line doesn't become a line with just a tab - * on it). - * - * Also, while the UTF-8 string can have arbitrarily - * long lines, the non-string parts of the lines are - * never long, so we can use a small temporary buffer - * on stack for that part. - * - * Also, note that if a line has one or more strings - * in it: - * - * - each string will be represented as a single '"' - * character in the output. - * - * - all string will exist in the same 'membuffer', - * separated by NUL characters (that cannot exist - * in a string, not even quoted). - */ -static const char *parse_one_string(const char *buf, const char *end, struct membuffer *b) -{ - const char *p = buf; - - /* - * We turn multiple strings one one line (think dive tags) into one - * membuffer that has NUL characters in between strings. - */ - if (b->len) - put_bytes(b, "", 1); - - while (p < end) { - char replace; - - switch (*p++) { - default: - continue; - case '\n': - if (p < end && *p == '\t') { - replace = '\n'; - break; - } - continue; - case '\\': - if (p < end) { - replace = *p; - break; - } - continue; - case '"': - replace = 0; - break; - } - put_bytes(b, buf, p - buf - 1); - if (!replace) - break; - put_bytes(b, &replace, 1); - buf = ++p; - } - return p; -} - -typedef void (line_fn_t)(char *, struct membuffer *, void *); -#define MAXLINE 500 -static unsigned parse_one_line(const char *buf, unsigned size, line_fn_t *fn, void *fndata, struct membuffer *b) -{ - const char *end = buf + size; - const char *p = buf; - char line[MAXLINE+1]; - int off = 0; - - while (p < end) { - char c = *p++; - if (c == '\n') - break; - line[off] = c; - off++; - if (off > MAXLINE) - off = MAXLINE; - if (c == '"') - p = parse_one_string(p, end, b); - } - line[off] = 0; - fn(line, b, fndata); - return p - buf; -} - -/* - * We keep on re-using the membuffer that we use for - * strings, but the callback function can "steal" it by - * saving its value and just clear the original. - */ -static void for_each_line(git_blob *blob, line_fn_t *fn, void *fndata) -{ - const char *content = git_blob_rawcontent(blob); - unsigned int size = git_blob_rawsize(blob); - struct membuffer str = { 0 }; - - while (size) { - unsigned int n = parse_one_line(content, size, fn, fndata, &str); - content += n; - size -= n; - - /* Re-use the allocation, but forget the data */ - str.len = 0; - } - free_buffer(&str); -} - -#define GIT_WALK_OK 0 -#define GIT_WALK_SKIP 1 - -static struct divecomputer *active_dc; -static struct dive *active_dive; -static dive_trip_t *active_trip; - -static void finish_active_trip(void) -{ - dive_trip_t *trip = active_trip; - - if (trip) { - active_trip = NULL; - insert_trip(&trip); - } -} - -static void finish_active_dive(void) -{ - struct dive *dive = active_dive; - - if (dive) { - /* check if we need to save pictures */ - FOR_EACH_PICTURE(dive) { - if (!picture_exists(picture)) - save_picture_from_git(picture); - } - /* free any memory we allocated to track pictures */ - while (pel) { - free(pel->data); - void *lastone = pel; - pel = pel->next; - free(lastone); - } - active_dive = NULL; - record_dive(dive); - } -} - -static struct dive *create_new_dive(timestamp_t when) -{ - struct dive *dive = alloc_dive(); - - /* We'll fill in more data from the dive file */ - dive->when = when; - - if (active_trip) - add_dive_to_trip(dive, active_trip); - return dive; -} - -static dive_trip_t *create_new_trip(int yyyy, int mm, int dd) -{ - dive_trip_t *trip = calloc(1, sizeof(dive_trip_t)); - struct tm tm = { 0 }; - - /* We'll fill in the real data from the trip descriptor file */ - tm.tm_year = yyyy; - tm.tm_mon = mm-1; - tm.tm_mday = dd; - trip->when = utc_mktime(&tm); - - return trip; -} - -static bool validate_date(int yyyy, int mm, int dd) -{ - return yyyy > 1970 && yyyy < 3000 && - mm > 0 && mm < 13 && - dd > 0 && dd < 32; -} - -static bool validate_time(int h, int m, int s) -{ - return h >= 0 && h < 24 && - m >= 0 && m < 60 && - s >=0 && s <= 60; -} - -/* - * Dive trip directory, name is 'nn-alphabetic[~hex]' - */ -static int dive_trip_directory(const char *root, const char *name) -{ - int yyyy = -1, mm = -1, dd = -1; - - if (sscanf(root, "%d/%d", &yyyy, &mm) != 2) - return GIT_WALK_SKIP; - dd = atoi(name); - if (!validate_date(yyyy, mm, dd)) - return GIT_WALK_SKIP; - finish_active_trip(); - active_trip = create_new_trip(yyyy, mm, dd); - return GIT_WALK_OK; -} - -/* - * Dive directory, name is [[yyyy-]mm-]nn-ddd-hh:mm:ss[~hex] in older git repositories - * but [[yyyy-]mm-]nn-ddd-hh=mm=ss[~hex] in newer repos as ':' is an illegal character for Windows files - * and 'timeoff' points to what should be the time part of - * the name (the first digit of the hour). - * - * The root path will be of the form yyyy/mm[/tripdir], - */ -static int dive_directory(const char *root, const git_tree_entry *entry, const char *name, int timeoff) -{ - int yyyy = -1, mm = -1, dd = -1; - int h, m, s; - int mday_off, month_off, year_off; - struct tm tm; - - /* Skip the '-' before the time */ - mday_off = timeoff; - if (!mday_off || name[--mday_off] != '-') - return GIT_WALK_SKIP; - /* Skip the day name */ - while (mday_off > 0 && name[--mday_off] != '-') - /* nothing */; - - mday_off = mday_off - 2; - month_off = mday_off - 3; - year_off = month_off - 5; - if (mday_off < 0) - return GIT_WALK_SKIP; - - /* Get the time of day -- parse both time formats so we can read old repos when not on Windows */ - if (sscanf(name+timeoff, "%d:%d:%d", &h, &m, &s) != 3 && sscanf(name+timeoff, "%d=%d=%d", &h, &m, &s) != 3) - return GIT_WALK_SKIP; - if (!validate_time(h, m, s)) - return GIT_WALK_SKIP; - - /* - * Using the "git_tree_walk()" interface is simple, but - * it kind of sucks as an interface because there is - * no sane way to pass the hierarchy to the callbacks. - * The "payload" is a fixed one-time thing: we'd like - * the "current trip" to be passed down to the dives - * that get parsed under that trip, but we can't. - * - * So "active_trip" is not the trip that is in the hierarchy - * _above_ us, it's just the trip that was _before_ us. But - * if a dive is not in a trip at all, we can't tell. - * - * We could just do a better walker that passes the - * return value around, but we hack around this by - * instead looking at the one hierarchical piece of - * data we have: the pathname to the current entry. - * - * This is pretty hacky. The magic '8' is the length - * of a pathname of the form 'yyyy/mm/'. - */ - if (strlen(root) == 8) - finish_active_trip(); - - /* - * Get the date. The day of the month is in the dive directory - * name, the year and month might be in the path leading up - * to it. - */ - dd = atoi(name + mday_off); - if (year_off < 0) { - if (sscanf(root, "%d/%d", &yyyy, &mm) != 2) - return GIT_WALK_SKIP; - } else - yyyy = atoi(name + year_off); - if (month_off >= 0) - mm = atoi(name + month_off); - - if (!validate_date(yyyy, mm, dd)) - return GIT_WALK_SKIP; - - /* Ok, close enough. We've gotten sufficient information */ - memset(&tm, 0, sizeof(tm)); - tm.tm_hour = h; - tm.tm_min = m; - tm.tm_sec = s; - tm.tm_year = yyyy - 1900; - tm.tm_mon = mm-1; - tm.tm_mday = dd; - - finish_active_dive(); - active_dive = create_new_dive(utc_mktime(&tm)); - memcpy(active_dive->git_id, git_tree_entry_id(entry)->id, 20); - return GIT_WALK_OK; -} - -static int picture_directory(const char *root, const char *name) -{ - (void) root; - (void) name; - if (!active_dive) - return GIT_WALK_SKIP; - return GIT_WALK_OK; -} - -/* - * Return the length of the string without the unique part. - */ -static int nonunique_length(const char *str) -{ - int len = 0; - - for (;;) { - char c = *str++; - if (!c || c == '~') - return len; - len++; - } -} - -/* - * When hitting a directory node, we have a couple of cases: - * - * - It's just a date entry - all numeric (either year or month): - * - * [yyyy|mm] - * - * We don't do anything with these, we just traverse into them. - * The numeric data will show up as part of the full path when - * we hit more interesting entries. - * - * - It's a trip directory. The name will be of the form - * - * nn-alphabetic[~hex] - * - * where 'nn' is the day of the month (year and month will be - * encoded in the path leading up to this). - * - * - It's a dive directory. The name will be of the form - * - * [[yyyy-]mm-]nn-ddd-hh=mm=ss[~hex] - * - * (older versions had this as [[yyyy-]mm-]nn-ddd-hh:mm:ss[~hex] - * but that faile on Windows) - * - * which describes the date and time of a dive (yyyy and mm - * are optional, and may be encoded in the path leading up to - * the dive). - * - * - It is a per-dive picture directory ("Pictures") - * - * - It's some random non-dive-data directory. - * - * If it doesn't match the above patterns, we'll ignore them - * for dive loading purposes, and not even recurse into them. - */ -static int walk_tree_directory(const char *root, const git_tree_entry *entry) -{ - const char *name = git_tree_entry_name(entry); - int digits = 0, len; - char c; - - if (!strcmp(name, "Pictures")) - return picture_directory(root, name); - - if (!strcmp(name, "01-Divesites")) - return GIT_WALK_OK; - - while (isdigit(c = name[digits])) - digits++; - - /* Doesn't start with two or four digits? Skip */ - if (digits != 4 && digits != 2) - return GIT_WALK_SKIP; - - /* Only digits? Do nothing, but recurse into it */ - if (!c) - return GIT_WALK_OK; - - /* All valid cases need to have a slash following */ - if (c != '-') - return GIT_WALK_SKIP; - - /* Do a quick check for a common dive case */ - len = nonunique_length(name); - - /* - * We know the len is at least 3, because we had at least - * two digits and a dash - */ - if (name[len-3] == ':' || name[len-3] == '=') - return dive_directory(root, entry, name, len-8); - - if (digits != 2) - return GIT_WALK_SKIP; - - return dive_trip_directory(root, name); -} - -git_blob *git_tree_entry_blob(git_repository *repo, const git_tree_entry *entry) -{ - const git_oid *id = git_tree_entry_id(entry); - git_blob *blob; - - if (git_blob_lookup(&blob, repo, id)) - return NULL; - return blob; -} - -static struct divecomputer *create_new_dc(struct dive *dive) -{ - struct divecomputer *dc = &dive->dc; - - while (dc->next) - dc = dc->next; - /* Did we already fill that in? */ - if (dc->samples || dc->model || dc->when) { - struct divecomputer *newdc = calloc(1, sizeof(*newdc)); - if (!newdc) - return NULL; - dc->next = newdc; - dc = newdc; - } - dc->when = dive->when; - dc->duration = dive->duration; - return dc; -} - -/* - * We should *really* try to delay the dive computer data parsing - * until necessary, in order to reduce load-time. The parsing is - * cheap, but the loading of the git blob into memory can be pretty - * costly. - */ -static int parse_divecomputer_entry(git_repository *repo, const git_tree_entry *entry, const char *suffix) -{ - (void) suffix; - git_blob *blob = git_tree_entry_blob(repo, entry); - - if (!blob) - return report_error("Unable to read divecomputer file"); - - active_dc = create_new_dc(active_dive); - for_each_line(blob, divecomputer_parser, active_dc); - git_blob_free(blob); - active_dc = NULL; - return 0; -} - -/* - * NOTE! The "git_id" for the dive is the hash for the whole dive directory. - * As such, it covers not just the dive, but the divecomputers and the - * pictures too. So if any of the dive computers change, the dive cache - * has to be invalidated too. - */ -static int parse_dive_entry(git_repository *repo, const git_tree_entry *entry, const char *suffix) -{ - struct dive *dive = active_dive; - git_blob *blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read dive file"); - if (*suffix) - dive->number = atoi(suffix+1); - cylinder_index = weightsystem_index = 0; - for_each_line(blob, dive_parser, active_dive); - git_blob_free(blob); - return 0; -} - -static int parse_site_entry(git_repository *repo, const git_tree_entry *entry, const char *suffix) -{ - if (*suffix == '\0') - return report_error("Dive site without uuid"); - uint32_t uuid = strtoul(suffix, NULL, 16); - struct dive_site *ds = alloc_or_get_dive_site(uuid); - git_blob *blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read dive site file"); - for_each_line(blob, site_parser, ds); - git_blob_free(blob); - return 0; -} - -static int parse_trip_entry(git_repository *repo, const git_tree_entry *entry) -{ - git_blob *blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read trip file"); - for_each_line(blob, trip_parser, active_trip); - git_blob_free(blob); - return 0; -} - -static int parse_settings_entry(git_repository *repo, const git_tree_entry *entry) -{ - git_blob *blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read settings file"); - set_save_userid_local(false); - for_each_line(blob, settings_parser, NULL); - git_blob_free(blob); - return 0; -} - -static int parse_picture_file(git_repository *repo, const git_tree_entry *entry, const char *name) -{ - /* remember the picture data so we can handle it when all dive data has been loaded - * the name of the git file is PIC- */ - git_blob *blob = git_tree_entry_blob(repo, entry); - const void *rawdata = git_blob_rawcontent(blob); - int len = git_blob_rawsize(blob); - struct picture_entry_list *new_pel = malloc(sizeof(struct picture_entry_list)); - new_pel->next = pel; - pel = new_pel; - pel->data = malloc(len); - memcpy(pel->data, rawdata, len); - pel->len = len; - pel->hash = strdup(name + 4); - git_blob_free(blob); - return 0; -} - -static int parse_picture_entry(git_repository *repo, const git_tree_entry *entry, const char *name) -{ - git_blob *blob; - struct picture *pic; - int hh, mm, ss, offset; - char sign; - - /* - * The format of the picture name files is just the offset within - * the dive in form [[+-]hh=mm=ss (previously [[+-]hh:mm:ss, but - * that didn't work on Windows), possibly followed by a hash to - * make the filename unique (which we can just ignore). - */ - if (sscanf(name, "%c%d:%d:%d", &sign, &hh, &mm, &ss) != 4 && - sscanf(name, "%c%d=%d=%d", &sign, &hh, &mm, &ss) != 4) - return report_error("Unknown file name %s", name); - offset = ss + 60*(mm + 60*hh); - if (sign == '-') - offset = -offset; - - blob = git_tree_entry_blob(repo, entry); - if (!blob) - return report_error("Unable to read picture file"); - - pic = alloc_picture(); - pic->offset.seconds = offset; - - for_each_line(blob, picture_parser, pic); - dive_add_picture(active_dive, pic); - git_blob_free(blob); - return 0; -} - -static int walk_tree_file(const char *root, const git_tree_entry *entry, git_repository *repo) -{ - struct dive *dive = active_dive; - dive_trip_t *trip = active_trip; - const char *name = git_tree_entry_name(entry); - if (verbose > 1) - fprintf(stderr, "git load handling file %s\n", name); - switch (*name) { - /* Picture file? They are saved as time offsets in the dive */ - case '-': case '+': - if (dive) - return parse_picture_entry(repo, entry, name); - break; - case 'D': - if (dive && !strncmp(name, "Divecomputer", 12)) - return parse_divecomputer_entry(repo, entry, name+12); - if (dive && !strncmp(name, "Dive", 4)) - return parse_dive_entry(repo, entry, name+4); - break; - case 'S': - if (!strncmp(name, "Site", 4)) - return parse_site_entry(repo, entry, name + 5); - break; - case '0': - if (trip && !strcmp(name, "00-Trip")) - return parse_trip_entry(repo, entry); - if (!strcmp(name, "00-Subsurface")) - return parse_settings_entry(repo, entry); - break; - case 'P': - if (dive && !strncmp(name, "PIC-", 4)) - return parse_picture_file(repo, entry, name); - break; - } - report_error("Unknown file %s%s (%p %p)", root, name, dive, trip); - return GIT_WALK_SKIP; -} - -static int walk_tree_cb(const char *root, const git_tree_entry *entry, void *payload) -{ - git_repository *repo = payload; - git_filemode_t mode = git_tree_entry_filemode(entry); - - if (mode == GIT_FILEMODE_TREE) - return walk_tree_directory(root, entry); - - walk_tree_file(root, entry, repo); - /* Ignore failed blob loads */ - return GIT_WALK_OK; -} - -static int load_dives_from_tree(git_repository *repo, git_tree *tree) -{ - git_tree_walk(tree, GIT_TREEWALK_PRE, walk_tree_cb, repo); - return 0; -} - -void clear_git_id(void) -{ - saved_git_id = NULL; -} - -void set_git_id(const struct git_oid * id) -{ - static char git_id_buffer[GIT_OID_HEXSZ+1]; - - git_oid_tostr(git_id_buffer, sizeof(git_id_buffer), id); - saved_git_id = git_id_buffer; -} - -static int find_commit(git_repository *repo, const char *branch, git_commit **commit_p) -{ - git_object *object; - - if (git_revparse_single(&object, repo, branch)) - return report_error("Unable to look up revision '%s'", branch); - if (git_object_peel((git_object **)commit_p, object, GIT_OBJ_COMMIT)) - return report_error("Revision '%s' is not a valid commit", branch); - return 0; -} - -static int do_git_load(git_repository *repo, const char *branch) -{ - int ret; - git_commit *commit; - git_tree *tree; - - ret = find_commit(repo, branch, &commit); - if (ret) - return ret; - if (git_commit_tree(&tree, commit)) - return report_error("Could not look up tree of commit in branch '%s'", branch); - ret = load_dives_from_tree(repo, tree); - if (!ret) - set_git_id(git_commit_id(commit)); - git_object_free((git_object *)tree); - return ret; -} - -const char *get_sha(git_repository *repo, const char *branch) -{ - static char git_id_buffer[GIT_OID_HEXSZ+1]; - git_commit *commit; - if (find_commit(repo, branch, &commit)) - return NULL; - git_oid_tostr(git_id_buffer, sizeof(git_id_buffer), (const git_oid *)commit); - return git_id_buffer; -} - -/* - * Like git_save_dives(), this silently returns a negative - * value if it's not a git repository at all (so that you - * can try to load it some other way. - * - * If it is a git repository, we return zero for success, - * or report an error and return 1 if the load failed. - */ -int git_load_dives(struct git_repository *repo, const char *branch) -{ - int ret; - - if (repo == dummy_git_repository) - return report_error("Unable to open git repository at '%s'", branch); - ret = do_git_load(repo, branch); - git_repository_free(repo); - free((void *)branch); - finish_active_dive(); - finish_active_trip(); - return ret; -} diff --git a/subsurface-core/macos.c b/subsurface-core/macos.c deleted file mode 100644 index 500412cd8..000000000 --- a/subsurface-core/macos.c +++ /dev/null @@ -1,218 +0,0 @@ -/* macos.c */ -/* implements Mac OS X specific functions */ -#include -#include -#include -#include "dive.h" -#include "display.h" -#include -#if !defined(__IPHONE_5_0) -#include -#endif -#include -#include -#include -#include -#include - -void subsurface_user_info(struct user_info *info) -{ - (void) info; - /* Nothing, let's use libgit2-20 on MacOS */ -} - -/* macos defines CFSTR to create a CFString object from a constant, - * but no similar macros if a C string variable is supposed to be - * the argument. We add this here (hardcoding the default allocator - * and MacRoman encoding */ -#define CFSTR_VAR(_var) CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, \ - (_var), kCFStringEncodingMacRoman, \ - kCFAllocatorNull) - -#define SUBSURFACE_PREFERENCES CFSTR("org.hohndel.subsurface") -#define ICON_NAME "Subsurface.icns" -#define UI_FONT "Arial 12" - -const char mac_system_divelist_default_font[] = "Arial"; -const char *system_divelist_default_font = mac_system_divelist_default_font; -double system_divelist_default_font_size = -1.0; - -void subsurface_OS_pref_setup(void) -{ - // nothing -} - -bool subsurface_ignore_font(const char *font) -{ - (void) font; - // there are no old default fonts to ignore - return false; -} - -static const char *system_default_path_append(const char *append) -{ - const char *home = getenv("HOME"); - const char *path = "/Library/Application Support/Subsurface"; - - int len = strlen(home) + strlen(path) + 1; - if (append) - len += strlen(append) + 1; - - char *buffer = (char *)malloc(len); - memset(buffer, 0, len); - strcat(buffer, home); - strcat(buffer, path); - if (append) { - strcat(buffer, "/"); - strcat(buffer, append); - } - - return buffer; -} - -const char *system_default_directory(void) -{ - static const char *path = NULL; - if (!path) - path = system_default_path_append(NULL); - return path; -} - -const char *system_default_filename(void) -{ - static char *filename = NULL; - if (!filename) { - const char *user = getenv("LOGNAME"); - if (same_string(user, "")) - user = "username"; - filename = calloc(strlen(user) + 5, 1); - strcat(filename, user); - strcat(filename, ".xml"); - } - static const char *path = NULL; - if (!path) - path = system_default_path_append(filename); - return path; -} - -int enumerate_devices(device_callback_t callback, void *userdata, int dc_type) -{ - int index = -1, entries = 0; - DIR *dp = NULL; - struct dirent *ep = NULL; - size_t i; - if (dc_type != DC_TYPE_UEMIS) { - const char *dirname = "/dev"; - const char *patterns[] = { - "tty.*", - "usbserial", - NULL - }; - - dp = opendir(dirname); - if (dp == NULL) { - return -1; - } - - while ((ep = readdir(dp)) != NULL) { - for (i = 0; patterns[i] != NULL; ++i) { - if (fnmatch(patterns[i], ep->d_name, 0) == 0) { - char filename[1024]; - int n = snprintf(filename, sizeof(filename), "%s/%s", dirname, ep->d_name); - if (n >= (int)sizeof(filename)) { - closedir(dp); - return -1; - } - callback(filename, userdata); - if (is_default_dive_computer_device(filename)) - index = entries; - entries++; - break; - } - } - } - closedir(dp); - } - if (dc_type != DC_TYPE_SERIAL) { - const char *dirname = "/Volumes"; - int num_uemis = 0; - dp = opendir(dirname); - if (dp == NULL) { - return -1; - } - - while ((ep = readdir(dp)) != NULL) { - if (fnmatch("UEMISSDA", ep->d_name, 0) == 0) { - char filename[1024]; - int n = snprintf(filename, sizeof(filename), "%s/%s", dirname, ep->d_name); - if (n >= (int)sizeof(filename)) { - closedir(dp); - return -1; - } - callback(filename, userdata); - if (is_default_dive_computer_device(filename)) - index = entries; - entries++; - num_uemis++; - break; - } - } - closedir(dp); - if (num_uemis == 1 && entries == 1) /* if we find exactly one entry and that's a Uemis, select it */ - index = 0; - } - return index; -} - -/* NOP wrappers to comform with windows.c */ -int subsurface_rename(const char *path, const char *newpath) -{ - return rename(path, newpath); -} - -int subsurface_open(const char *path, int oflags, mode_t mode) -{ - return open(path, oflags, mode); -} - -FILE *subsurface_fopen(const char *path, const char *mode) -{ - return fopen(path, mode); -} - -void *subsurface_opendir(const char *path) -{ - return (void *)opendir(path); -} - -int subsurface_access(const char *path, int mode) -{ - return access(path, mode); -} - -struct zip *subsurface_zip_open_readonly(const char *path, int flags, int *errorp) -{ - return zip_open(path, flags, errorp); -} - -int subsurface_zip_close(struct zip *zip) -{ - return zip_close(zip); -} - -/* win32 console */ -void subsurface_console_init(bool dedicated) -{ - (void) dedicated; - /* NOP */ -} - -void subsurface_console_exit(void) -{ - /* NOP */ -} - -bool subsurface_user_is_root() -{ - return (geteuid() == 0); -} diff --git a/subsurface-core/membuffer.c b/subsurface-core/membuffer.c deleted file mode 100644 index 053edb8f0..000000000 --- a/subsurface-core/membuffer.c +++ /dev/null @@ -1,288 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#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 deleted file mode 100644 index 434b34c71..000000000 --- a/subsurface-core/membuffer.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef MEMBUFFER_H -#define MEMBUFFER_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -struct membuffer { - unsigned int len, alloc; - char *buffer; -}; - -#ifdef __GNUC__ -#define __printf(x, y) __attribute__((__format__(__printf__, x, y))) -#else -#define __printf(x, y) -#endif - -extern char *detach_buffer(struct membuffer *b); -extern void free_buffer(struct membuffer *); -extern void flush_buffer(struct membuffer *, FILE *); -extern void put_bytes(struct membuffer *, const char *, int); -extern void put_string(struct membuffer *, const char *); -extern void put_quoted(struct membuffer *, const char *, int, int); -extern void strip_mb(struct membuffer *); -extern const char *mb_cstring(struct membuffer *); -extern __printf(2, 0) void put_vformat(struct membuffer *, const char *, va_list); -extern __printf(2, 3) void put_format(struct membuffer *, const char *fmt, ...); -extern __printf(2, 0) char *add_to_string_va(const char *old, const char *fmt, va_list args); -extern __printf(2, 3) char *add_to_string(const char *old, const char *fmt, ...); - -/* Helpers that use membuffers internally */ -extern __printf(1, 0) char *vformat_string(const char *, va_list); -extern __printf(1, 2) char *format_string(const char *, ...); - - -/* Output one of our "milli" values with type and pre/post data */ -extern void put_milli(struct membuffer *, const char *, int, const char *); - -/* - * Helper functions for showing particular types. If the type - * is empty, nothing is done, and the function returns false. - * Otherwise, it returns true. - * - * The two "const char *" at the end are pre/post data. - * - * The reason for the pre/post data is so that you can easily - * prepend and append a string without having to test whether the - * type is empty. So - * - * put_temperature(b, temp, "Temp=", " C\n"); - * - * writes nothing to the buffer if there is no temperature data, - * but otherwise would a string that looks something like - * - * "Temp=28.1 C\n" - * - * to the memory buffer (typically the post/pre will be some XML - * pattern and unit string or whatever). - */ -extern void put_temperature(struct membuffer *, temperature_t, const char *, const char *); -extern void put_depth(struct membuffer *, depth_t, const char *, const char *); -extern void put_duration(struct membuffer *, duration_t, const char *, const char *); -extern void put_pressure(struct membuffer *, pressure_t, const char *, const char *); -extern void put_salinity(struct membuffer *, int, const char *, const char *); -extern void put_degrees(struct membuffer *b, degrees_t value, const char *, const char *); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/subsurface-core/metrics.cpp b/subsurface-core/metrics.cpp deleted file mode 100644 index 3c66528b8..000000000 --- a/subsurface-core/metrics.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * metrics.cpp - * - * methods to find/compute essential UI metrics - * (font properties, icon sizes, etc) - * - */ - -#include "metrics.h" - -static IconMetrics dfltIconMetrics; - -IconMetrics::IconMetrics() : - sz_small(-1), - sz_med(-1), - sz_big(-1), - sz_pic(-1), - spacing(-1), - dpr(1.0) -{ -} - -QFont defaultModelFont() -{ - QFont font; -// font.setPointSizeF(font.pointSizeF() * 0.8); - return font; -} - -QFontMetrics defaultModelFontMetrics() -{ - return QFontMetrics(defaultModelFont()); -} - -// return the default icon size, computed as the multiple of 16 closest to -// the given height -static int defaultIconSize(int height) -{ - int ret = (height + 8)/16; - ret *= 16; - if (ret < 16) - ret = 16; - return ret; -} - -const IconMetrics & defaultIconMetrics() -{ - if (dfltIconMetrics.sz_small == -1) { - int small = defaultIconSize(defaultModelFontMetrics().height()); - dfltIconMetrics.sz_small = small; - dfltIconMetrics.sz_med = small + small/2; - dfltIconMetrics.sz_big = 2*small; - - dfltIconMetrics.sz_pic = 8*small; - - dfltIconMetrics.spacing = small/8; - } - - return dfltIconMetrics; -} - -void updateDevicePixelRatio(double dpr) -{ - dfltIconMetrics.dpr = dpr; -} diff --git a/subsurface-core/metrics.h b/subsurface-core/metrics.h deleted file mode 100644 index ca281b3b1..000000000 --- a/subsurface-core/metrics.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * metrics.h - * - * header file for common function to find/compute essential UI metrics - * (font properties, icon sizes, etc) - * - */ -#ifndef METRICS_H -#define METRICS_H - -#include -#include -#include - -QFont defaultModelFont(); -QFontMetrics defaultModelFontMetrics(); - -// Collection of icon/picture sizes and other metrics, resolution independent -struct IconMetrics { - // icon sizes - int sz_small; // ex 16px - int sz_med; // ex 24px - int sz_big; // ex 32px - // picture size - int sz_pic; // ex 128px - // icon spacing - int spacing; // ex 2px - // devicePixelRatio - double dpr; // 1.0 for traditional screens, HiDPI screens up to 3.0 - IconMetrics(); -}; - -const IconMetrics & defaultIconMetrics(); -void updateDevicePixelRatio(double dpr); - -#endif // METRICS_H diff --git a/subsurface-core/ostctools.c b/subsurface-core/ostctools.c deleted file mode 100644 index 9be591b0e..000000000 --- a/subsurface-core/ostctools.c +++ /dev/null @@ -1,193 +0,0 @@ -#include -#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 necessary to iterate once and again on a parsing - * function. Actually there's only one kind of archive for every DC model. - */ -void ostctools_import(const char *file, struct dive_table *divetable) -{ - FILE *archive; - device_data_t *devdata = calloc(1, sizeof(device_data_t)); - dc_family_t dc_fam; - unsigned char *buffer = calloc(65536, 1), *uc_tmp; - char *tmp; - struct dive *ostcdive = alloc_dive(); - dc_status_t rc = 0; - int model, ret, i = 0; - unsigned int serial; - struct extra_data *ptr; - - // Open the archive - if ((archive = subsurface_fopen(file, "rb")) == NULL) { - report_error(translate("gettextFromC", "Failed to read '%s'"), file); - free(ostcdive); - goto out; - } - - // Read dive number from the log - uc_tmp = calloc(2, 1); - fseek(archive, 258, 0); - fread(uc_tmp, 1, 2, archive); - ostcdive->number = uc_tmp[0] + (uc_tmp[1] << 8); - free(uc_tmp); - - // Read device's serial number - uc_tmp = calloc(2, 1); - fseek(archive, 265, 0); - fread(uc_tmp, 1, 2, archive); - serial = uc_tmp[0] + (uc_tmp[1] << 8); - free(uc_tmp); - - // Read dive's raw data, header + profile - fseek(archive, 456, 0); - while (!feof(archive)) { - fread(buffer + i, 1, 1, archive); - if (buffer[i] == 0xFD && buffer[i - 1] == 0xFD) - break; - i++; - } - - // Try to determine the dc family based on the header type - if (buffer[2] == 0x20 || buffer[2] == 0x21) { - dc_fam = DC_FAMILY_HW_OSTC; - } else { - switch (buffer[8]) { - case 0x22: - dc_fam = DC_FAMILY_HW_FROG; - break; - case 0x23: - dc_fam = DC_FAMILY_HW_OSTC3; - break; - default: - report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); - free(ostcdive); - fclose(archive); - goto out; - } - } - - // Try to determine the model based on serial number - switch (dc_fam) { - case DC_FAMILY_HW_OSTC: - if (serial > 7000) - model = 3; //2C - else if (serial > 2048) - model = 2; //2N - else if (serial > 300) - model = 1; //MK2 - else - model = 0; //OSTC - break; - case DC_FAMILY_HW_FROG: - model = 0; - break; - default: - if (serial > 10000) - model = 0x12; //Sport - else - model = 0x0A; //OSTC3 - } - - // Prepare data to pass to libdivecomputer. - ret = ostc_prepare_data(model, dc_fam, devdata); - if (ret == 0) { - report_error(translate("gettextFromC", "Unknown DC in dive %d"), ostcdive->number); - free(ostcdive); - fclose(archive); - goto out; - } - tmp = calloc(strlen(devdata->vendor) + strlen(devdata->model) + 28, 1); - sprintf(tmp, "%s %s (Imported from OSTCTools)", devdata->vendor, devdata->model); - ostcdive->dc.model = copy_string(tmp); - free(tmp); - - // Parse the dive data - rc = libdc_buffer_parser(ostcdive, devdata, buffer, i + 1); - if (rc != DC_STATUS_SUCCESS) - report_error(translate("gettextFromC", "Error - %s - parsing dive %d"), errmsg(rc), ostcdive->number); - - // Serial number is not part of the header nor the profile, so libdc won't - // catch it. If Serial is part of the extra_data, and set to zero, remove - // it from the list and add again. - tmp = calloc(12, 1); - sprintf(tmp, "%d", serial); - ostcdive->dc.serial = copy_string(tmp); - free(tmp); - - if (ostcdive->dc.extra_data) { - ptr = ostcdive->dc.extra_data; - while (strcmp(ptr->key, "Serial")) - ptr = ptr->next; - if (!strcmp(ptr->value, "0")) { - add_extra_data(&ostcdive->dc, "Serial", ostcdive->dc.serial); - *ptr = *(ptr)->next; - } - } else { - add_extra_data(&ostcdive->dc, "Serial", ostcdive->dc.serial); - } - record_dive_to_table(ostcdive, divetable); - mark_divelist_changed(true); - sort_table(divetable); - fclose(archive); -out: - free(devdata); - free(buffer); -} diff --git a/subsurface-core/parse-xml.c b/subsurface-core/parse-xml.c deleted file mode 100644 index e8782251e..000000000 --- a/subsurface-core/parse-xml.c +++ /dev/null @@ -1,3751 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#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, force_root; -int metric = 1; -int last_xml_version = -1; -int diveid = -1; - -static xmlDoc *test_xslt_transforms(xmlDoc *doc, const char **params); - -/* the dive table holds the overall dive list; target table points at - * the table we are currently filling */ -struct dive_table dive_table; -struct dive_table *target_table = NULL; - -/* Trim a character string by removing leading and trailing white space characters. - * Parameter: a pointer to a null-terminated character string (buffer); - * Return value: length of the trimmed string, excluding the terminal 0x0 byte - * The original pointer (buffer) remains valid after this function has been called - * and points to the trimmed string */ -int trimspace(char *buffer) { - int i, size, start, end; - size = strlen(buffer); - for(start = 0; isspace(buffer[start]); start++) - if (start >= size) return 0; // Find 1st character following leading whitespace - for(end = size - 1; isspace(buffer[end]); end--) // Find last character before trailing whitespace - if (end <= 0) return 0; - for(i = start; i <= end; i++) // Move the nonspace characters to the start of the string - buffer[i-start] = buffer[i]; - size = end - start + 1; - buffer[size] = 0x0; // then terminate the string - return size; // return string length -} - -/* - * Clear a dive_table - */ -void clear_table(struct dive_table *table) -{ - for (int i = 0; i < table->nr; i++) - free(table->dives[i]); - table->nr = 0; -} - -/* - * Add a dive into the dive_table array - */ -void record_dive_to_table(struct dive *dive, struct dive_table *table) -{ - assert(table != NULL); - struct dive **dives = grow_dive_table(table); - int nr = table->nr; - - dives[nr] = fixup_dive(dive); - table->nr = nr + 1; -} - -void record_dive(struct dive *dive) -{ - record_dive_to_table(dive, &dive_table); -} - -static void start_match(const char *type, const char *name, char *buffer) -{ - if (verbose > 2) - printf("Matching %s '%s' (%s)\n", - type, name, buffer); -} - -static void nonmatch(const char *type, const char *name, char *buffer) -{ - if (verbose > 1) - printf("Unable to match %s '%s' (%s)\n", - type, name, buffer); -} - -typedef void (*matchfn_t)(char *buffer, void *); - -static int match(const char *pattern, int plen, - const char *name, - matchfn_t fn, char *buf, void *data) -{ - switch (name[plen]) { - case '\0': - case '.': - break; - default: - return 0; - } - if (memcmp(pattern, name, plen)) - return 0; - fn(buf, data); - return 1; -} - - -struct units xml_parsing_units; -const struct units SI_units = SI_UNITS; -const struct units IMPERIAL_units = IMPERIAL_UNITS; - -/* - * Dive info as it is being built up.. - */ -#define MAX_EVENT_NAME 128 -static struct divecomputer *cur_dc; -static struct dive *cur_dive; -static struct dive_site *cur_dive_site; -degrees_t cur_latitude, cur_longitude; -static dive_trip_t *cur_trip = NULL; -static struct sample *cur_sample; -static struct picture *cur_picture; -static union { - struct event event; - char allocation[sizeof(struct event)+MAX_EVENT_NAME]; -} event_allocation = { .event.deleted = 1 }; -#define cur_event event_allocation.event -static struct { - struct { - const char *model; - uint32_t deviceid; - const char *nickname, *serial_nr, *firmware; - } dc; -} cur_settings; -static bool in_settings = false; -static bool in_userid = false; -static struct tm cur_tm; -static int cur_cylinder_index, cur_ws_index; -static int lastndl, laststoptime, laststopdepth, lastcns, lastpo2, lastindeco; -static int lastcylinderindex, lastsensor, next_o2_sensor; -static struct extra_data cur_extra_data; - -/* - * If we don't have an explicit dive computer, - * we use the implicit one that every dive has.. - */ -static struct divecomputer *get_dc(void) -{ - return cur_dc ?: &cur_dive->dc; -} - -static enum import_source { - UNKNOWN, - LIBDIVECOMPUTER, - DIVINGLOG, - UDDF, - SSRF_WS, -} import_source; - -static void divedate(const char *buffer, timestamp_t *when) -{ - int d, m, y; - int hh, mm, ss; - - hh = 0; - mm = 0; - ss = 0; - if (sscanf(buffer, "%d.%d.%d %d:%d:%d", &d, &m, &y, &hh, &mm, &ss) >= 3) { - /* This is ok, and we got at least the date */ - } else if (sscanf(buffer, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) >= 3) { - /* This is also ok */ - } else { - fprintf(stderr, "Unable to parse date '%s'\n", buffer); - return; - } - cur_tm.tm_year = y; - cur_tm.tm_mon = m - 1; - cur_tm.tm_mday = d; - cur_tm.tm_hour = hh; - cur_tm.tm_min = mm; - cur_tm.tm_sec = ss; - - *when = utc_mktime(&cur_tm); -} - -static void divetime(const char *buffer, timestamp_t *when) -{ - int h, m, s = 0; - - if (sscanf(buffer, "%d:%d:%d", &h, &m, &s) >= 2) { - cur_tm.tm_hour = h; - cur_tm.tm_min = m; - cur_tm.tm_sec = s; - *when = utc_mktime(&cur_tm); - } -} - -/* Libdivecomputer: "2011-03-20 10:22:38" */ -static void divedatetime(char *buffer, timestamp_t *when) -{ - int y, m, d; - int hr, min, sec; - - if (sscanf(buffer, "%d-%d-%d %d:%d:%d", - &y, &m, &d, &hr, &min, &sec) == 6) { - cur_tm.tm_year = y; - cur_tm.tm_mon = m - 1; - cur_tm.tm_mday = d; - cur_tm.tm_hour = hr; - cur_tm.tm_min = min; - cur_tm.tm_sec = sec; - *when = utc_mktime(&cur_tm); - } -} - -enum ParseState { - FINDSTART, - FINDEND -}; -static void divetags(char *buffer, struct tag_entry **tags) -{ - int i = 0, start = 0, end = 0; - enum ParseState state = FINDEND; - int len = buffer ? strlen(buffer) : 0; - - while (i < len) { - if (buffer[i] == ',') { - if (state == FINDSTART) { - /* Detect empty tags */ - } else if (state == FINDEND) { - /* Found end of tag */ - if (i > 0 && buffer[i - 1] != '\\') { - buffer[i] = '\0'; - state = FINDSTART; - taglist_add_tag(tags, buffer + start); - } else { - state = FINDSTART; - } - } - } else if (buffer[i] == ' ') { - /* Handled */ - } else { - /* Found start of tag */ - if (state == FINDSTART) { - state = FINDEND; - start = i; - } else if (state == FINDEND) { - end = i; - } - } - i++; - } - if (state == FINDEND) { - if (end < start) - end = len - 1; - if (len > 0) { - buffer[end + 1] = '\0'; - taglist_add_tag(tags, buffer + start); - } - } -} - -enum number_type { - NEITHER, - FLOAT -}; - -static enum number_type parse_float(const char *buffer, double *res, const char **endp) -{ - double val; - static bool first_time = true; - - errno = 0; - val = ascii_strtod(buffer, endp); - if (errno || *endp == buffer) - return NEITHER; - if (**endp == ',') { - if (IS_FP_SAME(val, rint(val))) { - /* we really want to send an error if this is a Subsurface native file - * as this is likely indication of a bug - but right now we don't have - * that information available */ - if (first_time) { - fprintf(stderr, "Floating point value with decimal comma (%s)?\n", buffer); - first_time = false; - } - /* Try again in permissive mode*/ - val = strtod_flags(buffer, endp, 0); - } - } - - *res = val; - return FLOAT; -} - -union int_or_float { - double fp; -}; - -static enum number_type integer_or_float(char *buffer, union int_or_float *res) -{ - const char *end; - return parse_float(buffer, &res->fp, &end); -} - -static void pressure(char *buffer, pressure_t *pressure) -{ - double mbar = 0.0; - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - /* Just ignore zero values */ - if (!val.fp) - break; - switch (xml_parsing_units.pressure) { - case PASCAL: - mbar = val.fp / 100; - break; - case BAR: - /* Assume mbar, but if it's really small, it's bar */ - mbar = val.fp; - if (fabs(mbar) < 5000) - mbar = mbar * 1000; - break; - case PSI: - mbar = psi_to_mbar(val.fp); - break; - } - if (fabs(mbar) > 5 && fabs(mbar) < 5000000) { - pressure->mbar = rint(mbar); - break; - } - /* fallthrough */ - default: - printf("Strange pressure reading %s\n", buffer); - } -} - -static void cylinder_use(char *buffer, enum cylinderuse *cyl_use) -{ - if (trimspace(buffer)) - *cyl_use = cylinderuse_from_text(buffer); -} - -static void salinity(char *buffer, int *salinity) -{ - union int_or_float val; - switch (integer_or_float(buffer, &val)) { - case FLOAT: - *salinity = rint(val.fp * 10.0); - break; - default: - printf("Strange salinity reading %s\n", buffer); - } -} - -static void depth(char *buffer, depth_t *depth) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - switch (xml_parsing_units.length) { - case METERS: - depth->mm = rint(val.fp * 1000); - break; - case FEET: - depth->mm = feet_to_mm(val.fp); - break; - } - break; - default: - printf("Strange depth reading %s\n", buffer); - } -} - -static void extra_data_start(void) -{ - memset(&cur_extra_data, 0, sizeof(struct extra_data)); -} - -static void extra_data_end(void) -{ - // don't save partial structures - we must have both key and value - if (cur_extra_data.key && cur_extra_data.value) - add_extra_data(cur_dc, cur_extra_data.key, cur_extra_data.value); -} - -static void weight(char *buffer, weight_t *weight) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - switch (xml_parsing_units.weight) { - case KG: - weight->grams = rint(val.fp * 1000); - break; - case LBS: - weight->grams = lbs_to_grams(val.fp); - break; - } - break; - default: - printf("Strange weight reading %s\n", buffer); - } -} - -static void temperature(char *buffer, temperature_t *temperature) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - switch (xml_parsing_units.temperature) { - case KELVIN: - temperature->mkelvin = val.fp * 1000; - break; - case CELSIUS: - temperature->mkelvin = C_to_mkelvin(val.fp); - break; - case FAHRENHEIT: - temperature->mkelvin = F_to_mkelvin(val.fp); - break; - } - break; - default: - printf("Strange temperature reading %s\n", buffer); - } - /* temperatures outside -40C .. +70C should be ignored */ - if (temperature->mkelvin < ZERO_C_IN_MKELVIN - 40000 || - temperature->mkelvin > ZERO_C_IN_MKELVIN + 70000) - temperature->mkelvin = 0; -} - -static void sampletime(char *buffer, duration_t *time) -{ - int i; - int min, sec; - - i = sscanf(buffer, "%d:%d", &min, &sec); - switch (i) { - case 1: - sec = min; - min = 0; - /* fallthrough */ - case 2: - time->seconds = sec + min * 60; - break; - default: - printf("Strange sample time reading %s\n", buffer); - } -} - -static void offsettime(char *buffer, offset_t *time) -{ - duration_t uoffset; - int sign = 1; - if (*buffer == '-') { - sign = -1; - buffer++; - } - /* yes, this could indeed fail if we have an offset > 34yrs - * - too bad */ - sampletime(buffer, &uoffset); - time->seconds = sign * uoffset.seconds; -} - -static void duration(char *buffer, duration_t *time) -{ - /* DivingLog 5.08 (and maybe other versions) appear to sometimes - * store the dive time as 44.00 instead of 44:00; - * This attempts to parse this in a fairly robust way */ - if (!strchr(buffer, ':') && strchr(buffer, '.')) { - char *mybuffer = strdup(buffer); - char *dot = strchr(mybuffer, '.'); - *dot = ':'; - sampletime(mybuffer, time); - free(mybuffer); - } else { - sampletime(buffer, time); - } -} - -static void percent(char *buffer, fraction_t *fraction) -{ - double val; - const char *end; - - switch (parse_float(buffer, &val, &end)) { - case FLOAT: - /* Turn fractions into percent unless explicit.. */ - if (val <= 1.0) { - while (isspace(*end)) - end++; - if (*end != '%') - val *= 100; - } - - /* Then turn percent into our integer permille format */ - if (val >= 0 && val <= 100.0) { - fraction->permille = rint(val * 10); - break; - } - default: - printf(translate("gettextFromC", "Strange percentage reading %s\n"), buffer); - break; - } -} - -static void gasmix(char *buffer, fraction_t *fraction) -{ - /* libdivecomputer does negative percentages. */ - if (*buffer == '-') - return; - if (cur_cylinder_index < MAX_CYLINDERS) - percent(buffer, fraction); -} - -static void gasmix_nitrogen(char *buffer, struct gasmix *gasmix) -{ - (void) buffer; - (void) gasmix; - /* Ignore n2 percentages. There's no value in them. */ -} - -static void cylindersize(char *buffer, volume_t *volume) -{ - union int_or_float val; - - switch (integer_or_float(buffer, &val)) { - case FLOAT: - volume->mliter = rint(val.fp * 1000); - break; - - default: - printf("Strange volume reading %s\n", buffer); - break; - } -} - -static void utf8_string(char *buffer, void *_res) -{ - char **res = _res; - int size; - size = trimspace(buffer); - if(size) - *res = strdup(buffer); -} - -static void event_name(char *buffer, char *name) -{ - int size = trimspace(buffer); - if (size >= MAX_EVENT_NAME) - size = MAX_EVENT_NAME-1; - memcpy(name, buffer, size); - name[size] = 0; -} - -// We don't use gauge as a mode, and pscr doesn't exist as a libdc divemode -const char *libdc_divemode_text[] = { "oc", "cc", "pscr", "freedive", "gauge"}; - -/* Extract the dive computer type from the xml text buffer */ -static void get_dc_type(char *buffer, enum dive_comp_type *dct) -{ - if (trimspace(buffer)) { - for (enum dive_comp_type i = 0; i < NUM_DC_TYPE; i++) { - if (strcmp(buffer, divemode_text[i]) == 0) - *dct = i; - else if (strcmp(buffer, libdc_divemode_text[i]) == 0) - *dct = i; - } - } -} - -#define MATCH(pattern, fn, dest) ({ \ - /* Silly type compatibility test */ \ - if (0) (fn)("test", dest); \ - match(pattern, strlen(pattern), name, (matchfn_t) (fn), buf, dest); }) - -static void get_index(char *buffer, int *i) -{ - *i = atoi(buffer); -} - -static void get_uint8(char *buffer, uint8_t *i) -{ - *i = atoi(buffer); -} - -static void get_bearing(char *buffer, bearing_t *bearing) -{ - bearing->degrees = atoi(buffer); -} - -static void get_rating(char *buffer, int *i) -{ - int j = atoi(buffer); - if (j >= 0 && j <= 5) { - *i = j; - } -} - -static void double_to_o2pressure(char *buffer, o2pressure_t *i) -{ - i->mbar = rint(ascii_strtod(buffer, NULL) * 1000.0); -} - -static void hex_value(char *buffer, uint32_t *i) -{ - *i = strtoul(buffer, NULL, 16); -} - -static void get_tripflag(char *buffer, tripflag_t *tf) -{ - *tf = strcmp(buffer, "NOTRIP") ? TF_NONE : NO_TRIP; -} - -/* - * Divinglog is crazy. The temperatures are in celsius. EXCEPT - * for the sample temperatures, that are in Fahrenheit. - * WTF? - * - * Oh, and I think Diving Log *internally* probably kept them - * in celsius, because I'm seeing entries like - * - * 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) -{ - (void) name; - if (prefs.save_userid_local) - set_userid(buf); -} - -static const char *country, *city; - -static void divinglog_place(char *place, uint32_t *uuid) -{ - char buffer[1024]; - - snprintf(buffer, sizeof(buffer), - "%s%s%s%s%s", - place, - city ? ", " : "", - city ? city : "", - country ? ", " : "", - country ? country : ""); - *uuid = get_dive_site_uuid_by_name(buffer, NULL); - if (*uuid == 0) - *uuid = create_dive_site(buffer, cur_dive->when); - - city = NULL; - country = NULL; -} - -static int divinglog_dive_match(struct dive *dive, const char *name, char *buf) -{ - return MATCH("divedate", divedate, &dive->when) || - MATCH("entrytime", divetime, &dive->when) || - MATCH("divetime", duration, &dive->dc.duration) || - MATCH("depth", depth, &dive->dc.maxdepth) || - MATCH("depthavg", depth, &dive->dc.meandepth) || - MATCH("tanktype", utf8_string, &dive->cylinder[0].type.description) || - MATCH("tanksize", cylindersize, &dive->cylinder[0].type.size) || - MATCH("presw", pressure, &dive->cylinder[0].type.workingpressure) || - MATCH("press", pressure, &dive->cylinder[0].start) || - MATCH("prese", pressure, &dive->cylinder[0].end) || - MATCH("comments", utf8_string, &dive->notes) || - MATCH("names.buddy", utf8_string, &dive->buddy) || - MATCH("name.country", utf8_string, &country) || - MATCH("name.city", utf8_string, &city) || - MATCH("name.place", divinglog_place, &dive->dive_site_uuid) || - 0; -} - -/* - * Uddf specifies ISO 8601 time format. - * - * There are many variations on that. This handles the useful cases. - */ -static void uddf_datetime(char *buffer, timestamp_t *when) -{ - char c; - int y, m, d, hh, mm, ss; - struct tm tm = { 0 }; - int i; - - i = sscanf(buffer, "%d-%d-%d%c%d:%d:%d", &y, &m, &d, &c, &hh, &mm, &ss); - if (i == 7) - goto success; - ss = 0; - if (i == 6) - goto success; - - i = sscanf(buffer, "%04d%02d%02d%c%02d%02d%02d", &y, &m, &d, &c, &hh, &mm, &ss); - if (i == 7) - goto success; - ss = 0; - if (i == 6) - goto success; -bad_date: - printf("Bad date time %s\n", buffer); - return; - -success: - if (c != 'T' && c != ' ') - goto bad_date; - tm.tm_year = y; - tm.tm_mon = m - 1; - tm.tm_mday = d; - tm.tm_hour = hh; - tm.tm_min = mm; - tm.tm_sec = ss; - *when = utc_mktime(&tm); -} - -#define uddf_datedata(name, offset) \ - static void uddf_##name(char *buffer, timestamp_t *when) \ - { \ - cur_tm.tm_##name = atoi(buffer) + offset; \ - *when = utc_mktime(&cur_tm); \ - } - -uddf_datedata(year, 0) -uddf_datedata(mon, -1) -uddf_datedata(mday, 0) -uddf_datedata(hour, 0) -uddf_datedata(min, 0) - -static int uddf_dive_match(struct dive *dive, const char *name, char *buf) -{ - return MATCH("datetime", uddf_datetime, &dive->when) || - MATCH("diveduration", duration, &dive->dc.duration) || - MATCH("greatestdepth", depth, &dive->dc.maxdepth) || - MATCH("year.date", uddf_year, &dive->when) || - MATCH("month.date", uddf_mon, &dive->when) || - MATCH("day.date", uddf_mday, &dive->when) || - MATCH("hour.time", uddf_hour, &dive->when) || - MATCH("minute.time", uddf_min, &dive->when) || - 0; -} - -/* - * This parses "floating point" into micro-degrees. - * We don't do exponentials etc, if somebody does - * GPS locations in that format, they are insane. - */ -degrees_t parse_degrees(char *buf, char **end) -{ - int sign = 1, decimals = 6, value = 0; - degrees_t ret; - - while (isspace(*buf)) - buf++; - switch (*buf) { - case '-': - sign = -1; - /* fallthrough */ - case '+': - buf++; - } - while (isdigit(*buf)) { - value = 10 * value + *buf - '0'; - buf++; - } - - /* Get the first six decimals if they exist */ - if (*buf == '.') - buf++; - do { - value *= 10; - if (isdigit(*buf)) { - value += *buf - '0'; - buf++; - } - } while (--decimals); - - /* Rounding */ - switch (*buf) { - case '5' ... '9': - value++; - } - while (isdigit(*buf)) - buf++; - - *end = buf; - ret.udeg = value * sign; - return ret; -} - -static void gps_lat(char *buffer, struct dive *dive) -{ - char *end; - degrees_t latitude = parse_degrees(buffer, &end); - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) { - dive->dive_site_uuid = create_dive_site_with_gps(NULL, latitude, (degrees_t){0}, dive->when); - } else { - if (ds->latitude.udeg && ds->latitude.udeg != latitude.udeg) - fprintf(stderr, "Oops, changing the latitude of existing dive site id %8x name %s; not good\n", ds->uuid, ds->name ?: "(unknown)"); - ds->latitude = latitude; - } -} - -static void gps_long(char *buffer, struct dive *dive) -{ - char *end; - degrees_t longitude = parse_degrees(buffer, &end); - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) { - dive->dive_site_uuid = create_dive_site_with_gps(NULL, (degrees_t){0}, longitude, dive->when); - } else { - if (ds->longitude.udeg && ds->longitude.udeg != longitude.udeg) - fprintf(stderr, "Oops, changing the longitude of existing dive site id %8x name %s; not good\n", ds->uuid, ds->name ?: "(unknown)"); - ds->longitude = longitude; - } - -} - -static void gps_location(char *buffer, struct dive_site *ds) -{ - char *end; - - ds->latitude = parse_degrees(buffer, &end); - ds->longitude = parse_degrees(end, &end); -} - -/* this is in qthelper.cpp, so including the .h file is a pain */ -extern const char *printGPSCoords(int lat, int lon); - -static void gps_in_dive(char *buffer, struct dive *dive) -{ - char *end; - struct dive_site *ds = NULL; - degrees_t latitude = parse_degrees(buffer, &end); - degrees_t longitude = parse_degrees(end, &end); - uint32_t uuid = dive->dive_site_uuid; - if (uuid == 0) { - // check if we have a dive site within 20 meters of that gps fix - uuid = get_dive_site_uuid_by_gps_proximity(latitude, longitude, 20, &ds); - - if (ds) { - // found a site nearby; in case it turns out this one had a different name let's - // remember the original coordinates so we can create the correct dive site later - cur_latitude = latitude; - cur_longitude = longitude; - dive->dive_site_uuid = uuid; - } else { - dive->dive_site_uuid = create_dive_site_with_gps("", latitude, longitude, dive->when); - ds = get_dive_site_by_uuid(dive->dive_site_uuid); - } - } else { - ds = get_dive_site_by_uuid(uuid); - if (dive_site_has_gps_location(ds) && - (latitude.udeg != 0 || longitude.udeg != 0) && - (ds->latitude.udeg != latitude.udeg || ds->longitude.udeg != longitude.udeg)) { - // Houston, we have a problem - fprintf(stderr, "dive site uuid in dive, but gps location (%10.6f/%10.6f) different from dive location (%10.6f/%10.6f)\n", - ds->latitude.udeg / 1000000.0, ds->longitude.udeg / 1000000.0, - latitude.udeg / 1000000.0, longitude.udeg / 1000000.0); - const char *coords = printGPSCoords(latitude.udeg, longitude.udeg); - ds->notes = add_to_string(ds->notes, translate("gettextFromC", "multiple GPS locations for this dive site; also %s\n"), coords); - free((void *)coords); - } else { - ds->latitude = latitude; - ds->longitude = longitude; - } - } -} - -static void add_dive_site(char *ds_name, struct dive *dive) -{ - static int suffix = 1; - char *buffer = ds_name; - char *to_free = NULL; - int size = trimspace(buffer); - if(size) { - uint32_t uuid = dive->dive_site_uuid; - struct dive_site *ds = get_dive_site_by_uuid(uuid); - if (uuid && !ds) { - // that's strange - we have a uuid but it doesn't exist - let's just ignore it - fprintf(stderr, "dive contains a non-existing dive site uuid %x\n", dive->dive_site_uuid); - uuid = 0; - } - if (!uuid) { - // if the dive doesn't have a uuid, check if there's already a dive site by this name - uuid = get_dive_site_uuid_by_name(buffer, &ds); - if (uuid && import_source == SSRF_WS) { - // when downloading GPS fixes from the Subsurface webservice we will often - // get a lot of dives with identical names (the autogenerated fixes). - // So in this case modify the name to make it unique - int name_size = strlen(buffer) + 10; // 8 digits - enough for 100 million sites - to_free = buffer = malloc(name_size); - do { - suffix++; - snprintf(buffer, name_size, "%s %8d", ds_name, suffix); - } while (get_dive_site_uuid_by_name(buffer, NULL) != 0); - ds = NULL; - } - } - if (ds) { - // we have a uuid, let's hope there isn't a different name - if (same_string(ds->name, "")) { - ds->name = copy_string(buffer); - } else if (!same_string(ds->name, buffer)) { - // if it's not the same name, it's not the same dive site - // but wait, we could have gotten this one based on GPS coords and could - // have had two different names for the same site... so let's search the other - // way around - uint32_t exact_match_uuid = get_dive_site_uuid_by_gps_and_name(buffer, ds->latitude, ds->longitude); - if (exact_match_uuid) { - dive->dive_site_uuid = exact_match_uuid; - } else { - dive->dive_site_uuid = create_dive_site(buffer, dive->when); - struct dive_site *newds = get_dive_site_by_uuid(dive->dive_site_uuid); - if (cur_latitude.udeg || cur_longitude.udeg) { - // we started this uuid with GPS data, so lets use those - newds->latitude = cur_latitude; - newds->longitude = cur_longitude; - } else { - newds->latitude = ds->latitude; - newds->longitude = ds->longitude; - } - newds->notes = add_to_string(newds->notes, translate("gettextFromC", "additional name for site: %s\n"), ds->name); - } - } else { - // add the existing dive site to the current dive - dive->dive_site_uuid = uuid; - } - } else { - dive->dive_site_uuid = create_dive_site(buffer, dive->when); - } - } - free(to_free); -} - -static void gps_picture_location(char *buffer, struct picture *pic) -{ - char *end; - - pic->latitude = parse_degrees(buffer, &end); - pic->longitude = parse_degrees(end, &end); -} - -/* We're in the top-level dive xml. Try to convert whatever value to a dive value */ -static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) -{ - start_match("dive", name, buf); - - switch (import_source) { - case DIVINGLOG: - if (divinglog_dive_match(dive, name, buf)) - return; - break; - - case UDDF: - if (uddf_dive_match(dive, name, buf)) - return; - break; - - default: - break; - } - if (MATCH("divesiteid", hex_value, &dive->dive_site_uuid)) - return; - if (MATCH("number", get_index, &dive->number)) - return; - if (MATCH("tags", divetags, &dive->tag_list)) - return; - if (MATCH("tripflag", get_tripflag, &dive->tripflag)) - return; - if (MATCH("date", divedate, &dive->when)) - return; - if (MATCH("time", divetime, &dive->when)) - return; - if (MATCH("datetime", divedatetime, &dive->when)) - return; - /* - * Legacy format note: per-dive depths and duration get saved - * in the first dive computer entry - */ - if (match_dc_data_fields(&dive->dc, name, buf)) - return; - - if (MATCH("filename.picture", utf8_string, &cur_picture->filename)) - return; - if (MATCH("offset.picture", offsettime, &cur_picture->offset)) - return; - if (MATCH("gps.picture", gps_picture_location, cur_picture)) - return; - if (MATCH("hash.picture", utf8_string, &cur_picture->hash)) - return; - if (MATCH("cylinderstartpressure", pressure, &dive->cylinder[0].start)) - return; - if (MATCH("cylinderendpressure", pressure, &dive->cylinder[0].end)) - return; - if (MATCH("gps", gps_in_dive, dive)) - return; - if (MATCH("Place", gps_in_dive, dive)) - return; - if (MATCH("latitude", gps_lat, dive)) - return; - if (MATCH("sitelat", gps_lat, dive)) - return; - if (MATCH("lat", gps_lat, dive)) - return; - if (MATCH("longitude", gps_long, dive)) - return; - if (MATCH("sitelon", gps_long, dive)) - return; - if (MATCH("lon", gps_long, dive)) - return; - if (MATCH("location", add_dive_site, dive)) - return; - if (MATCH("name.dive", add_dive_site, dive)) - return; - if (MATCH("suit", utf8_string, &dive->suit)) - return; - if (MATCH("divesuit", utf8_string, &dive->suit)) - return; - if (MATCH("notes", utf8_string, &dive->notes)) - return; - if (MATCH("divemaster", utf8_string, &dive->divemaster)) - return; - if (MATCH("buddy", utf8_string, &dive->buddy)) - return; - if (MATCH("rating.dive", get_rating, &dive->rating)) - return; - if (MATCH("visibility.dive", get_rating, &dive->visibility)) - return; - if (cur_ws_index < MAX_WEIGHTSYSTEMS) { - if (MATCH("description.weightsystem", utf8_string, &dive->weightsystem[cur_ws_index].description)) - return; - if (MATCH("weight.weightsystem", weight, &dive->weightsystem[cur_ws_index].weight)) - return; - if (MATCH("weight", weight, &dive->weightsystem[cur_ws_index].weight)) - return; - } - if (cur_cylinder_index < MAX_CYLINDERS) { - if (MATCH("size.cylinder", cylindersize, &dive->cylinder[cur_cylinder_index].type.size)) - return; - if (MATCH("workpressure.cylinder", pressure, &dive->cylinder[cur_cylinder_index].type.workingpressure)) - return; - if (MATCH("description.cylinder", utf8_string, &dive->cylinder[cur_cylinder_index].type.description)) - return; - if (MATCH("start.cylinder", pressure, &dive->cylinder[cur_cylinder_index].start)) - return; - if (MATCH("end.cylinder", pressure, &dive->cylinder[cur_cylinder_index].end)) - return; - if (MATCH("use.cylinder", cylinder_use, &dive->cylinder[cur_cylinder_index].cylinder_use)) - return; - if (MATCH("o2", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.o2)) - return; - if (MATCH("o2percent", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.o2)) - return; - if (MATCH("n2", gasmix_nitrogen, &dive->cylinder[cur_cylinder_index].gasmix)) - return; - if (MATCH("he", gasmix, &dive->cylinder[cur_cylinder_index].gasmix.he)) - return; - } - if (MATCH("air.divetemperature", temperature, &dive->airtemp)) - return; - if (MATCH("water.divetemperature", temperature, &dive->watertemp)) - return; - - nonmatch("dive", name, buf); -} - -/* We're in the top-level trip xml. Try to convert whatever value to a trip value */ -static void try_to_fill_trip(dive_trip_t **dive_trip_p, const char *name, char *buf) -{ - start_match("trip", name, buf); - - dive_trip_t *dive_trip = *dive_trip_p; - - if (MATCH("date", divedate, &dive_trip->when)) - return; - if (MATCH("time", divetime, &dive_trip->when)) - return; - if (MATCH("location", utf8_string, &dive_trip->location)) - return; - if (MATCH("notes", utf8_string, &dive_trip->notes)) - return; - - nonmatch("trip", name, buf); -} - -/* We're processing a divesite entry - try to fill the components */ -static void try_to_fill_dive_site(struct dive_site **ds_p, const char *name, char *buf) -{ - start_match("divesite", name, buf); - - struct dive_site *ds = *ds_p; - if (ds->taxonomy.category == NULL) - ds->taxonomy.category = alloc_taxonomy(); - - if (MATCH("uuid", hex_value, &ds->uuid)) - return; - if (MATCH("name", utf8_string, &ds->name)) - return; - if (MATCH("description", utf8_string, &ds->description)) - return; - if (MATCH("notes", utf8_string, &ds->notes)) - return; - if (MATCH("gps", gps_location, ds)) - return; - if (MATCH("cat.geo", get_index, (int *)&ds->taxonomy.category[ds->taxonomy.nr].category)) - return; - if (MATCH("origin.geo", get_index, (int *)&ds->taxonomy.category[ds->taxonomy.nr].origin)) - return; - if (MATCH("value.geo", utf8_string, &ds->taxonomy.category[ds->taxonomy.nr].value)) { - if (ds->taxonomy.nr < TC_NR_CATEGORIES) - ds->taxonomy.nr++; - return; - } - - nonmatch("divesite", name, buf); -} - -/* - * While in some formats file boundaries are dive boundaries, in many - * others (as for example in our native format) there are - * multiple dives per file, so there can be other events too that - * trigger a "new dive" marker and you may get some nesting due - * to that. Just ignore nesting levels. - * On the flipside it is possible that we start an XML file that ends - * up having no dives in it at all - don't create a bogus empty dive - * for those. It's not entirely clear what is the minimum set of data - * to make a dive valid, but if it has no location, no date and no - * samples I'm pretty sure it's useless. - */ -static bool is_dive(void) -{ - return (cur_dive && - (cur_dive->dive_site_uuid || cur_dive->when || cur_dive->dc.samples)); -} - -static void reset_dc_info(struct divecomputer *dc) -{ - /* WARN: reset dc info does't touch the dc? */ - (void) dc; - lastcns = lastpo2 = lastndl = laststoptime = laststopdepth = lastindeco = 0; - lastsensor = lastcylinderindex = 0; -} - -static void reset_dc_settings(void) -{ - free((void *)cur_settings.dc.model); - free((void *)cur_settings.dc.nickname); - free((void *)cur_settings.dc.serial_nr); - free((void *)cur_settings.dc.firmware); - cur_settings.dc.model = NULL; - cur_settings.dc.nickname = NULL; - cur_settings.dc.serial_nr = NULL; - cur_settings.dc.firmware = NULL; - cur_settings.dc.deviceid = 0; -} - -static void settings_start(void) -{ - in_settings = true; -} - -static void settings_end(void) -{ - in_settings = false; -} - -static void dc_settings_start(void) -{ - reset_dc_settings(); -} - -static void dc_settings_end(void) -{ - create_device_node(cur_settings.dc.model, cur_settings.dc.deviceid, cur_settings.dc.serial_nr, - cur_settings.dc.firmware, cur_settings.dc.nickname); - reset_dc_settings(); -} - -static void dive_site_start(void) -{ - if (cur_dive_site) - return; - cur_dive_site = calloc(1, sizeof(struct dive_site)); -} - -static void dive_site_end(void) -{ - if (!cur_dive_site) - return; - if (cur_dive_site->uuid) { - // we intentionally call this with '0' to ensure we get - // a new structure and then copy things into that new - // structure a few lines below (which sets the correct - // uuid) - struct dive_site *ds = alloc_or_get_dive_site(0); - if (cur_dive_site->taxonomy.nr == 0) { - free(cur_dive_site->taxonomy.category); - cur_dive_site->taxonomy.category = NULL; - } - copy_dive_site(cur_dive_site, ds); - - if (verbose > 3) - printf("completed dive site uuid %x8 name {%s}\n", ds->uuid, ds->name); - } - free_taxonomy(&cur_dive_site->taxonomy); - free(cur_dive_site); - cur_dive_site = NULL; -} - -// now we need to add the code to parse the parts of the divesite enry - -static void dive_start(void) -{ - if (cur_dive) - return; - cur_dive = alloc_dive(); - reset_dc_info(&cur_dive->dc); - memset(&cur_tm, 0, sizeof(cur_tm)); - if (cur_trip) { - add_dive_to_trip(cur_dive, cur_trip); - cur_dive->tripflag = IN_TRIP; - } -} - -static void dive_end(void) -{ - if (!cur_dive) - return; - if (!is_dive()) - free(cur_dive); - else - record_dive_to_table(cur_dive, target_table); - cur_dive = NULL; - cur_dc = NULL; - cur_latitude.udeg = 0; - cur_longitude.udeg = 0; - cur_cylinder_index = 0; - cur_ws_index = 0; -} - -static void trip_start(void) -{ - if (cur_trip) - return; - dive_end(); - cur_trip = calloc(1, sizeof(dive_trip_t)); - memset(&cur_tm, 0, sizeof(cur_tm)); -} - -static void trip_end(void) -{ - if (!cur_trip) - return; - insert_trip(&cur_trip); - cur_trip = NULL; -} - -static void event_start(void) -{ - memset(&cur_event, 0, sizeof(cur_event)); - cur_event.deleted = 0; /* Active */ -} - -static void event_end(void) -{ - struct divecomputer *dc = get_dc(); - if (strcmp(cur_event.name, "surface") != 0) { /* 123 is a magic event that we used for a while to encode images in dives */ - if (cur_event.type == 123) { - struct picture *pic = alloc_picture(); - pic->filename = strdup(cur_event.name); - /* theoretically this could fail - but we didn't support multi year offsets */ - pic->offset.seconds = cur_event.time.seconds; - dive_add_picture(cur_dive, pic); - } else { - struct event *ev; - /* At some point gas change events did not have any type. Thus we need to add - * one on import, if we encounter the type one missing. - */ - if (cur_event.type == 0 && strcmp(cur_event.name, "gaschange") == 0) - cur_event.type = cur_event.value >> 16 > 0 ? SAMPLE_EVENT_GASCHANGE2 : SAMPLE_EVENT_GASCHANGE; - ev = add_event(dc, cur_event.time.seconds, - cur_event.type, cur_event.flags, - cur_event.value, cur_event.name); - - /* - * Older logs might mark the dive to be CCR by having an "SP change" event at time 0:00. Better - * to mark them being CCR on import so no need for special treatments elsewhere on the code. - */ - if (ev && cur_event.time.seconds == 0 && cur_event.type == SAMPLE_EVENT_PO2 && dc->divemode==OC) { - dc->divemode = CCR; - } - - if (ev && event_is_gaschange(ev)) { - /* See try_to_fill_event() on why the filled-in index is one too big */ - ev->gas.index = cur_event.gas.index-1; - if (cur_event.gas.mix.o2.permille || cur_event.gas.mix.he.permille) - ev->gas.mix = cur_event.gas.mix; - } - } - } - cur_event.deleted = 1; /* No longer active */ -} - -static void picture_start(void) -{ - cur_picture = alloc_picture(); -} - -static void picture_end(void) -{ - dive_add_picture(cur_dive, cur_picture); - cur_picture = NULL; -} - -static void cylinder_start(void) -{ -} - -static void cylinder_end(void) -{ - cur_cylinder_index++; -} - -static void ws_start(void) -{ -} - -static void ws_end(void) -{ - cur_ws_index++; -} - -static void sample_start(void) -{ - cur_sample = prepare_sample(get_dc()); - cur_sample->ndl.seconds = lastndl; - cur_sample->in_deco = lastindeco; - cur_sample->stoptime.seconds = laststoptime; - cur_sample->stopdepth.mm = laststopdepth; - cur_sample->cns = lastcns; - cur_sample->setpoint.mbar = lastpo2; - cur_sample->sensor = lastsensor; - next_o2_sensor = 0; -} - -static void sample_end(void) -{ - if (!cur_dive) - return; - - finish_sample(get_dc()); - lastndl = cur_sample->ndl.seconds; - lastindeco = cur_sample->in_deco; - laststoptime = cur_sample->stoptime.seconds; - laststopdepth = cur_sample->stopdepth.mm; - lastcns = cur_sample->cns; - lastpo2 = cur_sample->setpoint.mbar; - cur_sample = NULL; -} - -static void divecomputer_start(void) -{ - struct divecomputer *dc; - - /* Start from the previous dive computer */ - dc = &cur_dive->dc; - while (dc->next) - dc = dc->next; - - /* Did we already fill that in? */ - if (dc->samples || dc->model || dc->when) { - struct divecomputer *newdc = calloc(1, sizeof(*newdc)); - if (newdc) { - dc->next = newdc; - dc = newdc; - } - } - - /* .. this is the one we'll use */ - cur_dc = dc; - reset_dc_info(dc); -} - -static void divecomputer_end(void) -{ - if (!cur_dc->when) - cur_dc->when = cur_dive->when; - cur_dc = NULL; -} - -static void userid_start(void) -{ - in_userid = true; - set_save_userid_local(true); //if the xml contains userid, keep saving it. -} - -static void userid_stop(void) -{ - in_userid = false; -} - -static bool entry(const char *name, char *buf) -{ - if (!strncmp(name, "version.program", sizeof("version.program") - 1) || - !strncmp(name, "version.divelog", sizeof("version.divelog") - 1)) { - last_xml_version = atoi(buf); - report_datafile_version(last_xml_version); - } - if (in_userid) { - try_to_fill_userid(name, buf); - return true; - } - if (in_settings) { - try_to_fill_dc_settings(name, buf); - try_to_match_autogroup(name, buf); - return true; - } - if (cur_dive_site) { - try_to_fill_dive_site(&cur_dive_site, name, buf); - return true; - } - if (!cur_event.deleted) { - try_to_fill_event(name, buf); - return true; - } - if (cur_sample) { - try_to_fill_sample(cur_sample, name, buf); - return true; - } - if (cur_dc) { - try_to_fill_dc(cur_dc, name, buf); - return true; - } - if (cur_dive) { - try_to_fill_dive(cur_dive, name, buf); - return true; - } - if (cur_trip) { - try_to_fill_trip(&cur_trip, name, buf); - return true; - } - return true; -} - -static const char *nodename(xmlNode *node, char *buf, int len) -{ - int levels = 2; - char *p = buf; - - if (!node || (node->type != XML_CDATA_SECTION_NODE && !node->name)) { - return "root"; - } - - if (node->type == XML_CDATA_SECTION_NODE || (node->parent && !strcmp((const char *)node->name, "text"))) - node = node->parent; - - /* Make sure it's always NUL-terminated */ - p[--len] = 0; - - for (;;) { - const char *name = (const char *)node->name; - char c; - while ((c = *name++) != 0) { - /* Cheaper 'tolower()' for ASCII */ - c = (c >= 'A' && c <= 'Z') ? c - 'A' + 'a' : c; - *p++ = c; - if (!--len) - return buf; - } - *p = 0; - node = node->parent; - if (!node || !node->name) - return buf; - *p++ = '.'; - if (!--len) - return buf; - if (!--levels) - return buf; - } -} - -#define MAXNAME 32 - -static bool visit_one_node(xmlNode *node) -{ - xmlChar *content; - static char buffer[MAXNAME]; - const char *name; - - content = node->content; - if (!content || xmlIsBlankNode(node)) - return true; - - name = nodename(node, buffer, sizeof(buffer)); - - return entry(name, (char *)content); -} - -static bool traverse(xmlNode *root); - -static bool traverse_properties(xmlNode *node) -{ - xmlAttr *p; - bool ret = true; - - for (p = node->properties; p; p = p->next) - if ((ret = traverse(p->children)) == false) - break; - return ret; -} - -static bool visit(xmlNode *n) -{ - return visit_one_node(n) && traverse_properties(n) && traverse(n->children); -} - -static void DivingLog_importer(void) -{ - import_source = DIVINGLOG; - - /* - * Diving Log units are really strange. - * - * Temperatures are in C, except in samples, - * when they are in Fahrenheit. Depths are in - * meters, an dpressure is in PSI in the samples, - * but in bar when it comes to working pressure. - * - * Crazy f*%^ morons. - */ - xml_parsing_units = SI_units; -} - -static void uddf_importer(void) -{ - import_source = UDDF; - xml_parsing_units = SI_units; - xml_parsing_units.pressure = PASCAL; - xml_parsing_units.temperature = KELVIN; -} - -static void subsurface_webservice(void) -{ - import_source = SSRF_WS; -} - -/* - * I'm sure this could be done as some fancy DTD rules. - * It's just not worth the headache. - */ -static struct nesting { - const char *name; - void (*start)(void), (*end)(void); -} nesting[] = { - { "divecomputerid", dc_settings_start, dc_settings_end }, - { "settings", settings_start, settings_end }, - { "site", dive_site_start, dive_site_end }, - { "dive", dive_start, dive_end }, - { "Dive", dive_start, dive_end }, - { "trip", trip_start, trip_end }, - { "sample", sample_start, sample_end }, - { "waypoint", sample_start, sample_end }, - { "SAMPLE", sample_start, sample_end }, - { "reading", sample_start, sample_end }, - { "event", event_start, event_end }, - { "mix", cylinder_start, cylinder_end }, - { "gasmix", cylinder_start, cylinder_end }, - { "cylinder", cylinder_start, cylinder_end }, - { "weightsystem", ws_start, ws_end }, - { "divecomputer", divecomputer_start, divecomputer_end }, - { "P", sample_start, sample_end }, - { "userid", userid_start, userid_stop}, - { "picture", picture_start, picture_end }, - { "extradata", extra_data_start, extra_data_end }, - - /* Import type recognition */ - { "Divinglog", DivingLog_importer }, - { "uddf", uddf_importer }, - { "output", subsurface_webservice }, - { NULL, } - }; - -static bool traverse(xmlNode *root) -{ - xmlNode *n; - bool ret = true; - - for (n = root; n; n = n->next) { - struct nesting *rule = nesting; - - if (!n->name) { - if ((ret = visit(n)) == false) - break; - continue; - } - - do { - if (!strcmp(rule->name, (const char *)n->name)) - break; - rule++; - } while (rule->name); - - if (rule->start) - rule->start(); - if ((ret = visit(n)) == false) - break; - if (rule->end) - rule->end(); - } - return ret; -} - -/* Per-file reset */ -static void reset_all(void) -{ - /* - * We reset the units for each file. You'd think it was - * a per-dive property, but I'm not going to trust people - * to do per-dive setup. If the xml does have per-dive - * data within one file, we might have to reset it per - * dive for that format. - */ - xml_parsing_units = SI_units; - import_source = UNKNOWN; -} - -/* divelog.de sends us xml files that claim to be iso-8859-1 - * but once we decode the HTML encoded characters they turn - * into UTF-8 instead. So skip the incorrect encoding - * declaration and decode the HTML encoded characters */ -const char *preprocess_divelog_de(const char *buffer) -{ - char *ret = strstr(buffer, ""); - - if (ret) { - xmlParserCtxtPtr ctx; - char buf[] = ""; - size_t i; - - for (i = 0; i < strlen(ret); ++i) - if (!isascii(ret[i])) - return buffer; - - ctx = xmlCreateMemoryParserCtxt(buf, sizeof(buf)); - ret = (char *)xmlStringLenDecodeEntities(ctx, (xmlChar *)ret, strlen(ret), XML_SUBSTITUTE_REF, 0, 0, 0); - - return ret; - } - return buffer; -} - -int parse_xml_buffer(const char *url, const char *buffer, int size, - struct dive_table *table, const char **params) -{ - (void) size; - xmlDoc *doc; - const char *res = preprocess_divelog_de(buffer); - int ret = 0; - - target_table = table; - doc = xmlReadMemory(res, strlen(res), url, NULL, 0); - if (res != buffer) - free((char *)res); - - if (!doc) - return report_error(translate("gettextFromC", "Failed to parse '%s'"), url); - - set_save_userid_local(false); - reset_all(); - dive_start(); - doc = test_xslt_transforms(doc, params); - if (!traverse(xmlDocGetRootElement(doc))) { - // we decided to give up on parsing... why? - ret = -1; - } - dive_end(); - xmlFreeDoc(doc); - return ret; -} - -void parse_mkvi_buffer(struct membuffer *txt, struct membuffer *csv, const char *starttime) -{ - (void) csv; - (void) txt; - dive_start(); - divedate(starttime, &cur_dive->when); - dive_end(); -} - -extern int dm4_events(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - event_start(); - if (data[1]) - cur_event.time.seconds = atoi(data[1]); - - if (data[2]) { - switch (atoi(data[2])) { - case 1: - /* 1 Mandatory Safety Stop */ - strcpy(cur_event.name, "safety stop (mandatory)"); - break; - case 3: - /* 3 Deco */ - /* What is Subsurface's term for going to - * deco? */ - strcpy(cur_event.name, "deco"); - break; - case 4: - /* 4 Ascent warning */ - strcpy(cur_event.name, "ascent"); - break; - case 5: - /* 5 Ceiling broken */ - strcpy(cur_event.name, "violation"); - break; - case 6: - /* 6 Mandatory safety stop ceiling error */ - strcpy(cur_event.name, "violation"); - break; - case 7: - /* 7 Below deco floor */ - strcpy(cur_event.name, "below floor"); - break; - case 8: - /* 8 Dive time alarm */ - strcpy(cur_event.name, "divetime"); - break; - case 9: - /* 9 Depth alarm */ - strcpy(cur_event.name, "maxdepth"); - break; - case 10: - /* 10 OLF 80% */ - case 11: - /* 11 OLF 100% */ - strcpy(cur_event.name, "OLF"); - break; - case 12: - /* 12 High pO₂ */ - strcpy(cur_event.name, "PO2"); - break; - case 13: - /* 13 Air time */ - strcpy(cur_event.name, "airtime"); - break; - case 17: - /* 17 Ascent warning */ - strcpy(cur_event.name, "ascent"); - break; - case 18: - /* 18 Ceiling error */ - strcpy(cur_event.name, "ceiling"); - break; - case 19: - /* 19 Surfaced */ - strcpy(cur_event.name, "surface"); - break; - case 20: - /* 20 Deco */ - strcpy(cur_event.name, "deco"); - break; - case 22: - case 32: - /* 22 Mandatory safety stop violation */ - /* 32 Deep stop violation */ - strcpy(cur_event.name, "violation"); - break; - case 30: - /* Tissue level warning */ - strcpy(cur_event.name, "tissue warning"); - break; - case 37: - /* Tank pressure alarm */ - strcpy(cur_event.name, "tank pressure"); - break; - case 257: - /* 257 Dive active */ - /* This seems to be given after surface when - * descending again. */ - strcpy(cur_event.name, "surface"); - break; - case 258: - /* 258 Bookmark */ - if (data[3]) { - strcpy(cur_event.name, "heading"); - cur_event.value = atoi(data[3]); - } else { - strcpy(cur_event.name, "bookmark"); - } - break; - case 259: - /* Deep stop */ - strcpy(cur_event.name, "Deep stop"); - break; - case 260: - /* Deep stop */ - strcpy(cur_event.name, "Deep stop cleared"); - break; - case 266: - /* Mandatory safety stop activated */ - strcpy(cur_event.name, "safety stop (mandatory)"); - break; - case 267: - /* Mandatory safety stop deactivated */ - /* DM5 shows this only on event list, not on the - * profile so skipping as well for now */ - break; - default: - strcpy(cur_event.name, "unknown"); - cur_event.value = atoi(data[2]); - break; - } - } - event_end(); - - return 0; -} - -extern int dm5_cylinders(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - cylinder_start(); - if (data[7] && atoi(data[7]) > 0 && atoi(data[7]) < 350000) - cur_dive->cylinder[cur_cylinder_index].start.mbar = atoi(data[7]); - if (data[8] && atoi(data[8]) > 0 && atoi(data[8]) < 350000) - cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[8])); - if (data[6]) { - /* DM5 shows tank size of 12 liters when the actual - * value is 0 (and using metric units). So we just use - * the same 12 liters when size is not available */ - if (atof(data[6]) == 0.0 && cur_dive->cylinder[cur_cylinder_index].start.mbar) - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = 12000; - else - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = (atof(data[6])) * 1000; - } - if (data[2]) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[2]) * 10; - if (data[3]) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[3]) * 10; - cylinder_end(); - return 0; -} - -extern int dm5_gaschange(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - event_start(); - if (data[0]) - cur_event.time.seconds = atoi(data[0]); - if (data[1]) { - strcpy(cur_event.name, "gaschange"); - cur_event.value = atof(data[1]); - } - event_end(); - - return 0; -} - -extern int dm4_tags(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - if (data[0]) - taglist_add_tag(&cur_dive->tag_list, data[0]); - - return 0; -} - -extern int dm4_dive(void *param, int columns, char **data, char **column) -{ - (void) columns; - (void) column; - unsigned int i; - int interval, retval = 0; - sqlite3 *handle = (sqlite3 *)param; - float *profileBlob; - unsigned char *tempBlob; - int *pressureBlob; - char *err = NULL; - char get_events_template[] = "select * from Mark where DiveId = %d"; - char get_tags_template[] = "select Text from DiveTag where DiveId = %d"; - char get_events[64]; - - dive_start(); - cur_dive->number = atoi(data[0]); - - cur_dive->when = (time_t)(atol(data[1])); - if (data[2]) - utf8_string(data[2], &cur_dive->notes); - - /* - * DM4 stores Duration and DiveTime. It looks like DiveTime is - * 10 to 60 seconds shorter than Duration. However, I have no - * idea what is the difference and which one should be used. - * Duration = data[3] - * DiveTime = data[15] - */ - if (data[3]) - cur_dive->duration.seconds = atoi(data[3]); - if (data[15]) - cur_dive->dc.duration.seconds = atoi(data[15]); - - /* - * TODO: the deviceid hash should be calculated here. - */ - settings_start(); - dc_settings_start(); - if (data[4]) - utf8_string(data[4], &cur_settings.dc.serial_nr); - if (data[5]) - utf8_string(data[5], &cur_settings.dc.model); - - cur_settings.dc.deviceid = 0xffffffff; - dc_settings_end(); - settings_end(); - - if (data[6]) - cur_dive->dc.maxdepth.mm = atof(data[6]) * 1000; - if (data[8]) - cur_dive->dc.airtemp.mkelvin = C_to_mkelvin(atoi(data[8])); - if (data[9]) - cur_dive->dc.watertemp.mkelvin = C_to_mkelvin(atoi(data[9])); - - /* - * TODO: handle multiple cylinders - */ - cylinder_start(); - if (data[22] && atoi(data[22]) > 0) - cur_dive->cylinder[cur_cylinder_index].start.mbar = atoi(data[22]); - else if (data[10] && atoi(data[10]) > 0) - cur_dive->cylinder[cur_cylinder_index].start.mbar = atoi(data[10]); - if (data[23] && atoi(data[23]) > 0) - cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[23])); - if (data[11] && atoi(data[11]) > 0) - cur_dive->cylinder[cur_cylinder_index].end.mbar = (atoi(data[11])); - if (data[12]) - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = (atof(data[12])) * 1000; - if (data[13]) - cur_dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = (atoi(data[13])); - if (data[20]) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[20]) * 10; - if (data[21]) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[21]) * 10; - cylinder_end(); - - if (data[14]) - cur_dive->dc.surface_pressure.mbar = (atoi(data[14]) * 1000); - - interval = data[16] ? atoi(data[16]) : 0; - profileBlob = (float *)data[17]; - tempBlob = (unsigned char *)data[18]; - pressureBlob = (int *)data[19]; - for (i = 0; interval && i * interval < cur_dive->duration.seconds; i++) { - sample_start(); - cur_sample->time.seconds = i * interval; - if (profileBlob) - cur_sample->depth.mm = profileBlob[i] * 1000; - else - cur_sample->depth.mm = cur_dive->dc.maxdepth.mm; - - if (data[18] && data[18][0]) - cur_sample->temperature.mkelvin = C_to_mkelvin(tempBlob[i]); - if (data[19] && data[19][0]) - cur_sample->cylinderpressure.mbar = pressureBlob[i]; - sample_end(); - } - - snprintf(get_events, sizeof(get_events) - 1, get_events_template, cur_dive->number); - retval = sqlite3_exec(handle, get_events, &dm4_events, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query dm4_events failed.\n"); - return 1; - } - - snprintf(get_events, sizeof(get_events) - 1, get_tags_template, cur_dive->number); - retval = sqlite3_exec(handle, get_events, &dm4_tags, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query dm4_tags failed.\n"); - return 1; - } - - dive_end(); - - /* - for (i=0; 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) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - /* StartTime is converted from Suunto's nano seconds to standard - * time. We also need epoch, not seconds since year 1. */ - char get_dives[] = "select D.DiveId,StartTime/10000000-62135596800,Note,Duration,SourceSerialNumber,Source,MaxDepth,SampleInterval,StartTemperature,BottomTemperature,D.StartPressure,D.EndPressure,Size,CylinderWorkPressure,SurfacePressure,DiveTime,SampleInterval,ProfileBlob,TemperatureBlob,PressureBlob,Oxygen,Helium,MIX.StartPressure,MIX.EndPressure FROM Dive AS D JOIN DiveMixture AS MIX ON D.DiveId=MIX.DiveId"; - - retval = sqlite3_exec(handle, get_dives, &dm4_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -int parse_dm5_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, - struct dive_table *table) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - /* StartTime is converted from Suunto's nano seconds to standard - * time. We also need epoch, not seconds since year 1. */ - char get_dives[] = "select DiveId,StartTime/10000000-62135596800,Note,Duration,coalesce(SourceSerialNumber,SerialNumber),Source,MaxDepth,SampleInterval,StartTemperature,BottomTemperature,StartPressure,EndPressure,'','',SurfacePressure,DiveTime,SampleInterval,ProfileBlob,TemperatureBlob,PressureBlob,'','','','',SampleBlob FROM Dive where Deleted is null"; - - retval = sqlite3_exec(handle, get_dives, &dm5_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -extern int shearwater_cylinders(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - cylinder_start(); - if (data[0]) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atof(data[0]) * 1000; - if (data[1]) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atof(data[1]) * 1000; - cylinder_end(); - - return 0; -} - -extern int shearwater_changes(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - event_start(); - if (data[0]) - cur_event.time.seconds = atoi(data[0]); - if (data[1]) { - strcpy(cur_event.name, "gaschange"); - cur_event.value = atof(data[1]) * 100; - } - event_end(); - - return 0; -} - - -extern int cobalt_profile_sample(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - sample_start(); - if (data[0]) - cur_sample->time.seconds = atoi(data[0]); - if (data[1]) - cur_sample->depth.mm = atoi(data[1]); - if (data[2]) - cur_sample->temperature.mkelvin = metric ? C_to_mkelvin(atof(data[2])) : F_to_mkelvin(atof(data[2])); - sample_end(); - - return 0; -} - - -extern int shearwater_profile_sample(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - sample_start(); - if (data[0]) - cur_sample->time.seconds = atoi(data[0]); - if (data[1]) - cur_sample->depth.mm = metric ? atof(data[1]) * 1000 : feet_to_mm(atof(data[1])); - if (data[2]) - cur_sample->temperature.mkelvin = metric ? C_to_mkelvin(atof(data[2])) : F_to_mkelvin(atof(data[2])); - if (data[3]) { - cur_sample->setpoint.mbar = atof(data[3]) * 1000; - cur_dive->dc.divemode = CCR; - } - if (data[4]) - cur_sample->ndl.seconds = atoi(data[4]) * 60; - if (data[5]) - cur_sample->cns = atoi(data[5]); - if (data[6]) - cur_sample->stopdepth.mm = metric ? atoi(data[6]) * 1000 : feet_to_mm(atoi(data[6])); - - /* We don't actually have data[3], but it should appear in the - * SQL query at some point. - if (data[3]) - cur_sample->cylinderpressure.mbar = metric ? atoi(data[3]) * 1000 : psi_to_mbar(atoi(data[3])); - */ - sample_end(); - - return 0; -} - -extern int shearwater_dive(void *param, int columns, char **data, char **column) -{ - (void) columns; - (void) column; - - int retval = 0; - sqlite3 *handle = (sqlite3 *)param; - char *err = NULL; - char get_profile_template[] = "select currentTime,currentDepth,waterTemp,averagePPO2,currentNdl,CNSPercent,decoCeiling from dive_log_records where diveLogId = %d"; - char get_cylinder_template[] = "select fractionO2,fractionHe from dive_log_records where diveLogId = %d group by fractionO2,fractionHe"; - char get_changes_template[] = "select a.currentTime,a.fractionO2,a.fractionHe from dive_log_records as a,dive_log_records as b where a.diveLogId = %d and b.diveLogId = %d and (a.id - 1) = b.id and (a.fractionO2 != b.fractionO2 or a.fractionHe != b.fractionHe) union select min(currentTime),fractionO2,fractionHe from dive_log_records"; - char get_buffer[1024]; - - dive_start(); - cur_dive->number = atoi(data[0]); - - cur_dive->when = (time_t)(atol(data[1])); - - if (data[2]) - add_dive_site(data[2], cur_dive); - if (data[3]) - utf8_string(data[3], &cur_dive->buddy); - if (data[4]) - utf8_string(data[4], &cur_dive->notes); - - metric = atoi(data[5]) == 1 ? 0 : 1; - - /* TODO: verify that metric calculation is correct */ - if (data[6]) - cur_dive->dc.maxdepth.mm = metric ? atof(data[6]) * 1000 : feet_to_mm(atof(data[6])); - - if (data[7]) - cur_dive->dc.duration.seconds = atoi(data[7]) * 60; - - if (data[8]) - cur_dive->dc.surface_pressure.mbar = atoi(data[8]); - /* - * TODO: the deviceid hash should be calculated here. - */ - settings_start(); - dc_settings_start(); - if (data[9]) - utf8_string(data[9], &cur_settings.dc.serial_nr); - if (data[10]) - utf8_string(data[10], &cur_settings.dc.model); - - cur_settings.dc.deviceid = 0xffffffff; - dc_settings_end(); - settings_end(); - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &shearwater_cylinders, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query shearwater_cylinders failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_changes_template, cur_dive->number, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &shearwater_changes, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query shearwater_changes failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &shearwater_profile_sample, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query shearwater_profile_sample failed.\n"); - return 1; - } - - dive_end(); - - return SQLITE_OK; -} - -extern int cobalt_cylinders(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - cylinder_start(); - if (data[0]) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atoi(data[0]) * 10; - if (data[1]) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atoi(data[1]) * 10; - if (data[2]) - cur_dive->cylinder[cur_cylinder_index].start.mbar = psi_to_mbar(atoi(data[2])); - if (data[3]) - cur_dive->cylinder[cur_cylinder_index].end.mbar = psi_to_mbar(atoi(data[3])); - if (data[4]) - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = atoi(data[4]) * 100; - if (data[5]) - cur_dive->cylinder[cur_cylinder_index].gas_used.mliter = atoi(data[5]) * 1000; - cylinder_end(); - - return 0; -} - -extern int cobalt_buddies(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - if (data[0]) - utf8_string(data[0], &cur_dive->buddy); - - return 0; -} - -/* - * We still need to figure out how to map free text visibility to - * Subsurface star rating. - */ - -extern int cobalt_visibility(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - (void) data; - return 0; -} - -extern int cobalt_location(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - static char *location = NULL; - if (data[0]) { - if (location) { - char *tmp = malloc(strlen(location) + strlen(data[0]) + 4); - if (!tmp) - return -1; - sprintf(tmp, "%s / %s", location, data[0]); - free(location); - location = NULL; - cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(tmp, cur_dive->when); - free(tmp); - } else { - location = strdup(data[0]); - } - } - return 0; -} - - -extern int cobalt_dive(void *param, int columns, char **data, char **column) -{ - (void) columns; - (void) column; - - int retval = 0; - sqlite3 *handle = (sqlite3 *)param; - char *err = NULL; - char get_profile_template[] = "select runtime*60,(DepthPressure*10000/SurfacePressure)-10000,p.Temperature from Dive AS d JOIN TrackPoints AS p ON d.Id=p.DiveId where d.Id=%d"; - char get_cylinder_template[] = "select FO2,FHe,StartingPressure,EndingPressure,TankSize,TankPressure,TotalConsumption from GasMixes where DiveID=%d and StartingPressure>0 group by FO2,FHe"; - char get_buddy_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=4"; - char get_visibility_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=3"; - char get_location_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=0"; - char get_site_template[] = "select l.Data from Items AS i, List AS l ON i.Value1=l.Id where i.DiveId=%d and l.Type=1"; - char get_buffer[1024]; - - dive_start(); - cur_dive->number = atoi(data[0]); - - cur_dive->when = (time_t)(atol(data[1])); - - if (data[4]) - utf8_string(data[4], &cur_dive->notes); - - /* data[5] should have information on Units used, but I cannot - * parse it at all based on the sample log I have received. The - * temperatures in the samples are all Imperial, so let's go by - * that. - */ - - metric = 0; - - /* Cobalt stores the pressures, not the depth */ - if (data[6]) - cur_dive->dc.maxdepth.mm = atoi(data[6]); - - if (data[7]) - cur_dive->dc.duration.seconds = atoi(data[7]); - - if (data[8]) - cur_dive->dc.surface_pressure.mbar = atoi(data[8]); - /* - * TODO: the deviceid hash should be calculated here. - */ - settings_start(); - dc_settings_start(); - if (data[9]) { - utf8_string(data[9], &cur_settings.dc.serial_nr); - cur_settings.dc.deviceid = atoi(data[9]); - cur_settings.dc.model = strdup("Cobalt import"); - } - - dc_settings_end(); - settings_end(); - - if (data[9]) { - cur_dive->dc.deviceid = atoi(data[9]); - cur_dive->dc.model = strdup("Cobalt import"); - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_cylinders, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_cylinders failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_buddy_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_buddies, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_buddies failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_visibility_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_visibility, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_visibility failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_location_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_location, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_location failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_site_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_location, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_location (site) failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, cur_dive->number); - retval = sqlite3_exec(handle, get_buffer, &cobalt_profile_sample, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query cobalt_profile_sample failed.\n"); - return 1; - } - - dive_end(); - - return SQLITE_OK; -} - - -int parse_shearwater_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, - struct dive_table *table) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - char get_dives[] = "select i.diveId,timestamp,location||' / '||site,buddy,notes,imperialUnits,maxDepth,maxTime,startSurfacePressure,computerSerial,computerModel FROM dive_info AS i JOIN dive_logs AS l ON i.diveId=l.diveId"; - - retval = sqlite3_exec(handle, get_dives, &shearwater_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -int parse_cobalt_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, - struct dive_table *table) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - char get_dives[] = "select Id,strftime('%s',DiveStartTime),LocationId,'buddy','notes',Units,(MaxDepthPressure*10000/SurfacePressure)-10000,DiveMinutes,SurfacePressure,SerialNumber,'model' from Dive where IsViewDeleted = 0"; - - retval = sqlite3_exec(handle, get_dives, &cobalt_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -extern int divinglog_cylinder(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - short dbl = 1; - //char get_cylinder_template[] = "select TankID,TankSize,PresS,PresE,PresW,O2,He,DblTank from Tank where LogID = %d"; - - /* - * Divinglog might have more cylinders than what we support. So - * better to ignore those. - */ - - if (cur_cylinder_index >= MAX_CYLINDERS) - return 0; - - if (data[7] && atoi(data[7]) > 0) - dbl = 2; - - cylinder_start(); - - /* - * Assuming that we have to double the cylinder size, if double - * is set - */ - - if (data[1] && atoi(data[1]) > 0) - cur_dive->cylinder[cur_cylinder_index].type.size.mliter = atol(data[1]) * 1000 * dbl; - - if (data[2] && atoi(data[2]) > 0) - cur_dive->cylinder[cur_cylinder_index].start.mbar = atol(data[2]) * 1000; - if (data[3] && atoi(data[3]) > 0) - cur_dive->cylinder[cur_cylinder_index].end.mbar = atol(data[3]) * 1000; - if (data[4] && atoi(data[4]) > 0) - cur_dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = atol(data[4]) * 1000; - if (data[5] && atoi(data[5]) > 0) - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = atol(data[5]) * 10; - if (data[6] && atoi(data[6]) > 0) - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = atol(data[6]) * 10; - - cylinder_end(); - - return 0; -} - -extern int divinglog_profile(void *handle, int columns, char **data, char **column) -{ - (void) handle; - (void) columns; - (void) column; - - int sinterval = 0; - unsigned long i, len, lenprofile2 = 0; - char *ptr, temp[4], pres[5], hbeat[4], stop[4], stime[4], ndl[4], ppo2_1[4], ppo2_2[4], ppo2_3[4], cns[5], setpoint[3]; - short oldcyl = -1; - - /* We do not have samples */ - if (!data[1]) - return 0; - - if (data[0]) - sinterval = atoi(data[0]); - - /* - * Profile - * - * DDDDDCRASWEE - * D: Depth (in meter with two decimals) - * C: Deco (1 = yes, 0 = no) - * R: RBT (Remaining Bottom Time warning) - * A: Ascent warning - * S: Decostop ignored - * W: Work warning - * E: Extra info (different for every computer) - * - * Example: 004500010000 - * 4.5 m, no deco, no RBT warning, ascanding too fast, no decostop ignored, no work, no extra info - * - * - * Profile2 - * - * TTTFFFFIRRR - * - * T: Temperature (in °C with one decimal) - * F: Tank pressure 1 (in bar with one decimal) - * I: Tank ID (0, 1, 2 ... 9) - * R: RBT (in min) - * - * Example: 25518051099 - * 25.5 °C, 180.5 bar, Tank 1, 99 min RBT - * - */ - - len = strlen(data[1]); - - if (data[2]) - lenprofile2 = strlen(data[2]); - - for (i = 0, ptr = data[1]; i * 12 < len; ++i) { - sample_start(); - - cur_sample->time.seconds = sinterval * i; - cur_sample->in_deco = ptr[5] - '0' ? true : false; - ptr[5] = 0; - cur_sample->depth.mm = atoi(ptr) * 10; - - if (i * 11 < lenprofile2) { - memcpy(temp, &data[2][i * 11], 3); - cur_sample->temperature.mkelvin = C_to_mkelvin(atoi(temp) / 10); - } - - if (data[2]) { - memcpy(pres, &data[2][i * 11 + 3], 4); - cur_sample->cylinderpressure.mbar = atoi(pres) * 100; - } - - if (data[3] && strlen(data[3])) { - memcpy(hbeat, &data[3][i * 14 + 8], 3); - cur_sample->heartbeat = atoi(hbeat); - } - - if (data[4] && strlen(data[4])) { - memcpy(stop, &data[4][i * 9 + 6], 3); - cur_sample->stopdepth.mm = atoi(stop) * 1000; - - memcpy(stime, &data[4][i * 9 + 3], 3); - cur_sample->stoptime.seconds = atoi(stime) * 60; - - /* - * Following value is NDL when not in deco, and - * either 0 or TTS when in deco. - */ - - memcpy(ndl, &data[4][i * 9 + 0], 3); - if (cur_sample->in_deco == false) - cur_sample->ndl.seconds = atoi(ndl) * 60; - else if (atoi(ndl)) - cur_sample->tts.seconds = atoi(ndl) * 60; - - if (cur_sample->in_deco == true) - cur_sample->ndl.seconds = 0; - } - - /* - * AAABBBCCCOOOONNNNSS - * - * A = ppO2 cell 1 (measured) - * B = ppO2 cell 2 (measured) - * C = ppO2 cell 3 (measured) - * O = OTU - * N = CNS - * S = Setpoint - * - * Example: 1121131141548026411 - * 1.12 bar, 1.13 bar, 1.14 bar, OTU = 154.8, CNS = 26.4, Setpoint = 1.1 - */ - - if (data[5] && strlen(data[5])) { - memcpy(ppo2_1, &data[5][i * 19 + 0], 3); - memcpy(ppo2_2, &data[5][i * 19 + 3], 3); - memcpy(ppo2_3, &data[5][i * 19 + 6], 3); - memcpy(cns, &data[5][i * 19 + 13], 4); - memcpy(setpoint, &data[5][i * 19 + 17], 2); - - if (atoi(ppo2_1) > 0) - cur_sample->o2sensor[0].mbar = atoi(ppo2_1) * 100; - if (atoi(ppo2_2) > 0) - cur_sample->o2sensor[1].mbar = atoi(ppo2_2) * 100; - if (atoi(ppo2_3) > 0) - cur_sample->o2sensor[2].mbar = atoi(ppo2_3) * 100; - if (atoi(cns) > 0) - cur_sample->cns = rint(atoi(cns) / 10); - if (atoi(setpoint) > 0) - cur_sample->setpoint.mbar = atoi(setpoint) * 100; - - } - - /* - * My best guess is that if we have o2sensors, then it - * is either CCR or PSCR dive. And the first time we - * have O2 sensor readings, we can count them to get - * the amount O2 sensors. - */ - - if (!cur_dive->dc.no_o2sensors) { - cur_dive->dc.no_o2sensors = cur_sample->o2sensor[0].mbar ? 1 : 0 + - cur_sample->o2sensor[1].mbar ? 1 : 0 + - cur_sample->o2sensor[2].mbar ? 1 : 0; - cur_dive->dc.divemode = CCR; - } - - ptr += 12; - sample_end(); - } - - for (i = 0, ptr = data[1]; i * 12 < len; ++i) { - /* Remaining bottom time warning */ - if (ptr[6] - '0') { - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "rbt"); - event_end(); - } - - /* Ascent warning */ - if (ptr[7] - '0') { - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "ascent"); - event_end(); - } - - /* Deco stop ignored */ - if (ptr[8] - '0') { - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "violation"); - event_end(); - } - - /* Workload warning */ - if (ptr[9] - '0') { - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "workload"); - event_end(); - } - ptr += 12; - } - - for (i = 0; i * 11 < lenprofile2; ++i) { - short tank = data[2][i * 11 + 7] - '0'; - if (oldcyl != tank) { - struct gasmix *mix = &cur_dive->cylinder[tank].gasmix; - int o2 = get_o2(mix); - int he = get_he(mix); - - event_start(); - cur_event.time.seconds = sinterval * i; - strcpy(cur_event.name, "gaschange"); - - o2 = (o2 + 5) / 10; - he = (he + 5) / 10; - cur_event.value = o2 + (he << 16); - - event_end(); - oldcyl = tank; - } - } - - return 0; -} - - -extern int divinglog_dive(void *param, int columns, char **data, char **column) -{ - (void) columns; - (void) column; - - int retval = 0; - sqlite3 *handle = (sqlite3 *)param; - char *err = NULL; - char get_profile_template[] = "select ProfileInt,Profile,Profile2,Profile3,Profile4,Profile5 from Logbook where ID = %d"; - char get_cylinder0_template[] = "select 0,TankSize,PresS,PresE,PresW,O2,He,DblTank from Logbook where ID = %d"; - char get_cylinder_template[] = "select TankID,TankSize,PresS,PresE,PresW,O2,He,DblTank from Tank where LogID = %d order by TankID"; - char get_buffer[1024]; - - dive_start(); - diveid = atoi(data[13]); - cur_dive->number = atoi(data[0]); - - cur_dive->when = (time_t)(atol(data[1])); - - if (data[2]) - cur_dive->dive_site_uuid = find_or_create_dive_site_with_name(data[2], cur_dive->when); - - if (data[3]) - utf8_string(data[3], &cur_dive->buddy); - - if (data[4]) - utf8_string(data[4], &cur_dive->notes); - - if (data[5]) - cur_dive->dc.maxdepth.mm = atof(data[5]) * 1000; - - if (data[6]) - cur_dive->dc.duration.seconds = atoi(data[6]) * 60; - - if (data[7]) - utf8_string(data[7], &cur_dive->divemaster); - - if (data[8]) - cur_dive->airtemp.mkelvin = C_to_mkelvin(atol(data[8])); - - if (data[9]) - cur_dive->watertemp.mkelvin = C_to_mkelvin(atol(data[9])); - - if (data[10]) { - cur_dive->weightsystem[0].weight.grams = atol(data[10]) * 1000; - cur_dive->weightsystem[0].description = strdup(translate("gettextFromC", "unknown")); - } - - if (data[11]) - cur_dive->suit = strdup(data[11]); - - settings_start(); - dc_settings_start(); - - if (data[12]) { - cur_dive->dc.model = strdup(data[12]); - } else { - cur_settings.dc.model = strdup("Divinglog import"); - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder0_template, diveid); - retval = sqlite3_exec(handle, get_buffer, &divinglog_cylinder, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query divinglog_cylinder0 failed.\n"); - return 1; - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_cylinder_template, diveid); - retval = sqlite3_exec(handle, get_buffer, &divinglog_cylinder, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query divinglog_cylinder failed.\n"); - return 1; - } - - - dc_settings_end(); - settings_end(); - - if (data[12]) { - cur_dive->dc.model = strdup(data[12]); - } else { - cur_dive->dc.model = strdup("Divinglog import"); - } - - snprintf(get_buffer, sizeof(get_buffer) - 1, get_profile_template, diveid); - retval = sqlite3_exec(handle, get_buffer, &divinglog_profile, 0, &err); - if (retval != SQLITE_OK) { - fprintf(stderr, "%s", "Database query divinglog_profile failed.\n"); - return 1; - } - - dive_end(); - - return SQLITE_OK; -} - - -int parse_divinglog_buffer(sqlite3 *handle, const char *url, const char *buffer, int size, - struct dive_table *table) -{ - (void) buffer; - (void) size; - - int retval; - char *err = NULL; - target_table = table; - - char get_dives[] = "select Number,strftime('%s',Divedate || ' ' || ifnull(Entrytime,'00:00')),Country || ' - ' || City || ' - ' || Place,Buddy,Comments,Depth,Divetime,Divemaster,Airtemp,Watertemp,Weight,Divesuit,Computer,ID from Logbook where UUID not in (select UUID from DeletedRecords)"; - - retval = sqlite3_exec(handle, get_dives, &divinglog_dive, handle, &err); - - if (retval != SQLITE_OK) { - fprintf(stderr, "Database query failed '%s'.\n", url); - return 1; - } - - return 0; -} - -/* - * Parse a unsigned 32-bit integer in little-endian mode, - * that is seconds since Jan 1, 2000. - */ -static timestamp_t parse_dlf_timestamp(unsigned char *buffer) -{ - timestamp_t offset; - - offset = buffer[3]; - offset = (offset << 8) + buffer[2]; - offset = (offset << 8) + buffer[1]; - offset = (offset << 8) + buffer[0]; - - // Jan 1, 2000 is 946684800 seconds after Jan 1, 1970, which is - // the Unix epoch date that "timestamp_t" uses. - return offset + 946684800; -} - -int parse_dlf_buffer(unsigned char *buffer, size_t size) -{ - unsigned char *ptr = buffer; - unsigned char event; - bool found; - unsigned int time = 0; - int i; - char serial[6]; - - target_table = &dive_table; - - // Check for the correct file magic - if (ptr[0] != 'D' || ptr[1] != 'i' || ptr[2] != 'v' || ptr[3] != 'E') - return -1; - - dive_start(); - divecomputer_start(); - - cur_dc->model = strdup("DLF import"); - // (ptr[7] << 8) + ptr[6] Is "Serial" - snprintf(serial, sizeof(serial), "%d", (ptr[7] << 8) + ptr[6]); - cur_dc->serial = strdup(serial); - cur_dc->when = parse_dlf_timestamp(ptr + 8); - cur_dive->when = cur_dc->when; - - cur_dc->duration.seconds = ((ptr[14] & 0xFE) << 16) + (ptr[13] << 8) + ptr[12]; - - // ptr[14] >> 1 is scrubber used in % - - // 3 bit dive type - switch((ptr[15] & 0x30) >> 3) { - case 0: // unknown - case 1: - cur_dc->divemode = OC; - break; - case 2: - cur_dc->divemode = CCR; - break; - case 3: - cur_dc->divemode = CCR; // mCCR - break; - case 4: - cur_dc->divemode = FREEDIVE; - break; - case 5: - cur_dc->divemode = OC; // Gauge - break; - case 6: - cur_dc->divemode = PSCR; // ASCR - break; - case 7: - cur_dc->divemode = PSCR; - break; - } - - cur_dc->maxdepth.mm = ((ptr[21] << 8) + ptr[20]) * 10; - cur_dc->surface_pressure.mbar = ((ptr[25] << 8) + ptr[24]) / 10; - - /* Done with parsing what we know about the dive header */ - ptr += 32; - - // We're going to interpret ppO2 saved as a sensor value in these modes. - if (cur_dc->divemode == CCR || cur_dc->divemode == PSCR) - cur_dc->no_o2sensors = 1; - - while (ptr < buffer + size) { - time = ((ptr[0] >> 4) & 0x0f) + - ((ptr[1] << 4) & 0xff0) + - (ptr[2] & 0x0f) * 3600; /* hours */ - event = ptr[0] & 0x0f; - switch (event) { - case 0: - /* Regular sample */ - sample_start(); - cur_sample->time.seconds = time; - cur_sample->depth.mm = ((ptr[5] << 8) + ptr[4]) * 10; - // Crazy precision on these stored values... - // Only store value if we're in CCR/PSCR mode, - // because we rather calculate ppo2 our selfs. - if (cur_dc->divemode == CCR || cur_dc->divemode == PSCR) - cur_sample->o2sensor[0].mbar = ((ptr[7] << 8) + ptr[6]) / 10; - // NDL in minutes, 10 bit - cur_sample->ndl.seconds = (((ptr[9] & 0x03) << 8) + ptr[8]) * 60; - // TTS in minutes, 10 bit - cur_sample->tts.seconds = (((ptr[10] & 0x0F) << 6) + (ptr[9] >> 2)) * 60; - // Temperature in 1/10 C, 10 bit signed - cur_sample->temperature.mkelvin = ((ptr[11] & 0x20) ? -1 : 1) * (((ptr[11] & 0x1F) << 4) + (ptr[10] >> 4)) * 100 + ZERO_C_IN_MKELVIN; - // ptr[11] & 0xF0 is unknown, and always 0xC in all checked files - cur_sample->stopdepth.mm = ((ptr[13] << 8) + ptr[12]) * 10; - if (cur_sample->stopdepth.mm) - cur_sample->in_deco = true; - //ptr[14] is helium content, always zero? - //ptr[15] is setpoint, always zero? - sample_end(); - break; - case 1: /* dive event */ - case 2: /* automatic parameter change */ - case 3: /* diver error */ - case 4: /* internal error */ - case 5: /* device activity log */ - event_start(); - cur_event.time.seconds = time; - switch (ptr[4]) { - case 1: - strcpy(cur_event.name, "Setpoint Manual"); - // There is a setpoint value somewhere... - break; - case 2: - strcpy(cur_event.name, "Setpoint Auto"); - // There is a setpoint value somewhere... - switch (ptr[7]) { - case 0: - strcat(cur_event.name, " Manual"); - break; - case 1: - strcat(cur_event.name, " Auto Start"); - break; - case 2: - strcat(cur_event.name, " Auto Hypox"); - break; - case 3: - strcat(cur_event.name, " Auto Timeout"); - break; - case 4: - strcat(cur_event.name, " Auto Ascent"); - break; - case 5: - strcat(cur_event.name, " Auto Stall"); - break; - case 6: - strcat(cur_event.name, " Auto SP Low"); - break; - default: - break; - } - break; - case 3: - // obsolete - strcpy(cur_event.name, "OC"); - break; - case 4: - // obsolete - strcpy(cur_event.name, "CCR"); - break; - case 5: - strcpy(cur_event.name, "gaschange"); - cur_event.type = SAMPLE_EVENT_GASCHANGE2; - cur_event.value = ptr[7] << 8 ^ ptr[6]; - - found = false; - for (i = 0; i < cur_cylinder_index; ++i) { - if (cur_dive->cylinder[i].gasmix.o2.permille == ptr[6] * 10 && cur_dive->cylinder[i].gasmix.he.permille == ptr[7] * 10) { - found = true; - break; - } - } - if (!found) { - cylinder_start(); - cur_dive->cylinder[cur_cylinder_index].gasmix.o2.permille = ptr[6] * 10; - cur_dive->cylinder[cur_cylinder_index].gasmix.he.permille = ptr[7] * 10; - cylinder_end(); - cur_event.gas.index = cur_cylinder_index; - } else { - cur_event.gas.index = i; - } - break; - case 6: - strcpy(cur_event.name, "Start"); - break; - case 7: - strcpy(cur_event.name, "Too Fast"); - break; - case 8: - strcpy(cur_event.name, "Above Ceiling"); - break; - case 9: - strcpy(cur_event.name, "Toxic"); - break; - case 10: - strcpy(cur_event.name, "Hypox"); - break; - case 11: - strcpy(cur_event.name, "Critical"); - break; - case 12: - strcpy(cur_event.name, "Sensor Disabled"); - break; - case 13: - strcpy(cur_event.name, "Sensor Enabled"); - break; - case 14: - strcpy(cur_event.name, "O2 Backup"); - break; - case 15: - strcpy(cur_event.name, "Peer Down"); - break; - case 16: - strcpy(cur_event.name, "HS Down"); - break; - case 17: - strcpy(cur_event.name, "Inconsistent"); - break; - case 18: - // key pressed - probably not - // interesting to view on profile - break; - case 19: - // obsolete - strcpy(cur_event.name, "SCR"); - break; - case 20: - strcpy(cur_event.name, "Above Stop"); - break; - case 21: - strcpy(cur_event.name, "Safety Miss"); - break; - case 22: - strcpy(cur_event.name, "Fatal"); - break; - case 23: - strcpy(cur_event.name, "Diluent"); - break; - case 24: - strcpy(cur_event.name, "gaschange"); - cur_event.type = SAMPLE_EVENT_GASCHANGE2; - cur_event.value = ptr[7] << 8 ^ ptr[6]; - event_end(); - // This is both a mode change and a gas change event - // so we encode it as two separate events. - event_start(); - strcpy(cur_event.name, "Change Mode"); - switch (ptr[8]) { - case 1: - strcat(cur_event.name, ": OC"); - break; - case 2: - strcat(cur_event.name, ": CCR"); - break; - case 3: - strcat(cur_event.name, ": mCCR"); - break; - case 4: - strcat(cur_event.name, ": Free"); - break; - case 5: - strcat(cur_event.name, ": Gauge"); - break; - case 6: - strcat(cur_event.name, ": ASCR"); - break; - case 7: - strcat(cur_event.name, ": PSCR"); - break; - default: - break; - } - event_end(); - break; - case 25: - strcpy(cur_event.name, "CCR O2 solenoid opened/closed"); - break; - case 26: - strcpy(cur_event.name, "User mark"); - break; - case 27: - snprintf(cur_event.name, MAX_EVENT_NAME, "%sGF Switch (%d/%d)", ptr[6] ? "Bailout, ": "", ptr[7], ptr[8]); - break; - case 28: - strcpy(cur_event.name, "Peer Up"); - break; - case 29: - strcpy(cur_event.name, "HS Up"); - break; - case 30: - snprintf(cur_event.name, MAX_EVENT_NAME, "CNS %d%%", ptr[6]); - break; - default: - // No values above 30 had any description - break; - } - event_end(); - break; - case 6: - /* device configuration */ - break; - case 7: - /* measure record */ - /* Po2 sample? Solenoid inject? */ - //fprintf(stderr, "%02X %02X%02X %02X%02X\n", ptr[5], ptr[6], ptr[7], ptr[8], ptr[9]); - break; - default: - /* Unknown... */ - break; - } - ptr += 16; - } - divecomputer_end(); - dive_end(); - return 0; -} - - -void parse_xml_init(void) -{ - LIBXML_TEST_VERSION -} - -void parse_xml_exit(void) -{ - xmlCleanupParser(); -} - -static struct xslt_files { - const char *root; - const char *file; - const char *attribute; -} xslt_files[] = { - { "SUUNTO", "SuuntoSDM.xslt", NULL }, - { "Dive", "SuuntoDM4.xslt", "xmlns" }, - { "Dive", "shearwater.xslt", "version" }, - { "JDiveLog", "jdivelog2subsurface.xslt", NULL }, - { "dives", "MacDive.xslt", NULL }, - { "DIVELOGSDATA", "divelogs.xslt", NULL }, - { "uddf", "uddf.xslt", NULL }, - { "UDDF", "uddf.xslt", NULL }, - { "profile", "udcf.xslt", NULL }, - { "Divinglog", "DivingLog.xslt", NULL }, - { "csv", "csv2xml.xslt", NULL }, - { "sensuscsv", "sensuscsv.xslt", NULL }, - { "SubsurfaceCSV", "subsurfacecsv.xslt", NULL }, - { "manualcsv", "manualcsv2xml.xslt", NULL }, - { "logbook", "DiveLog.xslt", NULL }, - { NULL, } - }; - -static xmlDoc *test_xslt_transforms(xmlDoc *doc, const char **params) -{ - struct xslt_files *info = xslt_files; - xmlDoc *transformed; - xsltStylesheetPtr xslt = NULL; - xmlNode *root_element = xmlDocGetRootElement(doc); - char *attribute; - - while (info->root) { - if ((strcasecmp((const char *)root_element->name, info->root) == 0)) { - if (info->attribute == NULL) - break; - else if (xmlGetProp(root_element, (const xmlChar *)info->attribute) != NULL) - break; - } - info++; - } - - if (info->root) { - attribute = (char *)xmlGetProp(xmlFirstElementChild(root_element), (const xmlChar *)"name"); - if (attribute) { - if (strcasecmp(attribute, "subsurface") == 0) { - free((void *)attribute); - return doc; - } - free((void *)attribute); - } - xmlSubstituteEntitiesDefault(1); - xslt = get_stylesheet(info->file); - if (xslt == NULL) { - report_error(translate("gettextFromC", "Can't open stylesheet %s"), info->file); - return doc; - } - transformed = xsltApplyStylesheet(xslt, doc, params); - xmlFreeDoc(doc); - xsltFreeStylesheet(xslt); - - return transformed; - } - return doc; -} diff --git a/subsurface-core/planner.c b/subsurface-core/planner.c deleted file mode 100644 index 705aad1cb..000000000 --- a/subsurface-core/planner.c +++ /dev/null @@ -1,1471 +0,0 @@ -/* planner.c - * - * code that allows us to plan future dives - * - * (c) Dirk Hohndel 2013 - */ -#include -#include -#include -#include -#include "dive.h" -#include "deco.h" -#include "divelist.h" -#include "planner.h" -#include "gettext.h" -#include "libdivecomputer/parser.h" - -#define TIMESTEP 2 /* second */ -#define DECOTIMESTEP 60 /* seconds. Unit of deco stop times */ - -int decostoplevels_metric[] = { 0, 3000, 6000, 9000, 12000, 15000, 18000, 21000, 24000, 27000, - 30000, 33000, 36000, 39000, 42000, 45000, 48000, 51000, 54000, 57000, - 60000, 63000, 66000, 69000, 72000, 75000, 78000, 81000, 84000, 87000, - 90000, 100000, 110000, 120000, 130000, 140000, 150000, 160000, 170000, - 180000, 190000, 200000, 220000, 240000, 260000, 280000, 300000, - 320000, 340000, 360000, 380000 }; -int decostoplevels_imperial[] = { 0, 3048, 6096, 9144, 12192, 15240, 18288, 21336, 24384, 27432, - 30480, 33528, 36576, 39624, 42672, 45720, 48768, 51816, 54864, 57912, - 60960, 64008, 67056, 70104, 73152, 76200, 79248, 82296, 85344, 88392, - 91440, 101600, 111760, 121920, 132080, 142240, 152400, 162560, 172720, - 182880, 193040, 203200, 223520, 243840, 264160, 284480, 304800, - 325120, 345440, 365760, 386080 }; - -double plangflow, plangfhigh; -bool plan_verbatim, plan_display_runtime, plan_display_duration, plan_display_transitions; - -pressure_t first_ceiling_pressure, max_bottom_ceiling_pressure = {}; - -const char *disclaimer; - -#if DEBUG_PLAN -void dump_plan(struct diveplan *diveplan) -{ - struct divedatapoint *dp; - struct tm tm; - - if (!diveplan) { - printf("Diveplan NULL\n"); - return; - } - utc_mkdate(diveplan->when, &tm); - - printf("\nDiveplan @ %04d-%02d-%02d %02d:%02d:%02d (surfpres %dmbar):\n", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec, - diveplan->surface_pressure); - dp = diveplan->dp; - while (dp) { - printf("\t%3u:%02u: %dmm gas: %d o2 %d h2\n", FRACTION(dp->time, 60), dp->depth, get_o2(&dp->gasmix), get_he(&dp->gasmix)); - dp = dp->next; - } -} -#endif - -bool diveplan_empty(struct diveplan *diveplan) -{ - struct divedatapoint *dp; - if (!diveplan || !diveplan->dp) - return true; - dp = diveplan->dp; - while (dp) { - if (dp->time) - return false; - dp = dp->next; - } - return true; -} - -/* get the gas at a certain time during the dive */ -void get_gas_at_time(struct dive *dive, struct divecomputer *dc, duration_t time, struct gasmix *gas) -{ - // we always start with the first gas, so that's our gas - // unless an event tells us otherwise - struct event *event = dc->events; - *gas = dive->cylinder[0].gasmix; - while (event && event->time.seconds <= time.seconds) { - if (!strcmp(event->name, "gaschange")) { - int cylinder_idx = get_cylinder_index(dive, event); - *gas = dive->cylinder[cylinder_idx].gasmix; - } - event = event->next; - } -} - -int get_gasidx(struct dive *dive, struct gasmix *mix) -{ - int gasidx = -1; - - while (++gasidx < MAX_CYLINDERS) - if (gasmix_distance(&dive->cylinder[gasidx].gasmix, mix) < 100) - return gasidx; - return -1; -} - -void interpolate_transition(struct dive *dive, duration_t t0, duration_t t1, depth_t d0, depth_t d1, const struct gasmix *gasmix, o2pressure_t po2) -{ - uint32_t j; - - for (j = t0.seconds; j < t1.seconds; j++) { - int depth = interpolate(d0.mm, d1.mm, j - t0.seconds, t1.seconds - t0.seconds); - add_segment(depth_to_bar(depth, dive), gasmix, 1, po2.mbar, dive, prefs.bottomsac); - } - if (d1.mm > d0.mm) - calc_crushing_pressure(depth_to_bar(d1.mm, &displayed_dive)); -} - -/* returns the tissue tolerance at the end of this (partial) dive */ -void tissue_at_end(struct dive *dive, char **cached_datap) -{ - struct divecomputer *dc; - struct sample *sample, *psample; - int i; - depth_t lastdepth = {}; - duration_t t0 = {}, t1 = {}; - struct gasmix gas; - - if (!dive) - return; - if (*cached_datap) { - restore_deco_state(*cached_datap); - } else { - init_decompression(dive); - cache_deco_state(cached_datap); - } - dc = &dive->dc; - if (!dc->samples) - return; - psample = sample = dc->sample; - - for (i = 0; i < dc->samples; i++, sample++) { - o2pressure_t setpoint; - - if (i) - setpoint = sample[-1].setpoint; - else - setpoint = sample[0].setpoint; - - t1 = sample->time; - get_gas_at_time(dive, dc, t0, &gas); - if (i > 0) - lastdepth = psample->depth; - - /* The ceiling in the deeper portion of a multilevel dive is sometimes critical for the VPM-B - * Boyle's law compensation. We should check the ceiling prior to ascending during the bottom - * portion of the dive. The maximum ceiling might be reached while ascending, but testing indicates - * that it is only marginally deeper than the ceiling at the start of ascent. - * Do not set the first_ceiling_pressure variable (used for the Boyle's law compensation calculation) - * at this stage, because it would interfere with calculating the ceiling at the end of the bottom - * portion of the dive. - * Remember the value for later. - */ - if ((prefs.deco_mode == VPMB) && (lastdepth.mm > sample->depth.mm)) { - pressure_t ceiling_pressure; - nuclear_regeneration(t0.seconds); - vpmb_start_gradient(); - ceiling_pressure.mbar = depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(dive, - depth_to_bar(lastdepth.mm, dive)), - dive->surface_pressure.mbar / 1000.0, - dive, - 1), - dive); - if (ceiling_pressure.mbar > max_bottom_ceiling_pressure.mbar) - max_bottom_ceiling_pressure.mbar = ceiling_pressure.mbar; - } - - interpolate_transition(dive, t0, t1, lastdepth, sample->depth, &gas, setpoint); - psample = sample; - t0 = t1; - } -} - - -/* if a default cylinder is set, use that */ -void fill_default_cylinder(cylinder_t *cyl) -{ - const char *cyl_name = prefs.default_cylinder; - struct tank_info_t *ti = tank_info; - pressure_t pO2 = {.mbar = 1600}; - - if (!cyl_name) - return; - while (ti->name != NULL) { - if (strcmp(ti->name, cyl_name) == 0) - break; - ti++; - } - if (ti->name == NULL) - /* didn't find it */ - return; - cyl->type.description = strdup(ti->name); - if (ti->ml) { - cyl->type.size.mliter = ti->ml; - cyl->type.workingpressure.mbar = ti->bar * 1000; - } else { - cyl->type.workingpressure.mbar = psi_to_mbar(ti->psi); - if (ti->psi) - cyl->type.size.mliter = cuft_to_l(ti->cuft) * 1000 / bar_to_atm(psi_to_bar(ti->psi)); - } - // MOD of air - cyl->depth = gas_mod(&cyl->gasmix, pO2, &displayed_dive, 1); -} - -/* make sure that the gas we are switching to is represented in our - * list of cylinders */ -static int verify_gas_exists(struct gasmix mix_in) -{ - int i; - cylinder_t *cyl; - - for (i = 0; i < MAX_CYLINDERS; i++) { - cyl = displayed_dive.cylinder + i; - if (cylinder_nodata(cyl)) - continue; - if (gasmix_distance(&cyl->gasmix, &mix_in) < 100) - return i; - } - fprintf(stderr, "this gas %s should have been on the cylinder list\nThings will fail now\n", gasname(&mix_in)); - return -1; -} - -/* calculate the new end pressure of the cylinder, based on its current end pressure and the - * latest segment. */ -static void update_cylinder_pressure(struct dive *d, int old_depth, int new_depth, int duration, int sac, cylinder_t *cyl, bool in_deco) -{ - volume_t gas_used; - pressure_t delta_p; - depth_t mean_depth; - int factor = 1000; - - if (d->dc.divemode == PSCR) - factor = prefs.pscr_ratio; - - if (!cyl) - return; - mean_depth.mm = (old_depth + new_depth) / 2; - gas_used.mliter = depth_to_atm(mean_depth.mm, d) * sac / 60 * duration * factor / 1000; - cyl->gas_used.mliter += gas_used.mliter; - if (in_deco) - cyl->deco_gas_used.mliter += gas_used.mliter; - if (cyl->type.size.mliter) { - delta_p.mbar = gas_used.mliter * 1000.0 / cyl->type.size.mliter; - cyl->end.mbar -= delta_p.mbar; - } -} - -/* simply overwrite the data in the displayed_dive - * return false if something goes wrong */ -static void create_dive_from_plan(struct diveplan *diveplan, bool track_gas) -{ - struct divedatapoint *dp; - struct divecomputer *dc; - struct sample *sample; - struct gasmix oldgasmix; - struct event *ev; - cylinder_t *cyl; - int oldpo2 = 0; - int lasttime = 0; - int lastdepth = 0; - enum dive_comp_type type = displayed_dive.dc.divemode; - - if (!diveplan || !diveplan->dp) - return; -#if DEBUG_PLAN & 4 - printf("in create_dive_from_plan\n"); - dump_plan(diveplan); -#endif - displayed_dive.salinity = diveplan->salinity; - // reset the cylinders and clear out the samples and events of the - // displayed dive so we can restart - reset_cylinders(&displayed_dive, track_gas); - dc = &displayed_dive.dc; - dc->when = displayed_dive.when = diveplan->when; - free(dc->sample); - dc->sample = NULL; - dc->samples = 0; - dc->alloc_samples = 0; - while ((ev = dc->events)) { - dc->events = dc->events->next; - free(ev); - } - dp = diveplan->dp; - cyl = &displayed_dive.cylinder[0]; - oldgasmix = cyl->gasmix; - sample = prepare_sample(dc); - sample->setpoint.mbar = dp->setpoint; - sample->sac.mliter = prefs.bottomsac; - oldpo2 = dp->setpoint; - if (track_gas && cyl->type.workingpressure.mbar) - sample->cylinderpressure.mbar = cyl->end.mbar; - sample->manually_entered = true; - finish_sample(dc); - while (dp) { - struct gasmix gasmix = dp->gasmix; - int po2 = dp->setpoint; - if (dp->setpoint) - type = CCR; - int time = dp->time; - int depth = dp->depth; - - if (time == 0) { - /* special entries that just inform the algorithm about - * additional gases that are available */ - if (verify_gas_exists(gasmix) < 0) - goto gas_error_exit; - dp = dp->next; - continue; - } - - /* Check for SetPoint change */ - if (oldpo2 != po2) { - /* this is a bad idea - we should get a different SAMPLE_EVENT type - * reserved for this in libdivecomputer... overloading SMAPLE_EVENT_PO2 - * with a different meaning will only cause confusion elsewhere in the code */ - add_event(dc, lasttime, SAMPLE_EVENT_PO2, 0, po2, "SP change"); - oldpo2 = po2; - } - - /* Make sure we have the new gas, and create a gas change event */ - if (gasmix_distance(&gasmix, &oldgasmix) > 0) { - int idx; - if ((idx = verify_gas_exists(gasmix)) < 0) - goto gas_error_exit; - /* need to insert a first sample for the new gas */ - add_gas_switch_event(&displayed_dive, dc, lasttime + 1, idx); - cyl = &displayed_dive.cylinder[idx]; - sample = prepare_sample(dc); - sample[-1].setpoint.mbar = po2; - sample->time.seconds = lasttime + 1; - sample->depth.mm = lastdepth; - sample->manually_entered = dp->entered; - sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac; - if (track_gas && cyl->type.workingpressure.mbar) - sample->cylinderpressure.mbar = cyl->sample_end.mbar; - finish_sample(dc); - oldgasmix = gasmix; - } - /* Create sample */ - sample = prepare_sample(dc); - /* set po2 at beginning of this segment */ - /* and keep it valid for last sample - where it likely doesn't matter */ - sample[-1].setpoint.mbar = po2; - sample->setpoint.mbar = po2; - sample->time.seconds = lasttime = time; - sample->depth.mm = lastdepth = depth; - sample->manually_entered = dp->entered; - sample->sac.mliter = dp->entered ? prefs.bottomsac : prefs.decosac; - if (track_gas && !sample[-1].setpoint.mbar) { /* Don't track gas usage for CCR legs of dive */ - update_cylinder_pressure(&displayed_dive, sample[-1].depth.mm, depth, time - sample[-1].time.seconds, - dp->entered ? diveplan->bottomsac : diveplan->decosac, cyl, !dp->entered); - if (cyl->type.workingpressure.mbar) - sample->cylinderpressure.mbar = cyl->end.mbar; - } - finish_sample(dc); - dp = dp->next; - } - dc->divemode = type; -#if DEBUG_PLAN & 32 - save_dive(stdout, &displayed_dive); -#endif - return; - -gas_error_exit: - report_error(translate("gettextFromC", "Too many gas mixes")); - return; -} - -void free_dps(struct diveplan *diveplan) -{ - if (!diveplan) - return; - struct divedatapoint *dp = diveplan->dp; - while (dp) { - struct divedatapoint *ndp = dp->next; - free(dp); - dp = ndp; - } - diveplan->dp = NULL; -} - -struct divedatapoint *create_dp(int time_incr, int depth, struct gasmix gasmix, int po2) -{ - struct divedatapoint *dp; - - dp = malloc(sizeof(struct divedatapoint)); - dp->time = time_incr; - dp->depth = depth; - dp->gasmix = gasmix; - dp->setpoint = po2; - dp->entered = false; - dp->next = NULL; - return dp; -} - -void add_to_end_of_diveplan(struct diveplan *diveplan, struct divedatapoint *dp) -{ - struct divedatapoint **lastdp = &diveplan->dp; - struct divedatapoint *ldp = *lastdp; - int lasttime = 0; - while (*lastdp) { - ldp = *lastdp; - if (ldp->time > lasttime) - lasttime = ldp->time; - lastdp = &(*lastdp)->next; - } - *lastdp = dp; - if (ldp && dp->time != 0) - dp->time += lasttime; -} - -struct divedatapoint *plan_add_segment(struct diveplan *diveplan, int duration, int depth, struct gasmix gasmix, int po2, bool entered) -{ - struct divedatapoint *dp = create_dp(duration, depth, gasmix, po2); - dp->entered = entered; - add_to_end_of_diveplan(diveplan, dp); - return (dp); -} - -struct gaschanges { - int depth; - int gasidx; -}; - - -static struct gaschanges *analyze_gaslist(struct diveplan *diveplan, int *gaschangenr, int depth, int *asc_cylinder) -{ - struct gasmix gas; - int nr = 0; - struct gaschanges *gaschanges = NULL; - struct divedatapoint *dp = diveplan->dp; - int best_depth = displayed_dive.cylinder[*asc_cylinder].depth.mm; - while (dp) { - if (dp->time == 0) { - gas = dp->gasmix; - if (dp->depth <= depth) { - int i = 0; - nr++; - gaschanges = realloc(gaschanges, nr * sizeof(struct gaschanges)); - while (i < nr - 1) { - if (dp->depth < gaschanges[i].depth) { - memmove(gaschanges + i + 1, gaschanges + i, (nr - i - 1) * sizeof(struct gaschanges)); - break; - } - i++; - } - gaschanges[i].depth = dp->depth; - gaschanges[i].gasidx = get_gasidx(&displayed_dive, &gas); - assert(gaschanges[i].gasidx != -1); - } else { - /* is there a better mix to start deco? */ - if (dp->depth < best_depth) { - best_depth = dp->depth; - *asc_cylinder = get_gasidx(&displayed_dive, &gas); - } - } - } - dp = dp->next; - } - *gaschangenr = nr; -#if DEBUG_PLAN & 16 - for (nr = 0; nr < *gaschangenr; nr++) { - int idx = gaschanges[nr].gasidx; - printf("gaschange nr %d: @ %5.2lfm gasidx %d (%s)\n", nr, gaschanges[nr].depth / 1000.0, - idx, gasname(&displayed_dive.cylinder[idx].gasmix)); - } -#endif - return gaschanges; -} - -/* sort all the stops into one ordered list */ -static int *sort_stops(int *dstops, int dnr, struct gaschanges *gstops, int gnr) -{ - int i, gi, di; - int total = dnr + gnr; - int *stoplevels = malloc(total * sizeof(int)); - - /* no gaschanges */ - if (gnr == 0) { - memcpy(stoplevels, dstops, dnr * sizeof(int)); - return stoplevels; - } - i = total - 1; - gi = gnr - 1; - di = dnr - 1; - while (i >= 0) { - if (dstops[di] > gstops[gi].depth) { - stoplevels[i] = dstops[di]; - di--; - } else if (dstops[di] == gstops[gi].depth) { - stoplevels[i] = dstops[di]; - di--; - gi--; - } else { - stoplevels[i] = gstops[gi].depth; - gi--; - } - i--; - if (di < 0) { - while (gi >= 0) - stoplevels[i--] = gstops[gi--].depth; - break; - } - if (gi < 0) { - while (di >= 0) - stoplevels[i--] = dstops[di--]; - break; - } - } - while (i >= 0) - stoplevels[i--] = 0; - -#if DEBUG_PLAN & 16 - int k; - for (k = gnr + dnr - 1; k >= 0; k--) { - printf("stoplevel[%d]: %5.2lfm\n", k, stoplevels[k] / 1000.0); - if (stoplevels[k] == 0) - break; - } -#endif - return stoplevels; -} - -static void add_plan_to_notes(struct diveplan *diveplan, struct dive *dive, bool show_disclaimer, int error) -{ - const unsigned int sz_buffer = 2000000; - const unsigned int sz_temp = 100000; - char *buffer = (char *)malloc(sz_buffer); - char *temp = (char *)malloc(sz_temp); - char *deco, *segmentsymbol; - static char buf[1000]; - int len, lastdepth = 0, lasttime = 0, lastsetpoint = -1, newdepth = 0, lastprintdepth = 0, lastprintsetpoint = -1; - struct gasmix lastprintgasmix = {{ -1 }, { -1 }}; - struct divedatapoint *dp = diveplan->dp; - bool gaschange_after = !plan_verbatim; - bool gaschange_before; - bool lastentered = true; - struct divedatapoint *nextdp = NULL; - - plan_verbatim = prefs.verbatim_plan; - plan_display_runtime = prefs.display_runtime; - plan_display_duration = prefs.display_duration; - plan_display_transitions = prefs.display_transitions; - - if (prefs.deco_mode == VPMB) { - deco = "VPM-B"; - } else { - deco = "BUHLMANN"; - } - - snprintf(buf, sizeof(buf), translate("gettextFromC", "DISCLAIMER / WARNING: THIS IS A NEW IMPLEMENTATION OF THE %s " - "ALGORITHM AND A DIVE PLANNER IMPLEMENTATION BASED ON THAT WHICH HAS " - "RECEIVED ONLY A LIMITED AMOUNT OF TESTING. WE STRONGLY RECOMMEND NOT TO " - "PLAN DIVES SIMPLY BASED ON THE RESULTS GIVEN HERE."), deco); - disclaimer = buf; - - if (!dp) { - free((void *)buffer); - free((void *)temp); - return; - } - - if (error) { - snprintf(temp, sz_temp, "%s", - translate("gettextFromC", "Decompression calculation aborted due to excessive time")); - snprintf(buffer, sz_buffer, "%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 )) { - // Print a symbol to indicate whether segment is an ascent, descent, constant depth (user entered) or deco stop - if (isascent) - segmentsymbol = "➚"; // up-right arrow for ascent - else if (dp->depth > lastdepth) - segmentsymbol = "➘"; // down-right arrow for descent - else if (dp->entered) - segmentsymbol = "➙"; // right arrow for entered entered segment at constant depth - else - segmentsymbol = "➖"; // heavey minus sign for deco stop - - len += snprintf(buffer + len, sz_buffer - len, "", segmentsymbol); - - 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); - if (!plan_verbatim) - len += snprintf(buffer + len, sz_buffer - len, "
%s%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) -{ - (void) bottom_time; - /* We need to make this configurable */ - - /* As an example (and possibly reasonable default) this is the Tech 1 provedure according - * to http://www.globalunderwaterexplorers.org/files/Standards_and_Procedures/SOP_Manual_Ver2.0.2.pdf */ - - if (depth * 4 > avg_depth * 3) { - return prefs.ascrate75; - } else { - if (depth * 2 > avg_depth) { - return prefs.ascrate50; - } else { - if (depth > 6000) - return prefs.ascratestops; - else - return prefs.ascratelast6m; - } - } -} - -void track_ascent_gas(int depth, cylinder_t *cylinder, int avg_depth, int bottom_time, bool safety_stop) -{ - while (depth > 0) { - int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; - if (deltad > depth) - deltad = depth; - update_cylinder_pressure(&displayed_dive, depth, depth - deltad, TIMESTEP, prefs.decosac, cylinder, true); - if (depth <= 5000 && depth >= (5000 - deltad) && safety_stop) { - update_cylinder_pressure(&displayed_dive, 5000, 5000, 180, prefs.decosac, cylinder, true); - safety_stop = false; - } - depth -= deltad; - } -} - -// Determine whether ascending to the next stop will break the ceiling. Return true if the ascent is ok, false if it isn't. -bool trial_ascent(int trial_depth, int stoplevel, int avg_depth, int bottom_time, struct gasmix *gasmix, int po2, double surface_pressure) -{ - - bool clear_to_ascend = true; - char *trial_cache = NULL; - - // For consistency with other VPM-B implementations, we should not start the ascent while the ceiling is - // deeper than the next stop (thus the offgasing during the ascent is ignored). - // However, we still need to make sure we don't break the ceiling due to on-gassing during ascent. - if (prefs.deco_mode == VPMB && (deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, - depth_to_bar(stoplevel, &displayed_dive)), - surface_pressure, &displayed_dive, 1) > stoplevel)) - return false; - - cache_deco_state(&trial_cache); - while (trial_depth > stoplevel) { - int deltad = ascent_velocity(trial_depth, avg_depth, bottom_time) * TIMESTEP; - if (deltad > trial_depth) /* don't test against depth above surface */ - deltad = trial_depth; - add_segment(depth_to_bar(trial_depth, &displayed_dive), - gasmix, - TIMESTEP, po2, &displayed_dive, prefs.decosac); - if (deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, depth_to_bar(trial_depth, &displayed_dive)), - surface_pressure, &displayed_dive, 1) > trial_depth - deltad) { - /* We should have stopped */ - clear_to_ascend = false; - break; - } - trial_depth -= deltad; - } - restore_deco_state(trial_cache); - free(trial_cache); - return clear_to_ascend; -} - -/* Determine if there is enough gas for the dive. Return true if there is enough. - * Also return true if this cannot be calculated because the cylinder doesn't have - * size or a starting pressure. - */ -bool enough_gas(int current_cylinder) -{ - cylinder_t *cyl; - cyl = &displayed_dive.cylinder[current_cylinder]; - - if (!cyl->start.mbar) - return true; - if (cyl->type.size.mliter) - return (float) (cyl->end.mbar - prefs.reserve_gas) * cyl->type.size.mliter / 1000.0 > (float) cyl->deco_gas_used.mliter; - else - return true; -} - -// Work out the stops. Return value is if there were any mandatory stops. - -bool plan(struct diveplan *diveplan, char **cached_datap, bool is_planner, bool show_disclaimer) -{ - int bottom_depth; - int bottom_gi; - int bottom_stopidx; - bool is_final_plan = true; - int deco_time; - int previous_deco_time; - char *bottom_cache = NULL; - struct sample *sample; - int po2; - int transitiontime, gi; - int current_cylinder; - int stopidx; - int depth; - struct gaschanges *gaschanges = NULL; - int gaschangenr; - int *decostoplevels; - int decostoplevelcount; - int *stoplevels = NULL; - bool stopping = false; - bool pendinggaschange = false; - int clock, previous_point_time; - int avg_depth, max_depth, bottom_time = 0; - int last_ascend_rate; - int best_first_ascend_cylinder; - struct gasmix gas, bottom_gas; - int o2time = 0; - int breaktime = -1; - int breakcylinder = 0; - int error = 0; - bool decodive = false; - - set_gf(diveplan->gflow, diveplan->gfhigh, prefs.gf_low_at_maxdepth); - if (!diveplan->surface_pressure) - diveplan->surface_pressure = SURFACE_PRESSURE; - displayed_dive.surface_pressure.mbar = diveplan->surface_pressure; - clear_deco(displayed_dive.surface_pressure.mbar / 1000.0); - max_bottom_ceiling_pressure.mbar = first_ceiling_pressure.mbar = 0; - create_dive_from_plan(diveplan, is_planner); - - // Do we want deco stop array in metres or feet? - if (prefs.units.length == METERS ) { - decostoplevels = decostoplevels_metric; - decostoplevelcount = sizeof(decostoplevels_metric) / sizeof(int); - } else { - decostoplevels = decostoplevels_imperial; - decostoplevelcount = sizeof(decostoplevels_imperial) / sizeof(int); - } - - /* If the user has selected last stop to be at 6m/20', we need to get rid of the 3m/10' stop. - * Otherwise reinstate the last stop 3m/10' stop. - */ - if (prefs.last_stop) - *(decostoplevels + 1) = 0; - else - *(decostoplevels + 1) = M_OR_FT(3,10); - - /* Let's start at the last 'sample', i.e. the last manually entered waypoint. */ - sample = &displayed_dive.dc.sample[displayed_dive.dc.samples - 1]; - - get_gas_at_time(&displayed_dive, &displayed_dive.dc, sample->time, &gas); - - po2 = sample->setpoint.mbar; - if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) { - report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); - current_cylinder = 0; - } - depth = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].depth.mm; - average_max_depth(diveplan, &avg_depth, &max_depth); - last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); - - /* if all we wanted was the dive just get us back to the surface */ - if (!is_planner) { - transitiontime = depth / 75; /* this still needs to be made configurable */ - plan_add_segment(diveplan, transitiontime, 0, gas, po2, false); - create_dive_from_plan(diveplan, is_planner); - return(false); - } - -#if DEBUG_PLAN & 4 - printf("gas %s\n", gasname(&gas)); - printf("depth %5.2lfm \n", depth / 1000.0); -#endif - - best_first_ascend_cylinder = current_cylinder; - /* Find the gases available for deco */ - - if (po2) { // Don't change gas in CCR mode - gaschanges = NULL; - gaschangenr = 0; - } else { - gaschanges = analyze_gaslist(diveplan, &gaschangenr, depth, &best_first_ascend_cylinder); - } - /* Find the first potential decostopdepth above current depth */ - for (stopidx = 0; stopidx < decostoplevelcount; stopidx++) - if (*(decostoplevels + stopidx) >= depth) - break; - if (stopidx > 0) - stopidx--; - /* Stoplevels are either depths of gas changes or potential deco stop depths. */ - stoplevels = sort_stops(decostoplevels, stopidx + 1, gaschanges, gaschangenr); - stopidx += gaschangenr; - - /* Keep time during the ascend */ - bottom_time = clock = previous_point_time = displayed_dive.dc.sample[displayed_dive.dc.samples - 1].time.seconds; - gi = gaschangenr - 1; - - /* Set tissue tolerance and initial vpmb gradient at start of ascent phase */ - tissue_at_end(&displayed_dive, cached_datap); - nuclear_regeneration(clock); - vpmb_start_gradient(); - - if(prefs.deco_mode == RECREATIONAL) { - bool safety_stop = prefs.safetystop && max_depth >= 10000; - track_ascent_gas(depth, &displayed_dive.cylinder[current_cylinder], avg_depth, bottom_time, safety_stop); - // How long can we stay at the current depth and still directly ascent to the surface? - do { - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - DECOTIMESTEP, po2, &displayed_dive, prefs.bottomsac); - update_cylinder_pressure(&displayed_dive, depth, depth, DECOTIMESTEP, prefs.bottomsac, &displayed_dive.cylinder[current_cylinder], false); - clock += DECOTIMESTEP; - } while (trial_ascent(depth, 0, avg_depth, bottom_time, &displayed_dive.cylinder[current_cylinder].gasmix, - po2, diveplan->surface_pressure / 1000.0) && - enough_gas(current_cylinder)); - - // We did stay one DECOTIMESTEP too many. - // In the best of all worlds, we would roll back also the last add_segment in terms of caching deco state, but - // let's ignore that since for the eventual ascent in recreational mode, nobody looks at the ceiling anymore, - // so we don't really have to compute the deco state. - update_cylinder_pressure(&displayed_dive, depth, depth, -DECOTIMESTEP, prefs.bottomsac, &displayed_dive.cylinder[current_cylinder], false); - clock -= DECOTIMESTEP; - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, true); - previous_point_time = clock; - do { - /* Ascend to surface */ - int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; - if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) { - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); - } - if (depth - deltad < 0) - deltad = depth; - - clock += TIMESTEP; - depth -= deltad; - if (depth <= 5000 && depth >= (5000 - deltad) && safety_stop) { - plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false); - previous_point_time = clock; - clock += 180; - plan_add_segment(diveplan, clock - previous_point_time, 5000, gas, po2, false); - previous_point_time = clock; - safety_stop = false; - } - } while (depth > 0); - plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false); - create_dive_from_plan(diveplan, is_planner); - add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error); - fixup_dc_duration(&displayed_dive.dc); - - free(stoplevels); - free(gaschanges); - return(false); - } - - if (best_first_ascend_cylinder != current_cylinder) { - current_cylinder = best_first_ascend_cylinder; - gas = displayed_dive.cylinder[current_cylinder].gasmix; - -#if DEBUG_PLAN & 16 - printf("switch to gas %d (%d/%d) @ %5.2lfm\n", best_first_ascend_cylinder, - (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[best_first_ascend_cylinder].depth / 1000.0); -#endif - } - - // VPM-B or Buehlmann Deco - tissue_at_end(&displayed_dive, cached_datap); - previous_deco_time = 100000000; - deco_time = 10000000; - cache_deco_state(&bottom_cache); // Lets us make several iterations - bottom_depth = depth; - bottom_gi = gi; - bottom_gas = gas; - bottom_stopidx = stopidx; - - //CVA - do { - is_final_plan = (prefs.deco_mode == BUEHLMANN) || (previous_deco_time - deco_time < 10); // CVA time converges - if (deco_time != 10000000) - vpmb_next_gradient(deco_time, diveplan->surface_pressure / 1000.0); - - previous_deco_time = deco_time; - restore_deco_state(bottom_cache); - - depth = bottom_depth; - gi = bottom_gi; - clock = previous_point_time = bottom_time; - gas = bottom_gas; - stopping = false; - decodive = false; - stopidx = bottom_stopidx; - breaktime = -1; - breakcylinder = 0; - o2time = 0; - first_ceiling_pressure.mbar = depth_to_mbar(deco_allowed_depth(tissue_tolerance_calc(&displayed_dive, - depth_to_bar(depth, &displayed_dive)), - diveplan->surface_pressure / 1000.0, - &displayed_dive, - 1), - &displayed_dive); - if (max_bottom_ceiling_pressure.mbar > first_ceiling_pressure.mbar) - first_ceiling_pressure.mbar = max_bottom_ceiling_pressure.mbar; - - last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); - if ((current_cylinder = get_gasidx(&displayed_dive, &gas)) == -1) { - report_error(translate("gettextFromC", "Can't find gas %s"), gasname(&gas)); - current_cylinder = 0; - } - - while (1) { - /* We will break out when we hit the surface */ - do { - /* Ascend to next stop depth */ - int deltad = ascent_velocity(depth, avg_depth, bottom_time) * TIMESTEP; - if (ascent_velocity(depth, avg_depth, bottom_time) != last_ascend_rate) { - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - stopping = false; - last_ascend_rate = ascent_velocity(depth, avg_depth, bottom_time); - } - if (depth - deltad < stoplevels[stopidx]) - deltad = depth - stoplevels[stopidx]; - - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - TIMESTEP, po2, &displayed_dive, prefs.decosac); - clock += TIMESTEP; - depth -= deltad; - } while (depth > 0 && depth > stoplevels[stopidx]); - - if (depth <= 0) - break; /* We are at the surface */ - - if (gi >= 0 && stoplevels[stopidx] <= gaschanges[gi].depth) { - /* We have reached a gas change. - * Record this in the dive plan */ - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - stopping = true; - - /* Check we need to change cylinder. - * We might not if the cylinder was chosen by the user - * or user has selected only to switch only at required stops. - * If current gas is hypoxic, we want to switch asap */ - - if (current_cylinder != gaschanges[gi].gasidx) { - if (!prefs.switch_at_req_stop || - !trial_ascent(depth, stoplevels[stopidx - 1], avg_depth, bottom_time, - &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0) || get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) < 160) { - current_cylinder = gaschanges[gi].gasidx; - gas = displayed_dive.cylinder[current_cylinder].gasmix; -#if DEBUG_PLAN & 16 - printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi].gasidx, - (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi].depth / 1000.0); -#endif - /* Stop for the minimum duration to switch gas */ - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac); - clock += prefs.min_switch_duration; - if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) - o2time += prefs.min_switch_duration; - } else { - /* The user has selected the option to switch gas only at required stops. - * Remember that we are waiting to switch gas - */ - pendinggaschange = true; - } - } - gi--; - } - --stopidx; - - /* Save the current state and try to ascend to the next stopdepth */ - while (1) { - /* Check if ascending to next stop is clear, go back and wait if we hit the ceiling on the way */ - if (trial_ascent(depth, stoplevels[stopidx], avg_depth, bottom_time, - &displayed_dive.cylinder[current_cylinder].gasmix, po2, diveplan->surface_pressure / 1000.0)) - break; /* We did not hit the ceiling */ - - /* Add a minute of deco time and then try again */ - decodive = true; - if (!stopping) { - /* The last segment was an ascend segment. - * Add a waypoint for start of this deco stop */ - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - stopping = true; - } - - /* Are we waiting to switch gas? - * Occurs when the user has selected the option to switch only at required stops - */ - if (pendinggaschange) { - current_cylinder = gaschanges[gi + 1].gasidx; - gas = displayed_dive.cylinder[current_cylinder].gasmix; -#if DEBUG_PLAN & 16 - printf("switch to gas %d (%d/%d) @ %5.2lfm\n", gaschanges[gi + 1].gasidx, - (get_o2(&gas) + 5) / 10, (get_he(&gas) + 5) / 10, gaschanges[gi + 1].depth / 1000.0); -#endif - /* Stop for the minimum duration to switch gas */ - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - prefs.min_switch_duration, po2, &displayed_dive, prefs.decosac); - clock += prefs.min_switch_duration; - if (prefs.doo2breaks && get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) - o2time += prefs.min_switch_duration; - pendinggaschange = false; - } - - /* Deco stop should end when runtime is at a whole minute */ - int this_decotimestep; - this_decotimestep = DECOTIMESTEP - clock % DECOTIMESTEP; - - add_segment(depth_to_bar(depth, &displayed_dive), - &displayed_dive.cylinder[current_cylinder].gasmix, - this_decotimestep, po2, &displayed_dive, prefs.decosac); - clock += this_decotimestep; - /* Finish infinite deco */ - if(clock >= 48 * 3600 && depth >= 6000) { - error = LONGDECO; - break; - } - if (prefs.doo2breaks) { - /* The backgas breaks option limits time on oxygen to 12 minutes, followed by 6 minutes on - * backgas (first defined gas). This could be customized if there were demand. - */ - if (get_o2(&displayed_dive.cylinder[current_cylinder].gasmix) == 1000) { - o2time += DECOTIMESTEP; - if (o2time >= 12 * 60) { - breaktime = 0; - breakcylinder = current_cylinder; - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - current_cylinder = 0; - gas = displayed_dive.cylinder[current_cylinder].gasmix; - } - } else { - if (breaktime >= 0) { - breaktime += DECOTIMESTEP; - if (breaktime >= 6 * 60) { - o2time = 0; - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - current_cylinder = breakcylinder; - gas = displayed_dive.cylinder[current_cylinder].gasmix; - breaktime = -1; - } - } - } - } - } - if (stopping) { - /* Next we will ascend again. Add a waypoint if we have spend deco time */ - if (is_final_plan) - plan_add_segment(diveplan, clock - previous_point_time, depth, gas, po2, false); - previous_point_time = clock; - stopping = false; - } - } - - deco_time = clock - bottom_time; - } while (!is_final_plan); - - plan_add_segment(diveplan, clock - previous_point_time, 0, gas, po2, false); - create_dive_from_plan(diveplan, is_planner); - add_plan_to_notes(diveplan, &displayed_dive, show_disclaimer, error); - fixup_dc_duration(&displayed_dive.dc); - - free(stoplevels); - free(gaschanges); - free(bottom_cache); - return decodive; -} - -/* - * Get a value in tenths (so "10.2" == 102, "9" = 90) - * - * Return negative for errors. - */ -static int get_tenths(const char *begin, const char **endp) -{ - char *end; - int value = strtol(begin, &end, 10); - - if (begin == end) - return -1; - value *= 10; - - /* Fraction? We only look at the first digit */ - if (*end == '.') { - end++; - if (!isdigit(*end)) - return -1; - value += *end - '0'; - do { - end++; - } while (isdigit(*end)); - } - *endp = end; - return value; -} - -static int get_permille(const char *begin, const char **end) -{ - int value = get_tenths(begin, end); - if (value >= 0) { - /* Allow a percentage sign */ - if (**end == '%') - ++*end; - } - return value; -} - -int validate_gas(const char *text, struct gasmix *gas) -{ - int o2, he; - - if (!text) - return 0; - - while (isspace(*text)) - text++; - - if (!*text) - return 0; - - if (!strcasecmp(text, translate("gettextFromC", "air"))) { - o2 = O2_IN_AIR; - he = 0; - text += strlen(translate("gettextFromC", "air")); - } else if (!strcasecmp(text, translate("gettextFromC", "oxygen"))) { - o2 = 1000; - he = 0; - text += strlen(translate("gettextFromC", "oxygen")); - } else if (!strncasecmp(text, translate("gettextFromC", "ean"), 3)) { - o2 = get_permille(text + 3, &text); - he = 0; - } else { - o2 = get_permille(text, &text); - he = 0; - if (*text == '/') - he = get_permille(text + 1, &text); - } - - /* We don't want any extra crud */ - while (isspace(*text)) - text++; - if (*text) - return 0; - - /* Validate the gas mix */ - if (*text || o2 < 1 || o2 > 1000 || he < 0 || o2 + he > 1000) - return 0; - - /* Let it rip */ - gas->o2.permille = o2; - gas->he.permille = he; - return 1; -} - -int validate_po2(const char *text, int *mbar_po2) -{ - int po2; - - if (!text) - return 0; - - po2 = get_tenths(text, &text); - if (po2 < 0) - return 0; - - while (isspace(*text)) - text++; - - while (isspace(*text)) - text++; - if (*text) - return 0; - - *mbar_po2 = po2 * 100; - return 1; -} diff --git a/subsurface-core/planner.h b/subsurface-core/planner.h deleted file mode 100644 index a675989e0..000000000 --- a/subsurface-core/planner.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef PLANNER_H -#define PLANNER_H - -#define LONGDECO 1 -#define NOT_RECREATIONAL 2 - -#ifdef __cplusplus -extern "C" { -#endif - -extern int validate_gas(const char *text, struct gasmix *gas); -extern int validate_po2(const char *text, int *mbar_po2); -extern timestamp_t current_time_notz(void); -extern void set_last_stop(bool last_stop_6m); -extern void set_verbatim(bool verbatim); -extern void set_display_runtime(bool display); -extern void set_display_duration(bool display); -extern void set_display_transitions(bool display); -extern void get_gas_at_time(struct dive *dive, struct divecomputer *dc, duration_t time, struct gasmix *gas); -extern int get_gasidx(struct dive *dive, struct gasmix *mix); -extern bool diveplan_empty(struct diveplan *diveplan); - -extern void free_dps(struct diveplan *diveplan); -extern struct dive *planned_dive; -extern char *cache_data; -extern const char *disclaimer; -extern double plangflow, plangfhigh; - -#ifdef __cplusplus -} -#endif -#endif // PLANNER_H diff --git a/subsurface-core/pluginmanager.cpp b/subsurface-core/pluginmanager.cpp deleted file mode 100644 index 28c978280..000000000 --- a/subsurface-core/pluginmanager.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "pluginmanager.h" - -#include -#include -#include -#include - -static QList _socialNetworks; - -PluginManager& PluginManager::instance() -{ - static PluginManager self; - return self; -} - -PluginManager::PluginManager() -{ -} - -void PluginManager::loadPlugins() -{ - QDir pluginsDir(qApp->applicationDirPath()); - -#if defined(Q_OS_WIN) - if (pluginsDir.dirName().toLower() == "debug" || pluginsDir.dirName().toLower() == "release") - pluginsDir.cdUp(); -#elif defined(Q_OS_MAC) - if (pluginsDir.dirName() == "MacOS") { - pluginsDir.cdUp(); - pluginsDir.cdUp(); - pluginsDir.cdUp(); - } -#endif - pluginsDir.cd("plugins"); - - qDebug() << "Plugins Directory: " << pluginsDir; - foreach (const QString& fileName, pluginsDir.entryList(QDir::Files)) { - QPluginLoader loader(pluginsDir.absoluteFilePath(fileName)); - QObject *plugin = loader.instance(); - if(!plugin) - continue; - - if (ISocialNetworkIntegration *social = qobject_cast(plugin)) { - qDebug() << "Adding the plugin: " << social->socialNetworkName(); - _socialNetworks.push_back(social); - } - } -} - -QList PluginManager::socialNetworkIntegrationPlugins() const -{ - return _socialNetworks; -} diff --git a/subsurface-core/pluginmanager.h b/subsurface-core/pluginmanager.h deleted file mode 100644 index 3f43b5db1..000000000 --- a/subsurface-core/pluginmanager.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef PLUGINMANAGER_H -#define PLUGINMANAGER_H - -#include - -#include "isocialnetworkintegration.h" - -class PluginManager { -public: - static PluginManager& instance(); - void loadPlugins(); - QList socialNetworkIntegrationPlugins() const; -private: - PluginManager(); - PluginManager(const PluginManager&); - PluginManager& operator=(const PluginManager&); -}; - -#endif diff --git a/subsurface-core/pref.h b/subsurface-core/pref.h deleted file mode 100644 index be684fd90..000000000 --- a/subsurface-core/pref.h +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef PREF_H -#define PREF_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "units.h" -#include "taxonomy.h" - -/* can't use 'bool' for the boolean values - different size in C and C++ */ -typedef struct -{ - short po2; - short pn2; - short phe; - double po2_threshold; - double pn2_threshold; - double phe_threshold; -} partial_pressure_graphs_t; - -typedef struct { - char *access_token; - char *user_id; - char *album_id; -} facebook_prefs_t; - -typedef struct { - bool enable_geocoding; - bool parse_dive_without_gps; - bool tag_existing_dives; - enum taxonomy_category category[3]; -} geocoding_prefs_t; - -typedef struct { - const char *language; - bool use_system_language; -} locale_prefs_t; - -enum deco_mode { - BUEHLMANN, - RECREATIONAL, - VPMB -}; - -struct preferences { - const char *divelist_font; - const char *default_filename; - const char *default_cylinder; - const char *cloud_base_url; - const char *cloud_git_url; - const char *time_format; - const char *date_format; - const char *date_format_short; - bool time_format_override; - bool date_format_override; - double font_size; - partial_pressure_graphs_t pp_graphs; - short mod; - double modpO2; - short ead; - short dcceiling; - short redceiling; - short calcceiling; - short calcceiling3m; - short calcalltissues; - short calcndltts; - short gflow; - short gfhigh; - int animation_speed; - bool gf_low_at_maxdepth; - bool show_ccr_setpoint; - bool show_ccr_sensors; - short display_invalid_dives; - short unit_system; - struct units units; - bool coordinates_traditional; - short show_sac; - short display_unused_tanks; - short show_average_depth; - short zoomed_plot; - short hrgraph; - short percentagegraph; - short rulergraph; - short tankbar; - short save_userid_local; - char *userid; - int ascrate75; // All rates in mm / sec - int ascrate50; - int ascratestops; - int ascratelast6m; - int descrate; - int bottompo2; - int decopo2; - int proxy_type; - char *proxy_host; - int proxy_port; - short proxy_auth; - char *proxy_user; - char *proxy_pass; - bool doo2breaks; - bool drop_stone_mode; - bool last_stop; // At 6m? - bool verbatim_plan; - bool display_runtime; - bool display_duration; - bool display_transitions; - bool safetystop; - bool switch_at_req_stop; - int reserve_gas; - int min_switch_duration; // seconds - int bottomsac; - int decosac; - int o2consumption; // ml per min - int pscr_ratio; // dump ratio times 1000 - int defaultsetpoint; // default setpoint in mbar - bool show_pictures_in_profile; - bool use_default_file; - short default_file_behavior; - facebook_prefs_t facebook; - char *cloud_storage_password; - char *cloud_storage_newpassword; - char *cloud_storage_email; - char *cloud_storage_email_encoded; - bool save_password_local; - short cloud_verification_status; - bool cloud_background_sync; - geocoding_prefs_t geocoding; - enum deco_mode deco_mode; - short conservatism_level; - int time_threshold; - int distance_threshold; - bool git_local_only; - locale_prefs_t locale; //: TODO: move the rest of locale based info here. -}; -enum unit_system_values { - METRIC, - IMPERIAL, - PERSONALIZE -}; - -enum def_file_behavior { - UNDEFINED_DEFAULT_FILE, - LOCAL_DEFAULT_FILE, - NO_DEFAULT_FILE, - CLOUD_DEFAULT_FILE -}; - -enum cloud_status { - CS_UNKNOWN, - CS_INCORRECT_USER_PASSWD, - CS_NEED_TO_VERIFY, - CS_VERIFIED -}; - -extern struct preferences prefs, default_prefs, informational_prefs; - -#define PP_GRAPHS_ENABLED (prefs.pp_graphs.po2 || prefs.pp_graphs.pn2 || prefs.pp_graphs.phe) - -extern const char *system_divelist_default_font; -extern double system_divelist_default_font_size; - -extern const char *system_default_directory(void); -extern const char *system_default_filename(); -extern bool subsurface_ignore_font(const char *font); -extern void subsurface_OS_pref_setup(); - -#ifdef __cplusplus -} -#endif - -#endif // PREF_H diff --git a/subsurface-core/prefs-macros.h b/subsurface-core/prefs-macros.h deleted file mode 100644 index fe459d3da..000000000 --- a/subsurface-core/prefs-macros.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef PREFSMACROS_H -#define PREFSMACROS_H - -#define SB(V, B) s.setValue(V, (int)(B->isChecked() ? 1 : 0)) - -#define GET_UNIT(name, field, f, t) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.units.field = (v.toInt() == (t)) ? (t) : (f); \ - else \ - prefs.units.field = default_prefs.units.field - -#define GET_BOOL(name, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = v.toBool(); \ - else \ - prefs.field = default_prefs.field - -#define GET_DOUBLE(name, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = v.toDouble(); \ - else \ - prefs.field = default_prefs.field - -#define GET_INT(name, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = v.toInt(); \ - else \ - prefs.field = default_prefs.field - -#define GET_ENUM(name, type, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = (enum type)v.toInt(); \ - else \ - prefs.field = default_prefs.field - -#define GET_INT_DEF(name, field, defval) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = v.toInt(); \ - else \ - prefs.field = defval - -#define GET_TXT(name, field) \ - v = s.value(QString(name)); \ - if (v.isValid()) \ - prefs.field = strdup(v.toString().toUtf8().constData()); \ - else \ - prefs.field = default_prefs.field - -#define SAVE_OR_REMOVE_SPECIAL(_setting, _default, _compare, _value) \ - if (_compare != _default) \ - s.setValue(_setting, _value); \ - else \ - s.remove(_setting) - -#define SAVE_OR_REMOVE(_setting, _default, _value) \ - if (_value != _default) \ - s.setValue(_setting, _value); \ - else \ - s.remove(_setting) - -#endif // PREFSMACROS_H - diff --git a/subsurface-core/profile.c b/subsurface-core/profile.c deleted file mode 100644 index 6576f6453..000000000 --- a/subsurface-core/profile.c +++ /dev/null @@ -1,1544 +0,0 @@ -/* profile.c */ -/* creates all the necessary data for drawing the dive profile - */ -#include "gettext.h" -#include -#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); - -extern bool in_planner(); -extern pressure_t first_ceiling_pressure; - -#ifdef DEBUG_PI -/* debugging tool - not normally used */ -static void dump_pi(struct plot_info *pi) -{ - int i; - - printf("pi:{nr:%d maxtime:%d meandepth:%d maxdepth:%d \n" - " maxpressure:%d mintemp:%d maxtemp:%d\n", - pi->nr, pi->maxtime, pi->meandepth, pi->maxdepth, - pi->maxpressure, pi->mintemp, pi->maxtemp); - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = &pi->entry[i]; - printf(" entry[%d]:{cylinderindex:%d sec:%d pressure:{%d,%d}\n" - " time:%d:%02d temperature:%d depth:%d stopdepth:%d stoptime:%d ndl:%d smoothed:%d po2:%lf phe:%lf pn2:%lf sum-pp %lf}\n", - i, entry->cylinderindex, entry->sec, - entry->pressure[0], entry->pressure[1], - entry->sec / 60, entry->sec % 60, - entry->temperature, entry->depth, entry->stopdepth, entry->stoptime, entry->ndl, entry->smoothed, - entry->pressures.o2, entry->pressures.he, entry->pressures.n2, - entry->pressures.o2 + entry->pressures.he + entry->pressures.n2); - } - printf(" }\n"); -} -#endif - -#define ROUND_UP(x, y) ((((x) + (y) - 1) / (y)) * (y)) -#define DIV_UP(x, y) (((x) + (y) - 1) / (y)) - -/* - * When showing dive profiles, we scale things to the - * current dive. However, we don't scale past less than - * 30 minutes or 90 ft, just so that small dives show - * up as such unless zoom is enabled. - * We also need to add 180 seconds at the end so the min/max - * plots correctly - */ -int get_maxtime(struct plot_info *pi) -{ - int seconds = pi->maxtime; - - int DURATION_THR = (pi->dive_type == FREEDIVING ? 60 : 600); - int CEILING = (pi->dive_type == FREEDIVING ? 30 : 60); - - if (prefs.zoomed_plot) { - /* Rounded up to one minute, with at least 2.5 minutes to - * spare. - * For dive times shorter than 10 minutes, we use seconds/4 to - * calculate the space dynamically. - * This is seamless since 600/4 = 150. - */ - if (seconds < DURATION_THR) - return ROUND_UP(seconds + seconds / 4, CEILING); - else - return ROUND_UP(seconds + DURATION_THR/4, CEILING); - } else { -#ifndef SUBSURFACE_MOBILE - /* min 30 minutes, rounded up to 5 minutes, with at least 2.5 minutes to spare */ - return MAX(30 * 60, ROUND_UP(seconds + DURATION_THR/4, CEILING * 5)); -#else - /* just add 2.5 minutes so we have a consistant right margin */ - return seconds + DURATION_THR / 4; -#endif - } -} - -/* get the maximum depth to which we want to plot - * take into account the additional vertical space needed to plot - * partial pressure graphs */ -int get_maxdepth(struct plot_info *pi) -{ - unsigned mm = pi->maxdepth; - int md; - - if (prefs.zoomed_plot) { - /* Rounded up to 10m, with at least 3m to spare */ - md = ROUND_UP(mm + 3000, 10000); - } else { - /* Minimum 30m, rounded up to 10m, with at least 3m to spare */ - md = MAX((unsigned)30000, ROUND_UP(mm + 3000, 10000)); - } - md += pi->maxpp * 9000; - return md; -} - -/* collect all event names and whether we display them */ -struct ev_select *ev_namelist; -int evn_allocated; -int evn_used; - -#if WE_DONT_USE_THIS /* we need to implement event filters in Qt */ -int evn_foreach (void (*callback)(const char *, bool *, void *), void *data) { - int i; - - for (i = 0; i < evn_used; i++) { - /* here we display an event name on screen - so translate */ - callback(translate("gettextFromC", ev_namelist[i].ev_name), &ev_namelist[i].plot_ev, data); - } - return i; -} -#endif /* WE_DONT_USE_THIS */ - -void clear_events(void) -{ - for (int i = 0; i < evn_used; i++) - free(ev_namelist[i].ev_name); - evn_used = 0; -} - -void remember_event(const char *eventname) -{ - int i = 0, len; - - if (!eventname || (len = strlen(eventname)) == 0) - return; - while (i < evn_used) { - if (!strncmp(eventname, ev_namelist[i].ev_name, len)) - return; - i++; - } - if (evn_used == evn_allocated) { - evn_allocated += 10; - ev_namelist = realloc(ev_namelist, evn_allocated * sizeof(struct ev_select)); - if (!ev_namelist) - /* we are screwed, but let's just bail out */ - return; - } - ev_namelist[evn_used].ev_name = strdup(eventname); - ev_namelist[evn_used].plot_ev = true; - evn_used++; -} - -/* UNUSED! */ -static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive) __attribute__((unused)); - -/* Get local sac-rate (in ml/min) between entry1 and entry2 */ -static int get_local_sac(struct plot_data *entry1, struct plot_data *entry2, struct dive *dive) -{ - int index = entry1->cylinderindex; - cylinder_t *cyl; - int duration = entry2->sec - entry1->sec; - int depth, airuse; - pressure_t a, b; - double atm; - - if (entry2->cylinderindex != index) - return 0; - if (duration <= 0) - return 0; - a.mbar = GET_PRESSURE(entry1); - b.mbar = GET_PRESSURE(entry2); - if (!b.mbar || a.mbar <= b.mbar) - return 0; - - /* Mean pressure in ATM */ - depth = (entry1->depth + entry2->depth) / 2; - atm = depth_to_atm(depth, dive); - - cyl = dive->cylinder + index; - - airuse = gas_volume(cyl, a) - gas_volume(cyl, b); - - /* milliliters per minute */ - return airuse / atm * 60 / duration; -} - -static void analyze_plot_info_minmax_minute(struct plot_data *entry, struct plot_data *first, struct plot_data *last, int index) -{ - struct plot_data *p = entry; - int time = entry->sec; - int seconds = 90 * (index + 1); - struct plot_data *min, *max; - int avg, nr; - - /* Go back 'seconds' in time */ - while (p > first) { - if (p[-1].sec < time - seconds) - break; - p--; - } - - /* Then go forward until we hit an entry past the time */ - min = max = p; - avg = p->depth; - nr = 1; - while (++p < last) { - int depth = p->depth; - if (p->sec > time + seconds) - break; - avg += depth; - nr++; - if (depth < min->depth) - min = p; - if (depth > max->depth) - max = p; - } - entry->min[index] = min; - entry->max[index] = max; - entry->avg[index] = (avg + nr / 2) / nr; -} - -static void analyze_plot_info_minmax(struct plot_data *entry, struct plot_data *first, struct plot_data *last) -{ - analyze_plot_info_minmax_minute(entry, first, last, 0); - analyze_plot_info_minmax_minute(entry, first, last, 1); - analyze_plot_info_minmax_minute(entry, first, last, 2); -} - -static velocity_t velocity(int speed) -{ - velocity_t v; - - if (speed < -304) /* ascent faster than -60ft/min */ - v = CRAZY; - else if (speed < -152) /* above -30ft/min */ - v = FAST; - else if (speed < -76) /* -15ft/min */ - v = MODERATE; - else if (speed < -25) /* -5ft/min */ - v = SLOW; - else if (speed < 25) /* very hard to find data, but it appears that the recommendations - for descent are usually about 2x ascent rate; still, we want - stable to mean stable */ - v = STABLE; - else if (speed < 152) /* between 5 and 30ft/min is considered slow */ - v = SLOW; - else if (speed < 304) /* up to 60ft/min is moderate */ - v = MODERATE; - else if (speed < 507) /* up to 100ft/min is fast */ - v = FAST; - else /* more than that is just crazy - you'll blow your ears out */ - v = CRAZY; - - return v; -} - -struct plot_info *analyze_plot_info(struct plot_info *pi) -{ - int i; - int nr = pi->nr; - - /* Smoothing function: 5-point triangular smooth */ - for (i = 2; i < nr; i++) { - struct plot_data *entry = pi->entry + i; - int depth; - - if (i < nr - 2) { - depth = entry[-2].depth + 2 * entry[-1].depth + 3 * entry[0].depth + 2 * entry[1].depth + entry[2].depth; - entry->smoothed = (depth + 4) / 9; - } - /* vertical velocity in mm/sec */ - /* Linus wants to smooth this - let's at least look at the samples that aren't FAST or CRAZY */ - if (entry[0].sec - entry[-1].sec) { - entry->speed = (entry[0].depth - entry[-1].depth) / (entry[0].sec - entry[-1].sec); - entry->velocity = velocity(entry->speed); - /* if our samples are short and we aren't too FAST*/ - if (entry[0].sec - entry[-1].sec < 15 && entry->velocity < FAST) { - int past = -2; - while (i + past > 0 && entry[0].sec - entry[past].sec < 15) - past--; - entry->velocity = velocity((entry[0].depth - entry[past].depth) / - (entry[0].sec - entry[past].sec)); - } - } else { - entry->velocity = STABLE; - entry->speed = 0; - } - } - - /* One-, two- and three-minute minmax data */ - for (i = 0; i < nr; i++) { - struct plot_data *entry = pi->entry + i; - analyze_plot_info_minmax(entry, pi->entry, pi->entry + nr); - } - - return pi; -} - -/* - * If the event has an explicit cylinder index, - * we return that. If it doesn't, we return the best - * match based on the gasmix. - * - * Some dive computers give cylinder indexes, some - * give just the gas mix. - */ -int get_cylinder_index(struct dive *dive, struct event *ev) -{ - int i; - int best = 0, score = INT_MAX; - int target_o2, target_he; - struct gasmix *g; - - if (ev->gas.index >= 0) - return ev->gas.index; - - g = get_gasmix_from_event(ev); - target_o2 = get_o2(g); - target_he = get_he(g); - - /* - * Try to find a cylinder that best matches the target gas - * mix. - */ - for (i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = dive->cylinder + i; - int delta_o2, delta_he, distance; - - if (cylinder_nodata(cyl)) - continue; - - delta_o2 = get_o2(&cyl->gasmix) - target_o2; - delta_he = get_he(&cyl->gasmix) - target_he; - distance = delta_o2 * delta_o2; - distance += delta_he * delta_he; - - if (distance >= score) - continue; - score = distance; - best = i; - } - return best; -} - -struct event *get_next_event(struct event *event, const char *name) -{ - if (!name || !*name) - return NULL; - while (event) { - if (!strcmp(event->name, name)) - return event; - event = event->next; - } - return event; -} - -static int count_events(struct divecomputer *dc) -{ - int result = 0; - struct event *ev = dc->events; - while (ev != NULL) { - result++; - ev = ev->next; - } - return result; -} - -static int set_cylinder_index(struct plot_info *pi, int i, int cylinderindex, int end) -{ - while (i < pi->nr) { - struct plot_data *entry = pi->entry + i; - if (entry->sec > end) - break; - if (entry->cylinderindex != cylinderindex) { - entry->cylinderindex = cylinderindex; - entry->pressure[0] = 0; - } - i++; - } - return i; -} - -static int set_setpoint(struct plot_info *pi, int i, int setpoint, int end) -{ - while (i < pi->nr) { - struct plot_data *entry = pi->entry + i; - if (entry->sec > end) - break; - entry->o2pressure.mbar = setpoint; - i++; - } - return i; -} - -/* normally the first cylinder has index 0... if not, we need to fix this up here */ -static int set_first_cylinder_index(struct plot_info *pi, int i, int cylinderindex, int end) -{ - while (i < pi->nr) { - struct plot_data *entry = pi->entry + i; - if (entry->sec > end) - break; - entry->cylinderindex = cylinderindex; - i++; - } - return i; -} - -static void check_gas_change_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) -{ - int i = 0, cylinderindex = 0; - struct event *ev = get_next_event(dc->events, "gaschange"); - - // for dive computers that tell us their first gas as an event on the first sample - // we need to make sure things are setup correctly - cylinderindex = explicit_first_cylinder(dive, dc); - set_first_cylinder_index(pi, 0, cylinderindex, INT_MAX); - - if (!ev) - return; - - do { - i = set_cylinder_index(pi, i, cylinderindex, ev->time.seconds); - cylinderindex = get_cylinder_index(dive, ev); - ev = get_next_event(ev->next, "gaschange"); - } while (ev); - set_cylinder_index(pi, i, cylinderindex, INT_MAX); -} - -static void check_setpoint_events(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) -{ - int i = 0; - pressure_t setpoint; - (void) dive; - setpoint.mbar = 0; - struct event *ev = get_next_event(dc->events, "SP change"); - - if (!ev) - return; - - do { - i = set_setpoint(pi, i, setpoint.mbar, ev->time.seconds); - setpoint.mbar = ev->value; - if (setpoint.mbar) - dc->divemode = CCR; - ev = get_next_event(ev->next, "SP change"); - } while (ev); - set_setpoint(pi, i, setpoint.mbar, INT_MAX); -} - - -struct plot_info calculate_max_limits_new(struct dive *dive, struct divecomputer *given_dc) -{ - struct divecomputer *dc = &(dive->dc); - bool seen = false; - static struct plot_info pi; - int maxdepth = dive->maxdepth.mm; - unsigned int maxtime = 0; - int maxpressure = 0, minpressure = INT_MAX; - int maxhr = 0, minhr = INT_MAX; - int mintemp = dive->mintemp.mkelvin; - int maxtemp = dive->maxtemp.mkelvin; - int cyl; - - /* Get the per-cylinder maximum pressure if they are manual */ - for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { - int mbar = dive->cylinder[cyl].start.mbar; - if (mbar > maxpressure) - maxpressure = mbar; - if (mbar < minpressure) - minpressure = mbar; - } - - /* Then do all the samples from all the dive computers */ - do { - if (dc == given_dc) - seen = true; - int i = dc->samples; - int lastdepth = 0; - struct sample *s = dc->sample; - - while (--i >= 0) { - int depth = s->depth.mm; - int pressure = s->cylinderpressure.mbar; - int temperature = s->temperature.mkelvin; - int heartbeat = s->heartbeat; - - if (!mintemp && temperature < mintemp) - mintemp = temperature; - if (temperature > maxtemp) - maxtemp = temperature; - - if (pressure && pressure < minpressure) - minpressure = pressure; - if (pressure > maxpressure) - maxpressure = pressure; - if (heartbeat > maxhr) - maxhr = heartbeat; - if (heartbeat < minhr) - minhr = heartbeat; - - if (depth > maxdepth) - maxdepth = s->depth.mm; - if ((depth > SURFACE_THRESHOLD || lastdepth > SURFACE_THRESHOLD) && - s->time.seconds > maxtime) - maxtime = s->time.seconds; - lastdepth = depth; - s++; - } - dc = dc->next; - if (dc == NULL && !seen) { - dc = given_dc; - seen = true; - } - } while (dc != NULL); - - if (minpressure > maxpressure) - minpressure = 0; - if (minhr > maxhr) - minhr = 0; - - memset(&pi, 0, sizeof(pi)); - pi.maxdepth = maxdepth; - pi.maxtime = maxtime; - pi.maxpressure = maxpressure; - pi.minpressure = minpressure; - pi.minhr = minhr; - pi.maxhr = maxhr; - pi.mintemp = mintemp; - pi.maxtemp = maxtemp; - return pi; -} - -/* copy the previous entry (we know this exists), update time and depth - * and zero out the sensor pressure (since this is a synthetic entry) - * increment the entry pointer and the count of synthetic entries. */ -#define INSERT_ENTRY(_time, _depth, _sac) \ - *entry = entry[-1]; \ - entry->sec = _time; \ - entry->depth = _depth; \ - entry->running_sum = (entry - 1)->running_sum + (_time - (entry - 1)->sec) * (_depth + (entry - 1)->depth) / 2; \ - SENSOR_PRESSURE(entry) = 0; \ - entry->sac = _sac; \ - entry++; \ - idx++ - -struct plot_data *populate_plot_entries(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) -{ - - int idx, maxtime, nr, i; - int lastdepth, lasttime, lasttemp = 0; - struct plot_data *plot_data; - struct event *ev = dc->events; - (void) dive; - maxtime = pi->maxtime; - - /* - * We want to have a plot_info event at least every 10s (so "maxtime/10+1"), - * but samples could be more dense than that (so add in dc->samples). We also - * need to have one for every event (so count events and add that) and - * additionally we want two surface events around the whole thing (thus the - * additional 4). There is also one extra space for a final entry - * that has time > maxtime (because there can be surface samples - * past "maxtime" in the original sample data) - */ - nr = dc->samples + 6 + maxtime / 10 + count_events(dc); - plot_data = calloc(nr, sizeof(struct plot_data)); - pi->entry = plot_data; - if (!plot_data) - return NULL; - pi->nr = nr; - idx = 2; /* the two extra events at the start */ - - lastdepth = 0; - lasttime = 0; - /* skip events at time = 0 */ - while (ev && ev->time.seconds == 0) - ev = ev->next; - for (i = 0; i < dc->samples; i++) { - struct plot_data *entry = plot_data + idx; - struct sample *sample = dc->sample + i; - int time = sample->time.seconds; - int offset, delta; - int depth = sample->depth.mm; - int sac = sample->sac.mliter; - - /* Add intermediate plot entries if required */ - delta = time - lasttime; - if (delta <= 0) { - time = lasttime; - delta = 1; // avoid divide by 0 - } - for (offset = 10; offset < delta; offset += 10) { - if (lasttime + offset > maxtime) - break; - - /* Add events if they are between plot entries */ - while (ev && (int)ev->time.seconds < lasttime + offset) { - INSERT_ENTRY(ev->time.seconds, interpolate(lastdepth, depth, ev->time.seconds - lasttime, delta), sac); - ev = ev->next; - } - - /* now insert the time interpolated entry */ - INSERT_ENTRY(lasttime + offset, interpolate(lastdepth, depth, offset, delta), sac); - - /* skip events that happened at this time */ - while (ev && (int)ev->time.seconds == lasttime + offset) - ev = ev->next; - } - - /* Add events if they are between plot entries */ - while (ev && (int)ev->time.seconds < time) { - INSERT_ENTRY(ev->time.seconds, interpolate(lastdepth, depth, ev->time.seconds - lasttime, delta), sac); - ev = ev->next; - } - - - entry->sec = time; - entry->depth = depth; - - entry->running_sum = (entry - 1)->running_sum + (time - (entry - 1)->sec) * (depth + (entry - 1)->depth) / 2; - entry->stopdepth = sample->stopdepth.mm; - entry->stoptime = sample->stoptime.seconds; - entry->ndl = sample->ndl.seconds; - entry->tts = sample->tts.seconds; - pi->has_ndl |= sample->ndl.seconds; - entry->in_deco = sample->in_deco; - entry->cns = sample->cns; - if (dc->divemode == CCR) { - entry->o2pressure.mbar = entry->o2setpoint.mbar = sample->setpoint.mbar; // for rebreathers - entry->o2sensor[0].mbar = sample->o2sensor[0].mbar; // for up to three rebreather O2 sensors - entry->o2sensor[1].mbar = sample->o2sensor[1].mbar; - entry->o2sensor[2].mbar = sample->o2sensor[2].mbar; - } else { - entry->pressures.o2 = sample->setpoint.mbar / 1000.0; - } - /* FIXME! sensor index -> cylinder index translation! */ - // entry->cylinderindex = sample->sensor; - SENSOR_PRESSURE(entry) = sample->cylinderpressure.mbar; - O2CYLINDER_PRESSURE(entry) = sample->o2cylinderpressure.mbar; - if (sample->temperature.mkelvin) - entry->temperature = lasttemp = sample->temperature.mkelvin; - else - entry->temperature = lasttemp; - entry->heartbeat = sample->heartbeat; - entry->bearing = sample->bearing.degrees; - entry->sac = sample->sac.mliter; - if (sample->rbt.seconds) - entry->rbt = sample->rbt.seconds; - /* skip events that happened at this time */ - while (ev && (int)ev->time.seconds == time) - ev = ev->next; - lasttime = time; - lastdepth = depth; - idx++; - - if (time > maxtime) - break; - } - - /* Add two final surface events */ - plot_data[idx++].sec = lasttime + 1; - plot_data[idx++].sec = lasttime + 2; - pi->nr = idx; - - return plot_data; -} - -#undef INSERT_ENTRY - -static void populate_cylinder_pressure_data(int idx, int start, int end, struct plot_info *pi, bool o2flag) -{ - int i; - - /* First: check that none of the entries has sensor pressure for this cylinder index */ - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - if (entry->cylinderindex != idx && !o2flag) - continue; - if (CYLINDER_PRESSURE(o2flag, entry)) - return; - } - - /* Then: populate the first entry with the beginning cylinder pressure */ - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - if (entry->cylinderindex != idx && !o2flag) - continue; - if (o2flag) - O2CYLINDER_PRESSURE(entry) = start; - else - SENSOR_PRESSURE(entry) = start; - break; - } - - /* .. and the last entry with the ending cylinder pressure */ - for (i = pi->nr; --i >= 0; /* nothing */) { - struct plot_data *entry = pi->entry + i; - if (entry->cylinderindex != idx && !o2flag) - continue; - if (o2flag) - O2CYLINDER_PRESSURE(entry) = end; - else - SENSOR_PRESSURE(entry) = end; - break; - } -} - -/* - * Calculate the sac rate between the two plot entries 'first' and 'last'. - * - * Everything in between has a cylinder pressure, and it's all the same - * cylinder. - */ -static int sac_between(struct dive *dive, struct plot_data *first, struct plot_data *last) -{ - int airuse; - double pressuretime; - pressure_t a, b; - cylinder_t *cyl; - - if (first == last) - return 0; - - /* Calculate air use - trivial */ - a.mbar = GET_PRESSURE(first); - b.mbar = GET_PRESSURE(last); - cyl = dive->cylinder + first->cylinderindex; - airuse = gas_volume(cyl, a) - gas_volume(cyl, b); - if (airuse <= 0) - return 0; - - /* Calculate depthpressure integrated over time */ - pressuretime = 0.0; - do { - int depth = (first[0].depth + first[1].depth) / 2; - int time = first[1].sec - first[0].sec; - double atm = depth_to_atm(depth, dive); - - pressuretime += atm * time; - } while (++first < last); - - /* Turn "atmseconds" into "atmminutes" */ - pressuretime /= 60; - - /* SAC = mliter per minute */ - return rint(airuse / pressuretime); -} - -/* - * Try to do the momentary sac rate for this entry, averaging over one - * minute. - */ -static void fill_sac(struct dive *dive, struct plot_info *pi, int idx) -{ - struct plot_data *entry = pi->entry + idx; - struct plot_data *first, *last; - int time; - - if (entry->sac) - return; - - if (!GET_PRESSURE(entry)) - return; - - /* - * Try to go back 30 seconds to get 'first'. - * Stop if the sensor changed, or if we went back too far. - */ - first = entry; - time = entry->sec - 30; - while (idx > 0) { - struct plot_data *prev = first-1; - if (prev->cylinderindex != first->cylinderindex) - break; - if (prev->depth < SURFACE_THRESHOLD && first->depth < SURFACE_THRESHOLD) - break; - if (prev->sec < time) - break; - if (!GET_PRESSURE(prev)) - break; - idx--; - first = prev; - } - - /* Now find an entry a minute after the first one */ - last = first; - time = first->sec + 60; - while (++idx < pi->nr) { - struct plot_data *next = last+1; - if (next->cylinderindex != last->cylinderindex) - break; - if (next->depth < SURFACE_THRESHOLD && last->depth < SURFACE_THRESHOLD) - break; - if (next->sec > time) - break; - if (!GET_PRESSURE(next)) - break; - last = next; - } - - /* Ok, now calculate the SAC between 'first' and 'last' */ - entry->sac = sac_between(dive, first, last); -} - -static void calculate_sac(struct dive *dive, struct plot_info *pi) -{ - for (int i = 0; i < pi->nr; i++) - fill_sac(dive, pi, i); -} - -static void populate_secondary_sensor_data(struct divecomputer *dc, struct plot_info *pi) -{ - (void) dc; - (void) pi; - /* We should try to see if it has interesting pressure data here */ -} - -static void setup_gas_sensor_pressure(struct dive *dive, struct divecomputer *dc, struct plot_info *pi) -{ - int i; - struct divecomputer *secondary; - - /* First, populate the pressures with the manual cylinder data.. */ - for (i = 0; i < MAX_CYLINDERS; i++) { - cylinder_t *cyl = dive->cylinder + i; - int start = cyl->start.mbar ?: cyl->sample_start.mbar; - int end = cyl->end.mbar ?: cyl->sample_end.mbar; - - if (!start || !end) - continue; - - populate_cylinder_pressure_data(i, start, end, pi, dive->cylinder[i].cylinder_use == OXYGEN); - } - - /* - * Here, we should try to walk through all the dive computers, - * and try to see if they have sensor data different from the - * primary dive computer (dc). - */ - secondary = &dive->dc; - do { - if (secondary == dc) - continue; - populate_secondary_sensor_data(dc, pi); - } while ((secondary = secondary->next) != NULL); -} - -#ifndef SUBSURFACE_MOBILE -/* calculate DECO STOP / TTS / NDL */ -static void calculate_ndl_tts(struct plot_data *entry, struct dive *dive, double surface_pressure) -{ - /* FIXME: This should be configurable */ - /* ascent speed up to first deco stop */ - const int ascent_s_per_step = 1; - const int ascent_mm_per_step = 200; /* 12 m/min */ - /* ascent speed between deco stops */ - const int ascent_s_per_deco_step = 1; - const int ascent_mm_per_deco_step = 16; /* 1 m/min */ - /* how long time steps in deco calculations? */ - const int time_stepsize = 60; - const int deco_stepsize = 3000; - /* at what depth is the current deco-step? */ - int next_stop = ROUND_UP(deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), - surface_pressure, dive, 1), deco_stepsize); - int ascent_depth = entry->depth; - /* at what time should we give up and say that we got enuff NDL? */ - int cylinderindex = entry->cylinderindex; - /* If iterating through a dive, entry->tts_calc needs to be reset */ - entry->tts_calc = 0; - - /* If we don't have a ceiling yet, calculate ndl. Don't try to calculate - * a ndl for lower values than 3m it would take forever */ - if (next_stop == 0) { - if (entry->depth < 3000) { - entry->ndl = MAX_PROFILE_DECO; - return; - } - /* stop if the ndl is above max_ndl seconds, and call it plenty of time */ - while (entry->ndl_calc < MAX_PROFILE_DECO && deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), surface_pressure, dive, 1) <= 0) { - entry->ndl_calc += time_stepsize; - add_segment(depth_to_bar(entry->depth, dive), - &dive->cylinder[cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, prefs.bottomsac); - } - /* we don't need to calculate anything else */ - return; - } - - /* We are in deco */ - entry->in_deco_calc = true; - - /* Add segments for movement to stopdepth */ - for (; ascent_depth > next_stop; ascent_depth -= ascent_mm_per_step, entry->tts_calc += ascent_s_per_step) { - add_segment(depth_to_bar(ascent_depth, dive), - &dive->cylinder[cylinderindex].gasmix, ascent_s_per_step, entry->o2pressure.mbar, dive, prefs.decosac); - next_stop = ROUND_UP(deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(ascent_depth, dive)), surface_pressure, dive, 1), deco_stepsize); - } - ascent_depth = next_stop; - - /* And how long is the current deco-step? */ - entry->stoptime_calc = 0; - entry->stopdepth_calc = next_stop; - next_stop -= deco_stepsize; - - /* And how long is the total TTS */ - while (next_stop >= 0) { - /* save the time for the first stop to show in the graph */ - if (ascent_depth == entry->stopdepth_calc) - entry->stoptime_calc += time_stepsize; - - entry->tts_calc += time_stepsize; - if (entry->tts_calc > MAX_PROFILE_DECO) - break; - add_segment(depth_to_bar(ascent_depth, dive), - &dive->cylinder[cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, prefs.decosac); - - if (deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(ascent_depth,dive)), surface_pressure, dive, 1) <= next_stop) { - /* move to the next stop and add the travel between stops */ - for (; ascent_depth > next_stop; ascent_depth -= ascent_mm_per_deco_step, entry->tts_calc += ascent_s_per_deco_step) - add_segment(depth_to_bar(ascent_depth, dive), - &dive->cylinder[cylinderindex].gasmix, ascent_s_per_deco_step, entry->o2pressure.mbar, dive, prefs.decosac); - ascent_depth = next_stop; - next_stop -= deco_stepsize; - } - } -} - -/* Let's try to do some deco calculations. - */ -void calculate_deco_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool print_mode) -{ - int i, count_iteration = 0; - double surface_pressure = (dc->surface_pressure.mbar ? dc->surface_pressure.mbar : get_surface_pressure_in_mbar(dive, true)) / 1000.0; - int last_ndl_tts_calc_time = 0; - int first_ceiling = 0; - bool first_iteration = true; - int final_tts = 0 , time_clear_ceiling = 0, time_deep_ceiling = 0, deco_time = 0, prev_deco_time = 10000000; - char *cache_data_initial = NULL; - /* For VPM-B outside the planner, cache the initial deco state for CVA iterations */ - if (prefs.deco_mode == VPMB && !in_planner()) - cache_deco_state(&cache_data_initial); - /* For VPM-B outside the planner, iterate until deco time converges (usually one or two iterations after the initial) - * Set maximum number of iterations to 10 just in case */ - while ((abs(prev_deco_time - deco_time) >= 30) && (count_iteration < 10)) { - for (i = 1; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - int j, t0 = (entry - 1)->sec, t1 = entry->sec; - int time_stepsize = 20; - - entry->ambpressure = depth_to_bar(entry->depth, dive); - entry->gfline = MAX((double)prefs.gflow, (entry->ambpressure - surface_pressure) / (gf_low_pressure_this_dive - surface_pressure) * - (prefs.gflow - prefs.gfhigh) + - prefs.gfhigh) * - (100.0 - AMB_PERCENTAGE) / 100.0 + AMB_PERCENTAGE; - if (t0 > t1) { - fprintf(stderr, "non-monotonous dive stamps %d %d\n", t0, t1); - int xchg = t1; - t1 = t0; - t0 = xchg; - } - if (t0 != t1 && t1 - t0 < time_stepsize) - time_stepsize = t1 - t0; - for (j = t0 + time_stepsize; j <= t1; j += time_stepsize) { - int depth = interpolate(entry[-1].depth, entry[0].depth, j - t0, t1 - t0); - add_segment(depth_to_bar(depth, dive), - &dive->cylinder[entry->cylinderindex].gasmix, time_stepsize, entry->o2pressure.mbar, dive, entry->sac); - if ((t1 - j < time_stepsize) && (j < t1)) - time_stepsize = t1 - j; - } - if (t0 == t1) { - entry->ceiling = (entry - 1)->ceiling; - } else { - /* Keep updating the VPM-B gradients until the start of the ascent phase of the dive. */ - if (prefs.deco_mode == VPMB && !in_planner() && (entry - 1)->ceiling >= first_ceiling && first_iteration == true) { - nuclear_regeneration(t1); - vpmb_start_gradient(); - /* For CVA calculations, start by guessing deco time = dive time remaining */ - deco_time = pi->maxtime - t1; - vpmb_next_gradient(deco_time, surface_pressure / 1000.0); - } - entry->ceiling = deco_allowed_depth(tissue_tolerance_calc(dive, depth_to_bar(entry->depth, dive)), surface_pressure, dive, !prefs.calcceiling3m); - /* If using VPM-B outside the planner, take first_ceiling_pressure as the deepest ceiling */ - if (prefs.deco_mode == VPMB && !in_planner()) { - if (entry->ceiling >= first_ceiling) { - time_deep_ceiling = t1; - first_ceiling = entry->ceiling; - first_ceiling_pressure.mbar = depth_to_mbar(first_ceiling, dive); - if (first_iteration) { - nuclear_regeneration(t1); - vpmb_start_gradient(); - /* For CVA calculations, start by guessing deco time = dive time remaining */ - deco_time = pi->maxtime - t1; - vpmb_next_gradient(deco_time, surface_pressure / 1000.0); - } - } - // Use the point where the ceiling clears as the end of deco phase for CVA calculations - if (entry->ceiling > 0) - time_clear_ceiling = 0; - else if (time_clear_ceiling == 0) - time_clear_ceiling = t1; - } - } - for (j = 0; j < 16; j++) { - double m_value = buehlmann_inertgas_a[j] + entry->ambpressure / buehlmann_inertgas_b[j]; - entry->ceilings[j] = deco_allowed_depth(tolerated_by_tissue[j], surface_pressure, dive, 1); - entry->percentages[j] = tissue_inertgas_saturation[j] < entry->ambpressure ? - tissue_inertgas_saturation[j] / entry->ambpressure * AMB_PERCENTAGE : - AMB_PERCENTAGE + (tissue_inertgas_saturation[j] - entry->ambpressure) / (m_value - entry->ambpressure) * (100.0 - AMB_PERCENTAGE); - } - - /* should we do more calculations? - * We don't for print-mode because this info doesn't show up there - * If the ceiling hasn't cleared by the last data point, we need tts for VPM-B CVA calculation - * It is not necessary to do these calculation on the first VPMB iteration, except for the last data point */ - if ((prefs.calcndltts && !print_mode && (prefs.deco_mode != VPMB || in_planner() || !first_iteration)) || - (prefs.deco_mode == VPMB && !in_planner() && i == pi->nr - 1)) { - /* only calculate ndl/tts on every 30 seconds */ - if ((entry->sec - last_ndl_tts_calc_time) < 30 && i != pi->nr - 1) { - struct plot_data *prev_entry = (entry - 1); - entry->stoptime_calc = prev_entry->stoptime_calc; - entry->stopdepth_calc = prev_entry->stopdepth_calc; - entry->tts_calc = prev_entry->tts_calc; - entry->ndl_calc = prev_entry->ndl_calc; - continue; - } - last_ndl_tts_calc_time = entry->sec; - - /* We are going to mess up deco state, so store it for later restore */ - char *cache_data = NULL; - cache_deco_state(&cache_data); - calculate_ndl_tts(entry, dive, surface_pressure); - if (prefs.deco_mode == VPMB && !in_planner() && i == pi->nr - 1) - final_tts = entry->tts_calc; - /* Restore "real" deco state for next real time step */ - restore_deco_state(cache_data); - free(cache_data); - } - } - if (prefs.deco_mode == VPMB && !in_planner()) { - prev_deco_time = deco_time; - // Do we need to update deco_time? - if (final_tts > 0) - deco_time = pi->maxtime + final_tts - time_deep_ceiling; - else if (time_clear_ceiling > 0) - deco_time = time_clear_ceiling - time_deep_ceiling; - vpmb_next_gradient(deco_time, surface_pressure / 1000.0); - final_tts = 0; - last_ndl_tts_calc_time = 0; - first_ceiling = 0; - first_iteration = false; - count_iteration ++; - restore_deco_state(cache_data_initial); - } else { - // With Buhlmann, or not in planner, iterating isn't needed. This makes the while condition false. - prev_deco_time = deco_time = 0; - } - } - free(cache_data_initial); -#if DECO_CALC_DEBUG & 1 - dump_tissues(); -#endif -} -#endif - -/* Function calculate_ccr_po2: This function takes information from one plot_data structure (i.e. one point on - * the dive profile), containing the oxygen sensor values of a CCR system and, for that plot_data structure, - * calculates the po2 value from the sensor data. Several rules are applied, depending on how many o2 sensors - * there are and the differences among the readings from these sensors. - */ -static int calculate_ccr_po2(struct plot_data *entry, struct divecomputer *dc) -{ - int sump = 0, minp = 999999, maxp = -999999; - int diff_limit = 100; // The limit beyond which O2 sensor differences are considered significant (default = 100 mbar) - int i, np = 0; - - for (i = 0; i < dc->no_o2sensors; i++) - if (entry->o2sensor[i].mbar) { // Valid reading - ++np; - sump += entry->o2sensor[i].mbar; - minp = MIN(minp, entry->o2sensor[i].mbar); - maxp = MAX(maxp, entry->o2sensor[i].mbar); - } - switch (np) { - case 0: // Uhoh - return entry->o2pressure.mbar; - case 1: // Return what we have - return sump; - case 2: // Take the average - return sump / 2; - case 3: // Voting logic - if (2 * maxp - sump + minp < diff_limit) { // Upper difference acceptable... - if (2 * minp - sump + maxp) // ...and lower difference acceptable - return sump / 3; - else - return (sump - minp) / 2; - } else { - if (2 * minp - sump + maxp) // ...but lower difference acceptable - return (sump - maxp) / 2; - else - return sump / 3; - } - default: // This should not happen - assert(np <= 3); - return 0; - } -} - -static void calculate_gas_information_new(struct dive *dive, struct plot_info *pi) -{ - int i; - double amb_pressure; - - for (i = 1; i < pi->nr; i++) { - int fn2, fhe; - struct plot_data *entry = pi->entry + i; - int cylinderindex = entry->cylinderindex; - - amb_pressure = depth_to_bar(entry->depth, dive); - - fill_pressures(&entry->pressures, amb_pressure, &dive->cylinder[cylinderindex].gasmix, entry->o2pressure.mbar / 1000.0, dive->dc.divemode); - fn2 = (int)(1000.0 * entry->pressures.n2 / amb_pressure); - fhe = (int)(1000.0 * entry->pressures.he / amb_pressure); - - /* Calculate MOD, EAD, END and EADD based on partial pressures calculated before - * so there is no difference in calculating between OC and CC - * END takes O₂ + N₂ (air) into account ("Narcotic" for trimix dives) - * EAD just uses N₂ ("Air" for nitrox dives) */ - pressure_t modpO2 = { .mbar = (int)(prefs.modpO2 * 1000) }; - entry->mod = (double)gas_mod(&dive->cylinder[cylinderindex].gasmix, modpO2, dive, 1).mm; - entry->end = (entry->depth + 10000) * (1000 - fhe) / 1000.0 - 10000; - entry->ead = (entry->depth + 10000) * fn2 / (double)N2_IN_AIR - 10000; - entry->eadd = (entry->depth + 10000) * - (entry->pressures.o2 / amb_pressure * O2_DENSITY + - entry->pressures.n2 / amb_pressure * N2_DENSITY + - entry->pressures.he / amb_pressure * HE_DENSITY) / - (O2_IN_AIR * O2_DENSITY + N2_IN_AIR * N2_DENSITY) * 1000 - 10000; - if (entry->mod < 0) - entry->mod = 0; - if (entry->ead < 0) - entry->ead = 0; - if (entry->end < 0) - entry->end = 0; - if (entry->eadd < 0) - entry->eadd = 0; - } -} - -void fill_o2_values(struct divecomputer *dc, struct plot_info *pi, struct dive *dive) -/* In the samples from each dive computer, there may be uninitialised oxygen - * sensor or setpoint values, e.g. when events were inserted into the dive log - * or if the dive computer does not report o2 values with every sample. But - * for drawing the profile a complete series of valid o2 pressure values is - * required. This function takes the oxygen sensor data and setpoint values - * from the structures of plotinfo and replaces the zero values with their - * last known values so that the oxygen sensor data are complete and ready - * for plotting. This function called by: create_plot_info_new() */ -{ - int i, j; - pressure_t last_sensor[3], o2pressure; - pressure_t amb_pressure; - - for (i = 0; i < pi->nr; i++) { - struct plot_data *entry = pi->entry + i; - - if (dc->divemode == CCR) { - if (i == 0) { // For 1st iteration, initialise the last_sensor values - for (j = 0; j < dc->no_o2sensors; j++) - last_sensor[j].mbar = pi->entry->o2sensor[j].mbar; - } else { // Now re-insert the missing oxygen pressure values - for (j = 0; j < dc->no_o2sensors; j++) - if (entry->o2sensor[j].mbar) - last_sensor[j].mbar = entry->o2sensor[j].mbar; - else - entry->o2sensor[j].mbar = last_sensor[j].mbar; - } // having initialised the empty o2 sensor values for this point on the profile, - amb_pressure.mbar = depth_to_mbar(entry->depth, dive); - o2pressure.mbar = calculate_ccr_po2(entry, dc); // ...calculate the po2 based on the sensor data - entry->o2pressure.mbar = MIN(o2pressure.mbar, amb_pressure.mbar); - } else { - entry->o2pressure.mbar = 0; // initialise po2 to zero for dctype = OC - } - } -} - -#ifdef DEBUG_GAS -/* A CCR debug function that writes the cylinder pressure and the oxygen values to the file debug_print_profiledata.dat: - * Called in create_plot_info_new() - */ -static void debug_print_profiledata(struct plot_info *pi) -{ - FILE *f1; - struct plot_data *entry; - int i; - if (!(f1 = fopen("debug_print_profiledata.dat", "w"))) { - printf("File open error for: debug_print_profiledata.dat\n"); - } else { - fprintf(f1, "id t1 gas gasint t2 t3 dil dilint t4 t5 setpoint sensor1 sensor2 sensor3 t6 po2 fo2\n"); - for (i = 0; i < pi->nr; i++) { - entry = pi->entry + i; - fprintf(f1, "%d gas=%8d %8d ; dil=%8d %8d ; o2_sp= %d %d %d %d PO2= %f\n", i, SENSOR_PRESSURE(entry), - INTERPOLATED_PRESSURE(entry), O2CYLINDER_PRESSURE(entry), INTERPOLATED_O2CYLINDER_PRESSURE(entry), - entry->o2pressure.mbar, entry->o2sensor[0].mbar, entry->o2sensor[1].mbar, entry->o2sensor[2].mbar, entry->pressures.o2); - } - fclose(f1); - } -} -#endif - -/* - * Create a plot-info with smoothing and ranged min/max - * - * This also makes sure that we have extra empty events on both - * sides, so that you can do end-points without having to worry - * about it. - */ -void create_plot_info_new(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool fast) -{ - int o2, he, o2max; -#ifndef SUBSURFACE_MOBILE - init_decompression(dive); -#endif - /* Create the new plot data */ - free((void *)last_pi_entry_new); - - get_dive_gas(dive, &o2, &he, &o2max); - if (dc->divemode == FREEDIVE){ - pi->dive_type = FREEDIVE; - } else if (he > 0) { - pi->dive_type = TRIMIX; - } else { - if (o2) - pi->dive_type = NITROX; - else - pi->dive_type = AIR; - } - - last_pi_entry_new = populate_plot_entries(dive, dc, pi); - - check_gas_change_events(dive, dc, pi); /* Populate the gas index from the gas change events */ - check_setpoint_events(dive, dc, pi); /* Populate setpoints */ - setup_gas_sensor_pressure(dive, dc, pi); /* Try to populate our gas pressure knowledge */ - if (!fast) { - populate_pressure_information(dive, dc, pi, false); /* .. calculate missing pressure entries for all gasses except o2 */ - if (dc->divemode == CCR) /* For CCR dives.. */ - populate_pressure_information(dive, dc, pi, true); /* .. calculate missing o2 gas pressure entries */ - } - fill_o2_values(dc, pi, dive); /* .. and insert the O2 sensor data having 0 values. */ - calculate_sac(dive, pi); /* Calculate sac */ -#ifndef SUBSURFACE_MOBILE - calculate_deco_information(dive, dc, pi, false); /* and ceiling information, using gradient factor values in Preferences) */ -#endif - calculate_gas_information_new(dive, pi); /* Calculate gas partial pressures */ - -#ifdef DEBUG_GAS - debug_print_profiledata(pi); -#endif - - pi->meandepth = dive->dc.meandepth.mm; - analyze_plot_info(pi); -} - -struct divecomputer *select_dc(struct dive *dive) -{ - unsigned int max = number_of_computers(dive); - unsigned int i = dc_number; - - /* Reset 'dc_number' if we've switched dives and it is now out of range */ - if (i >= max) - dc_number = i = 0; - - return get_dive_dc(dive, i); -} - -static void plot_string(struct plot_info *pi, struct plot_data *entry, struct membuffer *b, bool has_ndl) -{ - int pressurevalue, mod, ead, end, eadd; - const char *depth_unit, *pressure_unit, *temp_unit, *vertical_speed_unit; - double depthvalue, tempvalue, speedvalue, sacvalue; - int decimals; - const char *unit; - - depthvalue = get_depth_units(entry->depth, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "@: %d:%02d\nD: %.1f%s\n"), FRACTION(entry->sec, 60), depthvalue, depth_unit); - if (GET_PRESSURE(entry)) { - pressurevalue = get_pressure_units(GET_PRESSURE(entry), &pressure_unit); - put_format(b, translate("gettextFromC", "P: %d%s\n"), pressurevalue, pressure_unit); - } - if (entry->temperature) { - tempvalue = get_temp_units(entry->temperature, &temp_unit); - put_format(b, translate("gettextFromC", "T: %.1f%s\n"), tempvalue, temp_unit); - } - speedvalue = get_vertical_speed_units(abs(entry->speed), NULL, &vertical_speed_unit); - /* Ascending speeds are positive, descending are negative */ - if (entry->speed > 0) - speedvalue *= -1; - put_format(b, translate("gettextFromC", "V: %.1f%s\n"), speedvalue, vertical_speed_unit); - sacvalue = get_volume_units(entry->sac, &decimals, &unit); - if (entry->sac && prefs.show_sac) - put_format(b, translate("gettextFromC", "SAC: %.*f%s/min\n"), decimals, sacvalue, unit); - if (entry->cns) - put_format(b, translate("gettextFromC", "CNS: %u%%\n"), entry->cns); - if (prefs.pp_graphs.po2) - put_format(b, translate("gettextFromC", "pO%s: %.2fbar\n"), UTF8_SUBSCRIPT_2, entry->pressures.o2); - if (prefs.pp_graphs.pn2) - put_format(b, translate("gettextFromC", "pN%s: %.2fbar\n"), UTF8_SUBSCRIPT_2, entry->pressures.n2); - if (prefs.pp_graphs.phe) - put_format(b, translate("gettextFromC", "pHe: %.2fbar\n"), entry->pressures.he); - if (prefs.mod) { - mod = (int)get_depth_units(entry->mod, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "MOD: %d%s\n"), mod, depth_unit); - } - eadd = (int)get_depth_units(entry->eadd, NULL, &depth_unit); - if (prefs.ead) { - switch (pi->dive_type) { - case NITROX: - ead = (int)get_depth_units(entry->ead, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "EAD: %d%s\nEADD: %d%s\n"), ead, depth_unit, eadd, depth_unit); - break; - case TRIMIX: - end = (int)get_depth_units(entry->end, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "END: %d%s\nEADD: %d%s\n"), end, depth_unit, eadd, depth_unit); - break; - case AIR: - case FREEDIVING: - /* nothing */ - break; - } - } - if (entry->stopdepth) { - depthvalue = get_depth_units(entry->stopdepth, NULL, &depth_unit); - if (entry->ndl) { - /* this is a safety stop as we still have ndl */ - if (entry->stoptime) - put_format(b, translate("gettextFromC", "Safetystop: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60), - depthvalue, depth_unit); - else - put_format(b, translate("gettextFromC", "Safetystop: unkn time @ %.0f%s\n"), - depthvalue, depth_unit); - } else { - /* actual deco stop */ - if (entry->stoptime) - put_format(b, translate("gettextFromC", "Deco: %umin @ %.0f%s\n"), DIV_UP(entry->stoptime, 60), - depthvalue, depth_unit); - else - put_format(b, translate("gettextFromC", "Deco: unkn time @ %.0f%s\n"), - depthvalue, depth_unit); - } - } else if (entry->in_deco) { - put_string(b, translate("gettextFromC", "In deco\n")); - } else if (has_ndl) { - put_format(b, translate("gettextFromC", "NDL: %umin\n"), DIV_UP(entry->ndl, 60)); - } - if (entry->tts) - put_format(b, translate("gettextFromC", "TTS: %umin\n"), DIV_UP(entry->tts, 60)); - if (entry->stopdepth_calc && entry->stoptime_calc) { - depthvalue = get_depth_units(entry->stopdepth_calc, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "Deco: %umin @ %.0f%s (calc)\n"), DIV_UP(entry->stoptime_calc, 60), - depthvalue, depth_unit); - } else if (entry->in_deco_calc) { - /* This means that we have no NDL left, - * and we have no deco stop, - * so if we just accend to the surface slowly - * (ascent_mm_per_step / ascent_s_per_step) - * everything will be ok. */ - put_string(b, translate("gettextFromC", "In deco (calc)\n")); - } else if (prefs.calcndltts && entry->ndl_calc != 0) { - if(entry->ndl_calc < MAX_PROFILE_DECO) - put_format(b, translate("gettextFromC", "NDL: %umin (calc)\n"), DIV_UP(entry->ndl_calc, 60)); - else - put_format(b, "%s", translate("gettextFromC", "NDL: >2h (calc)\n")); - } - if (entry->tts_calc) { - if (entry->tts_calc < MAX_PROFILE_DECO) - put_format(b, translate("gettextFromC", "TTS: %umin (calc)\n"), DIV_UP(entry->tts_calc, 60)); - else - put_format(b, "%s", translate("gettextFromC", "TTS: >2h (calc)\n")); - } - if (entry->rbt) - put_format(b, translate("gettextFromC", "RBT: %umin\n"), DIV_UP(entry->rbt, 60)); - if (entry->ceiling) { - depthvalue = get_depth_units(entry->ceiling, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "Calculated ceiling %.0f%s\n"), depthvalue, depth_unit); - if (prefs.calcalltissues) { - int k; - for (k = 0; k < 16; k++) { - if (entry->ceilings[k]) { - depthvalue = get_depth_units(entry->ceilings[k], NULL, &depth_unit); - put_format(b, translate("gettextFromC", "Tissue %.0fmin: %.1f%s\n"), buehlmann_N2_t_halflife[k], depthvalue, depth_unit); - } - } - } - } - if (entry->heartbeat && prefs.hrgraph) - put_format(b, translate("gettextFromC", "heartbeat: %d\n"), entry->heartbeat); - if (entry->bearing) - put_format(b, translate("gettextFromC", "bearing: %d\n"), entry->bearing); - if (entry->running_sum) { - depthvalue = get_depth_units(entry->running_sum / entry->sec, NULL, &depth_unit); - put_format(b, translate("gettextFromC", "mean depth to here %.1f%s\n"), depthvalue, depth_unit); - } - - strip_mb(b); -} - -struct plot_data *get_plot_details_new(struct plot_info *pi, int time, struct membuffer *mb) -{ - struct plot_data *entry = NULL; - int i; - - for (i = 0; i < pi->nr; i++) { - entry = pi->entry + i; - if (entry->sec >= time) - break; - } - if (entry) - plot_string(pi, entry, mb, pi->has_ndl); - return (entry); -} - -/* Compare two plot_data entries and writes the results into a string */ -void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int bufsize, int sum) -{ - struct plot_data *start, *stop, *data; - const char *depth_unit, *pressure_unit, *vertical_speed_unit; - char *buf2 = malloc(bufsize); - int avg_speed, max_asc_speed, max_desc_speed; - int delta_depth, avg_depth, max_depth, min_depth; - int bar_used, last_pressure, pressurevalue; - int count, last_sec, delta_time; - - double depthvalue, speedvalue; - - if (bufsize > 0) - buf[0] = '\0'; - if (e1 == NULL || e2 == NULL) { - free(buf2); - return; - } - - if (e1->sec < e2->sec) { - start = e1; - stop = e2; - } else if (e1->sec > e2->sec) { - start = e2; - stop = e1; - } else { - free(buf2); - return; - } - count = 0; - avg_speed = 0; - max_asc_speed = 0; - max_desc_speed = 0; - - delta_depth = abs(start->depth - stop->depth); - delta_time = abs(start->sec - stop->sec); - avg_depth = 0; - max_depth = 0; - min_depth = INT_MAX; - bar_used = 0; - - last_sec = start->sec; - last_pressure = GET_PRESSURE(start); - - data = start; - while (data != stop) { - data = start + count; - if (sum) - avg_speed += abs(data->speed) * (data->sec - last_sec); - else - avg_speed += data->speed * (data->sec - last_sec); - avg_depth += data->depth * (data->sec - last_sec); - - if (data->speed > max_desc_speed) - max_desc_speed = data->speed; - if (data->speed < max_asc_speed) - max_asc_speed = data->speed; - - if (data->depth < min_depth) - min_depth = data->depth; - if (data->depth > max_depth) - max_depth = data->depth; - /* Try to detect gas changes */ - if (GET_PRESSURE(data) < last_pressure + 2000) - bar_used += last_pressure - GET_PRESSURE(data); - - count += 1; - last_sec = data->sec; - last_pressure = GET_PRESSURE(data); - } - avg_depth /= stop->sec - start->sec; - avg_speed /= stop->sec - start->sec; - - snprintf(buf, bufsize, translate("gettextFromC", "%sT: %d:%02d min"), UTF8_DELTA, delta_time / 60, delta_time % 60); - memcpy(buf2, buf, bufsize); - - depthvalue = get_depth_units(delta_depth, NULL, &depth_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s"), buf2, UTF8_DELTA, depthvalue, depth_unit); - memcpy(buf2, buf, bufsize); - - depthvalue = get_depth_units(min_depth, NULL, &depth_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s"), buf2, UTF8_DOWNWARDS_ARROW, depthvalue, depth_unit); - memcpy(buf2, buf, bufsize); - - depthvalue = get_depth_units(max_depth, NULL, &depth_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s"), buf2, UTF8_UPWARDS_ARROW, depthvalue, depth_unit); - memcpy(buf2, buf, bufsize); - - depthvalue = get_depth_units(avg_depth, NULL, &depth_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sD:%.1f%s\n"), buf2, UTF8_AVERAGE, depthvalue, depth_unit); - memcpy(buf2, buf, bufsize); - - speedvalue = get_vertical_speed_units(abs(max_desc_speed), NULL, &vertical_speed_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s%sV:%.2f%s"), buf2, UTF8_DOWNWARDS_ARROW, speedvalue, vertical_speed_unit); - memcpy(buf2, buf, bufsize); - - speedvalue = get_vertical_speed_units(abs(max_asc_speed), NULL, &vertical_speed_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sV:%.2f%s"), buf2, UTF8_UPWARDS_ARROW, speedvalue, vertical_speed_unit); - memcpy(buf2, buf, bufsize); - - speedvalue = get_vertical_speed_units(abs(avg_speed), NULL, &vertical_speed_unit); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sV:%.2f%s"), buf2, UTF8_AVERAGE, speedvalue, vertical_speed_unit); - memcpy(buf2, buf, bufsize); - - /* Only print if gas has been used */ - if (bar_used) { - pressurevalue = get_pressure_units(bar_used, &pressure_unit); - memcpy(buf2, buf, bufsize); - snprintf(buf, bufsize, translate("gettextFromC", "%s %sP:%d %s"), buf2, UTF8_DELTA, pressurevalue, pressure_unit); - } - - free(buf2); -} diff --git a/subsurface-core/profile.h b/subsurface-core/profile.h deleted file mode 100644 index abac9dd49..000000000 --- a/subsurface-core/profile.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef PROFILE_H -#define PROFILE_H - -#include "dive.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum { - STABLE, - SLOW, - MODERATE, - FAST, - CRAZY -} velocity_t; - -struct membuffer; -struct divecomputer; -struct plot_info; -struct plot_data { - unsigned int in_deco : 1; - int cylinderindex; - int sec; - /* pressure[0] is sensor cylinder pressure [when CCR, the pressure of the diluent cylinder] - * pressure[1] is interpolated cylinder pressure */ - int pressure[2]; - /* o2pressure[0] is o2 cylinder pressure [CCR] - * o2pressure[1] is interpolated o2 cylinder pressure [CCR] */ - int o2cylinderpressure[2]; - int temperature; - /* Depth info */ - int depth; - int ceiling; - int ceilings[16]; - int percentages[16]; - int ndl; - int tts; - int rbt; - int stoptime; - int stopdepth; - int cns; - int smoothed; - int sac; - int running_sum; - struct gas_pressures pressures; - pressure_t o2pressure; // for rebreathers, this is consensus measured po2, or setpoint otherwise. 0 for OC. - pressure_t o2sensor[3]; //for rebreathers with up to 3 PO2 sensors - pressure_t o2setpoint; - double mod, ead, end, eadd; - velocity_t velocity; - int speed; - struct plot_data *min[3]; - struct plot_data *max[3]; - int avg[3]; - /* values calculated by us */ - unsigned int in_deco_calc : 1; - int ndl_calc; - int tts_calc; - int stoptime_calc; - int stopdepth_calc; - int pressure_time; - int heartbeat; - int bearing; - double ambpressure; - double gfline; -}; - -struct ev_select { - char *ev_name; - bool plot_ev; -}; - -struct plot_info calculate_max_limits_new(struct dive *dive, struct divecomputer *given_dc); -void compare_samples(struct plot_data *e1, struct plot_data *e2, char *buf, int bufsize, int sum); -struct plot_data *populate_plot_entries(struct dive *dive, struct divecomputer *dc, struct plot_info *pi); -struct plot_info *analyze_plot_info(struct plot_info *pi); -void create_plot_info_new(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool fast); -void calculate_deco_information(struct dive *dive, struct divecomputer *dc, struct plot_info *pi, bool print_mode); -struct plot_data *get_plot_details_new(struct plot_info *pi, int time, struct membuffer *); - -/* - * When showing dive profiles, we scale things to the - * current dive. However, we don't scale past less than - * 30 minutes or 90 ft, just so that small dives show - * up as such unless zoom is enabled. - * We also need to add 180 seconds at the end so the min/max - * plots correctly - */ -int get_maxtime(struct plot_info *pi); - -/* get the maximum depth to which we want to plot - * take into account the additional verical space needed to plot - * partial pressure graphs */ -int get_maxdepth(struct plot_info *pi); - -#define SENSOR_PR 0 -#define INTERPOLATED_PR 1 -#define SENSOR_PRESSURE(_entry) (_entry)->pressure[SENSOR_PR] -#define O2CYLINDER_PRESSURE(_entry) (_entry)->o2cylinderpressure[SENSOR_PR] -#define CYLINDER_PRESSURE(_o2, _entry) (_o2 ? O2CYLINDER_PRESSURE(_entry) : SENSOR_PRESSURE(_entry)) -#define INTERPOLATED_PRESSURE(_entry) (_entry)->pressure[INTERPOLATED_PR] -#define INTERPOLATED_O2CYLINDER_PRESSURE(_entry) (_entry)->o2cylinderpressure[INTERPOLATED_PR] -#define GET_PRESSURE(_entry) (SENSOR_PRESSURE(_entry) ? SENSOR_PRESSURE(_entry) : INTERPOLATED_PRESSURE(_entry)) -#define GET_O2CYLINDER_PRESSURE(_entry) (O2CYLINDER_PRESSURE(_entry) ? O2CYLINDER_PRESSURE(_entry) : INTERPOLATED_O2CYLINDER_PRESSURE(_entry)) -#define SAC_WINDOW 45 /* sliding window in seconds for current SAC calculation */ - -#ifdef __cplusplus -} -#endif -#endif // PROFILE_H diff --git a/subsurface-core/qt-gui.h b/subsurface-core/qt-gui.h deleted file mode 100644 index 92532d22f..000000000 --- a/subsurface-core/qt-gui.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef QT_GUI_H -#define QT_GUI_H - -void init_qt_late(); -void init_ui(); - -void run_ui(); -void exit_ui(); - -#if defined(SUBSURFACE_MOBILE) -#include -extern QObject *qqWindowObject; -#endif - -#endif // QT_GUI_H diff --git a/subsurface-core/qt-init.cpp b/subsurface-core/qt-init.cpp deleted file mode 100644 index b52dfd970..000000000 --- a/subsurface-core/qt-init.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include -#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 deleted file mode 100644 index c45e86388..000000000 --- a/subsurface-core/qthelper.cpp +++ /dev/null @@ -1,1615 +0,0 @@ -#include "qthelper.h" -#include "helpers.h" -#include "gettextfromc.h" -#include "statistics.h" -#include "membuffer.h" -#include "subsurfacesysinfo.h" -#include "version.h" -#include "divecomputer.h" -#include "time.h" -#include "gettextfromc.h" -#include -#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 QLocale loc; - -#define translate(_context, arg) trGettext(arg) -static const QString DEGREE_SIGNS("dD" UTF8_DEGREE); - -QString weight_string(int weight_in_grams) -{ - QString str; - if (get_units()->weight == units::KG) { - int gr = weight_in_grams % 1000; - int kg = weight_in_grams / 1000; - if (kg >= 20.0) - str = QString("%1").arg(kg + (gr >= 500 ? 1 : 0)); - else - str = QString("%1.%2").arg(kg).arg((unsigned)(gr + 50) / 100); - } else { - double lbs = grams_to_lbs(weight_in_grams); - if (lbs >= 40.0) - lbs = rint(lbs + 0.5); - else - lbs = rint(lbs + 0.05); - str = QString("%1").arg(lbs, 0, 'f', lbs >= 40.0 ? 0 : 1); - } - return (str); -} - -QString distance_string(int distanceInMeters) -{ - QString str; - if(get_units()->length == units::METERS) { - if (distanceInMeters >= 1000) - str = QString(translate("gettextFromC", "%1km")).arg(distanceInMeters / 1000); - else - str = QString(translate("gettextFromC", "%1m")).arg(distanceInMeters); - } else { - double miles = m_to_mile(distanceInMeters); - if (miles >= 1.0) - str = QString(translate("gettextFromC", "%1mi")).arg((int)miles); - else - str = QString(translate("gettextFromC", "%1yd")).arg((int)(miles * 1760)); - } - return str; -} - -extern "C" const char *printGPSCoords(int lat, int lon) -{ - unsigned int latdeg, londeg; - unsigned int latmin, lonmin; - double latsec, lonsec; - QString lath, lonh, result; - - if (!lat && !lon) - return strdup(""); - - if (prefs.coordinates_traditional) { - lath = lat >= 0 ? translate("gettextFromC", "N") : translate("gettextFromC", "S"); - lonh = lon >= 0 ? translate("gettextFromC", "E") : translate("gettextFromC", "W"); - lat = abs(lat); - lon = abs(lon); - latdeg = lat / 1000000U; - londeg = lon / 1000000U; - latmin = (lat % 1000000U) * 60U; - lonmin = (lon % 1000000U) * 60U; - latsec = (latmin % 1000000) * 60; - lonsec = (lonmin % 1000000) * 60; - result.sprintf("%u%s%02d\'%06.3f\"%s %u%s%02d\'%06.3f\"%s", - latdeg, UTF8_DEGREE, latmin / 1000000, latsec / 1000000, lath.toUtf8().data(), - londeg, UTF8_DEGREE, lonmin / 1000000, lonsec / 1000000, lonh.toUtf8().data()); - } else { - result.sprintf("%f %f", (double) lat / 1000000.0, (double) lon / 1000000.0); - } - return strdup(result.toUtf8().data()); -} - -/** -* Try to parse in a generic manner a coordinate. -*/ -static bool parseCoord(const QString& txt, int& pos, const QString& positives, - const QString& negatives, const QString& others, - double& value) -{ - bool numberDefined = false, degreesDefined = false, - minutesDefined = false, secondsDefined = false; - double number = 0.0; - int posBeforeNumber = pos; - int sign = 0; - value = 0.0; - while (pos < txt.size()) { - if (txt[pos].isDigit()) { - if (numberDefined) - return false; - QRegExp numberRe("(\\d+(?:[\\.,]\\d+)?).*"); - if (!numberRe.exactMatch(txt.mid(pos))) - return false; - number = numberRe.cap(1).toDouble(); - numberDefined = true; - posBeforeNumber = pos; - pos += numberRe.cap(1).size() - 1; - } else if (positives.indexOf(txt[pos]) >= 0) { - if (sign != 0) - return false; - sign = 1; - if (degreesDefined || numberDefined) { - //sign after the degrees => - //at the end of the coordinate - ++pos; - break; - } - } else if (negatives.indexOf(txt[pos]) >= 0) { - if (sign != 0) { - if (others.indexOf(txt[pos]) >= 0) - //special case for the '-' sign => next coordinate - break; - return false; - } - sign = -1; - if (degreesDefined || numberDefined) { - //sign after the degrees => - //at the end of the coordinate - ++pos; - break; - } - } else if (others.indexOf(txt[pos]) >= 0) { - //we are at the next coordinate. - break; - } else if (DEGREE_SIGNS.indexOf(txt[pos]) >= 0 || - (txt[pos].isSpace() && !degreesDefined && numberDefined)) { - if (!numberDefined) - return false; - if (degreesDefined) { - //next coordinate => need to put back the number - pos = posBeforeNumber; - numberDefined = false; - break; - } - value += number; - numberDefined = false; - degreesDefined = true; - } else if (txt[pos] == '\'' || (txt[pos].isSpace() && !minutesDefined && numberDefined)) { - if (!numberDefined || minutesDefined) - return false; - value += number / 60.0; - numberDefined = false; - minutesDefined = true; - } else if (txt[pos] == '"' || (txt[pos].isSpace() && !secondsDefined && numberDefined)) { - if (!numberDefined || secondsDefined) - return false; - value += number / 3600.0; - numberDefined = false; - secondsDefined = true; - } else if ((numberDefined || minutesDefined || secondsDefined) && - (txt[pos] == ',' || txt[pos] == ';')) { - // next coordinate coming up - // eat the ',' and any subsequent white space - while (txt[++pos].isSpace()) - /* nothing */ ; - break; - } else { - return false; - } - ++pos; - } - if (!degreesDefined && numberDefined) { - value = number; //just a single number => degrees - } else if (!minutesDefined && numberDefined) { - value += number / 60.0; - } else if (!secondsDefined && numberDefined) { - value += number / 3600.0; - } else if (numberDefined) { - return false; - } - if (sign == -1) value *= -1.0; - return true; -} - -/** -* Parse special coordinate formats that cannot be handled by parseCoord. -*/ -static bool parseSpecialCoords(const QString& txt, double& latitude, double& longitude) { - QRegExp xmlFormat("(-?\\d+(?:\\.\\d+)?),?\\s+(-?\\d+(?:\\.\\d+)?)"); - if (xmlFormat.exactMatch(txt)) { - latitude = xmlFormat.cap(1).toDouble(); - longitude = xmlFormat.cap(2).toDouble(); - return true; - } - return false; -} - -bool parseGpsText(const QString &gps_text, double *latitude, double *longitude) -{ - static const QString POS_LAT = QString("+N") + translate("gettextFromC", "N"); - static const QString NEG_LAT = QString("-S") + translate("gettextFromC", "S"); - static const QString POS_LON = QString("+E") + translate("gettextFromC", "E"); - static const QString NEG_LON = QString("-W") + translate("gettextFromC", "W"); - - //remove the useless spaces (but keep the ones separating numbers) - static const QRegExp SPACE_CLEANER("\\s*([" + POS_LAT + NEG_LAT + POS_LON + - NEG_LON + DEGREE_SIGNS + "'\"\\s])\\s*"); - const QString normalized = gps_text.trimmed().toUpper().replace(SPACE_CLEANER, "\\1"); - - if (normalized.isEmpty()) { - *latitude = 0.0; - *longitude = 0.0; - return true; - } - if (parseSpecialCoords(normalized, *latitude, *longitude)) - return true; - int pos = 0; - return parseCoord(normalized, pos, POS_LAT, NEG_LAT, POS_LON + NEG_LON, *latitude) && - parseCoord(normalized, pos, POS_LON, NEG_LON, "", *longitude) && - pos == normalized.size(); -} - -#if 0 // we'll need something like this for the dive site management, eventually -bool gpsHasChanged(struct dive *dive, struct dive *master, const QString &gps_text, bool *parsed_out) -{ - double latitude, longitude; - int latudeg, longudeg; - bool ignore; - bool *parsed = parsed_out ?: &ignore; - *parsed = true; - - /* if we have a master and the dive's gps address is different from it, - * don't change the dive */ - if (master && (master->latitude.udeg != dive->latitude.udeg || - master->longitude.udeg != dive->longitude.udeg)) - return false; - - if (!(*parsed = parseGpsText(gps_text, &latitude, &longitude))) - return false; - - latudeg = rint(1000000 * latitude); - longudeg = rint(1000000 * longitude); - - /* if dive gps didn't change, nothing changed */ - if (dive->latitude.udeg == latudeg && dive->longitude.udeg == longudeg) - return false; - /* ok, update the dive and mark things changed */ - dive->latitude.udeg = latudeg; - dive->longitude.udeg = longudeg; - return true; -} -#endif - -QList 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 -#ifdef SUBSURFACE_MOBILE - QString userAgent = QString("Subsurface-mobile:%1(%2):").arg(subsurface_mobile_version()).arg(subsurface_canonical_version()); -#else - QString userAgent = QString("Subsurface:%1:").arg(subsurface_canonical_version()); -#endif - userAgent.append(SubsurfaceSysInfo::prettyOsName().replace(':', ' ') + ":"); - arch = SubsurfaceSysInfo::buildCpuArchitecture().replace(':', ' '); - userAgent.append(arch); - if (arch == "i386") - userAgent.append("/" + SubsurfaceSysInfo::currentCpuArchitecture()); - userAgent.append(":" + uiLanguage(NULL)); - return userAgent; - -} - -extern "C" const char *subsurface_user_agent() -{ - static QString uA = getUserAgent(); - - return strdup(qPrintable(uA)); -} - -QString uiLanguage(QLocale *callerLoc) -{ - QString shortDateFormat; - QString dateFormat; - QString timeFormat; - QSettings s; - QVariant v; - s.beginGroup("Language"); - - if (!s.value("UseSystemLanguage", true).toBool()) { - loc = QLocale(s.value("UiLanguage", QLocale().uiLanguages().first()).toString()); - } else { - loc = QLocale(QLocale().uiLanguages().first()); - } - QStringList languages = loc.uiLanguages(); - QString uiLang; - if (languages[0].contains('-')) - uiLang = languages[0]; - else if (languages.count() > 1 && languages[1].contains('-')) - uiLang = languages[1]; - else if (languages.count() > 2 && languages[2].contains('-')) - uiLang = languages[2]; - else - uiLang = languages[0]; - GET_BOOL("time_format_override", time_format_override); - GET_BOOL("date_format_override", date_format_override); - GET_TXT("time_format", time_format); - GET_TXT("date_format", date_format); - GET_TXT("date_format_short", date_format_short); - s.endGroup(); - - // there's a stupid Qt bug on MacOS where uiLanguages doesn't give us the country info - if (!uiLang.contains('-') && uiLang != loc.bcp47Name()) { - QLocale loc2(loc.bcp47Name()); - loc = loc2; - QStringList languages = loc2.uiLanguages(); - if (languages[0].contains('-')) - uiLang = languages[0]; - else if (languages.count() > 1 && languages[1].contains('-')) - uiLang = languages[1]; - else if (languages.count() > 2 && languages[2].contains('-')) - uiLang = languages[2]; - } - if (callerLoc) - *callerLoc = loc; - - if (!prefs.date_format_override || same_string(prefs.date_format_short, "") || same_string(prefs.date_format, "")) { - // derive our standard date format from what the locale gives us - // the short format is fine - // the long format uses long weekday and month names, so replace those with the short ones - // for time we don't want the time zone designator and don't want leading zeroes on the hours - shortDateFormat = loc.dateFormat(QLocale::ShortFormat); - dateFormat = loc.dateFormat(QLocale::LongFormat); - dateFormat.replace("dddd,", "ddd").replace("dddd", "ddd").replace("MMMM", "MMM"); - // special hack for Swedish as our switching from long weekday names to short weekday names - // messes things up there - dateFormat.replace("'en' 'den' d:'e'", " d"); - if (!prefs.date_format_override || same_string(prefs.date_format, "")) { - free((void*)prefs.date_format); - prefs.date_format = strdup(qPrintable(dateFormat)); - } - if (!prefs.date_format_override || same_string(prefs.date_format_short, "")) { - free((void*)prefs.date_format_short); - prefs.date_format_short = strdup(qPrintable(shortDateFormat)); - } - } - if (!prefs.time_format_override || same_string(prefs.time_format, "")) { - timeFormat = loc.timeFormat(); - timeFormat.replace("(t)", "").replace(" t", "").replace("t", "").replace("hh", "h").replace("HH", "H").replace("'kl'.", ""); - timeFormat.replace(".ss", "").replace(":ss", "").replace("ss", ""); - free((void*)prefs.time_format); - prefs.time_format = strdup(qPrintable(timeFormat)); - } - return uiLang; -} - -QLocale getLocale() -{ - return loc; -} - -void set_filename(const char *filename, bool force) -{ - if (!force && existing_filename) - return; - free((void *)existing_filename); - if (filename) - existing_filename = strdup(filename); - else - existing_filename = NULL; -} - -const QString get_dc_nickname(const char *model, uint32_t deviceid) -{ - const DiveComputerNode *existNode = dcList.getExact(model, deviceid); - - if (existNode && !existNode->nickName.isEmpty()) - return existNode->nickName; - else - return model; -} - -QString get_depth_string(int mm, bool showunit, bool showdecimal) -{ - if (prefs.units.length == units::METERS) { - double meters = mm / 1000.0; - return QString("%1%2").arg(meters, 0, 'f', (showdecimal && meters < 20.0) ? 1 : 0).arg(showunit ? translate("gettextFromC", "m") : ""); - } else { - double feet = mm_to_feet(mm); - return QString("%1%2").arg(feet, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "ft") : ""); - } -} - -QString get_depth_string(depth_t depth, bool showunit, bool showdecimal) -{ - return get_depth_string(depth.mm, showunit, showdecimal); -} - -QString get_depth_unit() -{ - if (prefs.units.length == units::METERS) - return QString("%1").arg(translate("gettextFromC", "m")); - else - return QString("%1").arg(translate("gettextFromC", "ft")); -} - -QString get_weight_string(weight_t weight, bool showunit) -{ - QString str = weight_string(weight.grams); - if (get_units()->weight == units::KG) { - str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "kg") : ""); - } else { - str = QString("%1%2").arg(str).arg(showunit ? translate("gettextFromC", "lbs") : ""); - } - return (str); -} - -QString get_weight_unit() -{ - if (prefs.units.weight == units::KG) - return QString("%1").arg(translate("gettextFromC", "kg")); - else - return QString("%1").arg(translate("gettextFromC", "lbs")); -} - -/* these methods retrieve used gas per cylinder */ -static unsigned start_pressure(cylinder_t *cyl) -{ - return cyl->start.mbar ?: cyl->sample_start.mbar; -} - -static unsigned end_pressure(cylinder_t *cyl) -{ - return cyl->end.mbar ?: cyl->sample_end.mbar; -} - -QString get_cylinder_used_gas_string(cylinder_t *cyl, bool showunit) -{ - int decimals; - const char *unit; - double gas_usage; - /* Get the cylinder gas use in mbar */ - gas_usage = start_pressure(cyl) - end_pressure(cyl); - /* Can we turn it into a volume? */ - if (cyl->type.size.mliter) { - gas_usage = bar_to_atm(gas_usage / 1000); - gas_usage *= cyl->type.size.mliter; - gas_usage = get_volume_units(gas_usage, &decimals, &unit); - } else { - gas_usage = get_pressure_units(gas_usage, &unit); - decimals = 0; - } - // translate("gettextFromC","%.*f %s" - return QString("%1 %2").arg(gas_usage, 0, 'f', decimals).arg(showunit ? unit : ""); -} - -QString get_temperature_string(temperature_t temp, bool showunit) -{ - if (temp.mkelvin == 0) { - return ""; //temperature not defined - } else if (prefs.units.temperature == units::CELSIUS) { - double celsius = mkelvin_to_C(temp.mkelvin); - return QString("%1%2%3").arg(celsius, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "C") : ""); - } else { - double fahrenheit = mkelvin_to_F(temp.mkelvin); - return QString("%1%2%3").arg(fahrenheit, 0, 'f', 1).arg(showunit ? (UTF8_DEGREE) : "").arg(showunit ? translate("gettextFromC", "F") : ""); - } -} - -QString get_temp_unit() -{ - if (prefs.units.temperature == units::CELSIUS) - return QString(UTF8_DEGREE "C"); - else - return QString(UTF8_DEGREE "F"); -} - -QString get_volume_string(volume_t volume, bool showunit) -{ - const char *unit; - int decimals; - double value = get_volume_units(volume.mliter, &decimals, &unit); - return QString("%1%2").arg(value, 0, 'f', decimals).arg(showunit ? unit : ""); -} - -QString get_volume_unit() -{ - const char *unit; - (void) get_volume_units(0, NULL, &unit); - return QString(unit); -} - -QString get_pressure_string(pressure_t pressure, bool showunit) -{ - if (prefs.units.pressure == units::BAR) { - double bar = pressure.mbar / 1000.0; - return QString("%1%2").arg(bar, 0, 'f', 1).arg(showunit ? translate("gettextFromC", "bar") : ""); - } else { - double psi = mbar_to_PSI(pressure.mbar); - return QString("%1%2").arg(psi, 0, 'f', 0).arg(showunit ? translate("gettextFromC", "psi") : ""); - } -} - -QString getSubsurfaceDataPath(QString folderToFind) -{ - QString execdir; - QDir folder; - - // first check if we are running in the build dir, so the path that we - // are looking for is just a subdirectory of the execution path; - // this also works on Windows as there we install the dirs - // under the application path - execdir = QCoreApplication::applicationDirPath(); - folder = QDir(execdir.append(QDir::separator()).append(folderToFind)); - if (folder.exists()) - return folder.absolutePath(); - - // next check for the Linux typical $(prefix)/share/subsurface - execdir = QCoreApplication::applicationDirPath(); - if (execdir.contains("bin")) { - folder = QDir(execdir.replace("bin", "share/subsurface/").append(folderToFind)); - if (folder.exists()) - return folder.absolutePath(); - } - // then look for the usual locations on a Mac - execdir = QCoreApplication::applicationDirPath(); - folder = QDir(execdir.append("/../Resources/share/").append(folderToFind)); - if (folder.exists()) - return folder.absolutePath(); - execdir = QCoreApplication::applicationDirPath(); - folder = QDir(execdir.append("/../Resources/").append(folderToFind)); - if (folder.exists()) - return folder.absolutePath(); - return QString(""); -} - -static const char *printing_templates = "printing_templates"; - -QString getPrintingTemplatePathUser() -{ - static QString path = QString(); - if (path.isEmpty()) - path = QString(system_default_directory()) + QDir::separator() + QString(printing_templates); - return path; -} - -QString getPrintingTemplatePathBundle() -{ - static QString path = QString(); - if (path.isEmpty()) - path = getSubsurfaceDataPath(printing_templates); - return path; -} - -void copyPath(QString src, QString dst) -{ - QDir dir(src); - if (!dir.exists()) - return; - foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { - QString dst_path = dst + QDir::separator() + d; - dir.mkpath(dst_path); - copyPath(src + QDir::separator() + d, dst_path); - } - foreach (QString f, dir.entryList(QDir::Files)) - QFile::copy(src + QDir::separator() + f, dst + QDir::separator() + f); -} - -int gettimezoneoffset(timestamp_t when) -{ - QDateTime dt1, dt2; - if (when == 0) - dt1 = QDateTime::currentDateTime(); - else - dt1 = QDateTime::fromMSecsSinceEpoch(when * 1000); - dt2 = dt1.toUTC(); - dt1.setTimeSpec(Qt::UTC); - return dt2.secsTo(dt1); -} - -int parseLengthToMm(const QString &text) -{ - int mm; - QString numOnly = text; - numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]")); - if (numOnly.isEmpty()) - return 0; - double number = numOnly.toDouble(); - if (text.contains(QObject::tr("m"), Qt::CaseInsensitive)) { - mm = number * 1000; - } else if (text.contains(QObject::tr("ft"), Qt::CaseInsensitive)) { - mm = feet_to_mm(number); - } else { - switch (prefs.units.length) { - case units::FEET: - mm = feet_to_mm(number); - break; - case units::METERS: - mm = number * 1000; - break; - default: - mm = 0; - } - } - return mm; - -} - -int parseTemperatureToMkelvin(const QString &text) -{ - int mkelvin; - QString numOnly = text; - numOnly.replace(",", ".").remove(QRegExp("[^-0-9.]")); - if (numOnly.isEmpty()) - return 0; - double number = numOnly.toDouble(); - if (text.contains(QObject::tr("C"), Qt::CaseInsensitive)) { - mkelvin = C_to_mkelvin(number); - } else if (text.contains(QObject::tr("F"), Qt::CaseInsensitive)) { - mkelvin = F_to_mkelvin(number); - } else { - switch (prefs.units.temperature) { - case units::CELSIUS: - mkelvin = C_to_mkelvin(number); - break; - case units::FAHRENHEIT: - mkelvin = F_to_mkelvin(number); - break; - default: - mkelvin = 0; - } - } - return mkelvin; -} - -int parseWeightToGrams(const QString &text) -{ - int grams; - QString numOnly = text; - numOnly.replace(",", ".").remove(QRegExp("[^0-9.]")); - if (numOnly.isEmpty()) - return 0; - double number = numOnly.toDouble(); - if (text.contains(QObject::tr("kg"), Qt::CaseInsensitive)) { - grams = rint(number * 1000); - } else if (text.contains(QObject::tr("lbs"), Qt::CaseInsensitive)) { - grams = lbs_to_grams(number); - } else { - switch (prefs.units.weight) { - case units::KG: - grams = rint(number * 1000); - break; - case units::LBS: - grams = lbs_to_grams(number); - break; - default: - grams = 0; - } - } - return grams; -} - -int parsePressureToMbar(const QString &text) -{ - int mbar; - QString numOnly = text; - numOnly.replace(",", ".").remove(QRegExp("[^0-9.]")); - if (numOnly.isEmpty()) - return 0; - double number = numOnly.toDouble(); - if (text.contains(QObject::tr("bar"), Qt::CaseInsensitive)) { - mbar = rint(number * 1000); - } else if (text.contains(QObject::tr("psi"), Qt::CaseInsensitive)) { - mbar = psi_to_mbar(number); - } else { - switch (prefs.units.pressure) { - case units::BAR: - mbar = rint(number * 1000); - break; - case units::PSI: - mbar = psi_to_mbar(number); - break; - default: - mbar = 0; - } - } - return mbar; -} - -int parseGasMixO2(const QString &text) -{ - QString gasString = text; - int o2, number; - if (gasString.contains(QObject::tr("AIR"), Qt::CaseInsensitive)) { - o2 = O2_IN_AIR; - } else if (gasString.contains(QObject::tr("EAN"), Qt::CaseInsensitive)) { - gasString.remove(QRegExp("[^0-9]")); - number = gasString.toInt(); - o2 = number * 10; - } else if (gasString.contains("/")) { - QStringList gasSplit = gasString.split("/"); - number = gasSplit[0].toInt(); - o2 = number * 10; - } else { - number = gasString.toInt(); - o2 = number * 10; - } - return o2; -} - -int parseGasMixHE(const QString &text) -{ - QString gasString = text; - int he, number; - if (gasString.contains("/")) { - QStringList gasSplit = gasString.split("/"); - number = gasSplit[1].toInt(); - he = number * 10; - } else { - he = 0; - } - return he; -} - -QString get_dive_duration_string(timestamp_t when, QString hourText, QString minutesText) -{ - int hrs, mins; - mins = (when + 59) / 60; - hrs = mins / 60; - mins -= hrs * 60; - - QString displayTime; - if (hrs) - displayTime = QString("%1%2%3%4").arg(hrs).arg(hourText).arg(mins, 2, 10, QChar('0')).arg(minutesText); - else - displayTime = QString("%1%2").arg(mins).arg(minutesText); - - return displayTime; -} - -QString get_dive_date_string(timestamp_t when) -{ - QDateTime ts; - ts.setMSecsSinceEpoch(when * 1000L); - return loc.toString(ts.toUTC(), QString(prefs.date_format) + " " + prefs.time_format); -} - -QString get_short_dive_date_string(timestamp_t when) -{ - QDateTime ts; - ts.setMSecsSinceEpoch(when * 1000L); - return loc.toString(ts.toUTC(), QString(prefs.date_format_short) + " " + prefs.time_format); -} - -const char *get_dive_date_c_string(timestamp_t when) -{ - QString text = get_dive_date_string(when); - return strdup(text.toUtf8().data()); -} - -bool is_same_day(timestamp_t trip_when, timestamp_t dive_when) -{ - static timestamp_t twhen = (timestamp_t) 0; - static struct tm tmt; - struct tm tmd; - - utc_mkdate(dive_when, &tmd); - - if (twhen != trip_when) { - twhen = trip_when; - utc_mkdate(twhen, &tmt); - } - - return ((tmd.tm_mday == tmt.tm_mday) && (tmd.tm_mon == tmt.tm_mon) && (tmd.tm_year == tmt.tm_year)); -} - -QString get_trip_date_string(timestamp_t when, int nr, bool getday) -{ - struct tm tm; - utc_mkdate(when, &tm); - QDateTime localTime = QDateTime::fromTime_t(when); - localTime.setTimeSpec(Qt::UTC); - QString ret ; - - QString suffix = " " + QObject::tr("(%n dive(s))", "", nr); - if (getday) { - ret = localTime.date().toString(prefs.date_format) + suffix; - } else { - ret = localTime.date().toString("MMM yy") + suffix; - } - return ret; - -} - -extern "C" void reverseGeoLookup(degrees_t latitude, degrees_t longitude, uint32_t uuid) -{ - QNetworkRequest request; - QNetworkAccessManager *rgl = new QNetworkAccessManager(); - request.setUrl(QString("http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&accept-language=%1&lat=%2&lon=%3") - .arg(uiLanguage(NULL)).arg(latitude.udeg / 1000000.0).arg(longitude.udeg / 1000000.0)); - request.setRawHeader("Accept", "text/json"); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - QNetworkReply *reply = rgl->get(request); - QEventLoop loop; - QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - loop.exec(); - QJsonParseError errorObject; - QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &errorObject); - if (errorObject.error != QJsonParseError::NoError) { - qDebug() << errorObject.errorString(); - } else { - QJsonObject obj = jsonDoc.object(); - QJsonObject address = obj.value("address").toObject(); - qDebug() << "found country:" << address.value("country").toString(); - struct dive_site *ds = get_dive_site_by_uuid(uuid); - ds->notes = add_to_string(ds->notes, "countrytag: %s", address.value("country").toString().toUtf8().data()); - } -} - -QHash hashOf; -QMutex hashOfMutex; -QHash localFilenameOf; -QHash thumbnailCache; - -extern "C" char * hashstring(char * filename) -{ - return hashOf[QString(filename)].toHex().data(); -} - -const QString hashfile_name() -{ - return QString(system_default_directory()).append("/hashes"); -} - -extern "C" char *hashfile_name_string() -{ - return strdup(hashfile_name().toUtf8().data()); -} - -void read_hashes() -{ - QFile hashfile(hashfile_name()); - if (hashfile.open(QIODevice::ReadOnly)) { - QDataStream stream(&hashfile); - stream >> localFilenameOf; - stream >> hashOf; - stream >> thumbnailCache; - hashfile.close(); - } -} - -void write_hashes() -{ - QSaveFile hashfile(hashfile_name()); - if (hashfile.open(QIODevice::WriteOnly)) { - QDataStream stream(&hashfile); - stream << localFilenameOf; - stream << hashOf; - stream << thumbnailCache; - hashfile.commit(); - } else { - qDebug() << "cannot open" << hashfile.fileName(); - } -} - -void add_hash(const QString filename, QByteArray hash) -{ - QMutexLocker locker(&hashOfMutex); - hashOf[filename] = hash; - localFilenameOf[hash] = filename; -} - -QByteArray hashFile(const QString filename) -{ - QCryptographicHash hash(QCryptographicHash::Sha1); - QFile imagefile(filename); - if (imagefile.exists() && imagefile.open(QIODevice::ReadOnly)) { - hash.addData(&imagefile); - add_hash(filename, hash.result()); - return hash.result(); - } else { - return QByteArray(); - } -} - -void learnHash(struct picture *picture, QByteArray hash) -{ - if (picture->hash) - free(picture->hash); - QMutexLocker locker(&hashOfMutex); - hashOf[QString(picture->filename)] = hash; - picture->hash = strdup(hash.toHex()); -} - -QString localFilePath(const QString originalFilename) -{ - if (hashOf.contains(originalFilename) && localFilenameOf.contains(hashOf[originalFilename])) - return localFilenameOf[hashOf[originalFilename]]; - else - return originalFilename; -} - -QString fileFromHash(char *hash) -{ - return localFilenameOf[QByteArray::fromHex(hash)]; -} - -// This needs to operate on a copy of picture as it frees it after finishing! -void updateHash(struct picture *picture) { - QByteArray hash = hashFile(fileFromHash(picture->hash)); - learnHash(picture, hash); - picture_free(picture); -} - -// This needs to operate on a copy of picture as it frees it after finishing! -void hashPicture(struct picture *picture) -{ - char *oldHash = copy_string(picture->hash); - learnHash(picture, hashFile(QString(picture->filename))); - if (!same_string(picture->hash, "") && !same_string(picture->hash, oldHash)) - mark_divelist_changed((true)); - free(oldHash); - picture_free(picture); -} - -extern "C" void cache_picture(struct picture *picture) -{ - QString filename = picture->filename; - if (!hashOf.contains(filename)) - QtConcurrent::run(hashPicture, clone_picture(picture)); -} - -void learnImages(const QDir dir, int max_recursions) -{ - QStringList filters, files; - - if (max_recursions) { - foreach (QString dirname, dir.entryList(QStringList(), QDir::NoDotAndDotDot | QDir::Dirs)) { - learnImages(QDir(dir.filePath(dirname)), max_recursions - 1); - } - } - - foreach (QString format, QImageReader::supportedImageFormats()) { - filters.append(QString("*.").append(format)); - } - - foreach (QString file, dir.entryList(filters, QDir::Files)) { - files.append(dir.absoluteFilePath(file)); - } - - QtConcurrent::blockingMap(files, hashFile); -} - -extern "C" const char *local_file_path(struct picture *picture) -{ - QString hashString = picture->hash; - if (hashString.isEmpty()) { - QByteArray hash = hashFile(picture->filename); - free(picture->hash); - picture->hash = strdup(hash.toHex().data()); - } - QString localFileName = fileFromHash(picture->hash); - if (localFileName.isEmpty()) - localFileName = picture->filename; - return strdup(qPrintable(localFileName)); -} - -extern "C" bool picture_exists(struct picture *picture) -{ - QString localFilename = fileFromHash(picture->hash); - QByteArray hash = hashFile(localFilename); - return same_string(hash.toHex().data(), picture->hash); -} - -const QString picturedir() -{ - return QString(system_default_directory()).append("/picturedata/"); -} - -extern "C" char *picturedir_string() -{ - return strdup(picturedir().toUtf8().data()); -} - -/* when we get a picture from git storage (local or remote) and can't find the picture - * based on its hash, we create a local copy with the hash as filename and the appropriate - * suffix */ -extern "C" void savePictureLocal(struct picture *picture, const char *data, int len) -{ - QString dirname = picturedir(); - QDir localPictureDir(dirname); - localPictureDir.mkpath(dirname); - QString suffix(picture->filename); - suffix.replace(QRegularExpression(".*\\."), ""); - QString filename(dirname + picture->hash + "." + suffix); - QSaveFile out(filename); - if (out.open(QIODevice::WriteOnly)) { - out.write(data, len); - out.commit(); - add_hash(filename, QByteArray::fromHex(picture->hash)); - } -} - -extern "C" void picture_load_exif_data(struct picture *p) -{ - EXIFInfo exif; - memblock mem; - - if (readfile(localFilePath(QString(p->filename)).toUtf8().data(), &mem) <= 0) - goto picture_load_exit; - if (exif.parseFrom((const unsigned char *)mem.buffer, (unsigned)mem.size) != PARSE_EXIF_SUCCESS) - goto picture_load_exit; - p->longitude.udeg= lrint(1000000.0 * exif.GeoLocation.Longitude); - p->latitude.udeg = lrint(1000000.0 * exif.GeoLocation.Latitude); - -picture_load_exit: - free(mem.buffer); - return; -} - -QString get_gas_string(struct gasmix gas) -{ - uint o2 = (get_o2(&gas) + 5) / 10, he = (get_he(&gas) + 5) / 10; - QString result = gasmix_is_air(&gas) ? QObject::tr("AIR") : he == 0 ? (o2 == 100 ? QObject::tr("OXYGEN") : QString("EAN%1").arg(o2, 2, 10, QChar('0'))) : QString("%1/%2").arg(o2).arg(he); - return result; -} - -QString get_divepoint_gas_string(const divedatapoint &p) -{ - return get_gas_string(p.gasmix); -} - -weight_t string_to_weight(const char *str) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - QString rest = QString(end).trimmed(); - QString local_kg = QObject::tr("kg"); - QString local_lbs = QObject::tr("lbs"); - weight_t weight; - - if (rest.startsWith("kg") || rest.startsWith(local_kg)) - goto kg; - // using just "lb" instead of "lbs" is intentional - some people might enter the singular - if (rest.startsWith("lb") || rest.startsWith(local_lbs)) - goto lbs; - if (prefs.units.weight == prefs.units.LBS) - goto lbs; -kg: - weight.grams = rint(value * 1000); - return weight; -lbs: - weight.grams = lbs_to_grams(value); - return weight; -} - -depth_t string_to_depth(const char *str) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - QString rest = QString(end).trimmed(); - QString local_ft = QObject::tr("ft"); - QString local_m = QObject::tr("m"); - depth_t depth; - - if (rest.startsWith("m") || rest.startsWith(local_m)) - goto m; - if (rest.startsWith("ft") || rest.startsWith(local_ft)) - goto ft; - if (prefs.units.length == prefs.units.FEET) - goto ft; -m: - depth.mm = rint(value * 1000); - return depth; -ft: - depth.mm = feet_to_mm(value); - return depth; -} - -pressure_t string_to_pressure(const char *str) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - QString rest = QString(end).trimmed(); - QString local_psi = QObject::tr("psi"); - QString local_bar = QObject::tr("bar"); - pressure_t pressure; - - if (rest.startsWith("bar") || rest.startsWith(local_bar)) - goto bar; - if (rest.startsWith("psi") || rest.startsWith(local_psi)) - goto psi; - if (prefs.units.pressure == prefs.units.PSI) - goto psi; -bar: - pressure.mbar = rint(value * 1000); - return pressure; -psi: - pressure.mbar = psi_to_mbar(value); - return pressure; -} - -volume_t string_to_volume(const char *str, pressure_t workp) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - QString rest = QString(end).trimmed(); - QString local_l = QObject::tr("l"); - QString local_cuft = QObject::tr("cuft"); - volume_t volume; - - if (rest.startsWith("l") || rest.startsWith("ℓ") || rest.startsWith(local_l)) - goto l; - if (rest.startsWith("cuft") || rest.startsWith(local_cuft)) - goto cuft; - /* - * If we don't have explicit units, and there is no working - * pressure, we're going to assume "liter" even in imperial - * measurements. - */ - if (!workp.mbar) - goto l; - if (prefs.units.volume == prefs.units.LITER) - goto l; -cuft: - if (workp.mbar) - value /= bar_to_atm(workp.mbar / 1000.0); - value = cuft_to_l(value); -l: - volume.mliter = rint(value * 1000); - return volume; -} - -fraction_t string_to_fraction(const char *str) -{ - const char *end; - double value = strtod_flags(str, &end, 0); - fraction_t fraction; - - fraction.permille = rint(value * 10); - return fraction; -} - -int getCloudURL(QString &filename) -{ - QString email = QString(prefs.cloud_storage_email); - email.replace(QRegularExpression("[^a-zA-Z0-9@._+-]"), ""); - if (email.isEmpty() || same_string(prefs.cloud_storage_password, "")) - return report_error("Please configure Cloud storage email and password in the preferences"); - if (email != prefs.cloud_storage_email_encoded) { - free(prefs.cloud_storage_email_encoded); - prefs.cloud_storage_email_encoded = strdup(qPrintable(email)); - } - filename = QString(QString(prefs.cloud_git_url) + "/%1[%1]").arg(email); - if (verbose) - qDebug() << "cloud URL set as" << filename; - return 0; -} - -extern "C" char *cloud_url() -{ - QString filename; - getCloudURL(filename); - return strdup(filename.toUtf8().data()); -} - -void loadPreferences() -{ - QSettings s; - QVariant v; - - uiLanguage(NULL); - s.beginGroup("Units"); - if (s.value("unit_system").toString() == "metric") { - prefs.unit_system = METRIC; - prefs.units = SI_units; - } else if (s.value("unit_system").toString() == "imperial") { - prefs.unit_system = IMPERIAL; - prefs.units = IMPERIAL_units; - } else { - prefs.unit_system = PERSONALIZE; - GET_UNIT("length", length, units::FEET, units::METERS); - GET_UNIT("pressure", pressure, units::PSI, units::BAR); - GET_UNIT("volume", volume, units::CUFT, units::LITER); - GET_UNIT("temperature", temperature, units::FAHRENHEIT, units::CELSIUS); - GET_UNIT("weight", weight, units::LBS, units::KG); - } - GET_UNIT("vertical_speed_time", vertical_speed_time, units::MINUTES, units::SECONDS); - GET_BOOL("coordinates", coordinates_traditional); - s.endGroup(); - s.beginGroup("TecDetails"); - GET_BOOL("po2graph", pp_graphs.po2); - GET_BOOL("pn2graph", pp_graphs.pn2); - GET_BOOL("phegraph", pp_graphs.phe); - GET_DOUBLE("po2threshold", pp_graphs.po2_threshold); - GET_DOUBLE("pn2threshold", pp_graphs.pn2_threshold); - GET_DOUBLE("phethreshold", pp_graphs.phe_threshold); - GET_BOOL("mod", mod); - GET_DOUBLE("modpO2", modpO2); - GET_BOOL("ead", ead); - GET_BOOL("redceiling", redceiling); - GET_BOOL("dcceiling", dcceiling); - GET_BOOL("calcceiling", calcceiling); - GET_BOOL("calcceiling3m", calcceiling3m); - GET_BOOL("calcndltts", calcndltts); - GET_BOOL("calcalltissues", calcalltissues); - GET_BOOL("hrgraph", hrgraph); - GET_BOOL("tankbar", tankbar); - GET_BOOL("RulerBar", rulergraph); - GET_BOOL("percentagegraph", percentagegraph); - GET_INT("gflow", gflow); - GET_INT("gfhigh", gfhigh); - GET_BOOL("gf_low_at_maxdepth", gf_low_at_maxdepth); - GET_BOOL("show_ccr_setpoint",show_ccr_setpoint); - GET_BOOL("show_ccr_sensors",show_ccr_sensors); - GET_BOOL("zoomed_plot", zoomed_plot); - set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); - GET_BOOL("show_sac", show_sac); - GET_BOOL("display_unused_tanks", display_unused_tanks); - GET_BOOL("show_average_depth", show_average_depth); - s.endGroup(); - - s.beginGroup("GeneralSettings"); - GET_TXT("default_filename", default_filename); - GET_INT("default_file_behavior", default_file_behavior); - if (prefs.default_file_behavior == UNDEFINED_DEFAULT_FILE) { - // undefined, so check if there's a filename set and - // use that, otherwise go with no default file - if (QString(prefs.default_filename).isEmpty()) - prefs.default_file_behavior = NO_DEFAULT_FILE; - else - prefs.default_file_behavior = LOCAL_DEFAULT_FILE; - } - GET_TXT("default_cylinder", default_cylinder); - GET_BOOL("use_default_file", use_default_file); - GET_INT("defaultsetpoint", defaultsetpoint); - GET_INT("o2consumption", o2consumption); - GET_INT("pscr_ratio", pscr_ratio); - s.endGroup(); - - s.beginGroup("Display"); - // get the font from the settings or our defaults - // respect the system default font size if none is explicitly set - QFont defaultFont = s.value("divelist_font", prefs.divelist_font).value(); - if (IS_FP_SAME(system_divelist_default_font_size, -1.0)) { - prefs.font_size = qApp->font().pointSizeF(); - system_divelist_default_font_size = prefs.font_size; // this way we don't save it on exit - } - prefs.font_size = s.value("font_size", prefs.font_size).toFloat(); - // painful effort to ignore previous default fonts on Windows - ridiculous - QString fontName = defaultFont.toString(); - if (fontName.contains(",")) - fontName = fontName.left(fontName.indexOf(",")); - if (subsurface_ignore_font(fontName.toUtf8().constData())) { - defaultFont = QFont(prefs.divelist_font); - } else { - free((void *)prefs.divelist_font); - prefs.divelist_font = strdup(fontName.toUtf8().constData()); - } - defaultFont.setPointSizeF(prefs.font_size); - qApp->setFont(defaultFont); - GET_INT("displayinvalid", display_invalid_dives); - s.endGroup(); - - s.beginGroup("Animations"); - GET_INT("animation_speed", animation_speed); - s.endGroup(); - - s.beginGroup("Network"); - GET_INT_DEF("proxy_type", proxy_type, QNetworkProxy::DefaultProxy); - GET_TXT("proxy_host", proxy_host); - GET_INT("proxy_port", proxy_port); - GET_BOOL("proxy_auth", proxy_auth); - GET_TXT("proxy_user", proxy_user); - GET_TXT("proxy_pass", proxy_pass); - s.endGroup(); - - s.beginGroup("CloudStorage"); - GET_TXT("email", cloud_storage_email); -#ifndef SUBSURFACE_MOBILE - GET_BOOL("save_password_local", save_password_local); -#else - // always save the password in Subsurface-mobile - prefs.save_password_local = true; -#endif - if (prefs.save_password_local) { // GET_TEXT macro is not a single statement - GET_TXT("password", cloud_storage_password); - } - GET_INT("cloud_verification_status", cloud_verification_status); - GET_BOOL("cloud_background_sync", cloud_background_sync); - GET_BOOL("git_local_only", git_local_only); - - // creating the git url here is simply a convenience when C code wants - // to compare against that git URL - it's always derived from the base URL - GET_TXT("cloud_base_url", cloud_base_url); - prefs.cloud_git_url = strdup(qPrintable(QString(prefs.cloud_base_url) + "/git")); - s.endGroup(); - - // Subsurface webservice id is stored outside of the groups - GET_TXT("subsurface_webservice_uid", userid); - - // but the related time / distance threshold (only used in the mobile app) - // are in their own group - s.beginGroup("locationService"); - GET_INT("distance_threshold", distance_threshold); - GET_INT("time_threshold", time_threshold); - s.endGroup(); - - // GeoManagement - s.beginGroup("geocoding"); -#ifdef DISABLED - GET_BOOL("enable_geocoding", geocoding.enable_geocoding); - GET_BOOL("parse_dive_without_gps", geocoding.parse_dive_without_gps); - GET_BOOL("tag_existing_dives", geocoding.tag_existing_dives); -#else - prefs.geocoding.enable_geocoding = true; -#endif - GET_ENUM("cat0", taxonomy_category, geocoding.category[0]); - GET_ENUM("cat1", taxonomy_category, geocoding.category[1]); - GET_ENUM("cat2", taxonomy_category, geocoding.category[2]); - s.endGroup(); - - // GPS service time and distance thresholds - s.beginGroup("LocationService"); - GET_INT("time_threshold", time_threshold); - GET_INT("distance_threshold", distance_threshold); - s.endGroup(); -} - -extern "C" bool isCloudUrl(const char *filename) -{ - QString email = QString(prefs.cloud_storage_email); - email.replace(QRegularExpression("[^a-zA-Z0-9@._+-]"), ""); - if (!email.isEmpty() && - QString(QString(prefs.cloud_git_url) + "/%1[%1]").arg(email) == filename) - return true; - return false; -} - -extern "C" bool getProxyString(char **buffer) -{ - if (prefs.proxy_type == QNetworkProxy::HttpProxy) { - QString proxy; - if (prefs.proxy_auth) - proxy = QString("http://%1:%2@%3:%4").arg(prefs.proxy_user).arg(prefs.proxy_pass) - .arg(prefs.proxy_host).arg(prefs.proxy_port); - else - proxy = QString("http://%1:%2").arg(prefs.proxy_host).arg(prefs.proxy_port); - if (buffer) - *buffer = strdup(qPrintable(proxy)); - return true; - } - return false; -} - -extern "C" void subsurface_mkdir(const char *dir) -{ - QDir directory; - if (!directory.mkpath(QString(dir))) - qDebug() << "failed to create path" << dir; -} - -extern "C" void parse_display_units(char *line) -{ - qDebug() << line; -} - -static QByteArray currentApplicationState; - -QByteArray getCurrentAppState() -{ - return currentApplicationState; -} - -void setCurrentAppState(QByteArray state) -{ - currentApplicationState = state; -} - -extern "C" bool in_planner() -{ - return (currentApplicationState == "PlanDive" || currentApplicationState == "EditPlannedDive"); -} - -void init_proxy() -{ - QNetworkProxy proxy; - proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type)); - proxy.setHostName(prefs.proxy_host); - proxy.setPort(prefs.proxy_port); - if (prefs.proxy_auth) { - proxy.setUser(prefs.proxy_user); - proxy.setPassword(prefs.proxy_pass); - } - QNetworkProxy::setApplicationProxy(proxy); -} - -QString getUUID() -{ - QString uuidString; - QSettings settings; - settings.beginGroup("UpdateManager"); - if (settings.contains("UUID")) { - uuidString = settings.value("UUID").toString(); - } else { - QUuid uuid = QUuid::createUuid(); - uuidString = uuid.toString(); - settings.setValue("UUID", uuidString); - } - uuidString.replace("{", "").replace("}", ""); - return uuidString; -} diff --git a/subsurface-core/qthelper.h b/subsurface-core/qthelper.h deleted file mode 100644 index 3a5ef60e4..000000000 --- a/subsurface-core/qthelper.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef QTHELPER_H -#define QTHELPER_H - -#include -#include -#include -#include "dive.h" -#include "divelist.h" -#include -#include - -// 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); -void add_hash(const QString filename, QByteArray hash); -void hashPicture(struct picture *picture); -QString localFilePath(const QString originalFilename); -QString fileFromHash(char *hash); -void learnHash(struct picture *picture, QByteArray hash); -extern "C" void cache_picture(struct picture *picture); -weight_t string_to_weight(const char *str); -depth_t string_to_depth(const char *str); -pressure_t string_to_pressure(const char *str); -volume_t string_to_volume(const char *str, pressure_t workp); -fraction_t string_to_fraction(const char *str); -int getCloudURL(QString &filename); -void loadPreferences(); -bool parseGpsText(const QString &gps_text, double *latitude, double *longitude); -QByteArray getCurrentAppState(); -void setCurrentAppState(QByteArray state); -extern "C" bool in_planner(); -extern "C" void subsurface_mkdir(const char *dir); -void init_proxy(); -QString getUUID(); - -#endif // QTHELPER_H diff --git a/subsurface-core/qthelperfromc.h b/subsurface-core/qthelperfromc.h deleted file mode 100644 index 32aed8949..000000000 --- a/subsurface-core/qthelperfromc.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef QTHELPERFROMC_H -#define QTHELPERFROMC_H - -bool getProxyString(char **buffer); -bool canReachCloudServer(); -void updateWindowTitle(); -bool isCloudUrl(const char *filename); -void subsurface_mkdir(const char *dir); -char *get_file_name(const char *fileName); -void copy_image_and_overwrite(const char *cfileName, const char *path, const char *cnewName); -char *hashstring(char *filename); -bool picture_exists(struct picture *picture); -char *move_away(const char *path); -const char *local_file_path(struct picture *picture); -void savePictureLocal(struct picture *picture, const char *data, int len); -void cache_picture(struct picture *picture); -char *cloud_url(); -char *hashfile_name_string(); -char *picturedir_string(); -const char *subsurface_user_agent(); - -#endif // QTHELPERFROMC_H diff --git a/subsurface-core/qtserialbluetooth.cpp b/subsurface-core/qtserialbluetooth.cpp deleted file mode 100644 index 6b104157a..000000000 --- a/subsurface-core/qtserialbluetooth.cpp +++ /dev/null @@ -1,416 +0,0 @@ -#include - -#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) -{ - (void)queue; - if (device == NULL) - return DC_STATUS_INVALIDARGS; -#if !defined(Q_OS_WIN) - if (device->socket == NULL) - return DC_STATUS_INVALIDARGS; -#endif - // TODO: add implementation - - return DC_STATUS_SUCCESS; -} - -static int qt_serial_get_received(serial_t *device) -{ -#if defined(Q_OS_WIN) - if (device == NULL) - return DC_STATUS_INVALIDARGS; - - // TODO use WSAIoctl to get the information - - return 0; -#else - if (device == NULL || device->socket == NULL) - return DC_STATUS_INVALIDARGS; - - return device->socket->bytesAvailable(); -#endif -} - -static int qt_serial_get_transmitted(serial_t *device) -{ -#if defined(Q_OS_WIN) - if (device == NULL) - return DC_STATUS_INVALIDARGS; - - // TODO add implementation - - return 0; -#else - if (device == NULL || device->socket == NULL) - return DC_STATUS_INVALIDARGS; - - return device->socket->bytesToWrite(); -#endif -} - -static int qt_serial_set_timeout(serial_t *device, long timeout) -{ - if (device == NULL) - return DC_STATUS_INVALIDARGS; - - device->timeout = timeout; - - return DC_STATUS_SUCCESS; -} - - -const dc_serial_operations_t qt_serial_ops = { - .open = qt_serial_open, - .close = qt_serial_close, - .read = qt_serial_read, - .write = qt_serial_write, - .flush = qt_serial_flush, - .get_received = qt_serial_get_received, - .get_transmitted = qt_serial_get_transmitted, - .set_timeout = qt_serial_set_timeout -}; - -extern void dc_serial_init (dc_serial_t *serial, void *data, const dc_serial_operations_t *ops); - -dc_status_t dc_serial_qt_open(dc_serial_t **out, dc_context_t *context, const char *devaddr) -{ - if (out == NULL) - return DC_STATUS_INVALIDARGS; - - // Allocate memory. - dc_serial_t *serial_device = (dc_serial_t *) malloc (sizeof (dc_serial_t)); - - if (serial_device == NULL) { - return DC_STATUS_NOMEMORY; - } - - // Initialize data and function pointers - dc_serial_init(serial_device, NULL, &qt_serial_ops); - - // Open the serial device. - dc_status_t rc = (dc_status_t)qt_serial_open (&serial_device->port, context, devaddr); - if (rc != DC_STATUS_SUCCESS) { - free (serial_device); - return rc; - } - - // Set the type of the device - serial_device->type = DC_TRANSPORT_BLUETOOTH; - - *out = serial_device; - - return DC_STATUS_SUCCESS; -} -} -#endif diff --git a/subsurface-core/save-git.c b/subsurface-core/save-git.c deleted file mode 100644 index e22019ab0..000000000 --- a/subsurface-core/save-git.c +++ /dev/null @@ -1,1249 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#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" - -#define VA_BUF(b, fmt) do { va_list args; va_start(args, fmt); put_vformat(b, fmt, args); va_end(args); } while (0) - -static void cond_put_format(int cond, struct membuffer *b, const char *fmt, ...) -{ - if (cond) { - VA_BUF(b, fmt); - } -} - -#define SAVE(str, x) cond_put_format(dive->x, b, str " %d\n", dive->x) - -static void show_gps(struct membuffer *b, degrees_t latitude, degrees_t longitude) -{ - if (latitude.udeg || longitude.udeg) { - put_degrees(b, latitude, "gps ", " "); - put_degrees(b, longitude, "", "\n"); - } -} - -static void quote(struct membuffer *b, const char *text) -{ - const char *p = text; - - for (;;) { - const char *escape; - - switch (*p++) { - default: - continue; - case 0: - escape = NULL; - break; - case 1 ... 8: - case 11: - case 12: - case 14 ... 31: - escape = "?"; - break; - case '\\': - escape = "\\\\"; - break; - case '"': - escape = "\\\""; - break; - case '\n': - escape = "\n\t"; - if (*p == '\n') - escape = "\n"; - break; - } - put_bytes(b, text, (p - text - 1)); - if (!escape) - break; - put_string(b, escape); - text = p; - } -} - -static void show_utf8(struct membuffer *b, const char *prefix, const char *value, const char *postfix) -{ - if (value) { - put_format(b, "%s\"", prefix); - quote(b, value); - put_format(b, "\"%s", postfix); - } -} - -static void save_overview(struct membuffer *b, struct dive *dive) -{ - show_utf8(b, "divemaster ", dive->divemaster, "\n"); - show_utf8(b, "buddy ", dive->buddy, "\n"); - show_utf8(b, "suit ", dive->suit, "\n"); - show_utf8(b, "notes ", dive->notes, "\n"); -} - -static void save_tags(struct membuffer *b, struct tag_entry *tags) -{ - const char *sep = " "; - - if (!tags) - return; - put_string(b, "tags"); - while (tags) { - show_utf8(b, sep, tags->tag->source ? : tags->tag->name, ""); - sep = ", "; - tags = tags->next; - } - put_string(b, "\n"); -} - -static void save_extra_data(struct membuffer *b, struct extra_data *ed) -{ - while (ed) { - if (ed->key && ed->value) - put_format(b, "keyvalue \"%s\" \"%s\"\n", ed->key ? : "", ed->value ? : ""); - ed = ed->next; - } -} - -static void put_gasmix(struct membuffer *b, struct gasmix *mix) -{ - int o2 = mix->o2.permille; - int he = mix->he.permille; - - if (o2) { - put_format(b, " o2=%u.%u%%", FRACTION(o2, 10)); - if (he) - put_format(b, " he=%u.%u%%", FRACTION(he, 10)); - } -} - -static void save_cylinder_info(struct membuffer *b, struct dive *dive) -{ - int i, nr; - - nr = nr_cylinders(dive); - for (i = 0; i < nr; i++) { - cylinder_t *cylinder = dive->cylinder + i; - int volume = cylinder->type.size.mliter; - const char *description = cylinder->type.description; - - put_string(b, "cylinder"); - if (volume) - put_milli(b, " vol=", volume, "l"); - put_pressure(b, cylinder->type.workingpressure, " workpressure=", "bar"); - show_utf8(b, " description=", description, ""); - strip_mb(b); - put_gasmix(b, &cylinder->gasmix); - put_pressure(b, cylinder->start, " start=", "bar"); - put_pressure(b, cylinder->end, " end=", "bar"); - if (cylinder->cylinder_use != OC_GAS) - put_format(b, " use=%s", cylinderuse_text[cylinder->cylinder_use]); - - put_string(b, "\n"); - } -} - -static void save_weightsystem_info(struct membuffer *b, struct dive *dive) -{ - int i, nr; - - nr = nr_weightsystems(dive); - for (i = 0; i < nr; i++) { - weightsystem_t *ws = dive->weightsystem + i; - int grams = ws->weight.grams; - const char *description = ws->description; - - put_string(b, "weightsystem"); - put_milli(b, " weight=", grams, "kg"); - show_utf8(b, " description=", description, ""); - put_string(b, "\n"); - } -} - -static void save_dive_temperature(struct membuffer *b, struct dive *dive) -{ - if (dive->airtemp.mkelvin != dc_airtemp(&dive->dc)) - put_temperature(b, dive->airtemp, "airtemp ", "°C\n"); - if (dive->watertemp.mkelvin != dc_watertemp(&dive->dc)) - put_temperature(b, dive->watertemp, "watertemp ", "°C\n"); -} - -static void save_depths(struct membuffer *b, struct divecomputer *dc) -{ - put_depth(b, dc->maxdepth, "maxdepth ", "m\n"); - put_depth(b, dc->meandepth, "meandepth ", "m\n"); -} - -static void save_temperatures(struct membuffer *b, struct divecomputer *dc) -{ - put_temperature(b, dc->airtemp, "airtemp ", "°C\n"); - put_temperature(b, dc->watertemp, "watertemp ", "°C\n"); -} - -static void save_airpressure(struct membuffer *b, struct divecomputer *dc) -{ - put_pressure(b, dc->surface_pressure, "surfacepressure ", "bar\n"); -} - -static void save_salinity(struct membuffer *b, struct divecomputer *dc) -{ - /* only save if we have a value that isn't the default of sea water */ - if (!dc->salinity || dc->salinity == SEAWATER_SALINITY) - return; - put_salinity(b, dc->salinity, "salinity ", "g/l\n"); -} - -static void show_date(struct membuffer *b, timestamp_t when) -{ - struct tm tm; - - utc_mkdate(when, &tm); - - put_format(b, "date %04u-%02u-%02u\n", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); - put_format(b, "time %02u:%02u:%02u\n", - tm.tm_hour, tm.tm_min, tm.tm_sec); -} - -static void show_index(struct membuffer *b, int value, const char *pre, const char *post) -{ - if (value) - put_format(b, " %s%d%s", pre, value, post); -} - -/* - * Samples are saved as densely as possible while still being readable, - * since they are the bulk of the data. - * - * For parsing, look at the units to figure out what the numbers are. - */ -static void save_sample(struct membuffer *b, struct sample *sample, struct sample *old) -{ - put_format(b, "%3u:%02u", FRACTION(sample->time.seconds, 60)); - put_milli(b, " ", sample->depth.mm, "m"); - put_temperature(b, sample->temperature, " ", "°C"); - put_pressure(b, sample->cylinderpressure, " ", "bar"); - put_pressure(b, sample->o2cylinderpressure," o2pressure=","bar"); - - /* - * We only show sensor information for samples with pressure, and only if it - * changed from the previous sensor we showed. - */ - if (sample->cylinderpressure.mbar && sample->sensor != old->sensor) { - put_format(b, " sensor=%d", sample->sensor); - old->sensor = sample->sensor; - } - - /* the deco/ndl values are stored whenever they change */ - if (sample->ndl.seconds != old->ndl.seconds) { - put_format(b, " ndl=%u:%02u", FRACTION(sample->ndl.seconds, 60)); - old->ndl = sample->ndl; - } - if (sample->tts.seconds != old->tts.seconds) { - put_format(b, " tts=%u:%02u", FRACTION(sample->tts.seconds, 60)); - old->tts = sample->tts; - } - if (sample->in_deco != old->in_deco) { - put_format(b, " in_deco=%d", sample->in_deco ? 1 : 0); - old->in_deco = sample->in_deco; - } - if (sample->stoptime.seconds != old->stoptime.seconds) { - put_format(b, " stoptime=%u:%02u", FRACTION(sample->stoptime.seconds, 60)); - old->stoptime = sample->stoptime; - } - - if (sample->stopdepth.mm != old->stopdepth.mm) { - put_milli(b, " stopdepth=", sample->stopdepth.mm, "m"); - old->stopdepth = sample->stopdepth; - } - - if (sample->cns != old->cns) { - put_format(b, " cns=%u%%", sample->cns); - old->cns = sample->cns; - } - - if (sample->rbt.seconds) - put_format(b, " rbt=%u:%02u", FRACTION(sample->rbt.seconds, 60)); - - if (sample->o2sensor[0].mbar != old->o2sensor[0].mbar) { - put_milli(b, " sensor1=", sample->o2sensor[0].mbar, "bar"); - old->o2sensor[0] = sample->o2sensor[0]; - } - - if ((sample->o2sensor[1].mbar) && (sample->o2sensor[1].mbar != old->o2sensor[1].mbar)) { - put_milli(b, " sensor2=", sample->o2sensor[1].mbar, "bar"); - old->o2sensor[1] = sample->o2sensor[1]; - } - - if ((sample->o2sensor[2].mbar) && (sample->o2sensor[2].mbar != old->o2sensor[2].mbar)) { - put_milli(b, " sensor3=", sample->o2sensor[2].mbar, "bar"); - old->o2sensor[2] = sample->o2sensor[2]; - } - - if (sample->setpoint.mbar != old->setpoint.mbar) { - put_milli(b, " po2=", sample->setpoint.mbar, "bar"); - old->setpoint = sample->setpoint; - } - show_index(b, sample->heartbeat, "heartbeat=", ""); - show_index(b, sample->bearing.degrees, "bearing=", "°"); - put_format(b, "\n"); -} - -static void save_samples(struct membuffer *b, int nr, struct sample *s) -{ - struct sample dummy = {}; - - while (--nr >= 0) { - save_sample(b, s, &dummy); - s++; - } -} - -static void save_one_event(struct membuffer *b, struct event *ev) -{ - put_format(b, "event %d:%02d", FRACTION(ev->time.seconds, 60)); - show_index(b, ev->type, "type=", ""); - show_index(b, ev->flags, "flags=", ""); - show_index(b, ev->value, "value=", ""); - show_utf8(b, " name=", ev->name, ""); - if (event_is_gaschange(ev)) { - if (ev->gas.index >= 0) { - show_index(b, ev->gas.index, "cylinder=", ""); - put_gasmix(b, &ev->gas.mix); - } else if (!event_gasmix_redundant(ev)) - put_gasmix(b, &ev->gas.mix); - } - put_string(b, "\n"); -} - -static void save_events(struct membuffer *b, struct event *ev) -{ - while (ev) { - save_one_event(b, ev); - ev = ev->next; - } -} - -static void save_dc(struct membuffer *b, struct dive *dive, struct divecomputer *dc) -{ - show_utf8(b, "model ", dc->model, "\n"); - if (dc->deviceid) - put_format(b, "deviceid %08x\n", dc->deviceid); - if (dc->diveid) - put_format(b, "diveid %08x\n", dc->diveid); - if (dc->when && dc->when != dive->when) - show_date(b, dc->when); - if (dc->duration.seconds && dc->duration.seconds != dive->dc.duration.seconds) - put_duration(b, dc->duration, "duration ", "min\n"); - if (dc->divemode != OC) { - put_format(b, "dctype %s\n", divemode_text[dc->divemode]); - put_format(b, "numberofoxygensensors %d\n",dc->no_o2sensors); - } - - save_depths(b, dc); - save_temperatures(b, dc); - save_airpressure(b, dc); - save_salinity(b, dc); - put_duration(b, dc->surfacetime, "surfacetime ", "min\n"); - - save_extra_data(b, dc->extra_data); - save_events(b, dc->events); - save_samples(b, dc->samples, dc->sample); -} - -/* - * Note that we don't save the date and time or dive - * number: they are encoded in the filename. - */ -static void create_dive_buffer(struct dive *dive, struct membuffer *b) -{ - put_format(b, "duration %u:%02u min\n", FRACTION(dive->dc.duration.seconds, 60)); - SAVE("rating", rating); - SAVE("visibility", visibility); - cond_put_format(dive->tripflag == NO_TRIP, b, "notrip\n"); - save_tags(b, dive->tag_list); - cond_put_format(dive->dive_site_uuid && get_dive_site_by_uuid(dive->dive_site_uuid), - b, "divesiteid %08x\n", dive->dive_site_uuid); - if (verbose && dive->dive_site_uuid && !get_dive_site_by_uuid(dive->dive_site_uuid)) - fprintf(stderr, "removed reference to non-existant dive site with uuid %08x\n", dive->dive_site_uuid); - save_overview(b, dive); - save_cylinder_info(b, dive); - save_weightsystem_info(b, dive); - save_dive_temperature(b, dive); -} - -static struct membuffer error_string_buffer = { 0 }; - -/* - * Note that the act of "getting" the error string - * buffer doesn't de-allocate the buffer, but it does - * set the buffer length to zero, so that any future - * error reports will overwrite the string rather than - * append to it. - */ -const char *get_error_string(void) -{ - const char *str; - - if (!error_string_buffer.len) - return ""; - str = mb_cstring(&error_string_buffer); - error_string_buffer.len = 0; - return str; -} - -int report_error(const char *fmt, ...) -{ - struct membuffer *buf = &error_string_buffer; - - /* Previous unprinted errors? Add a newline in between */ - if (buf->len) - put_bytes(buf, "\n", 1); - VA_BUF(buf, fmt); - mb_cstring(buf); - return -1; -} - -void report_message(const char *msg) -{ - (void)report_error("%s", msg); -} - -/* - * libgit2 has a "git_treebuilder" concept, but it's broken, and can not - * be used to do a flat tree (like the git "index") nor a recursive tree. - * Stupid. - * - * So we have to do that "keep track of recursive treebuilder entries" - * ourselves. We use 'git_treebuilder' for any regular files, and our own - * data structures for recursive trees. - * - * When finally writing it out, we traverse the subdirectories depth- - * first, writing them out, and then adding the written-out trees to - * the git_treebuilder they existed in. - */ -struct dir { - git_treebuilder *files; - struct dir *subdirs, *sibling; - char unique, name[1]; -}; - -static int tree_insert(git_treebuilder *dir, const char *name, int mkunique, git_oid *id, unsigned mode) -{ - int ret; - struct membuffer uniquename = { 0 }; - - if (mkunique && git_treebuilder_get(dir, name)) { - char hex[8]; - git_oid_nfmt(hex, 7, id); - hex[7] = 0; - put_format(&uniquename, "%s~%s", name, hex); - name = mb_cstring(&uniquename); - } - ret = git_treebuilder_insert(NULL, dir, name, id, mode); - free_buffer(&uniquename); - return ret; -} - -/* - * This does *not* make sure the new subdirectory doesn't - * alias some existing name. That is actually useful: you - * can create multiple directories with the same name, and - * set the "unique" flag, which will then append the SHA1 - * of the directory to the name when it is written. - */ -static struct dir *new_directory(git_repository *repo, struct dir *parent, struct membuffer *namebuf) -{ - struct dir *subdir; - const char *name = mb_cstring(namebuf); - int len = namebuf->len; - - subdir = malloc(sizeof(*subdir)+len); - - /* - * It starts out empty: no subdirectories of its own, - * and an empty treebuilder list of files. - */ - subdir->subdirs = NULL; - git_treebuilder_new(&subdir->files, repo, NULL); - memcpy(subdir->name, name, len); - subdir->unique = 0; - subdir->name[len] = 0; - - /* Add it to the list of subdirs of the parent */ - subdir->sibling = parent->subdirs; - parent->subdirs = subdir; - - return subdir; -} - -static struct dir *mktree(git_repository *repo, struct dir *dir, const char *fmt, ...) -{ - struct membuffer buf = { 0 }; - struct dir *subdir; - - VA_BUF(&buf, fmt); - for (subdir = dir->subdirs; subdir; subdir = subdir->sibling) { - if (subdir->unique) - continue; - if (strncmp(subdir->name, buf.buffer, buf.len)) - continue; - if (!subdir->name[buf.len]) - break; - } - if (!subdir) - subdir = new_directory(repo, dir, &buf); - free_buffer(&buf); - return subdir; -} - -/* - * The name of a dive is the date and the dive number (and possibly - * the uniqueness suffix). - * - * Note that the time of the dive may not be the same as the - * time of the directory structure it is created in: the dive - * might be part of a trip that straddles a month (or even a - * year). - * - * We do *not* want to use localized weekdays and cause peoples save - * formats to depend on their locale. - */ -static void create_dive_name(struct dive *dive, struct membuffer *name, struct tm *dirtm) -{ - struct tm tm; - static const char weekday[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; - - utc_mkdate(dive->when, &tm); - if (tm.tm_year != dirtm->tm_year) - put_format(name, "%04u-", tm.tm_year + 1900); - if (tm.tm_mon != dirtm->tm_mon) - put_format(name, "%02u-", tm.tm_mon+1); - - /* a colon is an illegal char in a file name on Windows - use an '=' instead */ - put_format(name, "%02u-%s-%02u=%02u=%02u", - tm.tm_mday, weekday[tm.tm_wday], - tm.tm_hour, tm.tm_min, tm.tm_sec); -} - -/* - * Write a membuffer to the git repo, and free it - */ -static int blob_insert(git_repository *repo, struct dir *tree, struct membuffer *b, const char *fmt, ...) -{ - int ret; - git_oid blob_id; - struct membuffer name = { 0 }; - - ret = git_blob_create_frombuffer(&blob_id, repo, b->buffer, b->len); - free_buffer(b); - if (ret) - return ret; - - VA_BUF(&name, fmt); - ret = tree_insert(tree->files, mb_cstring(&name), 1, &blob_id, GIT_FILEMODE_BLOB); - free_buffer(&name); - return ret; -} - -static int save_one_divecomputer(git_repository *repo, struct dir *tree, struct dive *dive, struct divecomputer *dc, int idx) -{ - int ret; - struct membuffer buf = { 0 }; - - save_dc(&buf, dive, dc); - ret = blob_insert(repo, tree, &buf, "Divecomputer%c%03u", idx ? '-' : 0, idx); - if (ret) - report_error("divecomputer tree insert failed"); - return ret; -} - -static int save_one_picture(git_repository *repo, struct dir *dir, struct picture *pic) -{ - int offset = pic->offset.seconds; - struct membuffer buf = { 0 }; - char sign = '+'; - unsigned h; - int error; - - show_utf8(&buf, "filename ", pic->filename, "\n"); - show_gps(&buf, pic->latitude, pic->longitude); - show_utf8(&buf, "hash ", pic->hash, "\n"); - - /* Picture loading will load even negative offsets.. */ - if (offset < 0) { - offset = -offset; - sign = '-'; - } - - /* Use full hh:mm:ss format to make it all sort nicely */ - h = offset / 3600; - offset -= h *3600; - error = blob_insert(repo, dir, &buf, "%c%02u=%02u=%02u", - sign, h, FRACTION(offset, 60)); -#if 0 - /* storing pictures into git was a mistake. This makes for HUGE git repositories */ - if (!error) { - /* next store the actual picture; we prefix all picture names - * with "PIC-" to make things easier on the parsing side */ - struct membuffer namebuf = { 0 }; - const char *localfn = local_file_path(pic); - put_format(&namebuf, "PIC-%s", pic->hash); - error = blob_insert_fromdisk(repo, dir, localfn, mb_cstring(&namebuf)); - free((void *)localfn); - } -#endif - return error; -} - -static int save_pictures(git_repository *repo, struct dir *dir, struct dive *dive) -{ - if (dive->picture_list) { - dir = mktree(repo, dir, "Pictures"); - FOR_EACH_PICTURE(dive) { - save_one_picture(repo, dir, picture); - } - } - return 0; -} - -static int save_one_dive(git_repository *repo, struct dir *tree, struct dive *dive, struct tm *tm, bool cached_ok) -{ - struct divecomputer *dc; - struct membuffer buf = { 0 }, name = { 0 }; - struct dir *subdir; - int ret, nr; - - /* Create dive directory */ - create_dive_name(dive, &name, tm); - - /* - * If the dive git ID is valid, we just create the whole directory - * with that ID - */ - if (cached_ok && dive_cache_is_valid(dive)) { - git_oid oid; - git_oid_fromraw(&oid, dive->git_id); - ret = tree_insert(tree->files, mb_cstring(&name), 1, - &oid, GIT_FILEMODE_TREE); - free_buffer(&name); - if (ret) - return report_error("cached dive tree insert failed"); - return 0; - } - - subdir = new_directory(repo, tree, &name); - subdir->unique = 1; - free_buffer(&name); - - create_dive_buffer(dive, &buf); - nr = dive->number; - ret = blob_insert(repo, subdir, &buf, - "Dive%c%d", nr ? '-' : 0, nr); - if (ret) - return report_error("dive save-file tree insert failed"); - - /* - * Save the dive computer data. If there is only one dive - * computer, use index 0 for that (which disables the index - * generation when naming it). - */ - dc = &dive->dc; - nr = dc->next ? 1 : 0; - do { - save_one_divecomputer(repo, subdir, dive, dc, nr++); - dc = dc->next; - } while (dc); - - /* Save the picture data, if any */ - save_pictures(repo, subdir, dive); - return 0; -} - -/* - * We'll mark the trip directories unique, so this does not - * need to be unique per se. It could be just "trip". But - * to make things a bit more user-friendly, we try to take - * the trip location into account. - * - * But no special characters, and no numbers (numbers in the - * name could be construed as a date). - * - * So we might end up with "02-Maui", and then the unique - * flag will make us write it out as "02-Maui~54b4" or - * similar. - */ -#define MAXTRIPNAME 15 -static void create_trip_name(dive_trip_t *trip, struct membuffer *name, struct tm *tm) -{ - put_format(name, "%02u-", tm->tm_mday); - if (trip->location) { - char ascii_loc[MAXTRIPNAME+1], *p = trip->location; - int i; - - for (i = 0; i < MAXTRIPNAME; ) { - char c = *p++; - switch (c) { - case 0: - case ',': - case '.': - break; - - case 'a' ... 'z': - case 'A' ... 'Z': - ascii_loc[i++] = c; - continue; - default: - continue; - } - break; - } - if (i > 1) { - put_bytes(name, ascii_loc, i); - return; - } - } - - /* No useful name? */ - put_string(name, "trip"); -} - -static int save_trip_description(git_repository *repo, struct dir *dir, dive_trip_t *trip, struct tm *tm) -{ - int ret; - git_oid blob_id; - struct membuffer desc = { 0 }; - - put_format(&desc, "date %04u-%02u-%02u\n", - tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); - put_format(&desc, "time %02u:%02u:%02u\n", - tm->tm_hour, tm->tm_min, tm->tm_sec); - - show_utf8(&desc, "location ", trip->location, "\n"); - show_utf8(&desc, "notes ", trip->notes, "\n"); - - ret = git_blob_create_frombuffer(&blob_id, repo, desc.buffer, desc.len); - free_buffer(&desc); - if (ret) - return report_error("trip blob creation failed"); - ret = tree_insert(dir->files, "00-Trip", 0, &blob_id, GIT_FILEMODE_BLOB); - if (ret) - return report_error("trip description tree insert failed"); - return 0; -} - -static void verify_shared_date(timestamp_t when, struct tm *tm) -{ - struct tm tmp_tm; - - utc_mkdate(when, &tmp_tm); - if (tmp_tm.tm_year != tm->tm_year) { - tm->tm_year = -1; - tm->tm_mon = -1; - } - if (tmp_tm.tm_mon != tm->tm_mon) - tm->tm_mon = -1; -} - -#define MIN_TIMESTAMP (0) -#define MAX_TIMESTAMP (0x7fffffffffffffff) - -static int save_one_trip(git_repository *repo, struct dir *tree, dive_trip_t *trip, struct tm *tm, bool cached_ok) -{ - int i; - struct dive *dive; - struct dir *subdir; - struct membuffer name = { 0 }; - timestamp_t first, last; - - /* Create trip directory */ - create_trip_name(trip, &name, tm); - subdir = new_directory(repo, tree, &name); - subdir->unique = 1; - free_buffer(&name); - - /* Trip description file */ - save_trip_description(repo, subdir, trip, tm); - - /* Make sure we write out the dates to the dives consistently */ - first = MAX_TIMESTAMP; - last = MIN_TIMESTAMP; - for_each_dive(i, dive) { - if (dive->divetrip != trip) - continue; - if (dive->when < first) - first = dive->when; - if (dive->when > last) - last = dive->when; - } - verify_shared_date(first, tm); - verify_shared_date(last, tm); - - /* Save each dive in the directory */ - for_each_dive(i, dive) { - if (dive->divetrip == trip) - save_one_dive(repo, subdir, dive, tm, cached_ok); - } - - return 0; -} - -static void save_units(void *_b) -{ - struct membuffer *b =_b; - if (prefs.unit_system == METRIC) - put_string(b, "units METRIC\n"); - else if (prefs.unit_system == IMPERIAL) - put_string(b, "units IMPERIAL\n"); - else - put_format(b, "units PERSONALIZE %s %s %s %s %s %s", - prefs.units.length == METERS ? "METERS" : "FEET", - prefs.units.volume == LITER ? "LITER" : "CUFT", - prefs.units.pressure == BAR ? "BAR" : prefs.units.pressure == PSI ? "PSI" : "PASCAL", - prefs.units.temperature == CELSIUS ? "CELSIUS" : prefs.units.temperature == FAHRENHEIT ? "FAHRENHEIT" : "KELVIN", - prefs.units.weight == KG ? "KG" : "LBS", - prefs.units.vertical_speed_time == SECONDS ? "SECONDS" : "MINUTES"); -} - -static void save_userid(void *_b) -{ - struct membuffer *b = _b; - if (prefs.save_userid_local) - put_format(b, "userid %30s\n", prefs.userid); -} - -static void save_one_device(void *_b, const char *model, uint32_t deviceid, - const char *nickname, const char *serial, const char *firmware) -{ - struct membuffer *b = _b; - - if (nickname && !strcmp(model, nickname)) - nickname = NULL; - if (serial && !*serial) serial = NULL; - if (firmware && !*firmware) firmware = NULL; - if (nickname && !*nickname) nickname = NULL; - if (!nickname && !serial && !firmware) - return; - - show_utf8(b, "divecomputerid ", model, ""); - put_format(b, " deviceid=%08x", deviceid); - show_utf8(b, " serial=", serial, ""); - show_utf8(b, " firmware=", firmware, ""); - show_utf8(b, " nickname=", nickname, ""); - put_string(b, "\n"); -} - -static void save_settings(git_repository *repo, struct dir *tree) -{ - struct membuffer b = { 0 }; - - put_format(&b, "version %d\n", DATAFORMAT_VERSION); - save_userid(&b); - call_for_each_dc(&b, save_one_device, false); - cond_put_format(autogroup, &b, "autogroup\n"); - save_units(&b); - - blob_insert(repo, tree, &b, "00-Subsurface"); -} - -static void save_divesites(git_repository *repo, struct dir *tree) -{ - struct dir *subdir; - struct membuffer dirname = { 0 }; - put_format(&dirname, "01-Divesites"); - subdir = new_directory(repo, tree, &dirname); - - for (int i = 0; i < dive_site_table.nr; i++) { - struct membuffer b = { 0 }; - struct dive_site *ds = get_dive_site(i); - if (dive_site_is_empty(ds)) { - int j; - struct dive *d; - for_each_dive(j, d) { - if (d->dive_site_uuid == ds->uuid) - d->dive_site_uuid = 0; - } - delete_dive_site(ds->uuid); - i--; // since we just deleted that one - continue; - } else if (ds->name && - (strncmp(ds->name, "Auto-created dive", 17) == 0 || - strncmp(ds->name, "New Dive", 8) == 0)) { - fprintf(stderr, "found an auto divesite %s\n", ds->name); - // these are the two default names for sites from - // the web service; if the site isn't used in any - // dive (really? you didn't rename it?), delete it - if (!is_dive_site_used(ds->uuid, false)) { - if (verbose) - fprintf(stderr, "Deleted unused auto-created dive site %s\n", ds->name); - delete_dive_site(ds->uuid); - i--; // since we just deleted that one - continue; - } - } - struct membuffer site_file_name = { 0 }; - put_format(&site_file_name, "Site-%08x", ds->uuid); - show_utf8(&b, "name ", ds->name, "\n"); - show_utf8(&b, "description ", ds->description, "\n"); - show_utf8(&b, "notes ", ds->notes, "\n"); - show_gps(&b, ds->latitude, ds->longitude); - for (int j = 0; j < ds->taxonomy.nr; j++) { - struct taxonomy *t = &ds->taxonomy.category[j]; - if (t->category != TC_NONE && t->value) { - put_format(&b, "geo cat %d origin %d ", t->category, t->origin); - show_utf8(&b, "", t->value, "\n" ); - } - } - blob_insert(repo, subdir, &b, mb_cstring(&site_file_name)); - } -} - -static int create_git_tree(git_repository *repo, struct dir *root, bool select_only, bool cached_ok) -{ - int i; - struct dive *dive; - dive_trip_t *trip; - - save_settings(repo, root); - - save_divesites(repo, root); - - for (trip = dive_trip_list; trip != NULL; trip = trip->next) - trip->index = 0; - - /* save the dives */ - int notify_increment = dive_table.nr > 10 ? dive_table.nr / 10 : 1; - int last_threshold = 0; - for_each_dive(i, dive) { - struct tm tm; - struct dir *tree; - char buf[] = "save dives x0%"; - - if (i / notify_increment > last_threshold) { - // notify of progress - we cover the range of 20..50 - last_threshold = i / notify_increment; - buf[11] = last_threshold + '0'; - git_storage_update_progress(20 + 3 * last_threshold, buf); - } - - trip = dive->divetrip; - - if (select_only) { - if (!dive->selected) - continue; - /* We don't save trips when doing selected dive saves */ - trip = NULL; - } - - /* Create the date-based hierarchy */ - utc_mkdate(trip ? trip->when : dive->when, &tm); - tree = mktree(repo, root, "%04d", tm.tm_year + 1900); - tree = mktree(repo, tree, "%02d", tm.tm_mon + 1); - - if (trip) { - /* Did we already save this trip? */ - if (trip->index) - continue; - trip->index = 1; - - /* Pass that new subdirectory in for save-trip */ - save_one_trip(repo, tree, trip, &tm, cached_ok); - continue; - } - - save_one_dive(repo, tree, dive, &tm, cached_ok); - } - return 0; -} - -/* - * See if we can find the parent ID that the git data came from - */ -static git_object *try_to_find_parent(const char *hex_id, git_repository *repo) -{ - git_oid object_id; - git_commit *commit; - - if (!hex_id) - return NULL; - if (git_oid_fromstr(&object_id, hex_id)) - return NULL; - if (git_commit_lookup(&commit, repo, &object_id)) - return NULL; - return (git_object *)commit; -} - -static int notify_cb(git_checkout_notify_t why, - const char *path, - const git_diff_file *baseline, - const git_diff_file *target, - const git_diff_file *workdir, - void *payload) -{ - (void) baseline; - (void) target; - (void) workdir; - (void) payload; - (void) why; - report_error("File '%s' does not match in working tree", path); - return 0; /* Continue with checkout */ -} - -static git_tree *get_git_tree(git_repository *repo, git_object *parent) -{ - git_tree *tree; - if (!parent) - return NULL; - if (git_tree_lookup(&tree, repo, git_commit_tree_id((const git_commit *) parent))) - return NULL; - return tree; -} - -int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree) -{ - git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - - opts.checkout_strategy = GIT_CHECKOUT_SAFE; - opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_DIRTY; - opts.notify_cb = notify_cb; - opts.baseline = get_git_tree(repo, parent); - return git_checkout_tree(repo, (git_object *) tree, &opts); -} - -static int get_authorship(git_repository *repo, git_signature **authorp) -{ -#if LIBGIT2_VER_MAJOR || LIBGIT2_VER_MINOR >= 20 - if (git_signature_default(authorp, repo) == 0) - return 0; -#endif - /* Default name information, with potential OS overrides */ - struct user_info user = { - .name = "Subsurface", - .email = "subsurace@subsurface-divelog.org" - }; - - subsurface_user_info(&user); - - /* git_signature_default() is too recent */ - return git_signature_now(authorp, user.name, user.email); -} - -static void create_commit_message(struct membuffer *msg) -{ - int nr = dive_table.nr; - struct dive *dive = get_dive(nr-1); - - if (dive) { - dive_trip_t *trip = dive->divetrip; - const char *location = get_dive_location(dive) ? : "no location"; - struct divecomputer *dc = &dive->dc; - const char *sep = "\n"; - - if (dive->number) - nr = dive->number; - - put_format(msg, "dive %d: %s", nr, location); - if (trip && trip->location && *trip->location && strcmp(trip->location, location)) - put_format(msg, " (%s)", trip->location); - put_format(msg, "\n"); - do { - if (dc->model && *dc->model) { - put_format(msg, "%s%s", sep, dc->model); - sep = ", "; - } - } while ((dc = dc->next) != NULL); - put_format(msg, "\n"); - } - put_format(msg, "Created by subsurface %s\n", subsurface_user_agent()); -} - -static int create_new_commit(git_repository *repo, const char *remote, const char *branch, git_oid *tree_id) -{ - int ret; - git_reference *ref; - git_object *parent; - git_oid commit_id; - git_signature *author; - git_commit *commit; - git_tree *tree; - - ret = git_branch_lookup(&ref, repo, branch, GIT_BRANCH_LOCAL); - switch (ret) { - default: - return report_error("Bad branch '%s' (%s)", branch, strerror(errno)); - case GIT_EINVALIDSPEC: - return report_error("Invalid branch name '%s'", branch); - case GIT_ENOTFOUND: /* We'll happily create it */ - ref = NULL; - parent = try_to_find_parent(saved_git_id, repo); - break; - case 0: - if (git_reference_peel(&parent, ref, GIT_OBJ_COMMIT)) - return report_error("Unable to look up parent in branch '%s'", branch); - - if (saved_git_id) { - if (existing_filename) - fprintf(stderr, "existing filename %s\n", existing_filename); - const git_oid *id = git_commit_id((const git_commit *) parent); - /* if we are saving to the same git tree we got this from, let's make - * sure there is no confusion */ - if (same_string(existing_filename, remote) && git_oid_strcmp(id, saved_git_id)) - return report_error("The git branch does not match the git parent of the source"); - } - - /* all good */ - break; - } - - if (git_tree_lookup(&tree, repo, tree_id)) - return report_error("Could not look up newly created tree"); - - if (get_authorship(repo, &author)) - return report_error("No user name configuration in git repo"); - - /* If the parent commit has the same tree ID, do not create a new commit */ - if (parent && git_oid_equal(tree_id, git_commit_tree_id((const git_commit *) parent))) { - /* If the parent already came from the ref, the commit is already there */ - if (ref) - return 0; - /* Else we do want to create the new branch, but with the old commit */ - commit = (git_commit *) parent; - } else { - struct membuffer commit_msg = { 0 }; - - create_commit_message(&commit_msg); - if (git_commit_create_v(&commit_id, repo, NULL, author, author, NULL, mb_cstring(&commit_msg), tree, parent != NULL, parent)) - return report_error("Git commit create failed (%s)", strerror(errno)); - free_buffer(&commit_msg); - - if (git_commit_lookup(&commit, repo, &commit_id)) - return report_error("Could not look up newly created commit"); - } - - if (!ref) { - if (git_branch_create(&ref, repo, branch, commit, 0)) - return report_error("Failed to create branch '%s'", branch); - } - /* - * If it's a checked-out branch, try to also update the working - * tree and index. If that fails (dirty working tree or whatever), - * this is not technically a save error (we did save things in - * the object database), but it can cause extreme confusion, so - * warn about it. - */ - if (git_branch_is_head(ref) && !git_repository_is_bare(repo)) { - if (update_git_checkout(repo, parent, tree)) { - const git_error *err = giterr_last(); - const char *errstr = err ? err->message : strerror(errno); - report_error("Git branch '%s' is checked out, but worktree is dirty (%s)", - branch, errstr); - } - } - - if (git_reference_set_target(&ref, ref, &commit_id, "Subsurface save event")) - return report_error("Failed to update branch '%s'", branch); - set_git_id(&commit_id); - - git_signature_free(author); - - return 0; -} - -static int write_git_tree(git_repository *repo, struct dir *tree, git_oid *result) -{ - int ret; - struct dir *subdir; - - /* Write out our subdirectories, add them to the treebuilder, and free them */ - while ((subdir = tree->subdirs) != NULL) { - git_oid id; - - if (!write_git_tree(repo, subdir, &id)) - tree_insert(tree->files, subdir->name, subdir->unique, &id, GIT_FILEMODE_TREE); - tree->subdirs = subdir->sibling; - free(subdir); - }; - - /* .. write out the resulting treebuilder */ - ret = git_treebuilder_write(result, tree->files); - - /* .. and free the now useless treebuilder */ - git_treebuilder_free(tree->files); - - return ret; -} - -int do_git_save(git_repository *repo, const char *branch, const char *remote, bool select_only, bool create_empty) -{ - struct dir tree; - git_oid id; - bool cached_ok; - - if (verbose) - fprintf(stderr, "git storage: do git save\n"); - - if (!create_empty) // so we are actually saving the dives - git_storage_update_progress(19, "start git save"); - - /* - * Check if we can do the cached writes - we need to - * have the original git commit we loaded in the repo - */ - cached_ok = try_to_find_parent(saved_git_id, repo); - - /* Start with an empty tree: no subdirectories, no files */ - tree.name[0] = 0; - tree.subdirs = NULL; - if (git_treebuilder_new(&tree.files, repo, NULL)) - return report_error("git treebuilder failed"); - - if (!create_empty) - /* Populate our tree data structure */ - if (create_git_tree(repo, &tree, select_only, cached_ok)) - return -1; - - if (verbose) - fprintf(stderr, "git storage, write git tree\n"); - - if (write_git_tree(repo, &tree, &id)) - return report_error("git tree write failed"); - - /* And save the tree! */ - if (create_new_commit(repo, remote, branch, &id)) - return report_error("creating commit failed"); - - if (remote && prefs.cloud_background_sync) { - /* now sync the tree with the cloud server */ - if (strstr(remote, prefs.cloud_git_url)) { - return sync_with_remote(repo, remote, branch, RT_HTTPS); - } - } - return 0; -} - -int git_save_dives(struct git_repository *repo, const char *branch, const char *remote, bool select_only) -{ - int ret; - - if (repo == dummy_git_repository) - return report_error("Unable to open git repository '%s'", branch); - ret = do_git_save(repo, branch, remote, select_only, false); - git_repository_free(repo); - free((void *)branch); - return ret; -} diff --git a/subsurface-core/save-html.c b/subsurface-core/save-html.c deleted file mode 100644 index 2d0ea9cf3..000000000 --- a/subsurface-core/save-html.c +++ /dev/null @@ -1,559 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include "save-html.h" -#include "qthelperfromc.h" -#include "gettext.h" -#include "stdio.h" - -void write_attribute(struct membuffer *b, const char *att_name, const char *value, const char *separator) -{ - if (!value) - value = "--"; - put_format(b, "\"%s\":\"", att_name); - put_HTML_quoted(b, value); - put_format(b, "\"%s", separator); -} - -void save_photos(struct membuffer *b, const char *photos_dir, struct dive *dive) -{ - struct picture *pic = dive->picture_list; - - if (!pic) - return; - - char *separator = "\"photos\":["; - do { - put_string(b, separator); - separator = ", "; - char *fname = get_file_name(local_file_path(pic)); - put_format(b, "{\"filename\":\"%s\"}", fname); - copy_image_and_overwrite(local_file_path(pic), photos_dir, fname); - free(fname); - pic = pic->next; - } while (pic); - put_string(b, "],"); -} - -void write_divecomputers(struct membuffer *b, struct dive *dive) -{ - put_string(b, "\"divecomputers\":["); - struct divecomputer *dc; - char *separator = ""; - for_each_dc (dive, dc) { - put_string(b, separator); - separator = ", "; - put_format(b, "{"); - write_attribute(b, "model", dc->model, ", "); - if (dc->deviceid) - put_format(b, "\"deviceid\":\"%08x\", ", dc->deviceid); - else - put_string(b, "\"deviceid\":\"--\", "); - if (dc->diveid) - put_format(b, "\"diveid\":\"%08x\" ", dc->diveid); - else - put_string(b, "\"diveid\":\"--\" "); - put_format(b, "}"); - } - put_string(b, "],"); -} - -void write_dive_status(struct membuffer *b, struct dive *dive) -{ - put_format(b, "\"sac\":\"%d\",", dive->sac); - put_format(b, "\"otu\":\"%d\",", dive->otu); - put_format(b, "\"cns\":\"%d\",", dive->cns); -} - -void put_HTML_bookmarks(struct membuffer *b, struct dive *dive) -{ - struct event *ev = dive->dc.events; - - if (!ev) - return; - - char *separator = "\"events\":["; - do { - put_string(b, separator); - separator = ", "; - put_format(b, "{\"name\":\"%s\",", ev->name); - put_format(b, "\"value\":\"%d\",", ev->value); - put_format(b, "\"type\":\"%d\",", ev->type); - put_format(b, "\"time\":\"%d\"}", ev->time.seconds); - ev = ev->next; - } while (ev); - put_string(b, "],"); -} - -static void put_weightsystem_HTML(struct membuffer *b, struct dive *dive) -{ - int i, nr; - - nr = nr_weightsystems(dive); - - put_string(b, "\"Weights\":["); - - char *separator = ""; - - for (i = 0; i < nr; i++) { - weightsystem_t *ws = dive->weightsystem + i; - int grams = ws->weight.grams; - const char *description = ws->description; - - put_string(b, separator); - separator = ", "; - put_string(b, "{"); - put_HTML_weight_units(b, grams, "\"weight\":\"", "\","); - write_attribute(b, "description", description, " "); - put_string(b, "}"); - } - put_string(b, "],"); -} - -static void put_cylinder_HTML(struct membuffer *b, struct dive *dive) -{ - int i, nr; - char *separator = "\"Cylinders\":["; - nr = nr_cylinders(dive); - - if (!nr) - put_string(b, separator); - - for (i = 0; i < nr; i++) { - cylinder_t *cylinder = dive->cylinder + i; - put_format(b, "%s{", separator); - separator = ", "; - write_attribute(b, "Type", cylinder->type.description, ", "); - if (cylinder->type.size.mliter) { - int volume = cylinder->type.size.mliter; - if (prefs.units.volume == CUFT && cylinder->type.workingpressure.mbar) - volume *= bar_to_atm(cylinder->type.workingpressure.mbar / 1000.0); - put_HTML_volume_units(b, volume, "\"Size\":\"", " \", "); - } else { - write_attribute(b, "Size", "--", ", "); - } - put_HTML_pressure_units(b, cylinder->type.workingpressure, "\"WPressure\":\"", " \", "); - - if (cylinder->start.mbar) { - put_HTML_pressure_units(b, cylinder->start, "\"SPressure\":\"", " \", "); - } else { - write_attribute(b, "SPressure", "--", ", "); - } - - if (cylinder->end.mbar) { - put_HTML_pressure_units(b, cylinder->end, "\"EPressure\":\"", " \", "); - } else { - write_attribute(b, "EPressure", "--", ", "); - } - - if (cylinder->gasmix.o2.permille) { - put_format(b, "\"O2\":\"%u.%u%%\",", FRACTION(cylinder->gasmix.o2.permille, 10)); - put_format(b, "\"He\":\"%u.%u%%\"", FRACTION(cylinder->gasmix.he.permille, 10)); - } else { - write_attribute(b, "O2", "Air", ""); - } - - put_string(b, "}"); - } - - put_string(b, "],"); -} - - -void put_HTML_samples(struct membuffer *b, struct dive *dive) -{ - int i; - put_format(b, "\"maxdepth\":%d,", dive->dc.maxdepth.mm); - put_format(b, "\"duration\":%d,", dive->dc.duration.seconds); - struct sample *s = dive->dc.sample; - - if (!dive->dc.samples) - return; - - char *separator = "\"samples\":["; - for (i = 0; i < dive->dc.samples; i++) { - put_format(b, "%s[%d,%d,%d,%d]", separator, s->time.seconds, s->depth.mm, s->cylinderpressure.mbar, s->temperature.mkelvin); - separator = ", "; - s++; - } - put_string(b, "],"); -} - -void put_HTML_coordinates(struct membuffer *b, struct dive *dive) -{ - struct dive_site *ds = get_dive_site_for_dive(dive); - if (!ds) - return; - degrees_t latitude = ds->latitude; - degrees_t longitude = ds->longitude; - - //don't put coordinates if in (0,0) - if (!latitude.udeg && !longitude.udeg) - return; - - put_string(b, "\"coordinates\":{"); - put_degrees(b, latitude, "\"lat\":\"", "\","); - put_degrees(b, longitude, "\"lon\":\"", "\""); - put_string(b, "},"); -} - -void put_HTML_date(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - struct tm tm; - utc_mkdate(dive->when, &tm); - put_format(b, "%s%04u-%02u-%02u%s", pre, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, post); -} - -void put_HTML_quoted(struct membuffer *b, const char *text) -{ - int is_html = 1, is_attribute = 1; - put_quoted(b, text, is_attribute, is_html); -} - -void put_HTML_notes(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - put_string(b, pre); - if (dive->notes) { - put_HTML_quoted(b, dive->notes); - } else { - put_string(b, "--"); - } - put_string(b, post); -} - -void put_HTML_pressure_units(struct membuffer *b, pressure_t pressure, const char *pre, const char *post) -{ - const char *unit; - double value; - - if (!pressure.mbar) { - put_format(b, "%s%s", pre, post); - return; - } - - value = get_pressure_units(pressure.mbar, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_volume_units(struct membuffer *b, unsigned int ml, const char *pre, const char *post) -{ - const char *unit; - double value; - int frac; - - value = get_volume_units(ml, &frac, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_weight_units(struct membuffer *b, unsigned int grams, const char *pre, const char *post) -{ - const char *unit; - double value; - int frac; - - value = get_weight_units(grams, &frac, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_time(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - struct tm tm; - utc_mkdate(dive->when, &tm); - put_format(b, "%s%02u:%02u:%02u%s", pre, tm.tm_hour, tm.tm_min, tm.tm_sec, post); -} - -void put_HTML_depth(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - const char *unit; - double value; - struct units *units_p = get_units(); - - if (!dive->maxdepth.mm) { - put_format(b, "%s--%s", pre, post); - return; - } - value = get_depth_units(dive->maxdepth.mm, NULL, &unit); - - switch (units_p->length) { - case METERS: - default: - put_format(b, "%s%.1f %s%s", pre, value, unit, post); - break; - case FEET: - put_format(b, "%s%.0f %s%s", pre, value, unit, post); - break; - } -} - -void put_HTML_airtemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - const char *unit; - double value; - - if (!dive->airtemp.mkelvin) { - put_format(b, "%s--%s", pre, post); - return; - } - value = get_temp_units(dive->airtemp.mkelvin, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_watertemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - const char *unit; - double value; - - if (!dive->watertemp.mkelvin) { - put_format(b, "%s--%s", pre, post); - return; - } - value = get_temp_units(dive->watertemp.mkelvin, &unit); - put_format(b, "%s%.1f %s%s", pre, value, unit, post); -} - -void put_HTML_tags(struct membuffer *b, struct dive *dive, const char *pre, const char *post) -{ - put_string(b, pre); - struct tag_entry *tag = dive->tag_list; - - if (!tag) - put_string(b, "[\"--\""); - - char *separator = "["; - while (tag) { - put_format(b, "%s\"", separator); - separator = ", "; - put_HTML_quoted(b, tag->tag->name); - put_string(b, "\""); - tag = tag->next; - } - put_string(b, "]"); - put_string(b, post); -} - -/* if exporting list_only mode, we neglect exporting the samples, bookmarks and cylinders */ -void write_one_dive(struct membuffer *b, struct dive *dive, const char *photos_dir, int *dive_no, const bool list_only) -{ - put_string(b, "{"); - put_format(b, "\"number\":%d,", *dive_no); - put_format(b, "\"subsurface_number\":%d,", dive->number); - put_HTML_date(b, dive, "\"date\":\"", "\","); - put_HTML_time(b, dive, "\"time\":\"", "\","); - write_attribute(b, "location", get_dive_location(dive), ", "); - put_HTML_coordinates(b, dive); - put_format(b, "\"rating\":%d,", dive->rating); - put_format(b, "\"visibility\":%d,", dive->visibility); - put_format(b, "\"dive_duration\":\"%u:%02u min\",", - FRACTION(dive->duration.seconds, 60)); - put_string(b, "\"temperature\":{"); - put_HTML_airtemp(b, dive, "\"air\":\"", "\","); - put_HTML_watertemp(b, dive, "\"water\":\"", "\""); - put_string(b, " },"); - write_attribute(b, "buddy", dive->buddy, ", "); - write_attribute(b, "divemaster", dive->divemaster, ", "); - write_attribute(b, "suit", dive->suit, ", "); - put_HTML_tags(b, dive, "\"tags\":", ","); - if (!list_only) { - put_cylinder_HTML(b, dive); - put_weightsystem_HTML(b, dive); - put_HTML_samples(b, dive); - put_HTML_bookmarks(b, dive); - write_dive_status(b, dive); - if (photos_dir && strcmp(photos_dir, "")) - save_photos(b, photos_dir, dive); - write_divecomputers(b, dive); - } - put_HTML_notes(b, dive, "\"notes\":\"", "\""); - put_string(b, "}\n"); - (*dive_no)++; -} - -void write_no_trip(struct membuffer *b, int *dive_no, bool selected_only, const char *photos_dir, const bool list_only, char *sep) -{ - int i; - struct dive *dive; - char *separator = ""; - bool found_sel_dive = 0; - - for_each_dive (i, dive) { - // write dive if it doesn't belong to any trip and the dive is selected - // or we are in exporting all dives mode. - if (!dive->divetrip && (dive->selected || !selected_only)) { - if (!found_sel_dive) { - put_format(b, "%c{", *sep); - (*sep) = ','; - put_format(b, "\"name\":\"Other\","); - put_format(b, "\"dives\":["); - found_sel_dive = 1; - } - put_string(b, separator); - separator = ", "; - write_one_dive(b, dive, photos_dir, dive_no, list_only); - } - } - if (found_sel_dive) - put_format(b, "]}\n\n"); -} - -void write_trip(struct membuffer *b, dive_trip_t *trip, int *dive_no, bool selected_only, const char *photos_dir, const bool list_only, char *sep) -{ - struct dive *dive; - char *separator = ""; - bool found_sel_dive = 0; - - for (dive = trip->dives; dive != NULL; dive = dive->next) { - if (!dive->selected && selected_only) - continue; - - // save trip if found at least one selected dive. - if (!found_sel_dive) { - found_sel_dive = 1; - put_format(b, "%c {", *sep); - (*sep) = ','; - put_format(b, "\"name\":\"%s\",", trip->location); - put_format(b, "\"dives\":["); - } - put_string(b, separator); - separator = ", "; - write_one_dive(b, dive, photos_dir, dive_no, list_only); - } - - // close the trip object if contain dives. - if (found_sel_dive) - put_format(b, "]}\n\n"); -} - -void write_trips(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only) -{ - int i, dive_no = 0; - struct dive *dive; - dive_trip_t *trip; - char sep_ = ' '; - char *sep = &sep_; - - for (trip = dive_trip_list; trip != NULL; trip = trip->next) - trip->index = 0; - - for_each_dive (i, dive) { - trip = dive->divetrip; - - /*Continue if the dive have no trips or we have seen this trip before*/ - if (!trip || trip->index) - continue; - - /* We haven't seen this trip before - save it and all dives */ - trip->index = 1; - write_trip(b, trip, &dive_no, selected_only, photos_dir, list_only, sep); - } - - /*Save all remaining trips into Others*/ - write_no_trip(b, &dive_no, selected_only, photos_dir, list_only, sep); -} - -void export_list(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only) -{ - put_string(b, "trips=["); - write_trips(b, photos_dir, selected_only, list_only); - put_string(b, "]"); -} - -void export_HTML(const char *file_name, const char *photos_dir, const bool selected_only, const bool list_only) -{ - FILE *f; - - struct membuffer buf = { 0 }; - export_list(&buf, photos_dir, selected_only, list_only); - - f = subsurface_fopen(file_name, "w+"); - if (!f) { - report_error(translate("gettextFromC", "Can't open file %s"), file_name); - } else { - flush_buffer(&buf, f); /*check for writing errors? */ - fclose(f); - } - free_buffer(&buf); -} - -void export_translation(const char *file_name) -{ - FILE *f; - - struct membuffer buf = { 0 }; - struct membuffer *b = &buf; - - //export translated words here - put_format(b, "translate={"); - - //Dive list view - write_attribute(b, "Number", translate("gettextFromC", "Number"), ", "); - write_attribute(b, "Date", translate("gettextFromC", "Date"), ", "); - write_attribute(b, "Time", translate("gettextFromC", "Time"), ", "); - write_attribute(b, "Location", translate("gettextFromC", "Location"), ", "); - write_attribute(b, "Air_Temp", translate("gettextFromC", "Air temp."), ", "); - write_attribute(b, "Water_Temp", translate("gettextFromC", "Water temp."), ", "); - write_attribute(b, "dives", translate("gettextFromC", "Dives"), ", "); - write_attribute(b, "Expand_All", translate("gettextFromC", "Expand all"), ", "); - write_attribute(b, "Collapse_All", translate("gettextFromC", "Collapse all"), ", "); - write_attribute(b, "trips", translate("gettextFromC", "Trips"), ", "); - write_attribute(b, "Statistics", translate("gettextFromC", "Statistics"), ", "); - write_attribute(b, "Advanced_Search", translate("gettextFromC", "Advanced search"), ", "); - - //Dive expanded view - write_attribute(b, "Rating", translate("gettextFromC", "Rating"), ", "); - write_attribute(b, "Visibility", translate("gettextFromC", "Visibility"), ", "); - write_attribute(b, "Duration", translate("gettextFromC", "Duration"), ", "); - write_attribute(b, "DiveMaster", translate("gettextFromC", "Divemaster"), ", "); - write_attribute(b, "Buddy", translate("gettextFromC", "Buddy"), ", "); - write_attribute(b, "Suit", translate("gettextFromC", "Suit"), ", "); - write_attribute(b, "Tags", translate("gettextFromC", "Tags"), ", "); - write_attribute(b, "Notes", translate("gettextFromC", "Notes"), ", "); - write_attribute(b, "Show_more_details", translate("gettextFromC", "Show more details"), ", "); - - //Yearly statistics view - write_attribute(b, "Yearly_statistics", translate("gettextFromC", "Yearly statistics"), ", "); - write_attribute(b, "Year", translate("gettextFromC", "Year"), ", "); - write_attribute(b, "Total_Time", translate("gettextFromC", "Total time"), ", "); - write_attribute(b, "Average_Time", translate("gettextFromC", "Average time"), ", "); - write_attribute(b, "Shortest_Time", translate("gettextFromC", "Shortest time"), ", "); - write_attribute(b, "Longest_Time", translate("gettextFromC", "Longest time"), ", "); - write_attribute(b, "Average_Depth", translate("gettextFromC", "Average depth"), ", "); - write_attribute(b, "Min_Depth", translate("gettextFromC", "Min. depth"), ", "); - write_attribute(b, "Max_Depth", translate("gettextFromC", "Max. depth"), ", "); - write_attribute(b, "Average_SAC", translate("gettextFromC", "Average SAC"), ", "); - write_attribute(b, "Min_SAC", translate("gettextFromC", "Min. SAC"), ", "); - write_attribute(b, "Max_SAC", translate("gettextFromC", "Max. SAC"), ", "); - write_attribute(b, "Average_Temp", translate("gettextFromC", "Average temp."), ", "); - write_attribute(b, "Min_Temp", translate("gettextFromC", "Min. temp."), ", "); - write_attribute(b, "Max_Temp", translate("gettextFromC", "Max. temp."), ", "); - write_attribute(b, "Back_to_List", translate("gettextFromC", "Back to list"), ", "); - - //dive detailed view - write_attribute(b, "Dive_No", translate("gettextFromC", "Dive No."), ", "); - write_attribute(b, "Dive_profile", translate("gettextFromC", "Dive profile"), ", "); - write_attribute(b, "Dive_information", translate("gettextFromC", "Dive information"), ", "); - write_attribute(b, "Dive_equipment", translate("gettextFromC", "Dive equipment"), ", "); - write_attribute(b, "Type", translate("gettextFromC", "Type"), ", "); - write_attribute(b, "Size", translate("gettextFromC", "Size"), ", "); - write_attribute(b, "Work_Pressure", translate("gettextFromC", "Work pressure"), ", "); - write_attribute(b, "Start_Pressure", translate("gettextFromC", "Start pressure"), ", "); - write_attribute(b, "End_Pressure", translate("gettextFromC", "End pressure"), ", "); - write_attribute(b, "Gas", translate("gettextFromC", "Gas"), ", "); - write_attribute(b, "Weight", translate("gettextFromC", "Weight"), ", "); - write_attribute(b, "Type", translate("gettextFromC", "Type"), ", "); - write_attribute(b, "Events", translate("gettextFromC", "Events"), ", "); - write_attribute(b, "Name", translate("gettextFromC", "Name"), ", "); - write_attribute(b, "Value", translate("gettextFromC", "Value"), ", "); - write_attribute(b, "Coordinates", translate("gettextFromC", "Coordinates"), ", "); - write_attribute(b, "Dive_Status", translate("gettextFromC", "Dive status"), " "); - - put_format(b, "}"); - - f = subsurface_fopen(file_name, "w+"); - if (!f) { - report_error(translate("gettextFromC", "Can't open file %s"), file_name); - } else { - flush_buffer(&buf, f); /*check for writing errors? */ - fclose(f); - } - free_buffer(&buf); -} diff --git a/subsurface-core/save-html.h b/subsurface-core/save-html.h deleted file mode 100644 index 13bb102b1..000000000 --- a/subsurface-core/save-html.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef HTML_SAVE_H -#define HTML_SAVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "dive.h" -#include "membuffer.h" - -void put_HTML_date(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_depth(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_airtemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_watertemp(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_time(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_notes(struct membuffer *b, struct dive *dive, const char *pre, const char *post); -void put_HTML_quoted(struct membuffer *b, const char *text); -void put_HTML_pressure_units(struct membuffer *b, pressure_t pressure, const char *pre, const char *post); -void put_HTML_weight_units(struct membuffer *b, unsigned int grams, const char *pre, const char *post); -void put_HTML_volume_units(struct membuffer *b, unsigned int ml, const char *pre, const char *post); - -void export_HTML(const char *file_name, const char *photos_dir, const bool selected_only, const bool list_only); -void export_list(struct membuffer *b, const char *photos_dir, bool selected_only, const bool list_only); - -void export_translation(const char *file_name); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/subsurface-core/save-xml.c b/subsurface-core/save-xml.c deleted file mode 100644 index 2335637e8..000000000 --- a/subsurface-core/save-xml.c +++ /dev/null @@ -1,749 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#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); - - save_dives_buffer(&buf, select_only); - - if (same_string(filename, "-")) { - f = stdout; - } else { - try_to_backup(filename); - error = -1; - f = subsurface_fopen(filename, "w"); - } - if (f) { - flush_buffer(&buf, f); - error = fclose(f); - } - if (error) - report_error("Save failed (%s)", strerror(errno)); - - free_buffer(&buf); - return error; -} - -int export_dives_xslt(const char *filename, const bool selected, const int units, const char *export_xslt) -{ - FILE *f; - struct membuffer buf = { 0 }; - xmlDoc *doc; - xsltStylesheetPtr xslt = NULL; - xmlDoc *transformed; - int res = 0; - char *params[3]; - int pnr = 0; - char unitstr[3]; - - if (verbose) - fprintf(stderr, "export_dives_xslt with stylesheet %s\n", export_xslt); - - if (!filename) - return report_error("No filename for export"); - - /* Save XML to file and convert it into a memory buffer */ - save_dives_buffer(&buf, selected); - - /* - * Parse the memory buffer into XML document and - * transform it to selected export format, finally dumping - * the XML into a character buffer. - */ - doc = xmlReadMemory(buf.buffer, buf.len, "divelog", NULL, 0); - free_buffer(&buf); - if (!doc) - return report_error("Failed to read XML memory"); - - /* Convert to export format */ - xslt = get_stylesheet(export_xslt); - if (!xslt) - return report_error("Failed to open export conversion stylesheet"); - - snprintf(unitstr, 3, "%d", units); - params[pnr++] = "units"; - params[pnr++] = unitstr; - params[pnr++] = NULL; - - transformed = xsltApplyStylesheet(xslt, doc, (const char **)params); - xmlFreeDoc(doc); - - /* Write the transformed export to file */ - f = subsurface_fopen(filename, "w"); - if (f) { - xsltSaveResultToFile(f, transformed, xslt); - fclose(f); - /* Check write errors? */ - } else { - res = report_error("Failed to open %s for writing (%s)", filename, strerror(errno)); - } - xsltFreeStylesheet(xslt); - xmlFreeDoc(transformed); - - return res; -} diff --git a/subsurface-core/serial_ftdi.c b/subsurface-core/serial_ftdi.c deleted file mode 100644 index ff1335171..000000000 --- a/subsurface-core/serial_ftdi.c +++ /dev/null @@ -1,665 +0,0 @@ -/* - * libdivecomputer - * - * Copyright (C) 2008 Jef Driesen - * Copyright (C) 2014 Venkatesh Shukla - * Copyright (C) 2015 Anton Lundin - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - */ - -#include // 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)) { - free(device); - ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); - return DC_STATUS_IO; - } - - if (ftdi_usb_purge_buffers(ftdi_ctx)) { - free(device); - ERROR (context, "%s", ftdi_get_error_string(ftdi_ctx)); - return DC_STATUS_IO; - } - - device->ftdi_ctx = ftdi_ctx; - - //FIXME: remove this when custom-serial have support for configure calls - serial_ftdi_configure (device, 115200, 8, 0, 1, 0); - - *out = device; - - return DC_STATUS_SUCCESS; -} - -// -// Close the serial port. -// -static int serial_ftdi_close (serial_t *device) -{ - if (device == NULL) - return 0; - - // Restore the initial terminal attributes. - // See if it is possible using libusb or libftdi - - int ret = ftdi_usb_close(device->ftdi_ctx); - if (ret < 0) { - ERROR (device->context, "Unable to close the ftdi device : %d (%s)\n", - ret, ftdi_get_error_string(device->ftdi_ctx)); - return ret; - } - - ftdi_free(device->ftdi_ctx); - - // Free memory. - free (device); - - return 0; -} - -// -// Configure the serial port (baudrate, databits, parity, stopbits and flowcontrol). -// -static dc_status_t serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "Configure: baudrate=%i, databits=%i, parity=%i, stopbits=%i, flowcontrol=%i", - baudrate, databits, parity, stopbits, flowcontrol); - - enum ftdi_bits_type ft_bits; - enum ftdi_stopbits_type ft_stopbits; - enum ftdi_parity_type ft_parity; - - if (ftdi_set_baudrate(device->ftdi_ctx, baudrate) < 0) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - - // Set the character size. - switch (databits) { - case 7: - ft_bits = BITS_7; - break; - case 8: - ft_bits = BITS_8; - break; - default: - return DC_STATUS_INVALIDARGS; - } - - // Set the parity type. - switch (parity) { - case SERIAL_PARITY_NONE: // No parity - ft_parity = NONE; - break; - case SERIAL_PARITY_EVEN: // Even parity - ft_parity = EVEN; - break; - case SERIAL_PARITY_ODD: // Odd parity - ft_parity = ODD; - break; - default: - return DC_STATUS_INVALIDARGS; - } - - // Set the number of stop bits. - switch (stopbits) { - case 1: // One stopbit - ft_stopbits = STOP_BIT_1; - break; - case 2: // Two stopbits - ft_stopbits = STOP_BIT_2; - break; - default: - return DC_STATUS_INVALIDARGS; - } - - // Set the attributes - if (ftdi_set_line_property(device->ftdi_ctx, ft_bits, ft_stopbits, ft_parity)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return DC_STATUS_IO; - } - - // Set the flow control. - switch (flowcontrol) { - case SERIAL_FLOWCONTROL_NONE: // No flow control. - if (ftdi_setflowctrl(device->ftdi_ctx, SIO_DISABLE_FLOW_CTRL) < 0) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return DC_STATUS_IO; - } - break; - case SERIAL_FLOWCONTROL_HARDWARE: // Hardware (RTS/CTS) flow control. - if (ftdi_setflowctrl(device->ftdi_ctx, SIO_RTS_CTS_HS) < 0) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return DC_STATUS_IO; - } - break; - case SERIAL_FLOWCONTROL_SOFTWARE: // Software (XON/XOFF) flow control. - if (ftdi_setflowctrl(device->ftdi_ctx, SIO_XON_XOFF_HS) < 0) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return DC_STATUS_IO; - } - break; - default: - return DC_STATUS_INVALIDARGS; - } - - device->baudrate = baudrate; - device->nbits = 1 + databits + stopbits + (parity ? 1 : 0); - - return DC_STATUS_SUCCESS; -} - -// -// Configure the serial port (timeouts). -// -static int serial_ftdi_set_timeout (serial_t *device, long timeout) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "Timeout: value=%li", timeout); - - device->timeout = timeout; - - return 0; -} - -static int serial_ftdi_set_halfduplex (serial_t *device, int value) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - // Most ftdi chips support full duplex operation. ft232rl does. - // Crosscheck other chips. - - device->halfduplex = value; - - return 0; -} - -static int serial_ftdi_read (serial_t *device, void *data, unsigned int size) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - // The total timeout. - long timeout = device->timeout; - - // The absolute target time. - struct timeval tve; - - static int backoff = 1; - int init = 1; - unsigned int nbytes = 0; - while (nbytes < size) { - struct timeval tvt; - if (timeout > 0) { - struct timeval now; - if (gettimeofday (&now, NULL) != 0) { - SYSERROR (device->context, errno); - return -1; - } - - if (init) { - // Calculate the initial timeout. - tvt.tv_sec = (timeout / 1000); - tvt.tv_usec = (timeout % 1000) * 1000; - // Calculate the target time. - timeradd (&now, &tvt, &tve); - } else { - // Calculate the remaining timeout. - if (timercmp (&now, &tve, <)) - timersub (&tve, &now, &tvt); - else - timerclear (&tvt); - } - init = 0; - } else if (timeout == 0) { - timerclear (&tvt); - } - - int n = ftdi_read_data (device->ftdi_ctx, (char *) data + nbytes, size - nbytes); - if (n < 0) { - if (n == LIBUSB_ERROR_INTERRUPTED) - continue; //Retry. - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; //Error during read call. - } else if (n == 0) { - // Exponential backoff. - if (backoff > MAX_BACKOFF) { - ERROR(device->context, "%s", "FTDI read timed out."); - return -1; - } - serial_ftdi_sleep (device, backoff); - backoff *= 2; - } else { - // Reset backoff to 1 on success. - backoff = 1; - } - - nbytes += n; - } - - INFO (device->context, "Read %d bytes", nbytes); - - return nbytes; -} - -static int serial_ftdi_write (serial_t *device, const void *data, unsigned int size) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - struct timeval tve, tvb; - if (device->halfduplex) { - // Get the current time. - if (gettimeofday (&tvb, NULL) != 0) { - SYSERROR (device->context, errno); - return -1; - } - } - - unsigned int nbytes = 0; - while (nbytes < size) { - - int n = ftdi_write_data (device->ftdi_ctx, (char *) data + nbytes, size - nbytes); - if (n < 0) { - if (n == LIBUSB_ERROR_INTERRUPTED) - continue; // Retry. - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; // Error during write call. - } else if (n == 0) { - break; // EOF. - } - - nbytes += n; - } - - if (device->halfduplex) { - // Get the current time. - if (gettimeofday (&tve, NULL) != 0) { - SYSERROR (device->context, errno); - return -1; - } - - // Calculate the elapsed time (microseconds). - struct timeval tvt; - timersub (&tve, &tvb, &tvt); - unsigned long elapsed = tvt.tv_sec * 1000000 + tvt.tv_usec; - - // Calculate the expected duration (microseconds). A 2 millisecond fudge - // factor is added because it improves the success rate significantly. - unsigned long expected = 1000000.0 * device->nbits / device->baudrate * size + 0.5 + 2000; - - // Wait for the remaining time. - if (elapsed < expected) { - unsigned long remaining = expected - elapsed; - - // The remaining time is rounded up to the nearest millisecond to - // match the Windows implementation. The higher resolution is - // pointless anyway, since we already added a fudge factor above. - serial_ftdi_sleep (device, (remaining + 999) / 1000); - } - } - - INFO (device->context, "Wrote %d bytes", nbytes); - - return nbytes; -} - -static int serial_ftdi_flush (serial_t *device, int queue) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "Flush: queue=%u, input=%i, output=%i", queue, - serial_ftdi_get_received (device), - serial_ftdi_get_transmitted (device)); - - switch (queue) { - case SERIAL_QUEUE_INPUT: - if (ftdi_usb_purge_tx_buffer(device->ftdi_ctx)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - break; - case SERIAL_QUEUE_OUTPUT: - if (ftdi_usb_purge_rx_buffer(device->ftdi_ctx)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - break; - default: - if (ftdi_usb_purge_buffers(device->ftdi_ctx)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - break; - } - - return 0; -} - -static int serial_ftdi_send_break (serial_t *device) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument)a - - INFO (device->context, "Break : One time period."); - - // no direct functions for sending break signals in libftdi. - // there is a suggestion to lower the baudrate and sending NUL - // and resetting the baudrate up again. But it has flaws. - // Not implementing it before researching more. - - return -1; -} - -static int serial_ftdi_set_break (serial_t *device, int level) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "Break: value=%i", level); - - // Not implemented in libftdi yet. Research it further. - - return -1; -} - -static int serial_ftdi_set_dtr (serial_t *device, int level) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "DTR: value=%i", level); - - if (ftdi_setdtr(device->ftdi_ctx, level)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - - return 0; -} - -static int serial_ftdi_set_rts (serial_t *device, int level) -{ - if (device == NULL) - return -1; // EINVAL (Invalid argument) - - INFO (device->context, "RTS: value=%i", level); - - if (ftdi_setrts(device->ftdi_ctx, level)) { - ERROR (device->context, "%s", ftdi_get_error_string(device->ftdi_ctx)); - return -1; - } - - return 0; -} - -const dc_serial_operations_t serial_ftdi_ops = { - .open = serial_ftdi_open, - .close = serial_ftdi_close, - .read = serial_ftdi_read, - .write = serial_ftdi_write, - .flush = serial_ftdi_flush, - .get_received = serial_ftdi_get_received, - .get_transmitted = NULL, /*NOT USED ANYWHERE! serial_ftdi_get_transmitted */ - .set_timeout = serial_ftdi_set_timeout -#ifdef FIXED_SSRF_CUSTOM_SERIAL - , - .configure = serial_ftdi_configure, -//static int serial_ftdi_configure (serial_t *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol) - .set_halfduplex = serial_ftdi_set_halfduplex, -//static int serial_ftdi_set_halfduplex (serial_t *device, int value) - .send_break = serial_ftdi_send_break, -//static int serial_ftdi_send_break (serial_t *device) - .set_break = serial_ftdi_set_break, -//static int serial_ftdi_set_break (serial_t *device, int level) - .set_dtr = serial_ftdi_set_dtr, -//static int serial_ftdi_set_dtr (serial_t *device, int level) - .set_rts = serial_ftdi_set_rts -//static int serial_ftdi_set_rts (serial_t *device, int level) -#endif -}; - -dc_status_t dc_serial_ftdi_open(dc_serial_t **out, dc_context_t *context) -{ - if (out == NULL) - return DC_STATUS_INVALIDARGS; - - // Allocate memory. - dc_serial_t *serial_device = (dc_serial_t *) malloc (sizeof (dc_serial_t)); - - if (serial_device == NULL) { - return DC_STATUS_NOMEMORY; - } - - // Initialize data and function pointers - dc_serial_init(serial_device, NULL, &serial_ftdi_ops); - - // Open the serial device. - dc_status_t rc = (dc_status_t) serial_ftdi_open (&serial_device->port, context, NULL); - if (rc != DC_STATUS_SUCCESS) { - free (serial_device); - return rc; - } - - // Set the type of the device - serial_device->type = DC_TRANSPORT_USB;; - - *out = serial_device; - - return DC_STATUS_SUCCESS; -} diff --git a/subsurface-core/sha1.c b/subsurface-core/sha1.c deleted file mode 100644 index acf8c5d9f..000000000 --- a/subsurface-core/sha1.c +++ /dev/null @@ -1,300 +0,0 @@ -/* - * SHA1 routine optimized to do word accesses rather than byte accesses, - * and to avoid unnecessary copies into the context array. - * - * This was initially based on the Mozilla SHA1 implementation, although - * none of the original Mozilla code remains. - */ - -/* this is only to get definitions for memcpy(), ntohl() and htonl() */ -#include -#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 deleted file mode 100644 index cab6ff77d..000000000 --- a/subsurface-core/sha1.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SHA1 routine optimized to do word accesses rather than byte accesses, - * and to avoid unnecessary copies into the context array. - * - * This was initially based on the Mozilla SHA1 implementation, although - * none of the original Mozilla code remains. - */ -#ifndef SHA1_H -#define SHA1_H - -typedef struct -{ - unsigned long long size; - unsigned int H[5]; - unsigned int W[16]; -} blk_SHA_CTX; - -void blk_SHA1_Init(blk_SHA_CTX *ctx); -void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len); -void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); - -/* Make us use the standard names */ -#define SHA_CTX blk_SHA_CTX -#define SHA1_Init blk_SHA1_Init -#define SHA1_Update blk_SHA1_Update -#define SHA1_Final blk_SHA1_Final - -/* Trivial helper function */ -static inline void SHA1(const void *dataIn, unsigned long len, unsigned char hashout[20]) -{ - SHA_CTX ctx; - - SHA1_Init(&ctx); - SHA1_Update(&ctx, dataIn, len); - SHA1_Final(hashout, &ctx); -} - -#endif // SHA1_H diff --git a/subsurface-core/statistics.c b/subsurface-core/statistics.c deleted file mode 100644 index 6a05cffc1..000000000 --- a/subsurface-core/statistics.c +++ /dev/null @@ -1,404 +0,0 @@ -/* statistics.c - * - * core logic for the Info & Stats page - - * char *get_time_string(int seconds, int maxdays); - * char *get_minutes(int seconds); - * void process_all_dives(struct dive *dive, struct dive **prev_dive); - * void get_selected_dives_text(char *buffer, int size); - */ -#include "gettext.h" -#include -#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; -stats_t *stats_by_type = NULL; - -static void process_temperatures(struct dive *dp, stats_t *stats) -{ - int min_temp, mean_temp, max_temp = 0; - - max_temp = dp->maxtemp.mkelvin; - if (max_temp && (!stats->max_temp || max_temp > stats->max_temp)) - stats->max_temp = max_temp; - - min_temp = dp->mintemp.mkelvin; - if (min_temp && (!stats->min_temp || min_temp < stats->min_temp)) - stats->min_temp = min_temp; - - if (min_temp || max_temp) { - mean_temp = min_temp; - if (mean_temp) - mean_temp = (mean_temp + max_temp) / 2; - else - mean_temp = max_temp; - stats->combined_temp += get_temp_units(mean_temp, NULL); - stats->combined_count++; - } -} - -static void process_dive(struct dive *dp, stats_t *stats) -{ - int old_tt, sac_time = 0; - uint32_t duration = dp->duration.seconds; - - old_tt = stats->total_time.seconds; - stats->total_time.seconds += duration; - if (duration > stats->longest_time.seconds) - stats->longest_time.seconds = duration; - if (stats->shortest_time.seconds == 0 || duration < stats->shortest_time.seconds) - stats->shortest_time.seconds = duration; - if (dp->maxdepth.mm > stats->max_depth.mm) - stats->max_depth.mm = dp->maxdepth.mm; - if (stats->min_depth.mm == 0 || dp->maxdepth.mm < stats->min_depth.mm) - stats->min_depth.mm = dp->maxdepth.mm; - - process_temperatures(dp, stats); - - /* Maybe we should drop zero-duration dives */ - if (!duration) - return; - stats->avg_depth.mm = (1.0 * old_tt * stats->avg_depth.mm + - duration * dp->meandepth.mm) / - stats->total_time.seconds; - if (dp->sac > 100) { /* less than .1 l/min is bogus, even with a pSCR */ - sac_time = stats->total_sac_time + duration; - stats->avg_sac.mliter = (1.0 * stats->total_sac_time * stats->avg_sac.mliter + - duration * dp->sac) / - sac_time; - if (dp->sac > stats->max_sac.mliter) - stats->max_sac.mliter = dp->sac; - if (stats->min_sac.mliter == 0 || dp->sac < stats->min_sac.mliter) - stats->min_sac.mliter = dp->sac; - stats->total_sac_time = sac_time; - } -} - -char *get_minutes(int seconds) -{ - static char buf[80]; - snprintf(buf, sizeof(buf), "%d:%.2d", FRACTION(seconds, 60)); - return buf; -} - -void process_all_dives(struct dive *dive, struct dive **prev_dive) -{ - int idx; - struct dive *dp; - struct tm tm; - int current_year = 0; - int current_month = 0; - int year_iter = 0; - int month_iter = 0; - int prev_month = 0, prev_year = 0; - int trip_iter = 0; - dive_trip_t *trip_ptr = 0; - unsigned int size, tsize; - - *prev_dive = NULL; - memset(&stats, 0, sizeof(stats)); - if (dive_table.nr > 0) { - stats.shortest_time.seconds = dive_table.dives[0]->duration.seconds; - stats.min_depth.mm = dive_table.dives[0]->maxdepth.mm; - stats.selection_size = dive_table.nr; - } - - /* allocate sufficient space to hold the worst - * case (one dive per year or all dives during - * one month) for yearly and monthly statistics*/ - - free(stats_yearly); - free(stats_monthly); - free(stats_by_trip); - free(stats_by_type); - - size = sizeof(stats_t) * (dive_table.nr + 1); - tsize = sizeof(stats_t) * (NUM_DC_TYPE + 1); - stats_yearly = malloc(size); - stats_monthly = malloc(size); - stats_by_trip = malloc(size); - stats_by_type = malloc(tsize); - if (!stats_yearly || !stats_monthly || !stats_by_trip || !stats_by_type) - return; - memset(stats_yearly, 0, size); - memset(stats_monthly, 0, size); - memset(stats_by_trip, 0, size); - memset(stats_by_type, 0, tsize); - stats_yearly[0].is_year = true; - - /* Setting the is_trip to true to show the location as first - * field in the statistics window */ - stats_by_type[0].location = strdup("All (by type stats)"); - stats_by_type[0].is_trip = true; - stats_by_type[1].location = strdup("OC"); - stats_by_type[1].is_trip = true; - stats_by_type[2].location = strdup("CCR"); - stats_by_type[2].is_trip = true; - stats_by_type[3].location = strdup("pSCR"); - stats_by_type[3].is_trip = true; - stats_by_type[4].location = strdup("Freedive"); - stats_by_type[4].is_trip = true; - - /* this relies on the fact that the dives in the dive_table - * are in chronological order */ - for_each_dive (idx, dp) { - if (dive && dp->when == dive->when) { - /* that's the one we are showing */ - if (idx > 0) - *prev_dive = dive_table.dives[idx - 1]; - } - process_dive(dp, &stats); - - /* yearly statistics */ - utc_mkdate(dp->when, &tm); - if (current_year == 0) - current_year = tm.tm_year + 1900; - - if (current_year != tm.tm_year + 1900) { - current_year = tm.tm_year + 1900; - process_dive(dp, &(stats_yearly[++year_iter])); - stats_yearly[year_iter].is_year = true; - } else { - process_dive(dp, &(stats_yearly[year_iter])); - } - stats_yearly[year_iter].selection_size++; - stats_yearly[year_iter].period = current_year; - - /* stats_by_type[0] is all the dives combined */ - stats_by_type[0].selection_size++; - process_dive(dp, &(stats_by_type[0])); - - process_dive(dp, &(stats_by_type[dp->dc.divemode + 1])); - stats_by_type[dp->dc.divemode + 1].selection_size++; - - if (dp->divetrip != NULL) { - if (trip_ptr != dp->divetrip) { - trip_ptr = dp->divetrip; - trip_iter++; - } - - /* stats_by_trip[0] is all the dives combined */ - stats_by_trip[0].selection_size++; - process_dive(dp, &(stats_by_trip[0])); - stats_by_trip[0].is_trip = true; - stats_by_trip[0].location = strdup("All (by trip stats)"); - - process_dive(dp, &(stats_by_trip[trip_iter])); - stats_by_trip[trip_iter].selection_size++; - stats_by_trip[trip_iter].is_trip = true; - stats_by_trip[trip_iter].location = dp->divetrip->location; - } - - /* monthly statistics */ - if (current_month == 0) { - current_month = tm.tm_mon + 1; - } else { - if (current_month != tm.tm_mon + 1) - current_month = tm.tm_mon + 1; - if (prev_month != current_month || prev_year != current_year) - month_iter++; - } - process_dive(dp, &(stats_monthly[month_iter])); - stats_monthly[month_iter].selection_size++; - stats_monthly[month_iter].period = current_month; - prev_month = current_month; - prev_year = current_year; - } -} - -/* make sure we skip the selected summary entries */ -void process_selected_dives(void) -{ - struct dive *dive; - unsigned int i, nr; - - memset(&stats_selection, 0, sizeof(stats_selection)); - - nr = 0; - for_each_dive(i, dive) { - if (dive->selected) { - process_dive(dive, &stats_selection); - nr++; - } - } - stats_selection.selection_size = nr; -} - -char *get_time_string_s(int seconds, int maxdays, bool freediving) -{ - static char buf[80]; - if (maxdays && seconds > 3600 * 24 * maxdays) { - snprintf(buf, sizeof(buf), translate("gettextFromC", "more than %d days"), maxdays); - } else { - int days = seconds / 3600 / 24; - int hours = (seconds - days * 3600 * 24) / 3600; - int minutes = (seconds - days * 3600 * 24 - hours * 3600) / 60; - int secs = (seconds - days * 3600 * 24 - hours * 3600 - minutes*60); - if (days > 0) - snprintf(buf, sizeof(buf), translate("gettextFromC", "%dd %dh %dmin"), days, hours, minutes); - else - if (freediving && seconds < 3600) - snprintf(buf, sizeof(buf), translate("gettextFromC", "%dmin %dsecs"), minutes, secs); - else - snprintf(buf, sizeof(buf), translate("gettextFromC", "%dh %dmin"), hours, minutes); - } - return buf; -} - -/* this gets called when at least two but not all dives are selected */ -static void get_ranges(char *buffer, int size) -{ - int i, len; - int first = -1, last = -1; - struct dive *dive; - - snprintf(buffer, size, "%s", translate("gettextFromC", "for dives #")); - for_each_dive (i, dive) { - if (!dive->selected) - continue; - if (dive->number < 1) { - /* uhh - weird numbers - bail */ - snprintf(buffer, size, "%s", translate("gettextFromC", "for selected dives")); - return; - } - len = strlen(buffer); - if (last == -1) { - snprintf(buffer + len, size - len, "%d", dive->number); - first = last = dive->number; - } else { - if (dive->number == last + 1) { - last++; - continue; - } else { - if (first == last) - snprintf(buffer + len, size - len, ", %d", dive->number); - else if (first + 1 == last) - snprintf(buffer + len, size - len, ", %d, %d", last, dive->number); - else - snprintf(buffer + len, size - len, "-%d, %d", last, dive->number); - first = last = dive->number; - } - } - } - len = strlen(buffer); - if (first != last) { - if (first + 1 == last) - snprintf(buffer + len, size - len, ", %d", last); - else - snprintf(buffer + len, size - len, "-%d", last); - } -} - -void get_selected_dives_text(char *buffer, size_t size) -{ - if (amount_selected == 1) { - if (current_dive) - snprintf(buffer, size, translate("gettextFromC", "for dive #%d"), current_dive->number); - else - snprintf(buffer, size, "%s", translate("gettextFromC", "for selected dive")); - } else if (amount_selected == (unsigned int)dive_table.nr) { - snprintf(buffer, size, "%s", translate("gettextFromC", "for all dives")); - } else if (amount_selected == 0) { - snprintf(buffer, size, "%s", translate("gettextFromC", "(no dives)")); - } else { - get_ranges(buffer, size); - if (strlen(buffer) == size - 1) { - /* add our own ellipse... the way Pango does this is ugly - * as it will leave partial numbers there which I don't like */ - size_t offset = 4; - while (offset < size && isdigit(buffer[size - offset])) - offset++; - strcpy(buffer + size - offset, "..."); - } - } -} - -#define SOME_GAS 5000 // 5bar drop in cylinder pressure makes cylinder used - -bool is_cylinder_used(struct dive *dive, int idx) -{ - struct divecomputer *dc; - bool firstGasExplicit = false; - if (cylinder_none(&dive->cylinder[idx])) - return false; - - if ((dive->cylinder[idx].start.mbar - dive->cylinder[idx].end.mbar) > SOME_GAS) - return true; - for_each_dc(dive, dc) { - struct event *event = get_next_event(dc->events, "gaschange"); - while (event) { - if (dc->sample && (event->time.seconds == 0 || - (dc->samples && dc->sample[0].time.seconds == event->time.seconds))) - firstGasExplicit = true; - if (get_cylinder_index(dive, event) == idx) - return true; - event = get_next_event(event->next, "gaschange"); - } - if (dc->divemode == CCR && (idx == dive->diluent_cylinder_index || idx == dive->oxygen_cylinder_index)) - return true; - } - if (idx == 0 && !firstGasExplicit) - return true; - return false; -} - -void get_gas_used(struct dive *dive, volume_t gases[MAX_CYLINDERS]) -{ - int idx; - for (idx = 0; idx < MAX_CYLINDERS; idx++) { - cylinder_t *cyl = &dive->cylinder[idx]; - pressure_t start, end; - - if (!is_cylinder_used(dive, idx)) - continue; - - start = cyl->start.mbar ? cyl->start : cyl->sample_start; - end = cyl->end.mbar ? cyl->end : cyl->sample_end; - if (end.mbar && start.mbar > end.mbar) - gases[idx].mliter = gas_volume(cyl, start) - gas_volume(cyl, end); - } -} - -/* Quite crude reverse-blender-function, but it produces a approx result */ -static void get_gas_parts(struct gasmix mix, volume_t vol, int o2_in_topup, volume_t *o2, volume_t *he) -{ - volume_t air = {}; - - if (gasmix_is_air(&mix)) { - o2->mliter = 0; - he->mliter = 0; - return; - } - - air.mliter = rint(((double)vol.mliter * (1000 - get_he(&mix) - get_o2(&mix))) / (1000 - o2_in_topup)); - he->mliter = rint(((double)vol.mliter * get_he(&mix)) / 1000.0); - o2->mliter += vol.mliter - he->mliter - air.mliter; -} - -void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot) -{ - int i, j; - struct dive *d; - for_each_dive (i, d) { - if (!d->selected) - continue; - volume_t diveGases[MAX_CYLINDERS] = {}; - get_gas_used(d, diveGases); - for (j = 0; j < MAX_CYLINDERS; j++) { - if (diveGases[j].mliter) { - volume_t o2 = {}, he = {}; - get_gas_parts(d->cylinder[j].gasmix, diveGases[j], O2_IN_AIR, &o2, &he); - o2_tot->mliter += o2.mliter; - he_tot->mliter += he.mliter; - } - } - } -} diff --git a/subsurface-core/statistics.h b/subsurface-core/statistics.h deleted file mode 100644 index 015c3481e..000000000 --- a/subsurface-core/statistics.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * statistics.h - * - * core logic functions called from statistics UI - * common types and variables - */ - -#ifndef STATISTICS_H -#define STATISTICS_H - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct -{ - int period; - duration_t total_time; - /* avg_time is simply total_time / nr -- let's not keep this */ - duration_t shortest_time; - duration_t longest_time; - depth_t max_depth; - depth_t min_depth; - depth_t avg_depth; - volume_t max_sac; - volume_t min_sac; - volume_t avg_sac; - int max_temp; - int min_temp; - double combined_temp; - unsigned int combined_count; - unsigned int selection_size; - unsigned int total_sac_time; - bool is_year; - bool is_trip; - char *location; -} stats_t; -extern stats_t stats_selection; -extern stats_t *stats_yearly; -extern stats_t *stats_monthly; -extern stats_t *stats_by_trip; -extern stats_t *stats_by_type; - -extern char *get_time_string_s(int seconds, int maxdays, bool freediving); -extern char *get_minutes(int seconds); -extern void process_all_dives(struct dive *dive, struct dive **prev_dive); -extern void get_selected_dives_text(char *buffer, size_t size); -extern void get_gas_used(struct dive *dive, volume_t gases[MAX_CYLINDERS]); -extern void process_selected_dives(void); -void selected_dives_gas_parts(volume_t *o2_tot, volume_t *he_tot); - -inline char *get_time_string(int seconds, int maxdays) { - return get_time_string_s( seconds, maxdays, false); -} -#ifdef __cplusplus -} -#endif - -#endif // STATISTICS_H diff --git a/subsurface-core/strndup.h b/subsurface-core/strndup.h deleted file mode 100644 index 84e18b60f..000000000 --- a/subsurface-core/strndup.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef STRNDUP_H -#define STRNDUP_H -#if __WIN32__ -static char *strndup (const char *s, size_t n) -{ - char *cpy; - size_t len = strlen(s); - if (n < len) - len = n; - if ((cpy = malloc(len + 1)) != - NULL) { - cpy[len] = - '\0'; - memcpy(cpy, - s, - len); - } - return cpy; -} -#endif -#endif /* STRNDUP_H */ diff --git a/subsurface-core/strtod.c b/subsurface-core/strtod.c deleted file mode 100644 index 81e5d42d1..000000000 --- a/subsurface-core/strtod.c +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Sane helper for 'strtod()'. - * - * Sad that we even need this, but the C library version has - * insane locale behavior, and while the Qt "doDouble()" routines - * are better in that regard, they don't have an end pointer - * (having replaced it with the completely idiotic "ok" boolean - * pointer instead). - * - * I wonder what drugs people are on sometimes. - * - * Right now we support the following flags to limit the - * parsing some ways: - * - * STRTOD_NO_SIGN - don't accept signs - * STRTOD_NO_DOT - no decimal dots, I'm European - * STRTOD_NO_COMMA - no comma, please, I'm C locale - * STRTOD_NO_EXPONENT - no exponent parsing, I'm human - * - * The "negative" flags are so that the common case can just - * use a flag value of 0, and only if you have some special - * requirements do you need to state those with explicit flags. - * - * So if you want the C locale kind of parsing, you'd use the - * STRTOD_NO_COMMA flag to disallow a decimal comma. But if you - * want a more relaxed "Hey, Europeans are people too, even if - * they have locales with commas", just pass in a zero flag. - */ -#include -#include "dive.h" - -double strtod_flags(const char *str, const char **ptr, unsigned int flags) -{ - char c; - const char *p = str, *ep; - double val = 0.0; - double decimal = 1.0; - int sign = 0, esign = 0; - int numbers = 0, dot = 0; - - /* skip spaces */ - while (isspace(c = *p++)) - /* */; - - /* optional sign */ - if (!(flags & STRTOD_NO_SIGN)) { - switch (c) { - case '-': - sign = 1; - /* fallthrough */ - case '+': - c = *p++; - } - } - - /* Mantissa */ - for (;; c = *p++) { - if ((c == '.' && !(flags & STRTOD_NO_DOT)) || - (c == ',' && !(flags & STRTOD_NO_COMMA))) { - if (dot) - goto done; - dot = 1; - continue; - } - if (c >= '0' && c <= '9') { - numbers++; - val = (val * 10) + (c - '0'); - if (dot) - decimal *= 10; - continue; - } - if (c != 'e' && c != 'E') - goto done; - if (flags & STRTOD_NO_EXPONENT) - goto done; - break; - } - - if (!numbers) - goto done; - - /* Exponent */ - ep = p; - c = *ep++; - switch (c) { - case '-': - esign = 1; - /* fallthrough */ - case '+': - c = *ep++; - } - - if (c >= '0' && c <= '9') { - p = ep; - int exponent = c - '0'; - - for (;;) { - c = *p++; - if (c < '0' || c > '9') - break; - exponent *= 10; - exponent += c - '0'; - } - - /* We're not going to bother playing games */ - if (exponent > 308) - exponent = 308; - - while (exponent-- > 0) { - if (esign) - decimal *= 10; - else - decimal /= 10; - } - } - -done: - if (!numbers) - goto no_conversion; - if (ptr) - *ptr = p - 1; - return (sign ? -val : val) / decimal; - -no_conversion: - if (ptr) - *ptr = str; - return 0.0; -} diff --git a/subsurface-core/subsurface-qt/DiveObjectHelper.cpp b/subsurface-core/subsurface-qt/DiveObjectHelper.cpp deleted file mode 100644 index ab88d2f3c..000000000 --- a/subsurface-core/subsurface-qt/DiveObjectHelper.cpp +++ /dev/null @@ -1,338 +0,0 @@ -#include "DiveObjectHelper.h" - -#include -#include - -#include "../qthelper.h" -#include "../helpers.h" - -static QString EMPTY_DIVE_STRING = QStringLiteral("--"); -enum returnPressureSelector {START_PRESSURE, END_PRESSURE}; - -static QString getFormattedWeight(struct dive *dive, unsigned int idx) -{ - weightsystem_t *weight = &dive->weightsystem[idx]; - if (!weight->description) - return QString(EMPTY_DIVE_STRING); - QString fmt = QString(weight->description); - fmt += ", " + get_weight_string(weight->weight, true); - return fmt; -} - -static QString getFormattedCylinder(struct dive *dive, unsigned int idx) -{ - cylinder_t *cyl = &dive->cylinder[idx]; - const char *desc = cyl->type.description; - if (!desc && idx > 0) - return QString(EMPTY_DIVE_STRING); - QString fmt = desc ? QString(desc) : QObject::tr("unknown"); - fmt += ", " + get_volume_string(cyl->type.size, true); - fmt += ", " + get_pressure_string(cyl->type.workingpressure, true); - fmt += ", " + get_pressure_string(cyl->start, false) + " - " + get_pressure_string(cyl->end, true); - fmt += ", " + get_gas_string(cyl->gasmix); - return fmt; -} - -static QString getPressures(struct dive *dive, enum returnPressureSelector ret) -{ - cylinder_t *cyl = &dive->cylinder[0]; - QString fmt; - if (ret == START_PRESSURE) { - if (cyl->start.mbar) - fmt = get_pressure_string(cyl->start, true); - else if (cyl->sample_start.mbar) - fmt = get_pressure_string(cyl->sample_start, true); - } - if (ret == END_PRESSURE) { - if (cyl->end.mbar) - fmt = get_pressure_string(cyl->end, true); - else if(cyl->sample_end.mbar) - fmt = get_pressure_string(cyl->sample_end, true); - } - return fmt; -} - -DiveObjectHelper::DiveObjectHelper(struct dive *d) : - m_dive(d) -{ -} - -DiveObjectHelper::~DiveObjectHelper() -{ -} - -int DiveObjectHelper::number() const -{ - return m_dive->number; -} - -int DiveObjectHelper::id() const -{ - return m_dive->id; -} - -QString DiveObjectHelper::date() const -{ - QDateTime localTime = QDateTime::fromTime_t(m_dive->when - gettimezoneoffset(m_dive->when)); - localTime.setTimeSpec(Qt::UTC); - return localTime.date().toString(prefs.date_format); -} - -timestamp_t DiveObjectHelper::timestamp() const -{ - return m_dive->when; -} - -QString DiveObjectHelper::time() const -{ - QDateTime localTime = QDateTime::fromTime_t(m_dive->when - gettimezoneoffset(m_dive->when)); - localTime.setTimeSpec(Qt::UTC); - return localTime.time().toString(prefs.time_format); -} - -QString DiveObjectHelper::location() const -{ - return get_dive_location(m_dive) ? QString::fromUtf8(get_dive_location(m_dive)) : EMPTY_DIVE_STRING; -} - -QString DiveObjectHelper::gps() const -{ - struct dive_site *ds = get_dive_site_by_uuid(m_dive->dive_site_uuid); - return ds ? QString(printGPSCoords(ds->latitude.udeg, ds->longitude.udeg)) : QString(); -} -QString DiveObjectHelper::duration() const -{ - return get_dive_duration_string(m_dive->duration.seconds, QObject::tr("h:"), QObject::tr("min")); -} - -bool DiveObjectHelper::noDive() const -{ - return m_dive->duration.seconds == 0 && m_dive->dc.duration.seconds == 0; -} - -QString DiveObjectHelper::depth() const -{ - return get_depth_string(m_dive->dc.maxdepth.mm, true, true); -} - -QString DiveObjectHelper::divemaster() const -{ - return m_dive->divemaster ? m_dive->divemaster : EMPTY_DIVE_STRING; -} - -QString DiveObjectHelper::buddy() const -{ - return m_dive->buddy ? m_dive->buddy : EMPTY_DIVE_STRING; -} - -QString DiveObjectHelper::airTemp() const -{ - QString temp = get_temperature_string(m_dive->airtemp, true); - if (temp.isEmpty()) { - temp = EMPTY_DIVE_STRING; - } - return temp; -} - -QString DiveObjectHelper::waterTemp() const -{ - QString temp = get_temperature_string(m_dive->watertemp, true); - if (temp.isEmpty()) { - temp = EMPTY_DIVE_STRING; - } - return temp; -} - -QString DiveObjectHelper::notes() const -{ - QString tmp = m_dive->notes ? QString::fromUtf8(m_dive->notes) : EMPTY_DIVE_STRING; - if (same_string(m_dive->dc.model, "planned dive")) { - QTextDocument notes; - #define _NOTES_BR "\n" - tmp.replace("", "" _NOTES_BR) - .replace("
", "
" _NOTES_BR) - .replace("", "" _NOTES_BR) - .replace("", "" _NOTES_BR); - notes.setHtml(tmp); - tmp = notes.toPlainText(); - tmp.replace(_NOTES_BR, "
"); - #undef _NOTES_BR - } else { - tmp.replace("\n", "
"); - } - return tmp; -} - -QString DiveObjectHelper::tags() const -{ - static char buffer[256]; - taglist_get_tagstring(m_dive->tag_list, buffer, 256); - return QString(buffer); -} - -QString DiveObjectHelper::gas() const -{ - /*WARNING: here should be the gastlist, returned - * from the get_gas_string function or this is correct? - */ - QString gas, gases; - for (int i = 0; i < MAX_CYLINDERS; i++) { - if (!is_cylinder_used(m_dive, i)) - continue; - gas = m_dive->cylinder[i].type.description; - if (!gas.isEmpty()) - gas += QChar(' '); - gas += gasname(&m_dive->cylinder[i].gasmix); - // if has a description and if such gas is not already present - if (!gas.isEmpty() && gases.indexOf(gas) == -1) { - if (!gases.isEmpty()) - gases += QString(" / "); - gases += gas; - } - } - return gases; -} - -QString DiveObjectHelper::sac() const -{ - if (!m_dive->sac) - return QString(); - const char *unit; - int decimal; - double value = get_volume_units(m_dive->sac, &decimal, &unit); - return QString::number(value, 'f', decimal).append(unit); -} - -QString DiveObjectHelper::weightList() const -{ - QString weights; - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) { - QString w = getFormattedWeight(m_dive, i); - if (w == EMPTY_DIVE_STRING) - continue; - weights += w + "; "; - } - return weights; -} - -QStringList DiveObjectHelper::weights() const -{ - QStringList weights; - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++) - weights << getFormattedWeight(m_dive, i); - return weights; -} - -bool DiveObjectHelper::singleWeight() const -{ - return weightsystem_none(&m_dive->weightsystem[1]); -} - -QString DiveObjectHelper::weight(int idx) const -{ - if ( (idx < 0) || idx > MAX_WEIGHTSYSTEMS ) - return QString(EMPTY_DIVE_STRING); - return getFormattedWeight(m_dive, idx); -} - -QString DiveObjectHelper::suit() const -{ - return m_dive->suit ? m_dive->suit : EMPTY_DIVE_STRING; -} - -QString DiveObjectHelper::cylinderList() const -{ - QString cylinders; - for (int i = 0; i < MAX_CYLINDERS; i++) { - QString cyl = getFormattedCylinder(m_dive, i); - if (cyl == EMPTY_DIVE_STRING) - continue; - cylinders += cyl + "; "; - } - return cylinders; -} - -QStringList DiveObjectHelper::cylinders() const -{ - QStringList cylinders; - for (int i = 0; i < MAX_CYLINDERS; i++) - cylinders << getFormattedCylinder(m_dive, i); - return cylinders; -} - -QString DiveObjectHelper::cylinder(int idx) const -{ - if ( (idx < 0) || idx > MAX_CYLINDERS) - return QString(EMPTY_DIVE_STRING); - return getFormattedCylinder(m_dive, idx); -} - -QString DiveObjectHelper::trip() const -{ - return m_dive->divetrip ? m_dive->divetrip->location : EMPTY_DIVE_STRING; -} - -// combine the pointer address with the trip location so that -// we detect multiple, destinct trips to the same location -QString DiveObjectHelper::tripMeta() const -{ - QString ret = EMPTY_DIVE_STRING; - if (m_dive->divetrip) - ret = QString::number((quint64)m_dive->divetrip, 16) + QLatin1Literal("::") + m_dive->divetrip->location; - return ret; -} - -QString DiveObjectHelper::maxcns() const -{ - return QString(m_dive->maxcns); -} - -QString DiveObjectHelper::otu() const -{ - return QString(m_dive->otu); -} - -int DiveObjectHelper::rating() const -{ - return m_dive->rating; -} - -QString DiveObjectHelper::sumWeight() const -{ - weight_t sum = { 0 }; - for (int i = 0; i < MAX_WEIGHTSYSTEMS; i++){ - sum.grams += m_dive->weightsystem[i].weight.grams; - } - return get_weight_string(sum, true); -} - -QString DiveObjectHelper::getCylinder() const -{ - QString getCylinder; - if (is_cylinder_used(m_dive, 1)){ - getCylinder = QObject::tr("Multiple"); - } - else { - getCylinder = m_dive->cylinder[0].type.description; - } - return getCylinder; -} - -QString DiveObjectHelper::startPressure() const -{ - QString startPressure = getPressures(m_dive, START_PRESSURE); - return startPressure; -} - -QString DiveObjectHelper::endPressure() const -{ - QString endPressure = getPressures(m_dive, END_PRESSURE); - return endPressure; -} - -QString DiveObjectHelper::firstGas() const -{ - QString gas; - gas = get_gas_string(m_dive->cylinder[0].gasmix); - return gas; -} diff --git a/subsurface-core/subsurface-qt/DiveObjectHelper.h b/subsurface-core/subsurface-qt/DiveObjectHelper.h deleted file mode 100644 index 602775ef8..000000000 --- a/subsurface-core/subsurface-qt/DiveObjectHelper.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef DIVE_QOBJECT_H -#define DIVE_QOBJECT_H - -#include "../dive.h" -#include -#include -#include - -class DiveObjectHelper : public QObject { - Q_OBJECT - Q_PROPERTY(int number READ number CONSTANT) - Q_PROPERTY(int id READ id CONSTANT) - Q_PROPERTY(int rating READ rating CONSTANT) - Q_PROPERTY(QString date READ date CONSTANT) - Q_PROPERTY(QString time READ time CONSTANT) - Q_PROPERTY(QString location READ location CONSTANT) - Q_PROPERTY(QString gps READ gps CONSTANT) - Q_PROPERTY(QString duration READ duration CONSTANT) - Q_PROPERTY(bool noDive READ noDive CONSTANT) - Q_PROPERTY(QString depth READ depth CONSTANT) - Q_PROPERTY(QString divemaster READ divemaster CONSTANT) - Q_PROPERTY(QString buddy READ buddy CONSTANT) - Q_PROPERTY(QString airTemp READ airTemp CONSTANT) - Q_PROPERTY(QString waterTemp READ waterTemp CONSTANT) - Q_PROPERTY(QString notes READ notes CONSTANT) - Q_PROPERTY(QString tags READ tags CONSTANT) - Q_PROPERTY(QString gas READ gas CONSTANT) - Q_PROPERTY(QString sac READ sac CONSTANT) - Q_PROPERTY(QString weightList READ weightList CONSTANT) - Q_PROPERTY(QStringList weights READ weights CONSTANT) - Q_PROPERTY(bool singleWeight READ singleWeight CONSTANT) - Q_PROPERTY(QString suit READ suit CONSTANT) - Q_PROPERTY(QString cylinderList READ cylinderList CONSTANT) - Q_PROPERTY(QStringList cylinders READ cylinders CONSTANT) - Q_PROPERTY(QString trip READ trip CONSTANT) - Q_PROPERTY(QString tripMeta READ tripMeta CONSTANT) - Q_PROPERTY(QString maxcns READ maxcns CONSTANT) - Q_PROPERTY(QString otu READ otu CONSTANT) - Q_PROPERTY(QString sumWeight READ sumWeight CONSTANT) - Q_PROPERTY(QString getCylinder READ getCylinder CONSTANT) - Q_PROPERTY(QString startPressure READ startPressure CONSTANT) - Q_PROPERTY(QString endPressure READ endPressure CONSTANT) - Q_PROPERTY(QString firstGas READ firstGas CONSTANT) -public: - DiveObjectHelper(struct dive *dive = NULL); - ~DiveObjectHelper(); - int number() const; - int id() const; - int rating() const; - QString date() const; - timestamp_t timestamp() const; - QString time() const; - QString location() const; - QString gps() const; - QString duration() const; - bool noDive() const; - QString depth() const; - QString divemaster() const; - QString buddy() const; - QString airTemp() const; - QString waterTemp() const; - QString notes() const; - QString tags() const; - QString gas() const; - QString sac() const; - QString weightList() const; - QStringList weights() const; - QString weight(int idx) const; - bool singleWeight() const; - QString suit() const; - QString cylinderList() const; - QStringList cylinders() const; - QString cylinder(int idx) const; - QString trip() const; - QString tripMeta() const; - QString maxcns() const; - QString otu() const; - QString sumWeight() const; - QString getCylinder() const; - QString startPressure() const; - QString endPressure() const; - QString firstGas() const; - -private: - struct dive *m_dive; -}; - Q_DECLARE_METATYPE(DiveObjectHelper *) - -#endif diff --git a/subsurface-core/subsurface-qt/SettingsObjectWrapper.cpp b/subsurface-core/subsurface-qt/SettingsObjectWrapper.cpp deleted file mode 100644 index e43be1a9b..000000000 --- a/subsurface-core/subsurface-qt/SettingsObjectWrapper.cpp +++ /dev/null @@ -1,1617 +0,0 @@ -#include "SettingsObjectWrapper.h" -#include -#include -#include - -#include "../dive.h" // TODO: remove copy_string from dive.h - - -static QString tecDetails = QStringLiteral("TecDetails"); - -PartialPressureGasSettings::PartialPressureGasSettings(QObject* parent): - QObject(parent), - group("TecDetails") -{ - -} - -short PartialPressureGasSettings::showPo2() const -{ - return prefs.pp_graphs.po2; -} - -short PartialPressureGasSettings::showPn2() const -{ - return prefs.pp_graphs.pn2; -} - -short PartialPressureGasSettings::showPhe() const -{ - return prefs.pp_graphs.phe; -} - -double PartialPressureGasSettings::po2Threshold() const -{ - return prefs.pp_graphs.po2_threshold; -} - -double PartialPressureGasSettings::pn2Threshold() const -{ - return prefs.pp_graphs.pn2_threshold; -} - -double PartialPressureGasSettings::pheThreshold() const -{ - return prefs.pp_graphs.phe_threshold; -} - -void PartialPressureGasSettings::setShowPo2(short value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("po2graph", value); - prefs.pp_graphs.po2 = value; - emit showPo2Changed(value); -} - -void PartialPressureGasSettings::setShowPn2(short value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("pn2graph", value); - prefs.pp_graphs.pn2 = value; - emit showPn2Changed(value); -} - -void PartialPressureGasSettings::setShowPhe(short value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("phegraph", value); - prefs.pp_graphs.phe = value; - emit showPheChanged(value); -} - -void PartialPressureGasSettings::setPo2Threshold(double value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("po2threshold", value); - prefs.pp_graphs.po2_threshold = value; - emit po2ThresholdChanged(value); -} - -void PartialPressureGasSettings::setPn2Threshold(double value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("pn2threshold", value); - prefs.pp_graphs.pn2_threshold = value; - emit pn2ThresholdChanged(value); -} - -void PartialPressureGasSettings::setPheThreshold(double value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("phethreshold", value); - prefs.pp_graphs.phe_threshold = value; - emit pheThresholdChanged(value); -} - - -TechnicalDetailsSettings::TechnicalDetailsSettings(QObject* parent): QObject(parent) -{ - -} - -double TechnicalDetailsSettings:: modp02() const -{ - return prefs.modpO2; -} - -bool TechnicalDetailsSettings::ead() const -{ - return prefs.ead; -} - -bool TechnicalDetailsSettings::dcceiling() const -{ - return prefs.dcceiling; -} - -bool TechnicalDetailsSettings::redceiling() const -{ - return prefs.redceiling; -} - -bool TechnicalDetailsSettings::calcceiling() const -{ - return prefs.calcceiling; -} - -bool TechnicalDetailsSettings::calcceiling3m() const -{ - return prefs.calcceiling3m; -} - -bool TechnicalDetailsSettings::calcalltissues() const -{ - return prefs.calcalltissues; -} - -bool TechnicalDetailsSettings::calcndltts() const -{ - return prefs.calcndltts; -} - -bool TechnicalDetailsSettings::gflow() const -{ - return prefs.gflow; -} - -bool TechnicalDetailsSettings::gfhigh() const -{ - return prefs.gfhigh; -} - -bool TechnicalDetailsSettings::hrgraph() const -{ - return prefs.hrgraph; -} - -bool TechnicalDetailsSettings::tankBar() const -{ - return prefs.tankbar; -} - -bool TechnicalDetailsSettings::percentageGraph() const -{ - return prefs.percentagegraph; -} - -bool TechnicalDetailsSettings::rulerGraph() const -{ - return prefs.rulergraph; -} - -bool TechnicalDetailsSettings::showCCRSetpoint() const -{ - return prefs.show_ccr_setpoint; -} - -bool TechnicalDetailsSettings::showCCRSensors() const -{ - return prefs.show_ccr_sensors; -} - -bool TechnicalDetailsSettings::zoomedPlot() const -{ - return prefs.zoomed_plot; -} - -bool TechnicalDetailsSettings::showSac() const -{ - return prefs.show_sac; -} - -bool TechnicalDetailsSettings::gfLowAtMaxDepth() const -{ - return prefs.gf_low_at_maxdepth; -} - -bool TechnicalDetailsSettings::displayUnusedTanks() const -{ - return prefs.display_unused_tanks; -} - -bool TechnicalDetailsSettings::showAverageDepth() const -{ - return prefs.show_average_depth; -} - -bool TechnicalDetailsSettings::mod() const -{ - return prefs.mod; -} - -bool TechnicalDetailsSettings::showPicturesInProfile() const -{ - return prefs.show_pictures_in_profile; -} - -void TechnicalDetailsSettings::setModp02(double value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("modpO2", value); - prefs.modpO2 = value; - emit modpO2Changed(value); -} - -void TechnicalDetailsSettings::setShowPicturesInProfile(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_pictures_in_profile", value); - prefs.show_pictures_in_profile = value; - emit showPicturesInProfileChanged(value); -} - -void TechnicalDetailsSettings::setEad(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("ead", value); - prefs.ead = value; - emit eadChanged(value); -} - -void TechnicalDetailsSettings::setMod(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("mod", value); - prefs.mod = value; - emit modChanged(value); -} - -void TechnicalDetailsSettings::setDCceiling(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("dcceiling", value); - prefs.dcceiling = value; - emit dcceilingChanged(value); -} - -void TechnicalDetailsSettings::setRedceiling(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("redceiling", value); - prefs.redceiling = value; - emit redceilingChanged(value); -} - -void TechnicalDetailsSettings::setCalcceiling(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("calcceiling", value); - prefs.calcceiling = value; - emit calcceilingChanged(value); -} - -void TechnicalDetailsSettings::setCalcceiling3m(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("calcceiling3m", value); - prefs.calcceiling3m = value; - emit calcceiling3mChanged(value); -} - -void TechnicalDetailsSettings::setCalcalltissues(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("calcalltissues", value); - prefs.calcalltissues = value; - emit calcalltissuesChanged(value); -} - -void TechnicalDetailsSettings::setCalcndltts(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("calcndltts", value); - prefs.calcndltts = value; - emit calcndlttsChanged(value); -} - -void TechnicalDetailsSettings::setGflow(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("gflow", value); - prefs.gflow = value; - set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); - emit gflowChanged(value); -} - -void TechnicalDetailsSettings::setGfhigh(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("gfhigh", value); - prefs.gfhigh = value; - set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); - emit gfhighChanged(value); -} - -void TechnicalDetailsSettings::setHRgraph(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("hrgraph", value); - prefs.hrgraph = value; - emit hrgraphChanged(value); -} - -void TechnicalDetailsSettings::setTankBar(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("tankbar", value); - prefs.tankbar = value; - emit tankBarChanged(value); -} - -void TechnicalDetailsSettings::setPercentageGraph(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("percentagegraph", value); - prefs.percentagegraph = value; - emit percentageGraphChanged(value); -} - -void TechnicalDetailsSettings::setRulerGraph(bool value) -{ - /* TODO: search for the QSettings of the RulerBar */ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("RulerBar", value); - prefs.rulergraph = value; - emit rulerGraphChanged(value); -} - -void TechnicalDetailsSettings::setShowCCRSetpoint(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_ccr_setpoint", value); - prefs.show_ccr_setpoint = value; - emit showCCRSetpointChanged(value); -} - -void TechnicalDetailsSettings::setShowCCRSensors(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_ccr_sensors", value); - prefs.show_ccr_sensors = value; - emit showCCRSensorsChanged(value); -} - -void TechnicalDetailsSettings::setZoomedPlot(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("zoomed_plot", value); - prefs.zoomed_plot = value; - emit zoomedPlotChanged(value); -} - -void TechnicalDetailsSettings::setShowSac(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_sac", value); - prefs.show_sac = value; - emit showSacChanged(value); -} - -void TechnicalDetailsSettings::setGfLowAtMaxDepth(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("gf_low_at_maxdepth", value); - prefs.gf_low_at_maxdepth = value; - set_gf(prefs.gflow, prefs.gfhigh, prefs.gf_low_at_maxdepth); - emit gfLowAtMaxDepthChanged(value); -} - -void TechnicalDetailsSettings::setDisplayUnusedTanks(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("display_unused_tanks", value); - prefs.display_unused_tanks = value; - emit displayUnusedTanksChanged(value); -} - -void TechnicalDetailsSettings::setShowAverageDepth(bool value) -{ - QSettings s; - s.beginGroup(tecDetails); - s.setValue("show_average_depth", value); - prefs.show_average_depth = value; - emit showAverageDepthChanged(value); -} - - - -FacebookSettings::FacebookSettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("WebApps")), - subgroup(QStringLiteral("Facebook")) -{ -} - -QString FacebookSettings::accessToken() const -{ - return QString(prefs.facebook.access_token); -} - -QString FacebookSettings::userId() const -{ - return QString(prefs.facebook.user_id); -} - -QString FacebookSettings::albumId() const -{ - return QString(prefs.facebook.album_id); -} - -void FacebookSettings::setAccessToken (const QString& value) -{ -#if SAVE_FB_CREDENTIALS - QSettings s; - s.beginGroup(group); - s.beginGroup(subgroup); - s.setValue("ConnectToken", value); -#endif - prefs.facebook.access_token = copy_string(qPrintable(value)); - emit accessTokenChanged(value); -} - -void FacebookSettings::setUserId(const QString& value) -{ -#if SAVE_FB_CREDENTIALS - QSettings s; - s.beginGroup(group); - s.beginGroup(subgroup); - s.setValue("UserId", value); -#endif - prefs.facebook.user_id = copy_string(qPrintable(value)); - emit userIdChanged(value); -} - -void FacebookSettings::setAlbumId(const QString& value) -{ -#if SAVE_FB_CREDENTIALS - QSettings s; - s.beginGroup(group); - s.beginGroup(subgroup); - s.setValue("AlbumId", value); -#endif - prefs.facebook.album_id = copy_string(qPrintable(value)); - emit albumIdChanged(value); -} - - -GeocodingPreferences::GeocodingPreferences(QObject *parent) : - QObject(parent), - group(QStringLiteral("geocoding")) -{ - -} - -bool GeocodingPreferences::enableGeocoding() const -{ - return prefs.geocoding.enable_geocoding; -} - -bool GeocodingPreferences::parseDiveWithoutGps() const -{ - return prefs.geocoding.parse_dive_without_gps; -} - -bool GeocodingPreferences::tagExistingDives() const -{ - return prefs.geocoding.tag_existing_dives; -} - -taxonomy_category GeocodingPreferences::firstTaxonomyCategory() const -{ - return prefs.geocoding.category[0]; -} - -taxonomy_category GeocodingPreferences::secondTaxonomyCategory() const -{ - return prefs.geocoding.category[1]; -} - -taxonomy_category GeocodingPreferences::thirdTaxonomyCategory() const -{ - return prefs.geocoding.category[2]; -} - -void GeocodingPreferences::setEnableGeocoding(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("enable_geocoding", value); - prefs.geocoding.enable_geocoding = value; - emit enableGeocodingChanged(value); -} - -void GeocodingPreferences::setParseDiveWithoutGps(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("parse_dives_without_gps", value); - prefs.geocoding.parse_dive_without_gps = value; - emit parseDiveWithoutGpsChanged(value); -} - -void GeocodingPreferences::setTagExistingDives(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("tag_existing_dives", value); - prefs.geocoding.tag_existing_dives = value; - emit tagExistingDivesChanged(value); -} - -void GeocodingPreferences::setFirstTaxonomyCategory(taxonomy_category value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cat0", value); - prefs.show_average_depth = value; - emit firstTaxonomyCategoryChanged(value); -} - -void GeocodingPreferences::setSecondTaxonomyCategory(taxonomy_category value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cat1", value); - prefs.show_average_depth = value; - emit secondTaxonomyCategoryChanged(value); -} - -void GeocodingPreferences::setThirdTaxonomyCategory(taxonomy_category value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cat2", value); - prefs.show_average_depth = value; - emit thirdTaxonomyCategoryChanged(value); -} - -ProxySettings::ProxySettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("Network")) -{ -} - -int ProxySettings::type() const -{ - return prefs.proxy_type; -} - -QString ProxySettings::host() const -{ - return prefs.proxy_host; -} - -int ProxySettings::port() const -{ - return prefs.proxy_port; -} - -short ProxySettings::auth() const -{ - return prefs.proxy_auth; -} - -QString ProxySettings::user() const -{ - return prefs.proxy_user; -} - -QString ProxySettings::pass() const -{ - return prefs.proxy_pass; -} - -void ProxySettings::setType(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_type", value); - prefs.proxy_type = value; - emit typeChanged(value); -} - -void ProxySettings::setHost(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_host", value); - free(prefs.proxy_host); - prefs.proxy_host = copy_string(qPrintable(value));; - emit hostChanged(value); -} - -void ProxySettings::setPort(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_port", value); - prefs.proxy_port = value; - emit portChanged(value); -} - -void ProxySettings::setAuth(short value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_auth", value); - prefs.proxy_auth = value; - emit authChanged(value); -} - -void ProxySettings::setUser(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_user", value); - free(prefs.proxy_user); - prefs.proxy_user = copy_string(qPrintable(value)); - emit userChanged(value); -} - -void ProxySettings::setPass(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("proxy_pass", value); - free(prefs.proxy_pass); - prefs.proxy_pass = copy_string(qPrintable(value)); - emit passChanged(value); -} - -CloudStorageSettings::CloudStorageSettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("CloudStorage")) -{ - -} - -bool CloudStorageSettings::gitLocalOnly() const -{ - return prefs.git_local_only; -} - -QString CloudStorageSettings::password() const -{ - return QString(prefs.cloud_storage_password); -} - -QString CloudStorageSettings::newPassword() const -{ - return QString(prefs.cloud_storage_newpassword); -} - -QString CloudStorageSettings::email() const -{ - return QString(prefs.cloud_storage_email); -} - -QString CloudStorageSettings::emailEncoded() const -{ - return QString(prefs.cloud_storage_email_encoded); -} - -bool CloudStorageSettings::savePasswordLocal() const -{ - return prefs.save_password_local; -} - -short CloudStorageSettings::verificationStatus() const -{ - return prefs.cloud_verification_status; -} - -bool CloudStorageSettings::backgroundSync() const -{ - return prefs.cloud_background_sync; -} - -QString CloudStorageSettings::userId() const -{ - return QString(prefs.userid); -} - -QString CloudStorageSettings::baseUrl() const -{ - return QString(prefs.cloud_base_url); -} - -QString CloudStorageSettings::gitUrl() const -{ - return QString(prefs.cloud_git_url); -} - -void CloudStorageSettings::setPassword(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("password", value); - free(prefs.proxy_pass); - prefs.proxy_pass = copy_string(qPrintable(value)); - emit passwordChanged(value); -} - -void CloudStorageSettings::setNewPassword(const QString& value) -{ - /*TODO: This looks like wrong, but 'new password' is not saved on disk, why it's on prefs? */ - free(prefs.cloud_storage_newpassword); - prefs.cloud_storage_newpassword = copy_string(qPrintable(value)); - emit newPasswordChanged(value); -} - -void CloudStorageSettings::setEmail(const QString& value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("email", value); - free(prefs.cloud_storage_email); - prefs.cloud_storage_email = copy_string(qPrintable(value)); - emit emailChanged(value); -} - -void CloudStorageSettings::setUserId(const QString& value) -{ - //WARNING: UserId is stored outside of any group, but it belongs to Cloud Storage. - QSettings s; - s.setValue("subsurface_webservice_uid", value); - free(prefs.userid); - prefs.userid = copy_string(qPrintable(value)); - emit userIdChanged(value); -} - -void CloudStorageSettings::setEmailEncoded(const QString& value) -{ - /*TODO: This looks like wrong, but 'email encoded' is not saved on disk, why it's on prefs? */ - free(prefs.cloud_storage_email_encoded); - prefs.cloud_storage_email_encoded = copy_string(qPrintable(value)); - emit emailEncodedChanged(value); -} - -void CloudStorageSettings::setSavePasswordLocal(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("save_password_local", value); - prefs.save_password_local = value; - emit savePasswordLocalChanged(value); -} - -void CloudStorageSettings::setVerificationStatus(short value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cloud_verification_status", value); - prefs.cloud_verification_status = value; - emit verificationStatusChanged(value); -} - -void CloudStorageSettings::setBackgroundSync(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("cloud_background_sync", value); - prefs.cloud_background_sync = value; - emit backgroundSyncChanged(value); -} - -void CloudStorageSettings::setBaseUrl(const QString& value) -{ - free((void*)prefs.cloud_base_url); - free((void*)prefs.cloud_git_url); - prefs.cloud_base_url = copy_string(qPrintable(value)); - prefs.cloud_git_url = copy_string(qPrintable(QString(prefs.cloud_base_url) + "/git")); -} - -void CloudStorageSettings::setGitUrl(const QString& value) -{ - Q_UNUSED(value); /* no op */ -} - -void CloudStorageSettings::setGitLocalOnly(bool value) -{ - prefs.git_local_only = value; -} - -DivePlannerSettings::DivePlannerSettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("Planner")) -{ -} - -bool DivePlannerSettings::lastStop() const -{ - return prefs.last_stop; -} - -bool DivePlannerSettings::verbatimPlan() const -{ - return prefs.verbatim_plan; -} - -bool DivePlannerSettings::displayRuntime() const -{ - return prefs.display_runtime; -} - -bool DivePlannerSettings::displayDuration() const -{ - return prefs.display_duration; -} - -bool DivePlannerSettings::displayTransitions() const -{ - return prefs.display_transitions; -} - -bool DivePlannerSettings::doo2breaks() const -{ - return prefs.doo2breaks; -} - -bool DivePlannerSettings::dropStoneMode() const -{ - return prefs.drop_stone_mode; -} - -bool DivePlannerSettings::safetyStop() const -{ - return prefs.safetystop; -} - -bool DivePlannerSettings::switchAtRequiredStop() const -{ - return prefs.switch_at_req_stop; -} - -int DivePlannerSettings::ascrate75() const -{ - return prefs.ascrate75; -} - -int DivePlannerSettings::ascrate50() const -{ - return prefs.ascrate50; -} - -int DivePlannerSettings::ascratestops() const -{ - return prefs.ascratestops; -} - -int DivePlannerSettings::ascratelast6m() const -{ - return prefs.ascratelast6m; -} - -int DivePlannerSettings::descrate() const -{ - return prefs.descrate; -} - -int DivePlannerSettings::bottompo2() const -{ - return prefs.bottompo2; -} - -int DivePlannerSettings::decopo2() const -{ - return prefs.decopo2; -} - -int DivePlannerSettings::reserveGas() const -{ - return prefs.reserve_gas; -} - -int DivePlannerSettings::minSwitchDuration() const -{ - return prefs.min_switch_duration; -} - -int DivePlannerSettings::bottomSac() const -{ - return prefs.bottomsac; -} - -int DivePlannerSettings::decoSac() const -{ - return prefs.decosac; -} - -short DivePlannerSettings::conservatismLevel() const -{ - return prefs.conservatism_level; -} - -deco_mode DivePlannerSettings::decoMode() const -{ - return prefs.deco_mode; -} - -void DivePlannerSettings::setLastStop(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("last_stop", value); - prefs.last_stop = value; - emit lastStopChanged(value); -} - -void DivePlannerSettings::setVerbatimPlan(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("verbatim_plan", value); - prefs.verbatim_plan = value; - emit verbatimPlanChanged(value); -} - -void DivePlannerSettings::setDisplayRuntime(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("display_runtime", value); - prefs.display_runtime = value; - emit displayRuntimeChanged(value); -} - -void DivePlannerSettings::setDisplayDuration(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("display_duration", value); - prefs.display_duration = value; - emit displayDurationChanged(value); -} - -void DivePlannerSettings::setDisplayTransitions(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("display_transitions", value); - prefs.display_transitions = value; - emit displayTransitionsChanged(value); -} - -void DivePlannerSettings::setDoo2breaks(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("doo2breaks", value); - prefs.doo2breaks = value; - emit doo2breaksChanged(value); -} - -void DivePlannerSettings::setDropStoneMode(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("drop_stone_mode", value); - prefs.drop_stone_mode = value; - emit dropStoneModeChanged(value); -} - -void DivePlannerSettings::setSafetyStop(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("safetystop", value); - prefs.safetystop = value; - emit safetyStopChanged(value); -} - -void DivePlannerSettings::setSwitchAtRequiredStop(bool value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("switch_at_req_stop", value); - prefs.switch_at_req_stop = value; - emit switchAtRequiredStopChanged(value); -} - -void DivePlannerSettings::setAscrate75(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("ascrate75", value); - prefs.ascrate75 = value; - emit ascrate75Changed(value); -} - -void DivePlannerSettings::setAscrate50(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("ascrate50", value); - prefs.ascrate50 = value; - emit ascrate50Changed(value); -} - -void DivePlannerSettings::setAscratestops(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("ascratestops", value); - prefs.ascratestops = value; - emit ascratestopsChanged(value); -} - -void DivePlannerSettings::setAscratelast6m(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("ascratelast6m", value); - prefs.ascratelast6m = value; - emit ascratelast6mChanged(value); -} - -void DivePlannerSettings::setDescrate(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("descrate", value); - prefs.descrate = value; - emit descrateChanged(value); -} - -void DivePlannerSettings::setBottompo2(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("bottompo2", value); - prefs.bottompo2 = value; - emit bottompo2Changed(value); -} - -void DivePlannerSettings::setDecopo2(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("decopo2", value); - prefs.decopo2 = value; - emit decopo2Changed(value); -} - -void DivePlannerSettings::setReserveGas(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("reserve_gas", value); - prefs.reserve_gas = value; - emit reserveGasChanged(value); -} - -void DivePlannerSettings::setMinSwitchDuration(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("min_switch_duration", value); - prefs.min_switch_duration = value; - emit minSwitchDurationChanged(value); -} - -void DivePlannerSettings::setBottomSac(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("bottomsac", value); - prefs.bottomsac = value; - emit bottomSacChanged(value); -} - -void DivePlannerSettings::setSecoSac(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("decosac", value); - prefs.decosac = value; - emit decoSacChanged(value); -} - -void DivePlannerSettings::setConservatismLevel(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("conservatism", value); - prefs.conservatism_level = value; - emit conservatismLevelChanged(value); -} - -void DivePlannerSettings::setDecoMode(deco_mode value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("deco_mode", value); - prefs.deco_mode = value; - emit decoModeChanged(value); -} - -UnitsSettings::UnitsSettings(QObject *parent) : - QObject(parent), - group(QStringLiteral("Units")) -{ - -} - -int UnitsSettings::length() const -{ - return prefs.units.length; -} - -int UnitsSettings::pressure() const -{ - return prefs.units.pressure; -} - -int UnitsSettings::volume() const -{ - return prefs.units.volume; -} - -int UnitsSettings::temperature() const -{ - return prefs.units.temperature; -} - -int UnitsSettings::weight() const -{ - return prefs.units.weight; -} - -int UnitsSettings::verticalSpeedTime() const -{ - return prefs.units.vertical_speed_time; -} - -QString UnitsSettings::unitSystem() const -{ - return QString(); /*FIXME: there's no char * units on the prefs. */ -} - -bool UnitsSettings::coordinatesTraditional() const -{ - return prefs.coordinates_traditional; -} - -void UnitsSettings::setLength(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("length", value); - prefs.units.length = (units::LENGHT) value; - emit lengthChanged(value); -} - -void UnitsSettings::setPressure(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("pressure", value); - prefs.units.pressure = (units::PRESSURE) value; - emit pressureChanged(value); -} - -void UnitsSettings::setVolume(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("volume", value); - prefs.units.volume = (units::VOLUME) value; - emit volumeChanged(value); -} - -void UnitsSettings::setTemperature(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("temperature", value); - prefs.units.temperature = (units::TEMPERATURE) value; - emit temperatureChanged(value); -} - -void UnitsSettings::setWeight(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("weight", value); - prefs.units.weight = (units::WEIGHT) value; - emit weightChanged(value); -} - -void UnitsSettings::setVerticalSpeedTime(int value) -{ - QSettings s; - s.beginGroup(group); - s.setValue("vertical_speed_time", value); - prefs.units.vertical_speed_time = (units::TIME) value; - emit verticalSpeedTimeChanged(value); -} - -void UnitsSettings::setCoordinatesTraditional(bool value) -{ - QSettings s; - s.setValue("coordinates", value); - prefs.coordinates_traditional = value; - emit coordinatesTraditionalChanged(value); -} - -void UnitsSettings::setUnitSystem(const QString& value) -{ - QSettings s; - s.setValue("unit_system", value); - - if (value == QStringLiteral("metric")) { - prefs.unit_system = METRIC; - prefs.units = SI_units; - } else if (value == QStringLiteral("imperial")) { - prefs.unit_system = IMPERIAL; - prefs.units = IMPERIAL_units; - } else { - prefs.unit_system = PERSONALIZE; - } - - emit unitSystemChanged(value); - // TODO: emit the other values here? -} - -GeneralSettingsObjectWrapper::GeneralSettingsObjectWrapper(QObject *parent) : - QObject(parent), - group(QStringLiteral("GeneralSettings")) -{ -} - -QString GeneralSettingsObjectWrapper::defaultFilename() const -{ - return prefs.default_filename; -} - -QString GeneralSettingsObjectWrapper::defaultCylinder() const -{ - return prefs.default_cylinder; -} - -short GeneralSettingsObjectWrapper::defaultFileBehavior() const -{ - return prefs.default_file_behavior; -} - -bool GeneralSettingsObjectWrapper::useDefaultFile() const -{ - return prefs.use_default_file; -} - -int GeneralSettingsObjectWrapper::defaultSetPoint() const -{ - return prefs.defaultsetpoint; -} - -int GeneralSettingsObjectWrapper::o2Consumption() const -{ - return prefs.o2consumption; -} - -int GeneralSettingsObjectWrapper::pscrRatio() const -{ - return prefs.pscr_ratio; -} - -void GeneralSettingsObjectWrapper::setDefaultFilename(const QString& value) -{ - QSettings s; - s.setValue("default_filename", value); - prefs.default_filename = copy_string(qPrintable(value)); - emit defaultFilenameChanged(value); -} - -void GeneralSettingsObjectWrapper::setDefaultCylinder(const QString& value) -{ - QSettings s; - s.setValue("default_cylinder", value); - prefs.default_cylinder = copy_string(qPrintable(value)); - emit defaultCylinderChanged(value); -} - -void GeneralSettingsObjectWrapper::setDefaultFileBehavior(short value) -{ - QSettings s; - s.setValue("default_file_behavior", value); - prefs.default_file_behavior = value; - if (prefs.default_file_behavior == UNDEFINED_DEFAULT_FILE) { - // undefined, so check if there's a filename set and - // use that, otherwise go with no default file - if (QString(prefs.default_filename).isEmpty()) - prefs.default_file_behavior = NO_DEFAULT_FILE; - else - prefs.default_file_behavior = LOCAL_DEFAULT_FILE; - } - emit defaultFileBehaviorChanged(value); -} - -void GeneralSettingsObjectWrapper::setUseDefaultFile(bool value) -{ - QSettings s; - s.setValue("use_default_file", value); - prefs.use_default_file = value; - emit useDefaultFileChanged(value); -} - -void GeneralSettingsObjectWrapper::setDefaultSetPoint(int value) -{ - QSettings s; - s.setValue("defaultsetpoint", value); - prefs.defaultsetpoint = value; - emit defaultSetPointChanged(value); -} - -void GeneralSettingsObjectWrapper::setO2Consumption(int value) -{ - QSettings s; - s.setValue("o2consumption", value); - prefs.o2consumption = value; - emit o2ConsumptionChanged(value); -} - -void GeneralSettingsObjectWrapper::setPscrRatio(int value) -{ - QSettings s; - s.setValue("pscr_ratio", value); - prefs.pscr_ratio = value; - emit pscrRatioChanged(value); -} - -DisplaySettingsObjectWrapper::DisplaySettingsObjectWrapper(QObject *parent) : - QObject(parent), - group(QStringLiteral("Display")) -{ -} - -QString DisplaySettingsObjectWrapper::divelistFont() const -{ - return prefs.divelist_font; -} - -double DisplaySettingsObjectWrapper::fontSize() const -{ - return prefs.font_size; -} - -short DisplaySettingsObjectWrapper::displayInvalidDives() const -{ - return prefs.display_invalid_dives; -} - -void DisplaySettingsObjectWrapper::setDivelistFont(const QString& value) -{ - QSettings s; - s.setValue("divelist_font", value); - QString newValue = value; - if (value.contains(",")) - newValue = value.left(value.indexOf(",")); - - if (!subsurface_ignore_font(newValue.toUtf8().constData())) { - free((void *)prefs.divelist_font); - prefs.divelist_font = strdup(newValue.toUtf8().constData()); - qApp->setFont(QFont(newValue)); - } - emit divelistFontChanged(newValue); -} - -void DisplaySettingsObjectWrapper::setFontSize(double value) -{ - QSettings s; - s.setValue("font_size", value); - prefs.font_size = value; - QFont defaultFont = qApp->font(); - defaultFont.setPointSizeF(prefs.font_size); - qApp->setFont(defaultFont); - emit fontSizeChanged(value); -} - -void DisplaySettingsObjectWrapper::setDisplayInvalidDives(short value) -{ - QSettings s; - s.setValue("displayinvalid", value); - prefs.display_invalid_dives = value; - emit displayInvalidDivesChanged(value); -} - -LanguageSettingsObjectWrapper::LanguageSettingsObjectWrapper(QObject *parent) : - QObject(parent), - group("Language") -{ -} - -QString LanguageSettingsObjectWrapper::language() const -{ - return prefs.locale.language; -} - -QString LanguageSettingsObjectWrapper::timeFormat() const -{ - return prefs.time_format; -} - -QString LanguageSettingsObjectWrapper::dateFormat() const -{ - return prefs.date_format; -} - -QString LanguageSettingsObjectWrapper::dateFormatShort() const -{ - return prefs.date_format_short; -} - -bool LanguageSettingsObjectWrapper::timeFormatOverride() const -{ - return prefs.time_format_override; -} - -bool LanguageSettingsObjectWrapper::dateFormatOverride() const -{ - return prefs.date_format_override; -} - -bool LanguageSettingsObjectWrapper::useSystemLanguage() const -{ - return prefs.locale.use_system_language; -} - -void LanguageSettingsObjectWrapper::setUseSystemLanguage(bool value) -{ - QSettings s; - s.setValue("UseSystemLanguage", value); - prefs.locale.use_system_language = copy_string(qPrintable(value)); - emit useSystemLanguageChanged(value); -} - -void LanguageSettingsObjectWrapper::setLanguage(const QString& value) -{ - QSettings s; - s.setValue("UiLanguage", value); - prefs.locale.language = copy_string(qPrintable(value)); - emit languageChanged(value); -} - -void LanguageSettingsObjectWrapper::setTimeFormat(const QString& value) -{ - QSettings s; - s.setValue("time_format", value); - prefs.time_format = copy_string(qPrintable(value));; - emit timeFormatChanged(value); -} - -void LanguageSettingsObjectWrapper::setDateFormat(const QString& value) -{ - QSettings s; - s.setValue("date_format", value); - prefs.date_format = copy_string(qPrintable(value));; - emit dateFormatChanged(value); -} - -void LanguageSettingsObjectWrapper::setDateFormatShort(const QString& value) -{ - QSettings s; - s.setValue("date_format_short", value); - prefs.date_format_short = copy_string(qPrintable(value));; - emit dateFormatShortChanged(value); -} - -void LanguageSettingsObjectWrapper::setTimeFormatOverride(bool value) -{ - QSettings s; - s.setValue("time_format_override", value); - prefs.time_format_override = value; - emit timeFormatOverrideChanged(value); -} - -void LanguageSettingsObjectWrapper::setDateFormatOverride(bool value) -{ - QSettings s; - s.setValue("date_format_override", value); - prefs.date_format_override = value; - emit dateFormatOverrideChanged(value); -} - -AnimationsSettingsObjectWrapper::AnimationsSettingsObjectWrapper(QObject* parent): - QObject(parent), - group("Animations") - -{ -} - -int AnimationsSettingsObjectWrapper::animationSpeed() const -{ - return prefs.animation_speed; -} - -void AnimationsSettingsObjectWrapper::setAnimationSpeed(int value) -{ - QSettings s; - s.setValue("animation_speed", value); - prefs.animation_speed = value; - emit animationSpeedChanged(value); -} - -LocationServiceSettingsObjectWrapper::LocationServiceSettingsObjectWrapper(QObject* parent): - QObject(parent), - group("locationService") -{ -} - -int LocationServiceSettingsObjectWrapper::distanceThreshold() const -{ - return prefs.distance_threshold; -} - -int LocationServiceSettingsObjectWrapper::timeThreshold() const -{ - return prefs.time_threshold; -} - -void LocationServiceSettingsObjectWrapper::setDistanceThreshold(int value) -{ - QSettings s; - s.setValue("distance_threshold", value); - prefs.distance_threshold = value; - emit distanceThresholdChanged(value); -} - -void LocationServiceSettingsObjectWrapper::setTimeThreshold(int value) -{ - QSettings s; - s.setValue("time_threshold", value); - prefs.time_threshold = value; - emit timeThresholdChanged( value); -} - -SettingsObjectWrapper::SettingsObjectWrapper(QObject* parent): -QObject(parent), - techDetails(new TechnicalDetailsSettings(this)), - pp_gas(new PartialPressureGasSettings(this)), - facebook(new FacebookSettings(this)), - geocoding(new GeocodingPreferences(this)), - proxy(new ProxySettings(this)), - cloud_storage(new CloudStorageSettings(this)), - planner_settings(new DivePlannerSettings(this)), - unit_settings(new UnitsSettings(this)), - general_settings(new GeneralSettingsObjectWrapper(this)), - display_settings(new DisplaySettingsObjectWrapper(this)), - language_settings(new LanguageSettingsObjectWrapper(this)), - animation_settings(new AnimationsSettingsObjectWrapper(this)), - location_settings(new LocationServiceSettingsObjectWrapper(this)) -{ -} - -void SettingsObjectWrapper::setSaveUserIdLocal(short int value) -{ - Q_UNUSED(value); - //TODO: Find where this is stored on the preferences. -} - -short int SettingsObjectWrapper::saveUserIdLocal() const -{ - return prefs.save_userid_local; -} - -SettingsObjectWrapper* SettingsObjectWrapper::instance() -{ - static SettingsObjectWrapper settings; - return &settings; -} diff --git a/subsurface-core/subsurface-qt/SettingsObjectWrapper.h b/subsurface-core/subsurface-qt/SettingsObjectWrapper.h deleted file mode 100644 index f115e2d86..000000000 --- a/subsurface-core/subsurface-qt/SettingsObjectWrapper.h +++ /dev/null @@ -1,642 +0,0 @@ -#ifndef SETTINGSOBJECTWRAPPER_H -#define SETTINGSOBJECTWRAPPER_H - -#include - -#include "../pref.h" -#include "../prefs-macros.h" - -/* Wrapper class for the Settings. This will allow - * seamlessy integration of the settings with the QML - * and QWidget frontends. This class will be huge, since - * I need tons of properties, one for each option. */ - -/* Control the state of the Partial Pressure Graphs preferences */ -class PartialPressureGasSettings : public QObject { - Q_OBJECT - Q_PROPERTY(short show_po2 READ showPo2 WRITE setShowPo2 NOTIFY showPo2Changed) - Q_PROPERTY(short show_pn2 READ showPn2 WRITE setShowPn2 NOTIFY showPn2Changed) - Q_PROPERTY(short show_phe READ showPhe WRITE setShowPhe NOTIFY showPheChanged) - Q_PROPERTY(double po2_threshold READ po2Threshold WRITE setPo2Threshold NOTIFY po2ThresholdChanged) - Q_PROPERTY(double pn2_threshold READ pn2Threshold WRITE setPn2Threshold NOTIFY pn2ThresholdChanged) - Q_PROPERTY(double phe_threshold READ pheThreshold WRITE setPheThreshold NOTIFY pheThresholdChanged) - -public: - PartialPressureGasSettings(QObject *parent); - short showPo2() const; - short showPn2() const; - short showPhe() const; - double po2Threshold() const; - double pn2Threshold() const; - double pheThreshold() const; - -public slots: - void setShowPo2(short value); - void setShowPn2(short value); - void setShowPhe(short value); - void setPo2Threshold(double value); - void setPn2Threshold(double value); - void setPheThreshold(double value); - -signals: - void showPo2Changed(short value); - void showPn2Changed(short value); - void showPheChanged(short value); - void po2ThresholdChanged(double value); - void pn2ThresholdChanged(double value); - void pheThresholdChanged(double value); -private: - QString group; -}; - -class TechnicalDetailsSettings : public QObject { - Q_OBJECT - Q_PROPERTY(double modpO2 READ modp02 WRITE setModp02 NOTIFY modpO2Changed) - Q_PROPERTY(bool ead READ ead WRITE setEad NOTIFY eadChanged) - Q_PROPERTY(bool mod READ mod WRITE setMod NOTIFY modChanged); - Q_PROPERTY(bool dcceiling READ dcceiling WRITE setDCceiling NOTIFY dcceilingChanged) - Q_PROPERTY(bool redceiling READ redceiling WRITE setRedceiling NOTIFY redceilingChanged) - Q_PROPERTY(bool calcceiling READ calcceiling WRITE setCalcceiling NOTIFY calcceilingChanged) - Q_PROPERTY(bool calcceiling3m READ calcceiling3m WRITE setCalcceiling3m NOTIFY calcceiling3mChanged) - Q_PROPERTY(bool calcalltissues READ calcalltissues WRITE setCalcalltissues NOTIFY calcalltissuesChanged) - Q_PROPERTY(bool calcndltts READ calcndltts WRITE setCalcndltts NOTIFY calcndlttsChanged) - Q_PROPERTY(bool gflow READ gflow WRITE setGflow NOTIFY gflowChanged) - Q_PROPERTY(bool gfhigh READ gfhigh WRITE setGfhigh NOTIFY gfhighChanged) - Q_PROPERTY(bool hrgraph READ hrgraph WRITE setHRgraph NOTIFY hrgraphChanged) - Q_PROPERTY(bool tankbar READ tankBar WRITE setTankBar NOTIFY tankBarChanged) - Q_PROPERTY(bool percentagegraph READ percentageGraph WRITE setPercentageGraph NOTIFY percentageGraphChanged) - Q_PROPERTY(bool rulergraph READ rulerGraph WRITE setRulerGraph NOTIFY rulerGraphChanged) - Q_PROPERTY(bool show_ccr_setpoint READ showCCRSetpoint WRITE setShowCCRSetpoint NOTIFY showCCRSetpointChanged) - Q_PROPERTY(bool show_ccr_sensors READ showCCRSensors WRITE setShowCCRSensors NOTIFY showCCRSensorsChanged) - Q_PROPERTY(bool zoomed_plot READ zoomedPlot WRITE setZoomedPlot NOTIFY zoomedPlotChanged) - Q_PROPERTY(bool show_sac READ showSac WRITE setShowSac NOTIFY showSacChanged) - Q_PROPERTY(bool gf_low_at_maxdepth READ gfLowAtMaxDepth WRITE setGfLowAtMaxDepth NOTIFY gfLowAtMaxDepthChanged) - Q_PROPERTY(bool display_unused_tanks READ displayUnusedTanks WRITE setDisplayUnusedTanks NOTIFY displayUnusedTanksChanged) - Q_PROPERTY(bool show_average_depth READ showAverageDepth WRITE setShowAverageDepth NOTIFY showAverageDepthChanged) - Q_PROPERTY(bool show_pictures_in_profile READ showPicturesInProfile WRITE setShowPicturesInProfile NOTIFY showPicturesInProfileChanged) -public: - TechnicalDetailsSettings(QObject *parent); - - double modp02() const; - bool ead() const; - bool mod() const; - bool dcceiling() const; - bool redceiling() const; - bool calcceiling() const; - bool calcceiling3m() const; - bool calcalltissues() const; - bool calcndltts() const; - bool gflow() const; - bool gfhigh() const; - bool hrgraph() const; - bool tankBar() const; - bool percentageGraph() const; - bool rulerGraph() const; - bool showCCRSetpoint() const; - bool showCCRSensors() const; - bool zoomedPlot() const; - bool showSac() const; - bool gfLowAtMaxDepth() const; - bool displayUnusedTanks() const; - bool showAverageDepth() const; - bool showPicturesInProfile() const; - -public slots: - void setMod(bool value); - void setModp02(double value); - void setEad(bool value); - void setDCceiling(bool value); - void setRedceiling(bool value); - void setCalcceiling(bool value); - void setCalcceiling3m(bool value); - void setCalcalltissues(bool value); - void setCalcndltts(bool value); - void setGflow(bool value); - void setGfhigh(bool value); - void setHRgraph(bool value); - void setTankBar(bool value); - void setPercentageGraph(bool value); - void setRulerGraph(bool value); - void setShowCCRSetpoint(bool value); - void setShowCCRSensors(bool value); - void setZoomedPlot(bool value); - void setShowSac(bool value); - void setGfLowAtMaxDepth(bool value); - void setDisplayUnusedTanks(bool value); - void setShowAverageDepth(bool value); - void setShowPicturesInProfile(bool value); - -signals: - void modpO2Changed(double value); - void eadChanged(bool value); - void modChanged(bool value); - void dcceilingChanged(bool value); - void redceilingChanged(bool value); - void calcceilingChanged(bool value); - void calcceiling3mChanged(bool value); - void calcalltissuesChanged(bool value); - void calcndlttsChanged(bool value); - void gflowChanged(bool value); - void gfhighChanged(bool value); - void hrgraphChanged(bool value); - void tankBarChanged(bool value); - void percentageGraphChanged(bool value); - void rulerGraphChanged(bool value); - void showCCRSetpointChanged(bool value); - void showCCRSensorsChanged(bool value); - void zoomedPlotChanged(bool value); - void showSacChanged(bool value); - void gfLowAtMaxDepthChanged(bool value); - void displayUnusedTanksChanged(bool value); - void showAverageDepthChanged(bool value); - void showPicturesInProfileChanged(bool value); -}; - -/* Control the state of the Facebook preferences */ -class FacebookSettings : public QObject { - Q_OBJECT - Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged) - Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged) - Q_PROPERTY(QString albumId READ albumId WRITE setAlbumId NOTIFY albumIdChanged) - -public: - FacebookSettings(QObject *parent); - QString accessToken() const; - QString userId() const; - QString albumId() const; - -public slots: - void setAccessToken (const QString& value); - void setUserId(const QString& value); - void setAlbumId(const QString& value); - -signals: - void accessTokenChanged(const QString& value); - void userIdChanged(const QString& value); - void albumIdChanged(const QString& value); -private: - QString group; - QString subgroup; -}; - -/* Control the state of the Geocoding preferences */ -class GeocodingPreferences : public QObject { - Q_OBJECT - Q_PROPERTY(bool enable_geocoding READ enableGeocoding WRITE setEnableGeocoding NOTIFY enableGeocodingChanged) - Q_PROPERTY(bool parse_dive_without_gps READ parseDiveWithoutGps WRITE setParseDiveWithoutGps NOTIFY parseDiveWithoutGpsChanged) - Q_PROPERTY(bool tag_existing_dives READ tagExistingDives WRITE setTagExistingDives NOTIFY tagExistingDivesChanged) - Q_PROPERTY(taxonomy_category first_category READ firstTaxonomyCategory WRITE setFirstTaxonomyCategory NOTIFY firstTaxonomyCategoryChanged) - Q_PROPERTY(taxonomy_category second_category READ secondTaxonomyCategory WRITE setSecondTaxonomyCategory NOTIFY secondTaxonomyCategoryChanged) - Q_PROPERTY(taxonomy_category third_category READ thirdTaxonomyCategory WRITE setThirdTaxonomyCategory NOTIFY thirdTaxonomyCategoryChanged) -public: - GeocodingPreferences(QObject *parent); - bool enableGeocoding() const; - bool parseDiveWithoutGps() const; - bool tagExistingDives() const; - taxonomy_category firstTaxonomyCategory() const; - taxonomy_category secondTaxonomyCategory() const; - taxonomy_category thirdTaxonomyCategory() const; - -public slots: - void setEnableGeocoding(bool value); - void setParseDiveWithoutGps(bool value); - void setTagExistingDives(bool value); - void setFirstTaxonomyCategory(taxonomy_category value); - void setSecondTaxonomyCategory(taxonomy_category value); - void setThirdTaxonomyCategory(taxonomy_category value); - -signals: - void enableGeocodingChanged(bool value); - void parseDiveWithoutGpsChanged(bool value); - void tagExistingDivesChanged(bool value); - void firstTaxonomyCategoryChanged(taxonomy_category value); - void secondTaxonomyCategoryChanged(taxonomy_category value); - void thirdTaxonomyCategoryChanged(taxonomy_category value); -private: - QString group; -}; - -class ProxySettings : public QObject { - Q_OBJECT - Q_PROPERTY(int type READ type WRITE setType NOTIFY typeChanged) - Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged) - Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged) - Q_PROPERTY(short auth READ auth WRITE setAuth NOTIFY authChanged) - Q_PROPERTY(QString user READ user WRITE setUser NOTIFY userChanged) - Q_PROPERTY(QString pass READ pass WRITE setPass NOTIFY passChanged) - -public: - ProxySettings(QObject *parent); - int type() const; - QString host() const; - int port() const; - short auth() const; - QString user() const; - QString pass() const; - -public slots: - void setType(int value); - void setHost(const QString& value); - void setPort(int value); - void setAuth(short value); - void setUser(const QString& value); - void setPass(const QString& value); - -signals: - void typeChanged(int value); - void hostChanged(const QString& value); - void portChanged(int value); - void authChanged(short value); - void userChanged(const QString& value); - void passChanged(const QString& value); -private: - QString group; -}; - -class CloudStorageSettings : public QObject { - Q_OBJECT - Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) - Q_PROPERTY(QString newpassword READ newPassword WRITE setNewPassword NOTIFY newPasswordChanged) - Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged) - Q_PROPERTY(QString email_encoded READ emailEncoded WRITE setEmailEncoded NOTIFY emailEncodedChanged) - Q_PROPERTY(QString userid READ userId WRITE setUserId NOTIFY userIdChanged) - Q_PROPERTY(QString base_url READ baseUrl WRITE setBaseUrl NOTIFY baseUrlChanged) - Q_PROPERTY(QString git_url READ gitUrl WRITE setGitUrl NOTIFY gitUrlChanged) - Q_PROPERTY(bool git_local_only READ gitLocalOnly WRITE setGitLocalOnly NOTIFY gitLocalOnlyChanged) - Q_PROPERTY(bool save_password_local READ savePasswordLocal WRITE setSavePasswordLocal NOTIFY savePasswordLocalChanged) - Q_PROPERTY(short verification_status READ verificationStatus WRITE setVerificationStatus NOTIFY verificationStatusChanged) - Q_PROPERTY(bool background_sync READ backgroundSync WRITE setBackgroundSync NOTIFY backgroundSyncChanged) -public: - CloudStorageSettings(QObject *parent); - QString password() const; - QString newPassword() const; - QString email() const; - QString emailEncoded() const; - QString userId() const; - QString baseUrl() const; - QString gitUrl() const; - bool savePasswordLocal() const; - short verificationStatus() const; - bool backgroundSync() const; - bool gitLocalOnly() const; - -public slots: - void setPassword(const QString& value); - void setNewPassword(const QString& value); - void setEmail(const QString& value); - void setEmailEncoded(const QString& value); - void setUserId(const QString& value); - void setBaseUrl(const QString& value); - void setGitUrl(const QString& value); - void setSavePasswordLocal(bool value); - void setVerificationStatus(short value); - void setBackgroundSync(bool value); - void setGitLocalOnly(bool value); - -signals: - void passwordChanged(const QString& value); - void newPasswordChanged(const QString& value); - void emailChanged(const QString& value); - void emailEncodedChanged(const QString& value); - void userIdChanged(const QString& value); - void baseUrlChanged(const QString& value); - void gitUrlChanged(const QString& value); - void savePasswordLocalChanged(bool value); - void verificationStatusChanged(short value); - void backgroundSyncChanged(bool value); - void gitLocalOnlyChanged(bool value); -private: - QString group; -}; - -class DivePlannerSettings : public QObject { - Q_OBJECT - Q_PROPERTY(bool last_stop READ lastStop WRITE setLastStop NOTIFY lastStopChanged) - Q_PROPERTY(bool verbatim_plan READ verbatimPlan WRITE setVerbatimPlan NOTIFY verbatimPlanChanged) - Q_PROPERTY(bool display_runtime READ displayRuntime WRITE setDisplayRuntime NOTIFY displayRuntimeChanged) - Q_PROPERTY(bool display_duration READ displayDuration WRITE setDisplayDuration NOTIFY displayDurationChanged) - Q_PROPERTY(bool display_transitions READ displayTransitions WRITE setDisplayTransitions NOTIFY displayTransitionsChanged) - Q_PROPERTY(bool doo2breaks READ doo2breaks WRITE setDoo2breaks NOTIFY doo2breaksChanged) - Q_PROPERTY(bool drop_stone_mode READ dropStoneMode WRITE setDropStoneMode NOTIFY dropStoneModeChanged) - Q_PROPERTY(bool safetystop READ safetyStop WRITE setSafetyStop NOTIFY safetyStopChanged) - Q_PROPERTY(bool switch_at_req_stop READ switchAtRequiredStop WRITE setSwitchAtRequiredStop NOTIFY switchAtRequiredStopChanged) - Q_PROPERTY(int ascrate75 READ ascrate75 WRITE setAscrate75 NOTIFY ascrate75Changed) - Q_PROPERTY(int ascrate50 READ ascrate50 WRITE setAscrate50 NOTIFY ascrate50Changed) - Q_PROPERTY(int ascratestops READ ascratestops WRITE setAscratestops NOTIFY ascratestopsChanged) - Q_PROPERTY(int ascratelast6m READ ascratelast6m WRITE setAscratelast6m NOTIFY ascratelast6mChanged) - Q_PROPERTY(int descrate READ descrate WRITE setDescrate NOTIFY descrateChanged) - Q_PROPERTY(int bottompo2 READ bottompo2 WRITE setBottompo2 NOTIFY bottompo2Changed) - Q_PROPERTY(int decopo2 READ decopo2 WRITE setDecopo2 NOTIFY decopo2Changed) - Q_PROPERTY(int reserve_gas READ reserveGas WRITE setReserveGas NOTIFY reserveGasChanged) - Q_PROPERTY(int min_switch_duration READ minSwitchDuration WRITE setMinSwitchDuration NOTIFY minSwitchDurationChanged) - Q_PROPERTY(int bottomsac READ bottomSac WRITE setBottomSac NOTIFY bottomSacChanged) - Q_PROPERTY(int decosac READ decoSac WRITE setSecoSac NOTIFY decoSacChanged) - Q_PROPERTY(short conservatism_level READ conservatismLevel WRITE setConservatismLevel NOTIFY conservatismLevelChanged) - Q_PROPERTY(deco_mode decoMode READ decoMode WRITE setDecoMode NOTIFY decoModeChanged) - -public: - DivePlannerSettings(QObject *parent = 0); - bool lastStop() const; - bool verbatimPlan() const; - bool displayRuntime() const; - bool displayDuration() const; - bool displayTransitions() const; - bool doo2breaks() const; - bool dropStoneMode() const; - bool safetyStop() const; - bool switchAtRequiredStop() const; - int ascrate75() const; - int ascrate50() const; - int ascratestops() const; - int ascratelast6m() const; - int descrate() const; - int bottompo2() const; - int decopo2() const; - int reserveGas() const; - int minSwitchDuration() const; - int bottomSac() const; - int decoSac() const; - short conservatismLevel() const; - deco_mode decoMode() const; - -public slots: - void setLastStop(bool value); - void setVerbatimPlan(bool value); - void setDisplayRuntime(bool value); - void setDisplayDuration(bool value); - void setDisplayTransitions(bool value); - void setDoo2breaks(bool value); - void setDropStoneMode(bool value); - void setSafetyStop(bool value); - void setSwitchAtRequiredStop(bool value); - void setAscrate75(int value); - void setAscrate50(int value); - void setAscratestops(int value); - void setAscratelast6m(int value); - void setDescrate(int value); - void setBottompo2(int value); - void setDecopo2(int value); - void setReserveGas(int value); - void setMinSwitchDuration(int value); - void setBottomSac(int value); - void setSecoSac(int value); - void setConservatismLevel(int value); - void setDecoMode(deco_mode value); - -signals: - void lastStopChanged(bool value); - void verbatimPlanChanged(bool value); - void displayRuntimeChanged(bool value); - void displayDurationChanged(bool value); - void displayTransitionsChanged(bool value); - void doo2breaksChanged(bool value); - void dropStoneModeChanged(bool value); - void safetyStopChanged(bool value); - void switchAtRequiredStopChanged(bool value); - void ascrate75Changed(int value); - void ascrate50Changed(int value); - void ascratestopsChanged(int value); - void ascratelast6mChanged(int value); - void descrateChanged(int value); - void bottompo2Changed(int value); - void decopo2Changed(int value); - void reserveGasChanged(int value); - void minSwitchDurationChanged(int value); - void bottomSacChanged(int value); - void decoSacChanged(int value); - void conservatismLevelChanged(int value); - void decoModeChanged(deco_mode value); - -private: - QString group; -}; - -class UnitsSettings : public QObject { - Q_OBJECT - Q_PROPERTY(int length READ length WRITE setLength NOTIFY lengthChanged) - Q_PROPERTY(int pressure READ pressure WRITE setPressure NOTIFY pressureChanged) - Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged) - Q_PROPERTY(int temperature READ temperature WRITE setTemperature NOTIFY temperatureChanged) - Q_PROPERTY(int weight READ weight WRITE setWeight NOTIFY weightChanged) - Q_PROPERTY(QString unit_system READ unitSystem WRITE setUnitSystem NOTIFY unitSystemChanged) - Q_PROPERTY(bool coordinates_traditional READ coordinatesTraditional WRITE setCoordinatesTraditional NOTIFY coordinatesTraditionalChanged) - Q_PROPERTY(int vertical_speed_time READ verticalSpeedTime WRITE setVerticalSpeedTime NOTIFY verticalSpeedTimeChanged) - -public: - UnitsSettings(QObject *parent = 0); - int length() const; - int pressure() const; - int volume() const; - int temperature() const; - int weight() const; - int verticalSpeedTime() const; - QString unitSystem() const; - bool coordinatesTraditional() const; - -public slots: - void setLength(int value); - void setPressure(int value); - void setVolume(int value); - void setTemperature(int value); - void setWeight(int value); - void setVerticalSpeedTime(int value); - void setUnitSystem(const QString& value); - void setCoordinatesTraditional(bool value); - -signals: - void lengthChanged(int value); - void pressureChanged(int value); - void volumeChanged(int value); - void temperatureChanged(int value); - void weightChanged(int value); - void verticalSpeedTimeChanged(int value); - void unitSystemChanged(const QString& value); - void coordinatesTraditionalChanged(bool value); -private: - QString group; -}; - -class GeneralSettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(QString default_filename READ defaultFilename WRITE setDefaultFilename NOTIFY defaultFilenameChanged) - Q_PROPERTY(QString default_cylinder READ defaultCylinder WRITE setDefaultCylinder NOTIFY defaultCylinderChanged) - Q_PROPERTY(short default_file_behavior READ defaultFileBehavior WRITE setDefaultFileBehavior NOTIFY defaultFileBehaviorChanged) - Q_PROPERTY(bool use_default_file READ useDefaultFile WRITE setUseDefaultFile NOTIFY useDefaultFileChanged) - Q_PROPERTY(int defaultsetpoint READ defaultSetPoint WRITE setDefaultSetPoint NOTIFY defaultSetPointChanged) - Q_PROPERTY(int o2consumption READ o2Consumption WRITE setO2Consumption NOTIFY o2ConsumptionChanged) - Q_PROPERTY(int pscr_ratio READ pscrRatio WRITE setPscrRatio NOTIFY pscrRatioChanged) - -public: - GeneralSettingsObjectWrapper(QObject *parent); - QString defaultFilename() const; - QString defaultCylinder() const; - short defaultFileBehavior() const; - bool useDefaultFile() const; - int defaultSetPoint() const; - int o2Consumption() const; - int pscrRatio() const; - -public slots: - void setDefaultFilename (const QString& value); - void setDefaultCylinder (const QString& value); - void setDefaultFileBehavior (short value); - void setUseDefaultFile (bool value); - void setDefaultSetPoint (int value); - void setO2Consumption (int value); - void setPscrRatio (int value); - -signals: - void defaultFilenameChanged(const QString& value); - void defaultCylinderChanged(const QString& value); - void defaultFileBehaviorChanged(short value); - void useDefaultFileChanged(bool value); - void defaultSetPointChanged(int value); - void o2ConsumptionChanged(int value); - void pscrRatioChanged(int value); -private: - QString group; -}; - -class DisplaySettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(QString divelist_font READ divelistFont WRITE setDivelistFont NOTIFY divelistFontChanged) - Q_PROPERTY(double font_size READ fontSize WRITE setFontSize NOTIFY fontSizeChanged) - Q_PROPERTY(short display_invalid_dives READ displayInvalidDives WRITE setDisplayInvalidDives NOTIFY displayInvalidDivesChanged) -public: - DisplaySettingsObjectWrapper(QObject *parent); - QString divelistFont() const; - double fontSize() const; - short displayInvalidDives() const; -public slots: - void setDivelistFont(const QString& value); - void setFontSize(double value); - void setDisplayInvalidDives(short value); -signals: - void divelistFontChanged(const QString& value); - void fontSizeChanged(double value); - void displayInvalidDivesChanged(short value); -private: - QString group; -}; - -class LanguageSettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged) - Q_PROPERTY(QString time_format READ timeFormat WRITE setTimeFormat NOTIFY timeFormatChanged) - Q_PROPERTY(QString date_format READ dateFormat WRITE setDateFormat NOTIFY dateFormatChanged) - Q_PROPERTY(QString date_format_short READ dateFormatShort WRITE setDateFormatShort NOTIFY dateFormatShortChanged) - Q_PROPERTY(bool time_format_override READ timeFormatOverride WRITE setTimeFormatOverride NOTIFY timeFormatOverrideChanged) - Q_PROPERTY(bool date_format_override READ dateFormatOverride WRITE setDateFormatOverride NOTIFY dateFormatOverrideChanged) - Q_PROPERTY(bool use_system_language READ useSystemLanguage WRITE setUseSystemLanguage NOTIFY useSystemLanguageChanged) - -public: - LanguageSettingsObjectWrapper(QObject *parent); - QString language() const; - QString timeFormat() const; - QString dateFormat() const; - QString dateFormatShort() const; - bool timeFormatOverride() const; - bool dateFormatOverride() const; - bool useSystemLanguage() const; - -public slots: - void setLanguage (const QString& value); - void setTimeFormat (const QString& value); - void setDateFormat (const QString& value); - void setDateFormatShort (const QString& value); - void setTimeFormatOverride (bool value); - void setDateFormatOverride (bool value); - void setUseSystemLanguage (bool value); -signals: - void languageChanged(const QString& value); - void timeFormatChanged(const QString& value); - void dateFormatChanged(const QString& value); - void dateFormatShortChanged(const QString& value); - void timeFormatOverrideChanged(bool value); - void dateFormatOverrideChanged(bool value); - void useSystemLanguageChanged(bool value); - -private: - QString group; -}; - -class AnimationsSettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(int animation_speed READ animationSpeed WRITE setAnimationSpeed NOTIFY animationSpeedChanged) -public: - AnimationsSettingsObjectWrapper(QObject *parent); - int animationSpeed() const; - -public slots: - void setAnimationSpeed(int value); - -signals: - void animationSpeedChanged(int value); - -private: - QString group; -}; - -class LocationServiceSettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(int time_threshold READ timeThreshold WRITE setTimeThreshold NOTIFY timeThresholdChanged) - Q_PROPERTY(int distance_threshold READ distanceThreshold WRITE setDistanceThreshold NOTIFY distanceThresholdChanged) -public: - LocationServiceSettingsObjectWrapper(QObject *parent); - int timeThreshold() const; - int distanceThreshold() const; -public slots: - void setTimeThreshold(int value); - void setDistanceThreshold(int value); -signals: - void timeThresholdChanged(int value); - void distanceThresholdChanged(int value); -private: - QString group; -}; - -class SettingsObjectWrapper : public QObject { - Q_OBJECT - Q_PROPERTY(short save_userid_local READ saveUserIdLocal WRITE setSaveUserIdLocal NOTIFY saveUserIdLocalChanged) - - Q_PROPERTY(TechnicalDetailsSettings* techical_details MEMBER techDetails CONSTANT) - Q_PROPERTY(PartialPressureGasSettings* pp_gas MEMBER pp_gas CONSTANT) - Q_PROPERTY(FacebookSettings* facebook MEMBER facebook CONSTANT) - Q_PROPERTY(GeocodingPreferences* geocoding MEMBER geocoding CONSTANT) - Q_PROPERTY(ProxySettings* proxy MEMBER proxy CONSTANT) - Q_PROPERTY(CloudStorageSettings* cloud_storage MEMBER cloud_storage CONSTANT) - Q_PROPERTY(DivePlannerSettings* planner MEMBER planner_settings CONSTANT) - Q_PROPERTY(UnitsSettings* units MEMBER unit_settings CONSTANT) - - Q_PROPERTY(GeneralSettingsObjectWrapper* general MEMBER general_settings CONSTANT) - Q_PROPERTY(DisplaySettingsObjectWrapper* display MEMBER display_settings CONSTANT) - Q_PROPERTY(LanguageSettingsObjectWrapper* language MEMBER language_settings CONSTANT) - Q_PROPERTY(AnimationsSettingsObjectWrapper* animation MEMBER animation_settings CONSTANT) - Q_PROPERTY(LocationServiceSettingsObjectWrapper* Location MEMBER location_settings CONSTANT) -public: - static SettingsObjectWrapper *instance(); - short saveUserIdLocal() const; - - TechnicalDetailsSettings *techDetails; - PartialPressureGasSettings *pp_gas; - FacebookSettings *facebook; - GeocodingPreferences *geocoding; - ProxySettings *proxy; - CloudStorageSettings *cloud_storage; - DivePlannerSettings *planner_settings; - UnitsSettings *unit_settings; - GeneralSettingsObjectWrapper *general_settings; - DisplaySettingsObjectWrapper *display_settings; - LanguageSettingsObjectWrapper *language_settings; - AnimationsSettingsObjectWrapper *animation_settings; - LocationServiceSettingsObjectWrapper *location_settings; - -public slots: - void setSaveUserIdLocal(short value); -private: - SettingsObjectWrapper(QObject *parent = NULL); -signals: - void saveUserIdLocalChanged(short value); -}; - -#endif diff --git a/subsurface-core/subsurfacestartup.c b/subsurface-core/subsurfacestartup.c deleted file mode 100644 index 6e0dede1c..000000000 --- a/subsurface-core/subsurfacestartup.c +++ /dev/null @@ -1,310 +0,0 @@ -#include "subsurfacestartup.h" -#include "version.h" -#include -#include -#include "gettext.h" -#include "qthelperfromc.h" -#include "git-access.h" -#include "libdivecomputer/version.h" - -struct preferences prefs, informational_prefs; -struct preferences default_prefs = { - .cloud_base_url = "https://cloud.subsurface-divelog.org/", - .units = SI_UNITS, - .unit_system = METRIC, - .coordinates_traditional = true, - .pp_graphs = { - .po2 = false, - .pn2 = false, - .phe = false, - .po2_threshold = 1.6, - .pn2_threshold = 4.0, - .phe_threshold = 13.0, - }, - .mod = false, - .modpO2 = 1.6, - .ead = false, - .hrgraph = false, - .percentagegraph = false, - .dcceiling = true, - .redceiling = false, - .calcceiling = false, - .calcceiling3m = false, - .calcndltts = false, - .gflow = 30, - .gfhigh = 75, - .animation_speed = 500, - .gf_low_at_maxdepth = false, - .show_ccr_setpoint = false, - .show_ccr_sensors = false, - .font_size = -1, - .display_invalid_dives = false, - .show_sac = false, - .display_unused_tanks = false, - .show_average_depth = true, - .ascrate75 = 9000 / 60, - .ascrate50 = 6000 / 60, - .ascratestops = 6000 / 60, - .ascratelast6m = 1000 / 60, - .descrate = 18000 / 60, - .bottompo2 = 1400, - .decopo2 = 1600, - .doo2breaks = false, - .drop_stone_mode = false, - .switch_at_req_stop = false, - .min_switch_duration = 60, - .last_stop = false, - .verbatim_plan = false, - .display_runtime = true, - .display_duration = true, - .display_transitions = true, - .safetystop = true, - .bottomsac = 20000, - .decosac = 17000, - .reserve_gas=40000, - .o2consumption = 720, - .pscr_ratio = 100, - .show_pictures_in_profile = true, - .tankbar = false, - .facebook = { - .user_id = NULL, - .album_id = NULL, - .access_token = NULL - }, - .defaultsetpoint = 1100, - .cloud_background_sync = true, - .geocoding = { - .enable_geocoding = true, - .parse_dive_without_gps = false, - .tag_existing_dives = false, - .category = { 0 } - }, - .deco_mode = BUEHLMANN, - .conservatism_level = 3, - .distance_threshold = 1000, - .time_threshold = 600 -}; - -int run_survey; - -struct units *get_units() -{ - return &prefs.units; -} - -/* random helper functions, used here or elsewhere */ -static int sortfn(const void *_a, const void *_b) -{ - const struct dive *a = (const struct dive *)*(void **)_a; - const struct dive *b = (const struct dive *)*(void **)_b; - - if (a->when < b->when) - return -1; - if (a->when > b->when) - return 1; - return 0; -} - -void sort_table(struct dive_table *table) -{ - qsort(table->dives, table->nr, sizeof(struct dive *), sortfn); -} - -const char *weekday(int wday) -{ - static const char wday_array[7][7] = { - /*++GETTEXT: these are three letter days - we allow up to six code bytes */ - QT_TRANSLATE_NOOP("gettextFromC", "Sun"), QT_TRANSLATE_NOOP("gettextFromC", "Mon"), QT_TRANSLATE_NOOP("gettextFromC", "Tue"), QT_TRANSLATE_NOOP("gettextFromC", "Wed"), QT_TRANSLATE_NOOP("gettextFromC", "Thu"), QT_TRANSLATE_NOOP("gettextFromC", "Fri"), QT_TRANSLATE_NOOP("gettextFromC", "Sat") - }; - return translate("gettextFromC", wday_array[wday]); -} - -const char *monthname(int mon) -{ - static const char month_array[12][7] = { - /*++GETTEXT: these are three letter months - we allow up to six code bytes*/ - QT_TRANSLATE_NOOP("gettextFromC", "Jan"), QT_TRANSLATE_NOOP("gettextFromC", "Feb"), QT_TRANSLATE_NOOP("gettextFromC", "Mar"), QT_TRANSLATE_NOOP("gettextFromC", "Apr"), QT_TRANSLATE_NOOP("gettextFromC", "May"), QT_TRANSLATE_NOOP("gettextFromC", "Jun"), - QT_TRANSLATE_NOOP("gettextFromC", "Jul"), QT_TRANSLATE_NOOP("gettextFromC", "Aug"), QT_TRANSLATE_NOOP("gettextFromC", "Sep"), QT_TRANSLATE_NOOP("gettextFromC", "Oct"), QT_TRANSLATE_NOOP("gettextFromC", "Nov"), QT_TRANSLATE_NOOP("gettextFromC", "Dec"), - }; - return translate("gettextFromC", month_array[mon]); -} - -/* - * track whether we switched to importing dives - */ -bool imported = false; - -static void print_version() -{ - printf("Subsurface v%s, ", subsurface_git_version()); - printf("built with libdivecomputer v%s\n", dc_version(NULL)); -} - -void print_files() -{ - const char *branch = 0; - const char *remote = 0; - const char *filename, *local_git; - - filename = cloud_url(); - - is_git_repository(filename, &branch, &remote, true); - printf("\nFile locations:\n\n"); - if (branch && remote) { - local_git = get_local_dir(remote, branch); - printf("Local git storage: %s\n", local_git); - } else { - printf("Unable to get local git directory\n"); - } - char *tmp = cloud_url(); - printf("Cloud URL: %s\n", tmp); - free(tmp); - tmp = hashfile_name_string(); - printf("Image hashes: %s\n", tmp); - free(tmp); - tmp = picturedir_string(); - printf("Local picture directory: %s\n\n", tmp); - free(tmp); -} - -static void print_help() -{ - print_version(); - printf("\nUsage: subsurface [options] [logfile ...] [--import logfile ...]"); - printf("\n\noptions include:"); - printf("\n --help|-h This help text"); - printf("\n --import logfile ... Logs before this option is treated as base, everything after is imported"); - printf("\n --verbose|-v Verbose debug (repeat to increase verbosity)"); - printf("\n --version Prints current version"); - printf("\n --survey Offer to submit a user survey"); - printf("\n --win32console Create a dedicated console if needed (Windows only). Add option before everything else\n\n"); -} - -void parse_argument(const char *arg) -{ - const char *p = arg + 1; - - do { - switch (*p) { - case 'h': - print_help(); - exit(0); - case 'v': - verbose++; - continue; - case 'q': - quit++; - continue; - case '-': - /* long options with -- */ - if (strcmp(arg, "--help") == 0) { - print_help(); - exit(0); - } - if (strcmp(arg, "--import") == 0) { - imported = true; /* mark the dives so far as the base, * everything after is imported */ - return; - } - if (strcmp(arg, "--verbose") == 0) { - verbose++; - return; - } - if (strcmp(arg, "--version") == 0) { - print_version(); - exit(0); - } - if (strcmp(arg, "--survey") == 0) { - run_survey = true; - return; - } - if (strcmp(arg, "--allow_run_as_root") == 0) { - ++force_root; - return; - } - if (strcmp(arg, "--win32console") == 0) - return; - /* fallthrough */ - case 'p': - /* ignore process serial number argument when run as native macosx app */ - if (strncmp(arg, "-psQT_TR_NOOP(", 5) == 0) { - return; - } - /* fallthrough */ - default: - fprintf(stderr, "Bad argument '%s'\n", arg); - exit(1); - } - } while (*++p); -} - -/* - * Under a POSIX setup, the locale string should have a format - * like [language[_territory][.codeset][@modifier]]. - * - * So search for the underscore, and see if the "territory" is - * US, and turn on imperial units by default. - * - * I guess Burma and Liberia should trigger this too. I'm too - * lazy to look up the territory names, though. - */ -void setup_system_prefs(void) -{ - const char *env; - - subsurface_OS_pref_setup(); - default_prefs.divelist_font = strdup(system_divelist_default_font); - default_prefs.font_size = system_divelist_default_font_size; - default_prefs.default_filename = system_default_filename(); - - env = getenv("LC_MEASUREMENT"); - if (!env) - env = getenv("LC_ALL"); - if (!env) - env = getenv("LANG"); - if (!env) - return; - env = strchr(env, '_'); - if (!env) - return; - env++; - if (strncmp(env, "US", 2)) - return; - - default_prefs.units = IMPERIAL_units; -} - -/* copy a preferences block, including making copies of all included strings */ -void copy_prefs(struct preferences *src, struct preferences *dest) -{ - *dest = *src; - dest->divelist_font = copy_string(src->divelist_font); - dest->default_filename = copy_string(src->default_filename); - dest->default_cylinder = copy_string(src->default_cylinder); - dest->cloud_base_url = copy_string(src->cloud_base_url); - dest->cloud_git_url = copy_string(src->cloud_git_url); - dest->userid = copy_string(src->userid); - dest->proxy_host = copy_string(src->proxy_host); - dest->proxy_user = copy_string(src->proxy_user); - dest->proxy_pass = copy_string(src->proxy_pass); - dest->time_format = copy_string(src->time_format); - dest->date_format = copy_string(src->date_format); - dest->date_format_short = copy_string(src->date_format_short); - dest->cloud_storage_password = copy_string(src->cloud_storage_password); - dest->cloud_storage_newpassword = copy_string(src->cloud_storage_newpassword); - dest->cloud_storage_email = copy_string(src->cloud_storage_email); - dest->cloud_storage_email_encoded = copy_string(src->cloud_storage_email_encoded); - dest->facebook.access_token = copy_string(src->facebook.access_token); - dest->facebook.user_id = copy_string(src->facebook.user_id); - dest->facebook.album_id = copy_string(src->facebook.album_id); -} - -/* - * Free strduped prefs before exit. - * - * These are not real leaks but they plug the holes found by eg. - * valgrind so you can find the real leaks. - */ -void free_prefs(void) -{ - // nop -} diff --git a/subsurface-core/subsurfacestartup.h b/subsurface-core/subsurfacestartup.h deleted file mode 100644 index 3ccc24aa4..000000000 --- a/subsurface-core/subsurfacestartup.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef SUBSURFACESTARTUP_H -#define SUBSURFACESTARTUP_H - -#include "dive.h" -#include "divelist.h" -#include "libdivecomputer.h" - -#ifdef __cplusplus -extern "C" { -#else -#include -#endif - -extern bool imported; - -void setup_system_prefs(void); -void parse_argument(const char *arg); -void free_prefs(void); -void copy_prefs(struct preferences *src, struct preferences *dest); -void print_files(void); - -#ifdef __cplusplus -} -#endif - -#endif // SUBSURFACESTARTUP_H diff --git a/subsurface-core/subsurfacesysinfo.cpp b/subsurface-core/subsurfacesysinfo.cpp deleted file mode 100644 index a7173b169..000000000 --- a/subsurface-core/subsurfacesysinfo.cpp +++ /dev/null @@ -1,620 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Copyright (C) 2014 Intel Corporation -** Contact: http://www.qt-project.org/legal -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "subsurfacesysinfo.h" -#include - -#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 deleted file mode 100644 index b2c267b83..000000000 --- a/subsurface-core/subsurfacesysinfo.h +++ /dev/null @@ -1,65 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Copyright (C) 2014 Intel Corporation -** Contact: http://www.qt-project.org/legal -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef SUBSURFACESYSINFO_H -#define SUBSURFACESYSINFO_H - -#include - -class SubsurfaceSysInfo : public QSysInfo { -public: -#if QT_VERSION < 0x050400 - static QString buildCpuArchitecture(); - static QString currentCpuArchitecture(); - static QString buildAbi(); - - static QString kernelType(); - static QString kernelVersion(); - static QString productType(); - static QString productVersion(); - static QString prettyProductName(); -#endif - static QString prettyOsName(); -}; - - -#endif // SUBSURFACESYSINFO_H diff --git a/subsurface-core/taxonomy.c b/subsurface-core/taxonomy.c deleted file mode 100644 index 670d85ad0..000000000 --- a/subsurface-core/taxonomy.c +++ /dev/null @@ -1,48 +0,0 @@ -#include "taxonomy.h" -#include "gettext.h" -#include - -char *taxonomy_category_names[TC_NR_CATEGORIES] = { - QT_TRANSLATE_NOOP("getTextFromC", "None"), - QT_TRANSLATE_NOOP("getTextFromC", "Ocean"), - QT_TRANSLATE_NOOP("getTextFromC", "Country"), - QT_TRANSLATE_NOOP("getTextFromC", "State"), - QT_TRANSLATE_NOOP("getTextFromC", "County"), - QT_TRANSLATE_NOOP("getTextFromC", "Town"), - QT_TRANSLATE_NOOP("getTextFromC", "City") -}; - -// these are the names for geoname.org -char *taxonomy_api_names[TC_NR_CATEGORIES] = { - "none", - "name", - "countryName", - "adminName1", - "adminName2", - "toponymName", - "adminName3" -}; - -struct taxonomy *alloc_taxonomy() -{ - return calloc(TC_NR_CATEGORIES, sizeof(struct taxonomy)); -} - -void free_taxonomy(struct taxonomy_data *t) -{ - if (t) { - for (int i = 0; i < t->nr; i++) - free((void *)t->category[i].value); - free(t->category); - t->category = NULL; - t->nr = 0; - } -} - -int taxonomy_index_for_category(struct taxonomy_data *t, enum taxonomy_category cat) -{ - for (int i = 0; i < t->nr; i++) - if (t->category[i].category == cat) - return i; - return -1; -} diff --git a/subsurface-core/taxonomy.h b/subsurface-core/taxonomy.h deleted file mode 100644 index 51245d562..000000000 --- a/subsurface-core/taxonomy.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef TAXONOMY_H -#define TAXONOMY_H - -#ifdef __cplusplus -extern "C" { -#endif - -enum taxonomy_category { - TC_NONE, - TC_OCEAN, - TC_COUNTRY, - TC_ADMIN_L1, - TC_ADMIN_L2, - TC_LOCALNAME, - TC_ADMIN_L3, - TC_NR_CATEGORIES -}; - -extern char *taxonomy_category_names[TC_NR_CATEGORIES]; -extern char *taxonomy_api_names[TC_NR_CATEGORIES]; - -struct taxonomy { - int category; /* the category for this tag: ocean, country, admin_l1, admin_l2, localname, etc */ - const char *value; /* the value returned, parsed, or manually entered for that category */ - enum { GEOCODED, PARSED, MANUAL, COPIED } origin; -}; - -/* the data block contains 3 taxonomy structures - unused ones have a tag value of NONE */ -struct taxonomy_data { - int nr; - struct taxonomy *category; -}; - -struct taxonomy *alloc_taxonomy(); -void free_taxonomy(struct taxonomy_data *t); -int taxonomy_index_for_category(struct taxonomy_data *t, enum taxonomy_category cat); - -#ifdef __cplusplus -} -#endif -#endif // TAXONOMY_H diff --git a/subsurface-core/time.c b/subsurface-core/time.c deleted file mode 100644 index 0893f19d8..000000000 --- a/subsurface-core/time.c +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include "dive.h" - -/* - * Convert 64-bit timestamp to 'struct tm' in UTC. - * - * On 32-bit machines, only do 64-bit arithmetic for the seconds - * part, after that we do everything in 'long'. 64-bit divides - * are unnecessary once you're counting minutes (32-bit minutes: - * 8000+ years). - */ -void utc_mkdate(timestamp_t timestamp, struct tm *tm) -{ - static const unsigned int mdays[] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, - }; - static const unsigned int mdays_leap[] = { - 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, - }; - unsigned long val; - unsigned int leapyears; - int m; - const unsigned int *mp; - - memset(tm, 0, sizeof(*tm)); - - /* seconds since 1970 -> minutes since 1970 */ - tm->tm_sec = timestamp % 60; - val = timestamp /= 60; - - /* Do the simple stuff */ - tm->tm_min = val % 60; - val /= 60; - tm->tm_hour = val % 24; - val /= 24; - - /* Jan 1, 1970 was a Thursday (tm_wday=4) */ - tm->tm_wday = (val + 4) % 7; - - /* - * Now we're in "days since Jan 1, 1970". To make things easier, - * let's make it "days since Jan 1, 1968", since that's a leap-year - */ - val += 365 + 366; - - /* This only works up until 2099 (2100 isn't a leap-year) */ - leapyears = val / (365 * 4 + 1); - val %= (365 * 4 + 1); - tm->tm_year = 68 + leapyears * 4; - - /* Handle the leap-year itself */ - mp = mdays_leap; - if (val > 365) { - tm->tm_year++; - val -= 366; - tm->tm_year += val / 365; - val %= 365; - mp = mdays; - } - - for (m = 0; m < 12; m++) { - if (val < *mp) - break; - val -= *mp++; - } - tm->tm_mday = val + 1; - tm->tm_mon = m; -} - -timestamp_t utc_mktime(struct tm *tm) -{ - static const int mdays[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 - }; - int year = tm->tm_year; - int month = tm->tm_mon; - int day = tm->tm_mday; - - /* First normalize relative to 1900 */ - if (year < 70) - year += 100; - else if (year > 1900) - year -= 1900; - - /* Normalized to Jan 1, 1970: unix time */ - year -= 70; - - if (year < 0 || year > 129) /* algo only works for 1970-2099 */ - return -1; - if (month < 0 || month > 11) /* array bounds */ - return -1; - if (month < 2 || (year + 2) % 4) - day--; - if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) - return -1; - return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24 * 60 * 60UL + - tm->tm_hour * 60 * 60 + tm->tm_min * 60 + tm->tm_sec; -} diff --git a/subsurface-core/uemis-downloader.c b/subsurface-core/uemis-downloader.c deleted file mode 100644 index b9b532303..000000000 --- a/subsurface-core/uemis-downloader.c +++ /dev/null @@ -1,1403 +0,0 @@ -/* - * uemis-downloader.c - * - * Copyright (c) Dirk Hohndel - * 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; - -static int max_deleted_seen = -1; - -/* helper function to parse the Uemis data structures */ -static void uemis_ts(char *buffer, void *_when) -{ - struct tm tm; - timestamp_t *when = _when; - - memset(&tm, 0, sizeof(tm)); - sscanf(buffer, "%d-%d-%dT%d:%d:%d", - &tm.tm_year, &tm.tm_mon, &tm.tm_mday, - &tm.tm_hour, &tm.tm_min, &tm.tm_sec); - tm.tm_mon -= 1; - tm.tm_year -= 1900; - *when = utc_mktime(&tm); -} - -/* float minutes */ -static void uemis_duration(char *buffer, duration_t *duration) -{ - duration->seconds = rint(ascii_strtod(buffer, NULL) * 60); -} - -/* int cm */ -static void uemis_depth(char *buffer, depth_t *depth) -{ - depth->mm = atoi(buffer) * 10; -} - -static void uemis_get_index(char *buffer, int *idx) -{ - *idx = atoi(buffer); -} - -/* space separated */ -static void uemis_add_string(const char *buffer, char **text, const char *delimit) -{ - /* do nothing if this is an empty buffer (Uemis sometimes returns a single - * space for empty buffers) */ - if (!buffer || !*buffer || (*buffer == ' ' && *(buffer + 1) == '\0')) - return; - if (!*text) { - *text = strdup(buffer); - } else { - char *buf = malloc(strlen(buffer) + strlen(*text) + 2); - strcpy(buf, *text); - strcat(buf, delimit); - strcat(buf, buffer); - free(*text); - *text = buf; - } -} - -/* still unclear if it ever reports lbs */ -static void uemis_get_weight(char *buffer, weightsystem_t *weight, int diveid) -{ - weight->weight.grams = uemis_get_weight_unit(diveid) ? - lbs_to_grams(ascii_strtod(buffer, NULL)) : - ascii_strtod(buffer, NULL) * 1000; - weight->description = strdup(translate("gettextFromC", "unknown")); -} - -static struct dive *uemis_start_dive(uint32_t deviceid) -{ - struct dive *dive = alloc_dive(); - dive->downloaded = true; - dive->dc.model = strdup("Uemis Zurich"); - dive->dc.deviceid = deviceid; - return dive; -} - -static struct dive *get_dive_by_uemis_diveid(device_data_t *devdata, uint32_t object_id) -{ - for (int i = 0; i < devdata->download_table->nr; i++) { - if (object_id == devdata->download_table->dives[i]->dc.diveid) - return devdata->download_table->dives[i]; - } - return NULL; -} - -static void record_uemis_dive(device_data_t *devdata, struct dive *dive) -{ - if (devdata->create_new_trip) { - if (!devdata->trip) - devdata->trip = create_and_hookup_trip_from_dive(dive); - else - add_dive_to_trip(dive, devdata->trip); - } - record_dive_to_table(dive, devdata->download_table); -} - -/* send text to the importer progress bar */ -static void uemis_info(const char *fmt, ...) -{ - static char buffer[256]; - va_list ap; - - va_start(ap, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, ap); - va_end(ap); - progress_bar_text = buffer; - if (verbose) - fprintf(stderr, "Uemis downloader: %s\n", buffer); -} - -static long bytes_available(int file) -{ - long result; - long now = lseek(file, 0, SEEK_CUR); - if (now == -1) - return 0; - result = lseek(file, 0, SEEK_END); - lseek(file, now, SEEK_SET); - if (now == -1 || result == -1) - return 0; - return result; -} - -static int number_of_file(char *path) -{ - int count = 0; -#ifdef WIN32 - struct _wdirent *entry; - _WDIR *dirp = (_WDIR *)subsurface_opendir(path); -#else - struct dirent *entry; - DIR *dirp = (DIR *)subsurface_opendir(path); -#endif - - while (dirp) { -#ifdef WIN32 - entry = _wreaddir(dirp); - if (!entry) - break; -#else - entry = readdir(dirp); - if (!entry) - break; - if (strstr(entry->d_name, ".TXT") || strstr(entry->d_name, ".txt")) /* If the entry is a regular file */ -#endif - count++; - } -#ifdef WIN32 - _wclosedir(dirp); -#else - closedir(dirp); -#endif - return count; -} - -static char *build_filename(const char *path, const char *name) -{ - int len = strlen(path) + strlen(name) + 2; - char *buf = malloc(len); -#if WIN32 - snprintf(buf, len, "%s\\%s", path, name); -#else - snprintf(buf, len, "%s/%s", path, name); -#endif - return buf; -} - -/* Check if there's a req.txt file and get the starting filenr from it. - * Test for the maximum number of ANS files (I believe this is always - * 4000 but in case there are differences depending on firmware, this - * code is easy enough */ -static bool uemis_init(const char *path) -{ - char *ans_path; - int i; - - if (!path) - return false; - /* let's check if this is indeed a Uemis DC */ - reqtxt_path = build_filename(path, "req.txt"); - reqtxt_file = subsurface_open(reqtxt_path, O_RDONLY | O_CREAT, 0666); - if (reqtxt_file < 0) { -#if UEMIS_DEBUG & 1 - fprintf(debugfile, ":EE req.txt can't be opened\n"); -#endif - return false; - } - if (bytes_available(reqtxt_file) > 5) { - char tmp[6]; - read(reqtxt_file, tmp, 5); - tmp[5] = '\0'; -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "::r req.txt \"%s\"\n", tmp); -#endif - if (sscanf(tmp + 1, "%d", &filenr) != 1) - return false; - } else { - filenr = 0; -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "::r req.txt skipped as there were fewer than 5 bytes\n"); -#endif - } - close(reqtxt_file); - - /* It would be nice if we could simply go back to the first set of - * ANS files. But with a FAT filesystem that isn't possible */ - ans_path = build_filename(path, "ANS"); - number_of_files = number_of_file(ans_path); - free(ans_path); - /* initialize the array in which we collect the answers */ - for (i = 0; i < NUM_PARAM_BUFS; i++) - param_buff[i] = ""; - return true; -} - -static void str_append_with_delim(char *s, char *t) -{ - int len = strlen(s); - snprintf(s + len, BUFLEN - len, "%s{", t); -} - -/* The communication protocol with the DC is truly funky. - * After you write your request to the req.txt file you call this function. - * It writes the number of the next ANS file at the beginning of the req.txt - * file (prefixed by 'n' or 'r') and then again at the very end of it, after - * the full request (this time without the prefix). - * Then it syncs (not needed on Windows) and closes the file. */ -static void trigger_response(int file, char *command, int nr, long tailpos) -{ - char fl[10]; - - snprintf(fl, 8, "%s%04d", command, nr); -#if UEMIS_DEBUG & 4 - fprintf(debugfile, ":tr %s (after seeks)\n", fl); -#endif - if (lseek(file, 0, SEEK_SET) == -1) - goto fs_error; - if (write(file, fl, strlen(fl)) == -1) - goto fs_error; - if (lseek(file, tailpos, SEEK_SET) == -1) - goto fs_error; - if (write(file, fl + 1, strlen(fl + 1)) == -1) - goto fs_error; -#ifndef WIN32 - fsync(file); -#endif -fs_error: - close(file); -} - -static char *next_token(char **buf) -{ - char *q, *p = strchr(*buf, '{'); - if (p) - *p = '\0'; - else - p = *buf + strlen(*buf) - 1; - q = *buf; - *buf = p + 1; - return q; -} - -/* poor man's tokenizer that understands a quoted delimiter ('{') */ -static char *next_segment(char *buf, int *offset, int size) -{ - int i = *offset; - int seg_size; - bool done = false; - char *segment; - - while (!done) { - if (i < size) { - if (i < size - 1 && buf[i] == '\\' && - (buf[i + 1] == '\\' || buf[i + 1] == '{')) - memcpy(buf + i, buf + i + 1, size - i - 1); - else if (buf[i] == '{') - done = true; - i++; - } else { - done = true; - } - } - seg_size = i - *offset - 1; - if (seg_size < 0) - seg_size = 0; - segment = malloc(seg_size + 1); - memcpy(segment, buf + *offset, seg_size); - segment[seg_size] = '\0'; - *offset = i; - return segment; -} - -/* a dynamically growing buffer to store the potentially massive responses. - * The binary data block can be more than 100k in size (base64 encoded) */ -static void buffer_add(char **buffer, int *buffer_size, char *buf) -{ - if (!buf) - return; - if (!*buffer) { - *buffer = strdup(buf); - *buffer_size = strlen(*buffer) + 1; - } else { - *buffer_size += strlen(buf); - *buffer = realloc(*buffer, *buffer_size); - strcat(*buffer, buf); - } -#if UEMIS_DEBUG & 8 - fprintf(debugfile, "added \"%s\" to buffer - new length %d\n", buf, *buffer_size); -#endif -} - -/* are there more ANS files we can check? */ -static bool next_file(int max) -{ - if (filenr >= max) - return false; - filenr++; - return true; -} - -/* try and do a quick decode - without trying to get to fancy in case the data - * straddles a block boundary... - * we are parsing something that looks like this: - * object_id{int{2{date{ts{2011-04-05T12:38:04{duration{float{12.000... - */ -static char *first_object_id_val(char *buf) -{ - char *object, *bufend; - if (!buf) - return NULL; - bufend = buf + strlen(buf); - object = strstr(buf, "object_id"); - if (object && object + 14 < bufend) { - /* get the value */ - char tmp[36]; - char *p = object + 14; - char *t = tmp; - -#if UEMIS_DEBUG & 4 - char debugbuf[50]; - strncpy(debugbuf, object, 49); - debugbuf[49] = '\0'; - fprintf(debugfile, "buf |%s|\n", debugbuf); -#endif - while (p < bufend && *p != '{' && t < tmp + 9) - *t++ = *p++; - if (*p == '{') { - /* found the object_id - let's quickly look for the date */ - if (strncmp(p, "{date{ts{", 9) == 0 && strstr(p, "{duration{") != NULL) { - /* cool - that was easy */ - *t++ = ','; - *t++ = ' '; - /* skip the 9 characters we just found and take the date, ignoring the seconds - * and replace the silly 'T' in the middle with a space */ - strncpy(t, p + 9, 16); - if (*(t + 10) == 'T') - *(t + 10) = ' '; - t += 16; - } - *t = '\0'; - return strdup(tmp); - } - } - return NULL; -} - -/* ultra-simplistic; it doesn't deal with the case when the object_id is - * split across two chunks. It also doesn't deal with the discrepancy between - * object_id and dive number as understood by the dive computer */ -static void show_progress(char *buf, const char *what) -{ - char *val = first_object_id_val(buf); - if (val) { -/* let the user know what we are working on */ -#if UEMIS_DEBUG & 8 - fprintf(debugfile, "reading %s\n %s\n %s\n", what, val, buf); -#endif - uemis_info(translate("gettextFromC", "%s %s"), what, val); - free(val); - } -} - -static void uemis_increased_timeout(int *timeout) -{ - if (*timeout < UEMIS_MAX_TIMEOUT) - *timeout += UEMIS_LONG_TIMEOUT; - usleep(*timeout); -} - -/* send a request to the dive computer and collect the answer */ -static bool uemis_get_answer(const char *path, char *request, int n_param_in, - int n_param_out, const char **error_text) -{ - int i = 0, file_length; - char sb[BUFLEN]; - char fl[13]; - char tmp[101]; - const char *what = translate("gettextFromC", "data"); - bool searching = true; - bool assembling_mbuf = false; - bool ismulti = false; - bool found_answer = false; - bool more_files = true; - bool answer_in_mbuf = false; - char *ans_path; - int ans_file; - int timeout = UEMIS_LONG_TIMEOUT; - - reqtxt_file = subsurface_open(reqtxt_path, O_RDWR | O_CREAT, 0666); - if (reqtxt_file == -1) { - *error_text = "can't open req.txt"; -#ifdef UEMIS_DEBUG - fprintf(debugfile, "open %s failed with errno %d\n", reqtxt_path, errno); -#endif - return false; - } - snprintf(sb, BUFLEN, "n%04d12345678", filenr); - str_append_with_delim(sb, request); - for (i = 0; i < n_param_in; i++) - str_append_with_delim(sb, param_buff[i]); - if (!strcmp(request, "getDivelogs") || !strcmp(request, "getDeviceData") || !strcmp(request, "getDirectory") || - !strcmp(request, "getDivespot") || !strcmp(request, "getDive")) { - answer_in_mbuf = true; - str_append_with_delim(sb, ""); - if (!strcmp(request, "getDivelogs")) - what = translate("gettextFromC", "divelog #"); - else if (!strcmp(request, "getDivespot")) - what = translate("gettextFromC", "divespot #"); - else if (!strcmp(request, "getDive")) - what = translate("gettextFromC", "details for #"); - } - str_append_with_delim(sb, ""); - file_length = strlen(sb); - snprintf(fl, 10, "%08d", file_length - 13); - memcpy(sb + 5, fl, strlen(fl)); -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "::w req.txt \"%s\"\n", sb); -#endif - int written = write(reqtxt_file, sb, strlen(sb)); - if (written == -1 || (size_t)written != strlen(sb)) { - *error_text = translate("gettextFromC", ERR_FS_SHORT_WRITE); - return false; - } - if (!next_file(number_of_files)) { - *error_text = translate("gettextFromC", ERR_FS_FULL); - more_files = false; - } - trigger_response(reqtxt_file, "n", filenr, file_length); - usleep(timeout); - free(mbuf); - mbuf = NULL; - mbuf_size = 0; - while (searching || assembling_mbuf) { - if (import_thread_cancelled) - return false; - progress_bar_fraction = filenr / (double)UEMIS_MAX_FILES; - snprintf(fl, 13, "ANS%d.TXT", filenr - 1); - ans_path = build_filename(build_filename(path, "ANS"), fl); - ans_file = subsurface_open(ans_path, O_RDONLY, 0666); - read(ans_file, tmp, 100); - close(ans_file); -#if UEMIS_DEBUG & 8 - tmp[100] = '\0'; - fprintf(debugfile, "::t %s \"%s\"\n", ans_path, tmp); -#elif UEMIS_DEBUG & 4 - char pbuf[4]; - pbuf[0] = tmp[0]; - pbuf[1] = tmp[1]; - pbuf[2] = tmp[2]; - pbuf[3] = 0; - fprintf(debugfile, "::t %s \"%s...\"\n", ans_path, pbuf); -#endif - free(ans_path); - if (tmp[0] == '1') { - searching = false; - if (tmp[1] == 'm') { - assembling_mbuf = true; - ismulti = true; - } - if (tmp[2] == 'e') - assembling_mbuf = false; - if (assembling_mbuf) { - if (!next_file(number_of_files)) { - *error_text = translate("gettextFromC", ERR_FS_FULL); - more_files = false; - assembling_mbuf = false; - } - reqtxt_file = subsurface_open(reqtxt_path, O_RDWR | O_CREAT, 0666); - if (reqtxt_file == -1) { - *error_text = "can't open req.txt"; - fprintf(stderr, "open %s failed with errno %d\n", reqtxt_path, errno); - return false; - } - trigger_response(reqtxt_file, "n", filenr, file_length); - } - } else { - if (!next_file(number_of_files - 1)) { - *error_text = translate("gettextFromC", ERR_FS_FULL); - more_files = false; - assembling_mbuf = false; - searching = false; - } - reqtxt_file = subsurface_open(reqtxt_path, O_RDWR | O_CREAT, 0666); - if (reqtxt_file == -1) { - *error_text = "can't open req.txt"; - fprintf(stderr, "open %s failed with errno %d\n", reqtxt_path, errno); - return false; - } - trigger_response(reqtxt_file, "r", filenr, file_length); - uemis_increased_timeout(&timeout); - } - if (ismulti && more_files && tmp[0] == '1') { - int size; - snprintf(fl, 13, "ANS%d.TXT", assembling_mbuf ? filenr - 2 : filenr - 1); - ans_path = build_filename(build_filename(path, "ANS"), fl); - ans_file = subsurface_open(ans_path, O_RDONLY, 0666); - free(ans_path); - size = bytes_available(ans_file); - if (size > 3) { - char *buf; - int r; - if (lseek(ans_file, 3, SEEK_CUR) == -1) - goto fs_error; - buf = malloc(size - 2); - if ((r = read(ans_file, buf, size - 3)) != size - 3) { - free(buf); - goto fs_error; - } - buf[r] = '\0'; - buffer_add(&mbuf, &mbuf_size, buf); - show_progress(buf, what); - free(buf); - param_buff[3]++; - } - close(ans_file); - timeout = UEMIS_TIMEOUT; - usleep(UEMIS_TIMEOUT); - } - } - if (more_files) { - int size = 0, j = 0; - char *buf = NULL; - - if (!ismulti) { - snprintf(fl, 13, "ANS%d.TXT", filenr - 1); - ans_path = build_filename(build_filename(path, "ANS"), fl); - ans_file = subsurface_open(ans_path, O_RDONLY, 0666); - free(ans_path); - size = bytes_available(ans_file); - if (size > 3) { - int r; - if (lseek(ans_file, 3, SEEK_CUR) == -1) - goto fs_error; - buf = malloc(size - 2); - if ((r = read(ans_file, buf, size - 3)) != size - 3) { - free(buf); - goto fs_error; - } - buf[r] = '\0'; - buffer_add(&mbuf, &mbuf_size, buf); - show_progress(buf, what); -#if UEMIS_DEBUG & 8 - fprintf(debugfile, "::r %s \"%s\"\n", fl, buf); -#endif - } - size -= 3; - close(ans_file); - } else { - ismulti = false; - } -#if UEMIS_DEBUG & 8 - fprintf(debugfile, ":r: %s\n", buf); -#endif - if (!answer_in_mbuf) - for (i = 0; i < n_param_out && j < size; i++) - param_buff[i] = next_segment(buf, &j, size); - found_answer = true; - free(buf); - } -#if UEMIS_DEBUG & 1 - for (i = 0; i < n_param_out; i++) - fprintf(debugfile, "::: %d: %s\n", i, param_buff[i]); -#endif - return found_answer; -fs_error: - close(ans_file); - return false; -} - -static bool parse_divespot(char *buf) -{ - char *bp = buf + 1; - char *tp = next_token(&bp); - char *tag, *type, *val; - char locationstring[1024] = ""; - int divespot, len; - double latitude = 0.0, longitude = 0.0; - - // dive spot got deleted, so fail here - if (strstr(bp, "deleted{bool{true")) - return false; - // not a dive spot, fail here - if (strcmp(tp, "divespot")) - return false; - do - tag = next_token(&bp); - while (*tag && strcmp(tag, "object_id")); - if (!*tag) - return false; - next_token(&bp); - val = next_token(&bp); - divespot = atoi(val); - do { - tag = next_token(&bp); - type = next_token(&bp); - val = next_token(&bp); - if (!strcmp(type, "string") && *val && strcmp(val, " ")) { - len = strlen(locationstring); - snprintf(locationstring + len, sizeof(locationstring) - len, - "%s%s", len ? ", " : "", val); - } else if (!strcmp(type, "float")) { - if (!strcmp(tag, "longitude")) - longitude = ascii_strtod(val, NULL); - else if (!strcmp(tag, "latitude")) - latitude = ascii_strtod(val, NULL); - } - } while (tag && *tag); - - uemis_set_divelocation(divespot, locationstring, longitude, latitude); - return true; -} - -static char *suit[] = {"", QT_TRANSLATE_NOOP("gettextFromC", "wetsuit"), QT_TRANSLATE_NOOP("gettextFromC", "semidry"), QT_TRANSLATE_NOOP("gettextFromC", "drysuit")}; -static char *suit_type[] = {"", QT_TRANSLATE_NOOP("gettextFromC", "shorty"), QT_TRANSLATE_NOOP("gettextFromC", "vest"), QT_TRANSLATE_NOOP("gettextFromC", "long john"), QT_TRANSLATE_NOOP("gettextFromC", "jacket"), QT_TRANSLATE_NOOP("gettextFromC", "full suit"), QT_TRANSLATE_NOOP("gettextFromC", "2 pcs full suit")}; -static char *suit_thickness[] = {"", "0.5-2mm", "2-3mm", "3-5mm", "5-7mm", "8mm+", QT_TRANSLATE_NOOP("gettextFromC", "membrane")}; - -static void parse_tag(struct dive *dive, char *tag, char *val) -{ - /* we can ignore computer_id, water and gas as those are redundant - * with the binary data and would just get overwritten */ -#if UEMIS_DEBUG & 4 - if (strcmp(tag, "file_content")) - fprintf(debugfile, "Adding to dive %d : %s = %s\n", dive->dc.diveid, tag, val); -#endif - if (!strcmp(tag, "date")) { - uemis_ts(val, &dive->when); - } else if (!strcmp(tag, "duration")) { - uemis_duration(val, &dive->dc.duration); - } else if (!strcmp(tag, "depth")) { - uemis_depth(val, &dive->dc.maxdepth); - } else if (!strcmp(tag, "file_content")) { - uemis_parse_divelog_binary(val, dive); - } else if (!strcmp(tag, "altitude")) { - uemis_get_index(val, &dive->dc.surface_pressure.mbar); - } else if (!strcmp(tag, "f32Weight")) { - uemis_get_weight(val, &dive->weightsystem[0], dive->dc.diveid); - } else if (!strcmp(tag, "notes")) { - uemis_add_string(val, &dive->notes, " "); - } else if (!strcmp(tag, "u8DiveSuit")) { - if (*suit[atoi(val)]) - uemis_add_string(translate("gettextFromC", suit[atoi(val)]), &dive->suit, " "); - } else if (!strcmp(tag, "u8DiveSuitType")) { - if (*suit_type[atoi(val)]) - uemis_add_string(translate("gettextFromC", suit_type[atoi(val)]), &dive->suit, " "); - } else if (!strcmp(tag, "u8SuitThickness")) { - if (*suit_thickness[atoi(val)]) - uemis_add_string(translate("gettextFromC", suit_thickness[atoi(val)]), &dive->suit, " "); - } else if (!strcmp(tag, "nickname")) { - uemis_add_string(val, &dive->buddy, ","); - } -} - -static bool uemis_delete_dive(device_data_t *devdata, uint32_t diveid) -{ - struct dive *dive = NULL; - - if (devdata->download_table->dives[devdata->download_table->nr - 1]->dc.diveid == diveid) { - /* we hit the last one in the array */ - dive = devdata->download_table->dives[devdata->download_table->nr - 1]; - } else { - for (int i = 0; i < devdata->download_table->nr - 1; i++) { - if (devdata->download_table->dives[i]->dc.diveid == diveid) { - dive = devdata->download_table->dives[i]; - for (int x = i; x < devdata->download_table->nr - 1; x++) - devdata->download_table->dives[i] = devdata->download_table->dives[x + 1]; - } - } - } - if (dive) { - devdata->download_table->dives[--devdata->download_table->nr] = NULL; - if (dive->tripflag != TF_NONE) - remove_dive_from_trip(dive, false); - - free(dive->dc.sample); - free((void *)dive->notes); - free((void *)dive->divemaster); - free((void *)dive->buddy); - free((void *)dive->suit); - taglist_free(dive->tag_list); - free(dive); - - return true; - } - return false; -} - -/* This function is called for both divelog and dive information that we get - * from the SDA (what an insane design, btw). The object_id in the divelog - * matches the logfilenr in the dive information (which has its own, often - * different object_id) - we use this as the diveid. - * We create the dive when parsing the divelog and then later, when we parse - * the dive information we locate the already created dive via its diveid. - * Most things just get parsed and converted into our internal data structures, - * but the dive location API is even more crazy. We just get an id that is an - * index into yet another data store that we read out later. In order to - * correctly populate the location and gps data from that we need to remember - * the addresses of those fields for every dive that references the divespot. */ -static bool process_raw_buffer(device_data_t *devdata, uint32_t deviceid, char *inbuf, char **max_divenr, int *for_dive) -{ - char *buf = strdup(inbuf); - char *tp, *bp, *tag, *type, *val; - bool done = false; - int inbuflen = strlen(inbuf); - char *endptr = buf + inbuflen; - bool is_log = false, is_dive = false; - char *sections[10]; - size_t s, nr_sections = 0; - struct dive *dive = NULL; - char dive_no[10]; - -#if UEMIS_DEBUG & 8 - fprintf(debugfile, "p_r_b %s\n", inbuf); -#endif - if (for_dive) - *for_dive = -1; - bp = buf + 1; - tp = next_token(&bp); - if (strcmp(tp, "divelog") == 0) { - /* this is a divelog */ - is_log = true; - tp = next_token(&bp); - /* is it a valid entry or nothing ? */ - if (strcmp(tp, "1.0") != 0 || strstr(inbuf, "divelog{1.0{{{{")) { - free(buf); - return false; - } - } else if (strcmp(tp, "dive") == 0) { - /* this is dive detail */ - is_dive = true; - tp = next_token(&bp); - if (strcmp(tp, "1.0") != 0) { - free(buf); - return false; - } - } else { - /* don't understand the buffer */ - free(buf); - return false; - } - if (is_log) { - dive = uemis_start_dive(deviceid); - } else { - /* remember, we don't know if this is the right entry, - * so first test if this is even a valid entry */ - if (strstr(inbuf, "deleted{bool{true")) { -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "p_r_b entry deleted\n"); -#endif - /* oops, this one isn't valid, suggest to try the previous one */ - free(buf); - return false; - } - /* quickhack and workaround to capture the original dive_no - * I am doing this so I don't have to change the original design - * but when parsing a dive we never parse the dive number because - * at the time it's being read the *dive variable is not set because - * the dive_no tag comes before the object_id in the uemis ans file - */ - dive_no[0] = '\0'; - char *dive_no_buf = strdup(inbuf); - char *dive_no_ptr = strstr(dive_no_buf, "dive_no{int{") + 12; - if (dive_no_ptr) { - char *dive_no_end = strstr(dive_no_ptr, "{"); - if (dive_no_end) { - *dive_no_end = '\0'; - strncpy(dive_no, dive_no_ptr, 9); - dive_no[9] = '\0'; - } - } - free(dive_no_buf); - } - while (!done) { - /* the valid buffer ends with a series of delimiters */ - if (bp >= endptr - 2 || !strcmp(bp, "{{")) - break; - tag = next_token(&bp); - /* we also end if we get an empty tag */ - if (*tag == '\0') - break; - for (s = 0; s < nr_sections; s++) - if (!strcmp(tag, sections[s])) { - tag = next_token(&bp); - break; - } - type = next_token(&bp); - if (!strcmp(type, "1.0")) { - /* this tells us the sections that will follow; the tag here - * is of the format dive-
*/ - sections[nr_sections] = strchr(tag, '-') + 1; -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "Expect to find section %s\n", sections[nr_sections]); -#endif - if (nr_sections < sizeof(sections) - 1) - nr_sections++; - continue; - } - val = next_token(&bp); -#if UEMIS_DEBUG & 8 - if (strlen(val) < 20) - fprintf(debugfile, "Parsed %s, %s, %s\n*************************\n", tag, type, val); -#endif - if (is_log && strcmp(tag, "object_id") == 0) { - free(*max_divenr); - *max_divenr = strdup(val); - dive->dc.diveid = atoi(val); -#if UEMIS_DEBUG % 2 - fprintf(debugfile, "Adding new dive from log with object_id %d.\n", atoi(val)); -#endif - } else if (is_dive && strcmp(tag, "logfilenr") == 0) { - /* this one tells us which dive we are adding data to */ - dive = get_dive_by_uemis_diveid(devdata, atoi(val)); - if (strcmp(dive_no, "0")) - dive->number = atoi(dive_no); - if (for_dive) - *for_dive = atoi(val); - } else if (!is_log && dive && !strcmp(tag, "divespot_id")) { - int divespot_id = atoi(val); - if (divespot_id != -1) { - dive->dive_site_uuid = create_dive_site("from Uemis", dive->when); - uemis_mark_divelocation(dive->dc.diveid, divespot_id, dive->dive_site_uuid); - } -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "Created divesite %d for diveid : %d\n", dive->dive_site_uuid, dive->dc.diveid); -#endif - } else if (dive) { - parse_tag(dive, tag, val); - } - if (is_log && !strcmp(tag, "file_content")) - done = true; - /* done with one dive (got the file_content tag), but there could be more: - * a '{' indicates the end of the record - but we need to see another "{{" - * later in the buffer to know that the next record is complete (it could - * be a short read because of some error */ - if (done && ++bp < endptr && *bp != '{' && strstr(bp, "{{")) { - done = false; - record_uemis_dive(devdata, dive); - mark_divelist_changed(true); - dive = uemis_start_dive(deviceid); - } - } - if (is_log) { - if (dive->dc.diveid) { - record_uemis_dive(devdata, dive); - mark_divelist_changed(true); - } else { /* partial dive */ - free(dive); - free(buf); - return false; - } - } - free(buf); - return true; -} - -static char *uemis_get_divenr(char *deviceidstr, int force) -{ - uint32_t deviceid, maxdiveid = 0; - int i; - char divenr[10]; - struct dive_table *table; - deviceid = atoi(deviceidstr); - - /* - * If we are are retrying after a disconnect/reconnect, we - * will look up the highest dive number in the dives we - * already have. - * - * Also, if "force_download" is true, do this even if we - * don't have any dives (maxdiveid will remain zero) - */ - if (force || downloadTable.nr) - table = &downloadTable; - else - table = &dive_table; - - for (i = 0; i < table->nr; i++) { - struct dive *d = table->dives[i]; - struct divecomputer *dc; - if (!d) - continue; - for_each_dc (d, dc) { - if (dc->model && !strcmp(dc->model, "Uemis Zurich") && - (dc->deviceid == 0 || dc->deviceid == 0x7fffffff || dc->deviceid == deviceid) && - dc->diveid > maxdiveid) - maxdiveid = dc->diveid; - } - } - if (max_deleted_seen >= 0 && maxdiveid < (uint32_t)max_deleted_seen) { - maxdiveid = max_deleted_seen; -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "overriding max seen with max deleted seen %d\n", max_deleted_seen); -#endif - } - snprintf(divenr, 10, "%d", maxdiveid); - return strdup(divenr); -} - -#if UEMIS_DEBUG -static int bufCnt = 0; -static bool do_dump_buffer_to_file(char *buf, char *prefix) -{ - char path[100]; - char date[40]; - char obid[40]; - if (!buf) - return false; - - if (strstr(buf, "date{ts{")) - strncpy(date, strstr(buf, "date{ts{"), sizeof(date)); - else - strncpy(date, "date{ts{no-date{", sizeof(date)); - - if (!strstr(buf, "object_id{int{")) - return false; - - strncpy(obid, strstr(buf, "object_id{int{"), sizeof(obid)); - char *ptr1 = strstr(date, "date{ts{"); - char *ptr2 = strstr(obid, "object_id{int{"); - char *pdate = next_token(&ptr1); - pdate = next_token(&ptr1); - pdate = next_token(&ptr1); - char *pobid = next_token(&ptr2); - pobid = next_token(&ptr2); - pobid = next_token(&ptr2); - snprintf(path, sizeof(path), "/%s/%s/UEMIS Dump/%s_%s_Uemis_dump_%s_in_round_%d_%d.txt", home, user, prefix, pdate, pobid, debug_round, bufCnt); - int dumpFile = subsurface_open(path, O_RDWR | O_CREAT, 0666); - if (dumpFile == -1) - return false; - write(dumpFile, buf, strlen(buf)); - close(dumpFile); - bufCnt++; - return true; -} -#endif - -/* do some more sophisticated calculations here to try and predict if the next round of - * divelog/divedetail reads will fit into the UEMIS buffer, - * filenr holds now the uemis filenr after having read several logs including the dive details, - * fCapacity will five us the average number of files needed for all currently loaded data - * remember the maximum file usage per dive - * return : UEMIS_MEM_OK if there is enough memory for a full round - * UEMIS_MEM_CRITICAL if the memory is good for reading the dive logs - * UEMIS_MEM_FULL if the memory is exhausted - */ -static int get_memory(struct dive_table *td, int checkpoint) -{ - if (td->nr <= 0) - return UEMIS_MEM_OK; - - switch (checkpoint) { - case UEMIS_CHECK_LOG: - if (filenr / td->nr > max_mem_used) - max_mem_used = filenr / td->nr; - - /* check if a full block of dive logs + dive details and dive spot fit into the UEMIS buffer */ -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "max_mem_used %d (from td->nr %d) * block_size %d > max_files %d - filenr %d?\n", max_mem_used, td->nr, UEMIS_LOG_BLOCK_SIZE, UEMIS_MAX_FILES, filenr); -#endif - if (max_mem_used * UEMIS_LOG_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) - return UEMIS_MEM_FULL; - break; - case UEMIS_CHECK_DETAILS: - /* check if the next set of dive details and dive spot fit into the UEMIS buffer */ - if ((UEMIS_DIVE_DETAILS_SIZE + UEMIS_SPOT_BLOCK_SIZE) * UEMIS_LOG_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) - return UEMIS_MEM_FULL; - break; - case UEMIS_CHECK_SINGLE_DIVE: - if (UEMIS_DIVE_DETAILS_SIZE + UEMIS_SPOT_BLOCK_SIZE > UEMIS_MAX_FILES - filenr) - return UEMIS_MEM_FULL; - break; - } - return UEMIS_MEM_OK; -} - -/* mark a dive as deleted by setting download to false - * this will be picked up by some cleaning statement later */ -static void do_delete_dives(struct dive_table *td, int idx) -{ - for (int x = idx; x < td->nr; x++) - td->dives[x]->downloaded = false; -} - -static bool load_uemis_divespot(const char *mountpath, int divespot_id) -{ - char divespotnr[10]; - snprintf(divespotnr, sizeof(divespotnr), "%d", divespot_id); - param_buff[2] = divespotnr; -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "getDivespot %d\n", divespot_id); -#endif - bool success = uemis_get_answer(mountpath, "getDivespot", 3, 0, NULL); - if (mbuf && success) { -#if UEMIS_DEBUG & 16 - do_dump_buffer_to_file(mbuf, "Spot"); -#endif - return parse_divespot(mbuf); - } - return false; -} - -static void get_uemis_divespot(const char *mountpath, int divespot_id, struct dive *dive) -{ - struct dive_site *nds = get_dive_site_by_uuid(dive->dive_site_uuid); - if (nds && nds->name && strstr(nds->name,"from Uemis")) { - if (load_uemis_divespot(mountpath, divespot_id)) { - /* get the divesite based on the diveid, this should give us - * the newly created site - */ - struct dive_site *ods = NULL; - /* with the divesite name we got from parse_dive, that is called on load_uemis_divespot - * we search all existing divesites if we have one with the same name already. The function - * returns the first found which is luckily not the newly created. - */ - (void)get_dive_site_uuid_by_name(nds->name, &ods); - if (ods) { - /* if the uuid's are the same, the new site is a duplicate and can be deleted */ - if (nds->uuid != ods->uuid) { - delete_dive_site(nds->uuid); - dive->dive_site_uuid = ods->uuid; - } - } - } else { - /* if we can't load the dive site details, delete the site we - * created in process_raw_buffer - */ - delete_dive_site(dive->dive_site_uuid); - } - } -} - -static bool get_matching_dive(int idx, char *newmax, int *uemis_mem_status, struct device_data_t *data, const char *mountpath, const char deviceidnr) -{ - struct dive *dive = data->download_table->dives[idx]; - char log_file_no_to_find[20]; - char dive_to_read_buf[10]; - bool found = false; - bool found_below = false; - bool found_above = false; - int deleted_files = 0; - - snprintf(log_file_no_to_find, sizeof(log_file_no_to_find), "logfilenr{int{%d", dive->dc.diveid); -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "Looking for dive details to go with divelog id %d\n", dive->dc.diveid); -#endif - while (!found) { - if (import_thread_cancelled) - break; - snprintf(dive_to_read_buf, sizeof(dive_to_read_buf), "%d", dive_to_read); - param_buff[2] = dive_to_read_buf; - (void)uemis_get_answer(mountpath, "getDive", 3, 0, NULL); -#if UEMIS_DEBUG & 16 - do_dump_buffer_to_file(mbuf, "Dive"); -#endif - *uemis_mem_status = get_memory(data->download_table, UEMIS_CHECK_SINGLE_DIVE); - if (*uemis_mem_status == UEMIS_MEM_OK) { - /* if the memory isn's completely full we can try to read more divelog vs. dive details - * UEMIS_MEM_CRITICAL means not enough space for a full round but the dive details - * and the divespots should fit into the UEMIS memory - * The match we do here is to map the object_id to the logfilenr, we do this - * by iterating through the last set of loaded divelogs and then find the corresponding - * dive with the matching logfilenr */ - if (mbuf) { - if (strstr(mbuf, log_file_no_to_find)) { - /* we found the logfilenr that matches our object_id from the divelog we were looking for - * we mark the search successful even if the dive has been deleted. */ - found = true; - if (strstr(mbuf, "deleted{bool{true") == NULL) { - process_raw_buffer(data, deviceidnr, mbuf, &newmax, NULL); - /* remember the last log file number as it is very likely that subsequent dives - * have the same or higher logfile number. - * UEMIS unfortunately deletes dives by deleting the dive details and not the logs. */ -#if UEMIS_DEBUG & 2 - d_time = get_dive_date_c_string(dive->when); - fprintf(debugfile, "Matching divelog id %d from %s with dive details %d\n", dive->dc.diveid, d_time, dive_to_read); -#endif - int divespot_id = uemis_get_divespot_id_by_diveid(dive->dc.diveid); - if (divespot_id >= 0) - get_uemis_divespot(mountpath, divespot_id, dive); - - } else { - /* in this case we found a deleted file, so let's increment */ -#if UEMIS_DEBUG & 2 - d_time = get_dive_date_c_string(dive->when); - fprintf(debugfile, "TRY matching divelog id %d from %s with dive details %d but details are deleted\n", dive->dc.diveid, d_time, dive_to_read); -#endif - deleted_files++; - max_deleted_seen = dive_to_read; - /* mark this log entry as deleted and cleanup later, otherwise we mess up our array */ - dive->downloaded = false; -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "Deleted dive from %s, with id %d from table -- newmax is %s\n", d_time, dive->dc.diveid, newmax); -#endif - } - } else { - uint32_t nr_found = 0; - char *logfilenr = strstr(mbuf, "logfilenr"); - if (logfilenr) { - sscanf(logfilenr, "logfilenr{int{%u", &nr_found); - if (nr_found >= dive->dc.diveid || nr_found == 0) { - found_above = true; - dive_to_read = dive_to_read - 2; - } else { - found_below = true; - } - if (dive_to_read < -1) - dive_to_read = -1; - } - } - } - if (found_above && found_below) - break; - dive_to_read++; - } else { - /* At this point the memory of the UEMIS is full, let's cleanup all divelog files were - * we could not match the details to. */ - do_delete_dives(data->download_table, idx); - return false; - } - } - /* decrement iDiveToRead by the amount of deleted entries found to assure - * we are not missing any valid matches when processing subsequent logs */ - dive_to_read = (dive_to_read - deleted_files > 0 ? dive_to_read - deleted_files : 0); - deleted_files = 0; - return true; -} - -const char *do_uemis_import(device_data_t *data) -{ - const char *mountpath = data->devname; - short force_download = data->force_download; - char *newmax = NULL; - int first, start, end = -2; - uint32_t deviceidnr; - char *deviceid = NULL; - const char *result = NULL; - char *endptr; - bool success, once = true; - int match_dive_and_log = 0; - int uemis_mem_status = UEMIS_MEM_OK; - -#if UEMIS_DEBUG - home = getenv("HOME"); - user = getenv("LOGNAME"); -#endif - uemis_info(translate("gettextFromC", "Initialise communication")); - if (!uemis_init(mountpath)) { - free(reqtxt_path); - return translate("gettextFromC", "Uemis init failed"); - } - - if (!uemis_get_answer(mountpath, "getDeviceId", 0, 1, &result)) - goto bail; - deviceid = strdup(param_buff[0]); - deviceidnr = atoi(deviceid); - - /* param_buff[0] is still valid */ - if (!uemis_get_answer(mountpath, "initSession", 1, 6, &result)) - goto bail; - - uemis_info(translate("gettextFromC", "Start download")); - if (!uemis_get_answer(mountpath, "processSync", 0, 2, &result)) - goto bail; - - /* before starting the long download, check if user pressed cancel */ - if (import_thread_cancelled) - goto bail; - - param_buff[1] = "notempty"; - newmax = uemis_get_divenr(deviceid, force_download); - if (verbose) - fprintf(stderr, "Uemis downloader: start looking at dive nr %s", newmax); - - first = start = atoi(newmax); - dive_to_read = first; - for (;;) { -#if UEMIS_DEBUG & 2 - debug_round++; -#endif -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i inner loop start %d end %d newmax %s\n", start, end, newmax); -#endif - /* start at the last filled download table index */ - match_dive_and_log = data->download_table->nr; - sprintf(newmax, "%d", start); - param_buff[2] = newmax; - param_buff[3] = 0; - success = uemis_get_answer(mountpath, "getDivelogs", 3, 0, &result); - uemis_mem_status = get_memory(data->download_table, UEMIS_CHECK_DETAILS); - /* first, remove any leading garbage... this needs to start with a '{' */ - char *realmbuf = mbuf; - if (mbuf) - realmbuf = strchr(mbuf, '{'); - if (success && realmbuf && uemis_mem_status != UEMIS_MEM_FULL) { -#if UEMIS_DEBUG & 16 - do_dump_buffer_to_file(realmbuf, "Divelogs"); -#endif - /* process the buffer we have assembled */ - if (!process_raw_buffer(data, deviceidnr, realmbuf, &newmax, NULL)) { - /* if no dives were downloaded, mark end appropriately */ - if (end == -2) - end = start - 1; - success = false; - } - if (once) { - char *t = first_object_id_val(realmbuf); - if (t && atoi(t) > start) - start = atoi(t); - free(t); - once = false; - } - /* clean up mbuf */ - endptr = strstr(realmbuf, "{{{"); - if (endptr) - *(endptr + 2) = '\0'; - /* last object_id we parsed */ - sscanf(newmax, "%d", &end); -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i after download and parse start %d end %d newmax %s progress %4.2f\n", start, end, newmax, progress_bar_fraction); -#endif - /* The way this works is that I am reading the current dive from what has been loaded during the getDiveLogs call to the UEMIS. - * As the object_id of the divelog entry and the object_id of the dive details are not necessarily the same, the match needs - * to happen based on the logfilenr. - * What the following part does is to optimize the mapping by using - * dive_to_read = the dive details entry that need to be read using the object_id - * logFileNoToFind = map the logfilenr of the dive details with the object_id = diveid from the get dive logs */ - for (int i = match_dive_and_log; i < data->download_table->nr; i++) { - bool success = get_matching_dive(i, newmax, &uemis_mem_status, data, mountpath, deviceidnr); - if (!success) - break; - if (import_thread_cancelled) - break; - } - - start = end; - - /* Do some memory checking here */ - uemis_mem_status = get_memory(data->download_table, UEMIS_CHECK_LOG); - if (uemis_mem_status != UEMIS_MEM_OK) { -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i out of memory, bailing\n"); -#endif - break; - } - /* if the user clicked cancel, exit gracefully */ - if (import_thread_cancelled) { -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i thread canceled, bailing\n"); -#endif - break; - } - /* if we got an error or got nothing back, stop trying */ - if (!success || !param_buff[3]) { -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i after download nothing found, giving up\n"); -#endif - break; - } -#if UEMIS_DEBUG & 2 - if (debug_round != -1) - if (debug_round-- == 0) { - fprintf(debugfile, "d_u_i debug_round is now 0, bailing\n"); - goto bail; - } -#endif - } else { - /* some of the loading from the UEMIS failed at the divelog level - * if the memory status = full, we can't even load the divespots and/or buddies. - * The loaded block of divelogs is useless and all new loaded divelogs need to - * be deleted from the download_table. - */ - if (uemis_mem_status == UEMIS_MEM_FULL) - do_delete_dives(data->download_table, match_dive_and_log); -#if UEMIS_DEBUG & 4 - fprintf(debugfile, "d_u_i out of memory, bailing instead of processing\n"); -#endif - break; - } - } - - if (end == -2 && sscanf(newmax, "%d", &end) != 1) - end = start; - -#if UEMIS_DEBUG & 2 - fprintf(debugfile, "Done: read from object_id %d to %d\n", first, end); -#endif - - /* Regardless on where we are with the memory situation, it's time now - * to see if we have to clean some dead bodies from our download table */ - next_table_index = 0; - while (next_table_index < data->download_table->nr) { - if (!data->download_table->dives[next_table_index]->downloaded) - uemis_delete_dive(data, data->download_table->dives[next_table_index]->dc.diveid); - else - next_table_index++; - } - - if (uemis_mem_status != UEMIS_MEM_OK) - result = translate("gettextFromC", ERR_FS_ALMOST_FULL); - -bail: - (void)uemis_get_answer(mountpath, "terminateSync", 0, 3, &result); - if (!strcmp(param_buff[0], "error")) { - if (!strcmp(param_buff[2], "Out of Memory")) - result = translate("gettextFromC", ERR_FS_FULL); - else - result = param_buff[2]; - } - free(deviceid); - free(reqtxt_path); - if (!data->download_table->nr) - result = translate("gettextFromC", ERR_NO_FILES); - return result; -} diff --git a/subsurface-core/uemis.c b/subsurface-core/uemis.c deleted file mode 100644 index 5635d5630..000000000 --- a/subsurface-core/uemis.c +++ /dev/null @@ -1,392 +0,0 @@ -/* - * uemis.c - * - * UEMIS SDA file importer - * AUTHOR: Dirk Hohndel - Copyright 2011 - * - * Licensed under the MIT license. - */ -#include -#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 { - uint32_t diveid; - int lbs; - int divespot; - int dive_site_uuid; - struct uemis_helper *next; -}; -static struct uemis_helper *uemis_helper = NULL; - -static struct uemis_helper *uemis_get_helper(uint32_t diveid) -{ - struct uemis_helper **php = &uemis_helper; - struct uemis_helper *hp = *php; - - while (hp) { - if (hp->diveid == diveid) - return hp; - if (hp->next) { - hp = hp->next; - continue; - } - php = &hp->next; - break; - } - hp = *php = calloc(1, sizeof(struct uemis_helper)); - hp->diveid = diveid; - hp->next = NULL; - return hp; -} - -static void uemis_weight_unit(int diveid, int lbs) -{ - struct uemis_helper *hp = uemis_get_helper(diveid); - if (hp) - hp->lbs = lbs; -} - -int uemis_get_weight_unit(uint32_t diveid) -{ - struct uemis_helper *hp = uemis_helper; - while (hp) { - if (hp->diveid == diveid) - return hp->lbs; - hp = hp->next; - } - /* odd - we should have found this; default to kg */ - return 0; -} - -void uemis_mark_divelocation(int diveid, int divespot, uint32_t dive_site_uuid) -{ - struct uemis_helper *hp = uemis_get_helper(diveid); - hp->divespot = divespot; - hp->dive_site_uuid = dive_site_uuid; -} - -/* support finding a dive spot based on the diveid */ -int uemis_get_divespot_id_by_diveid(uint32_t diveid) -{ - struct uemis_helper *hp = uemis_helper; - while (hp) { - if (hp->diveid == diveid) - return hp->divespot; - hp = hp->next; - } - return -1; -} - -void uemis_set_divelocation(int divespot, char *text, double longitude, double latitude) -{ - struct uemis_helper *hp = uemis_helper; - while (hp) { - if (hp->divespot == divespot) { - struct dive_site *ds = get_dive_site_by_uuid(hp->dive_site_uuid); - if (ds) { - ds->name = strdup(text); - ds->longitude.udeg = round(longitude * 1000000); - ds->latitude.udeg = round(latitude * 1000000); - } - } - hp = hp->next; - } -} - -/* Create events from the flag bits and other data in the sample; - * These bits basically represent what is displayed on screen at sample time. - * Many of these 'warnings' are way hyper-active and seriously clutter the - * profile plot - so these are disabled by default - * - * we mark all the strings for translation, but we store the untranslated - * strings and only convert them when displaying them on screen - this way - * when we write them to the XML file we'll always have the English strings, - * regardless of locale - */ -static void uemis_event(struct dive *dive, struct divecomputer *dc, struct sample *sample, uemis_sample_t *u_sample) -{ - uint8_t *flags = u_sample->flags; - int stopdepth; - static int lastndl; - - if (flags[1] & 0x01) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Safety stop violation")); - if (flags[1] & 0x08) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Speed alarm")); -#if WANT_CRAZY_WARNINGS - if (flags[1] & 0x06) /* both bits 1 and 2 are a warning */ - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Speed warning")); - if (flags[1] & 0x10) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂ green warning")); -#endif - if (flags[1] & 0x20) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂ ascend warning")); - if (flags[1] & 0x40) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂ ascend alarm")); - /* flags[2] reflects the deco / time bar - * flags[3] reflects more display details on deco and pO2 */ - if (flags[4] & 0x01) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Tank pressure info")); - if (flags[4] & 0x04) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "RGT warning")); - if (flags[4] & 0x08) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "RGT alert")); - if (flags[4] & 0x40) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Tank change suggested")); - if (flags[4] & 0x80) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Depth limit exceeded")); - if (flags[5] & 0x01) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Max deco time warning")); - if (flags[5] & 0x04) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Dive time info")); - if (flags[5] & 0x08) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Dive time alert")); - if (flags[5] & 0x10) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Marker")); - if (flags[6] & 0x02) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "No tank data")); - if (flags[6] & 0x04) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Low battery warning")); - if (flags[6] & 0x08) - add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Low battery alert")); -/* flags[7] reflects the little on screen icons that remind of previous - * warnings / alerts - not useful for events */ - -#if UEMIS_DEBUG & 32 - int i, j; - for (i = 0; i < 8; i++) { - printf(" %d: ", 29 + i); - for (j = 7; j >= 0; j--) - printf("%c", flags[i] & 1 << j ? '1' : '0'); - } - printf("\n"); -#endif - /* now add deco / NDL - * we don't use events but store this in the sample - that makes much more sense - * for the way we display this information - * What we know about the encoding so far: - * flags[3].bit0 | flags[5].bit1 != 0 ==> in deco - * flags[0].bit7 == 1 ==> Safety Stop - * otherwise NDL */ - stopdepth = rel_mbar_to_depth(u_sample->hold_depth, dive); - if ((flags[3] & 1) | (flags[5] & 2)) { - /* deco */ - sample->in_deco = true; - sample->stopdepth.mm = stopdepth; - sample->stoptime.seconds = u_sample->hold_time * 60; - sample->ndl.seconds = 0; - } else if (flags[0] & 128) { - /* safety stop - distinguished from deco stop by having - * both ndl and stop information */ - sample->in_deco = false; - sample->stopdepth.mm = stopdepth; - sample->stoptime.seconds = u_sample->hold_time * 60; - sample->ndl.seconds = lastndl; - } else { - /* NDL */ - sample->in_deco = false; - lastndl = sample->ndl.seconds = u_sample->hold_time * 60; - sample->stopdepth.mm = 0; - sample->stoptime.seconds = 0; - } -#if UEMIS_DEBUG & 32 - printf("%dm:%ds: p_amb_tol:%d surface:%d holdtime:%d holddepth:%d/%d ---> stopdepth:%d stoptime:%d ndl:%d\n", - sample->time.seconds / 60, sample->time.seconds % 60, u_sample->p_amb_tol, dive->dc.surface_pressure.mbar, - u_sample->hold_time, u_sample->hold_depth, stopdepth, sample->stopdepth.mm, sample->stoptime.seconds, sample->ndl.seconds); -#endif -} - -/* - * parse uemis base64 data blob into struct dive - */ -void uemis_parse_divelog_binary(char *base64, void *datap) -{ - int datalen; - int i; - uint8_t *data; - struct sample *sample = NULL; - uemis_sample_t *u_sample; - struct dive *dive = datap; - struct divecomputer *dc = &dive->dc; - int template, gasoffset; - uint8_t active = 0; - char version[5]; - - datalen = uemis_convert_base64(base64, &data); - dive->dc.airtemp.mkelvin = C_to_mkelvin((*(uint16_t *)(data + 45)) / 10.0); - dive->dc.surface_pressure.mbar = *(uint16_t *)(data + 43); - if (*(uint8_t *)(data + 19)) - dive->dc.salinity = SEAWATER_SALINITY; /* avg grams per 10l sea water */ - else - dive->dc.salinity = FRESHWATER_SALINITY; /* grams per 10l fresh water */ - - /* this will allow us to find the last dive read so far from this computer */ - dc->model = strdup("Uemis Zurich"); - dc->deviceid = *(uint32_t *)(data + 9); - dc->diveid = *(uint16_t *)(data + 7); - /* remember the weight units used in this dive - we may need this later when - * parsing the weight */ - uemis_weight_unit(dc->diveid, *(uint8_t *)(data + 24)); - /* dive template in use: - 0 = air - 1 = nitrox (B) - 2 = nitrox (B+D) - 3 = nitrox (B+T+D) - uemis cylinder data is insane - it stores seven tank settings in a block - and the template tells us which of the four groups of tanks we need to look at - */ - gasoffset = template = *(uint8_t *)(data + 115); - if (template == 3) - gasoffset = 4; - if (template == 0) - template = 1; - for (i = 0; i < template; i++) { - float volume = *(float *)(data + 116 + 25 * (gasoffset + i)) * 1000.0; - /* uemis always assumes a working pressure of 202.6bar (!?!?) - I first thought - * it was 3000psi, but testing against all my dives gets me that strange number. - * Still, that's of course completely bogus and shows they don't get how - * cylinders are named in non-metric parts of the world... - * we store the incorrect working pressure to get the SAC calculations "close" - * but the user will have to correct this manually - */ - dive->cylinder[i].type.size.mliter = rint(volume); - dive->cylinder[i].type.workingpressure.mbar = 202600; - dive->cylinder[i].gasmix.o2.permille = *(uint8_t *)(data + 120 + 25 * (gasoffset + i)) * 10; - dive->cylinder[i].gasmix.he.permille = 0; - } - /* first byte of divelog data is at offset 0x123 */ - i = 0x123; - u_sample = (uemis_sample_t *)(data + i); - while ((i <= datalen) && (data[i] != 0 || data[i + 1] != 0)) { - if (u_sample->active_tank != active) { - if (u_sample->active_tank >= MAX_CYLINDERS) { - fprintf(stderr, "got invalid sensor #%d was #%d\n", u_sample->active_tank, active); - } else { - active = u_sample->active_tank; - add_gas_switch_event(dive, dc, u_sample->dive_time, active); - } - } - sample = prepare_sample(dc); - sample->time.seconds = u_sample->dive_time; - sample->depth.mm = rel_mbar_to_depth(u_sample->water_pressure, dive); - sample->temperature.mkelvin = C_to_mkelvin(u_sample->dive_temperature / 10.0); - sample->sensor = active; - sample->cylinderpressure.mbar = - (u_sample->tank_pressure_high * 256 + u_sample->tank_pressure_low) * 10; - sample->cns = u_sample->cns; - uemis_event(dive, dc, sample, u_sample); - finish_sample(dc); - i += 0x25; - u_sample++; - } - if (sample) - dive->dc.duration.seconds = sample->time.seconds - 1; - - /* get data from the footer */ - char buffer[24]; - - snprintf(version, sizeof(version), "%1u.%02u", data[18], data[17]); - add_extra_data(dc, "FW Version", version); - snprintf(buffer, sizeof(buffer), "%08x", *(uint32_t *)(data + 9)); - add_extra_data(dc, "Serial", buffer); - snprintf(buffer, sizeof(buffer), "%d", *(uint16_t *)(data + i + 35)); - add_extra_data(dc, "main battery after dive", buffer); - snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 24), 60)); - add_extra_data(dc, "no fly time", buffer); - snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 26), 60)); - add_extra_data(dc, "no dive time", buffer); - snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 28), 60)); - add_extra_data(dc, "desat time", buffer); - snprintf(buffer, sizeof(buffer), "%u", *(uint16_t *)(data + i + 30)); - add_extra_data(dc, "allowed altitude", buffer); - - return; -} diff --git a/subsurface-core/uemis.h b/subsurface-core/uemis.h deleted file mode 100644 index 1758b4b32..000000000 --- a/subsurface-core/uemis.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * defines and prototypes for the uemis Zurich SDA file parser - */ - -#ifndef UEMIS_H -#define UEMIS_H - -#include -#include "dive.h" - -#ifdef __cplusplus -extern "C" { -#endif - -void uemis_parse_divelog_binary(char *base64, void *divep); -int uemis_get_weight_unit(uint32_t diveid); -void uemis_mark_divelocation(int diveid, int divespot, uint32_t dive_site_uuid); -void uemis_set_divelocation(int divespot, char *text, double longitude, double latitude); -int uemis_get_divespot_id_by_diveid(uint32_t diveid); - -typedef struct -{ - uint16_t dive_time; - uint16_t water_pressure; // (in cbar) - uint16_t dive_temperature; // (in dC) - uint8_t ascent_speed; // (units unclear) - uint8_t work_fact; - uint8_t cold_fact; - uint8_t bubble_fact; - uint16_t ascent_time; - uint16_t ascent_time_opt; - uint16_t p_amb_tol; - uint16_t satt; - uint16_t hold_depth; - uint16_t hold_time; - uint8_t active_tank; - // bloody glib, when compiled for Windows, forces the whole program to use - // the Windows packing rules. So to avoid problems on Windows (and since - // only tank_pressure is currently used and that exactly once) I give in and - // make this silly low byte / high byte 8bit entries - uint8_t tank_pressure_low; // (in cbar) - uint8_t tank_pressure_high; - uint8_t consumption_low; // (units unclear) - uint8_t consumption_high; - uint8_t rgt; // (remaining gas time in minutes) - uint8_t cns; - uint8_t flags[8]; -} __attribute((packed)) uemis_sample_t; - -#ifdef __cplusplus -} -#endif - -#endif // UEMIS_H diff --git a/subsurface-core/units.h b/subsurface-core/units.h deleted file mode 100644 index 029bb64fa..000000000 --- a/subsurface-core/units.h +++ /dev/null @@ -1,280 +0,0 @@ -#ifndef UNITS_H -#define UNITS_H - -#include -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define O2_IN_AIR 209 // permille -#define N2_IN_AIR 781 -#define O2_DENSITY 1429 // mg/Liter -#define N2_DENSITY 1251 -#define HE_DENSITY 179 -#define SURFACE_PRESSURE 1013 // mbar -#define SURFACE_PRESSURE_STRING "1013" -#define ZERO_C_IN_MKELVIN 273150 // mKelvin - -#ifdef __cplusplus -#define M_OR_FT(_m, _f) ((prefs.units.length == units::METERS) ? ((_m) * 1000) : (feet_to_mm(_f))) -#else -#define M_OR_FT(_m, _f) ((prefs.units.length == METERS) ? ((_m) * 1000) : (feet_to_mm(_f))) -#endif - -/* Salinity is expressed in weight in grams per 10l */ -#define SEAWATER_SALINITY 10300 -#define FRESHWATER_SALINITY 10000 - -#include -/* - * Some silly typedefs to make our units very explicit. - * - * Also, the units are chosen so that values can be expressible as - * integers, so that we never have FP rounding issues. And they - * are small enough that converting to/from imperial units doesn't - * really matter. - * - * We also strive to make '0' a meaningless number saying "not - * initialized", since many values are things that may not have - * been reported (eg cylinder pressure or temperature from dive - * computers that don't support them). But sometimes -1 is an even - * more explicit way of saying "not there". - * - * Thus "millibar" for pressure, for example, or "millikelvin" for - * temperatures. Doing temperatures in celsius or fahrenheit would - * make for loss of precision when converting from one to the other, - * and using millikelvin is SI-like but also means that a temperature - * of '0' is clearly just a missing temperature or cylinder pressure. - * - * Also strive to use units that can not possibly be mistaken for a - * valid value in a "normal" system without conversion. If the max - * depth of a dive is '20000', you probably didn't convert from mm on - * output, or if the max depth gets reported as "0.2ft" it was either - * a really boring dive, or there was some missing input conversion, - * and a 60-ft dive got recorded as 60mm. - * - * Doing these as "structs containing value" means that we always - * have to explicitly write out those units in order to get at the - * actual value. So there is hopefully little fear of using a value - * in millikelvin as Fahrenheit by mistake. - * - * We don't actually use these all yet, so maybe they'll change, but - * I made a number of types as guidelines. - */ -typedef int64_t timestamp_t; - -typedef struct -{ - uint32_t seconds; // durations up to 68 yrs -} duration_t; - -typedef struct -{ - int32_t seconds; // offsets up to +/- 34 yrs -} offset_t; - -typedef struct -{ - int32_t mm; -} depth_t; // depth to 2000 km - -typedef struct -{ - int32_t mbar; // pressure up to 2000 bar -} pressure_t; - -typedef struct -{ - uint16_t mbar; -} o2pressure_t; // pressure up to 65 bar - -typedef struct -{ - int16_t degrees; -} bearing_t; // compass bearing - -typedef struct -{ - uint32_t mkelvin; // up to 1750 degrees K (temperatures in K are always positive) -} temperature_t; - -typedef struct -{ - int mliter; -} volume_t; - -typedef struct -{ - int permille; -} fraction_t; - -typedef struct -{ - int grams; -} weight_t; - -typedef struct -{ - int udeg; -} degrees_t; - -static inline double udeg_to_radians(int udeg) -{ - return (udeg * M_PI) / (1000000.0 * 180.0); -} - -static inline double grams_to_lbs(int grams) -{ - return grams / 453.6; -} - -static inline int lbs_to_grams(double lbs) -{ - return rint(lbs * 453.6); -} - -static inline double ml_to_cuft(int ml) -{ - return ml / 28316.8466; -} - -static inline double cuft_to_l(double cuft) -{ - return cuft * 28.3168466; -} - -static inline double mm_to_feet(int mm) -{ - return mm * 0.00328084; -} - -static inline double m_to_mile(int m) -{ - return m / 1609.344; -} - -static inline unsigned long feet_to_mm(double feet) -{ - return rint(feet * 304.8); -} - -static inline int to_feet(depth_t depth) -{ - return rint(mm_to_feet(depth.mm)); -} - -static inline double mkelvin_to_C(int mkelvin) -{ - return (mkelvin - ZERO_C_IN_MKELVIN) / 1000.0; -} - -static inline double mkelvin_to_F(int mkelvin) -{ - return mkelvin * 9 / 5000.0 - 459.670; -} - -static inline unsigned long F_to_mkelvin(double f) -{ - return rint((f - 32) * 1000 / 1.8 + ZERO_C_IN_MKELVIN); -} - -static inline unsigned long C_to_mkelvin(double c) -{ - return rint(c * 1000 + ZERO_C_IN_MKELVIN); -} - -static inline double psi_to_bar(double psi) -{ - return psi / 14.5037738; -} - -static inline long psi_to_mbar(double psi) -{ - return rint(psi_to_bar(psi) * 1000); -} - -static inline int to_PSI(pressure_t pressure) -{ - return rint(pressure.mbar * 0.0145037738); -} - -static inline double bar_to_atm(double bar) -{ - return bar / SURFACE_PRESSURE * 1000; -} - -static inline double mbar_to_atm(int mbar) -{ - return (double)mbar / SURFACE_PRESSURE; -} - -static inline int mbar_to_PSI(int mbar) -{ - pressure_t p = { mbar }; - return to_PSI(p); -} - -/* - * We keep our internal data in well-specified units, but - * the input and output may come in some random format. This - * keeps track of those units. - */ -/* turns out in Win32 PASCAL is defined as a calling convention */ -#ifdef WIN32 -#undef PASCAL -#endif -struct units { - enum LENGHT { - METERS, - FEET - } length; - enum VOLUME { - LITER, - CUFT - } volume; - enum PRESSURE { - BAR, - PSI, - PASCAL - } pressure; - enum TEMPERATURE { - CELSIUS, - FAHRENHEIT, - KELVIN - } temperature; - enum WEIGHT { - KG, - LBS - } weight; - enum TIME { - SECONDS, - MINUTES - } vertical_speed_time; -}; - -/* - * We're going to default to SI units for input. Yes, - * technically the SI unit for pressure is Pascal, but - * we default to bar (10^5 pascal), which people - * actually use. Similarly, C instead of Kelvin. - * And kg instead of g. - */ -#define SI_UNITS \ - { \ - .length = METERS, .volume = LITER, .pressure = BAR, .temperature = CELSIUS, .weight = KG, .vertical_speed_time = MINUTES \ - } - -#define IMPERIAL_UNITS \ - { \ - .length = FEET, .volume = CUFT, .pressure = PSI, .temperature = FAHRENHEIT, .weight = LBS, .vertical_speed_time = MINUTES \ - } - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/subsurface-core/version.c b/subsurface-core/version.c deleted file mode 100644 index 764e4d2db..000000000 --- a/subsurface-core/version.c +++ /dev/null @@ -1,18 +0,0 @@ -#include "ssrf-version.h" - -const char *subsurface_git_version(void) -{ - return GIT_VERSION_STRING; -} - -const char *subsurface_canonical_version(void) -{ - return CANONICAL_VERSION_STRING; -} - -#ifdef SUBSURFACE_MOBILE -const char *subsurface_mobile_version(void) -{ - return MOBILE_VERSION_STRING; -} -#endif diff --git a/subsurface-core/version.h b/subsurface-core/version.h deleted file mode 100644 index 0a3204bd9..000000000 --- a/subsurface-core/version.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef VERSION_H -#define VERSION_H - -#ifdef __cplusplus -extern "C" { -#endif - -const char *subsurface_git_version(void); -const char *subsurface_canonical_version(void); - -#ifdef SUBSURFACE_MOBILE -const char *subsurface_mobile_version(void); -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/subsurface-core/webservice.h b/subsurface-core/webservice.h deleted file mode 100644 index 052b8aae7..000000000 --- a/subsurface-core/webservice.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef WEBSERVICE_H -#define WEBSERVICE_H - -#ifdef __cplusplus -extern "C" { -#endif - -//extern void webservice_download_dialog(void); -//extern bool webservice_request_user_xml(const gchar *, gchar **, unsigned int *, unsigned int *); -extern int divelogde_upload(char *fn, char **error); -extern unsigned int download_dialog_parse_response(char *xmldata, unsigned int len); - -enum { - DD_STATUS_OK, - DD_STATUS_ERROR_CONNECT, - DD_STATUS_ERROR_ID, - DD_STATUS_ERROR_PARSE, -}; - - -#ifdef __cplusplus -} -#endif -#endif // WEBSERVICE_H diff --git a/subsurface-core/windows.c b/subsurface-core/windows.c deleted file mode 100644 index 58d3beaad..000000000 --- a/subsurface-core/windows.c +++ /dev/null @@ -1,454 +0,0 @@ -/* windows.c */ -/* implements Windows specific functions */ -#include -#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 -} - -bool subsurface_user_is_root() -{ - /* FIXME: Detect admin rights */ - return (false); -} diff --git a/subsurface-core/windowtitleupdate.cpp b/subsurface-core/windowtitleupdate.cpp deleted file mode 100644 index 963455f1d..000000000 --- a/subsurface-core/windowtitleupdate.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "windowtitleupdate.h" - -WindowTitleUpdate *WindowTitleUpdate::m_instance = NULL; - -WindowTitleUpdate::WindowTitleUpdate(QObject *parent) : QObject(parent) -{ - Q_ASSERT_X(m_instance == NULL, "WindowTitleUpdate", "WindowTitleUpdate recreated!"); - - m_instance = this; -} - -WindowTitleUpdate *WindowTitleUpdate::instance() -{ - return m_instance; -} - -WindowTitleUpdate::~WindowTitleUpdate() -{ - m_instance = NULL; -} - -void WindowTitleUpdate::emitSignal() -{ - emit updateTitle(); -} - -extern "C" void updateWindowTitle() -{ - WindowTitleUpdate *wt = WindowTitleUpdate::instance(); - if (wt) - wt->emitSignal(); -} diff --git a/subsurface-core/windowtitleupdate.h b/subsurface-core/windowtitleupdate.h deleted file mode 100644 index 8650e5868..000000000 --- a/subsurface-core/windowtitleupdate.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef WINDOWTITLEUPDATE_H -#define WINDOWTITLEUPDATE_H - -#include - -class WindowTitleUpdate : public QObject -{ - Q_OBJECT -public: - explicit WindowTitleUpdate(QObject *parent = 0); - ~WindowTitleUpdate(); - static WindowTitleUpdate *instance(); - void emitSignal(); -signals: - void updateTitle(); -private: - static WindowTitleUpdate *m_instance; -}; - -#endif // WINDOWTITLEUPDATE_H diff --git a/subsurface-core/worldmap-options.h b/subsurface-core/worldmap-options.h deleted file mode 100644 index 177443563..000000000 --- a/subsurface-core/worldmap-options.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef WORLDMAP_OPTIONS_H -#define WORLDMAP_OPTIONS_H - -const char *map_options = "center: new google.maps.LatLng(0,0),\n\tzoom: 3,\n\tminZoom: 2,\n\tmapTypeId: google.maps.MapTypeId.SATELLITE\n\t"; -const char *css = "\n\thtml { height: 100% }\n\tbody { height: 100%; margin: 0; padding: 0 }\n\t#map-canvas { height: 100% }\n"; - -#endif // WORLDMAP-OPTIONS_H diff --git a/subsurface-core/worldmap-save.c b/subsurface-core/worldmap-save.c deleted file mode 100644 index e7e8bcc30..000000000 --- a/subsurface-core/worldmap-save.c +++ /dev/null @@ -1,117 +0,0 @@ -// Clang has a bug on zero-initialization of C structs. -#pragma clang diagnostic ignored "-Wmissing-field-initializers" - -#include -#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); - put_string(b, "

"); - put_HTML_quoted(b, translate("gettextFromC", "Max. depth:")); - put_HTML_depth(b, dive, " ", "

"); - 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 deleted file mode 100644 index 102ea40e5..000000000 --- a/subsurface-core/worldmap-save.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef WORLDMAP_SAVE_H -#define WORLDMAP_SAVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -extern void export_worldmap_HTML(const char *file_name, const bool selected_only); - - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/subsurface-desktop-helper.cpp b/subsurface-desktop-helper.cpp index 420f75249..ef349ad3d 100644 --- a/subsurface-desktop-helper.cpp +++ b/subsurface-desktop-helper.cpp @@ -1,27 +1,26 @@ /* qt-gui.cpp */ /* Qt UI implementation */ -#include "dive.h" -#include "display.h" +#include "core/dive.h" +#include "core/display.h" #include "desktop-widgets/mainwindow.h" -#include "helpers.h" -#include "pluginmanager.h" +#include "core/helpers.h" +#include "core/pluginmanager.h" #include #include #include #include - -#include "qt-gui.h" +#include "core/qt-gui.h" #ifdef SUBSURFACE_MOBILE #include #include #include #include -#include "qt-mobile/qmlmanager.h" +#include "mobile-widgets/qmlmanager.h" #include "qt-models/divelistmodel.h" -#include "qt-mobile/qmlprofile.h" +#include "mobile-widgets/qmlprofile.h" QObject *qqWindowObject = NULL; #endif diff --git a/subsurface-desktop-main.cpp b/subsurface-desktop-main.cpp index b93b642bd..0bdad42a2 100644 --- a/subsurface-desktop-main.cpp +++ b/subsurface-desktop-main.cpp @@ -5,16 +5,16 @@ #include #include -#include "dive.h" -#include "qt-gui.h" -#include "subsurfacestartup.h" +#include "core/dive.h" +#include "core/qt-gui.h" +#include "core/subsurfacestartup.h" #include "desktop-widgets/mainwindow.h" #include "desktop-widgets/maintab.h" #include "profile-widget/profilewidget2.h" -#include "preferences/preferencesdialog.h" +#include "desktop-widgets/preferences/preferencesdialog.h" #include "desktop-widgets/diveplanner.h" -#include "subsurface-core/color.h" -#include "qthelper.h" +#include "core/color.h" +#include "core/qthelper.h" #include #include diff --git a/subsurface-mobile-helper.cpp b/subsurface-mobile-helper.cpp index 214d65e79..b4a54d11c 100644 --- a/subsurface-mobile-helper.cpp +++ b/subsurface-mobile-helper.cpp @@ -1,25 +1,25 @@ /* qt-gui.cpp */ /* Qt UI implementation */ -#include "dive.h" -#include "display.h" -#include "helpers.h" +#include "core/dive.h" +#include "core/display.h" +#include "core/helpers.h" #include #include #include #include -#include "qt-gui.h" +#include "core/qt-gui.h" #include #include #include #include #include -#include "qt-mobile/qmlmanager.h" +#include "mobile-widgets/qmlmanager.h" #include "qt-models/divelistmodel.h" #include "qt-models/gpslistmodel.h" -#include "qt-mobile/qmlprofile.h" +#include "mobile-widgets/qmlprofile.h" QObject *qqWindowObject = NULL; diff --git a/subsurface-mobile-main.cpp b/subsurface-mobile-main.cpp index 940c9225b..e5965090b 100644 --- a/subsurface-mobile-main.cpp +++ b/subsurface-mobile-main.cpp @@ -5,12 +5,12 @@ #include #include -#include "dive.h" -#include "qt-gui.h" -#include "subsurfacestartup.h" -#include "subsurface-core/color.h" -#include "qthelper.h" -#include "helpers.h" +#include "core/dive.h" +#include "core/qt-gui.h" +#include "core/subsurfacestartup.h" +#include "core/color.h" +#include "core/qthelper.h" +#include "core/helpers.h" #include #include -- cgit v1.2.3-70-g09d2