diff --git a/CMakeLists.txt b/CMakeLists.txt index 765a4e158..bc059abef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,479 +1,480 @@ PROJECT(kstars CXX C) set (KStars_VERSION_MAJOR 3) set (KStars_VERSION_MINOR 0) set (KStars_VERSION_REVISION 0) set (CMAKE_CXX_STANDARD 11) #Build KStars Lite with -DKSTARS_LITE=ON option(BUILD_KSTARS_LITE "Build KStars Lite" OFF) # minimal requirements cmake_minimum_required (VERSION 2.8.12 FATAL_ERROR) string(TIMESTAMP KSTARS_BUILD_TS UTC) if(BUILD_KSTARS_LITE) set (QT_MIN_VERSION "5.7.0") #QtQuickControls 2 is available only in the Qt 5.7.0 # Download the translations if KStars Lite is built SET(KDE_L10N_AUTO_TRANSLATIONS ON) SET(KDE_L10N_BRANCH "trunk") else() set (QT_MIN_VERSION "5.4.0") endif() # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) # Instruct CMake to run moc automatically when needed. set(CMAKE_AUTOMOC ON) # Ccache support IF (ANDROID OR UNIX OR APPLE) FIND_PROGRAM(CCACHE_FOUND ccache) SET(CCACHE_SUPPORT OFF CACHE BOOL "Enable ccache support") IF ((CCACHE_FOUND OR ANDROID) AND CCACHE_SUPPORT MATCHES ON) SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) ENDIF () ENDIF () if(CMAKE_SYSTEM_NAME STREQUAL Android) add_definitions(-DANDROID -D__STDC_LIMIT_MACROS) set(ANDROID TRUE) endif() if("${CMAKE_TOOLCHAIN_FILE}" MATCHES "android.toolchain.cmake$") include(${CMAKE_BINARY_DIR}/kf5/kde/install/share/ECM/cmake/ECMConfig.cmake) endif() find_package(ECM 1.7.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ${CMAKE_MODULE_PATH}) if(BUILD_KSTARS_LITE) if("${CMAKE_TOOLCHAIN_FILE}" MATCHES "android.toolchain.cmake$") set(QT_ANDROID $ENV{QT_ANDROID}) set(Qt5Core_DIR "${QT_ANDROID}/lib/cmake/Qt5Core") set(Qt5Gui_DIR "${QT_ANDROID}/lib/cmake/Qt5Gui") set(Qt5Network_DIR "${QT_ANDROID}/lib/cmake/Qt5Network") set(Qt5Positioning_DIR "${QT_ANDROID}/lib/cmake/Qt5Positioning") set(Qt5Qml_DIR "${QT_ANDROID}/lib/cmake/Qt5Qml") set(Qt5Quick_DIR "${QT_ANDROID}/lib/cmake/Qt5Quick") set(Qt5Widgets_DIR "${QT_ANDROID}/lib/cmake/Qt5Widgets") include(${QT_ANDROID}/lib/cmake/Qt5/Qt5Config.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Core/Qt5CoreConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Gui/Qt5GuiConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Qml/Qt5QmlConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Quick/Qt5QuickConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5QuickControls2/Qt5QuickControls2Config.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Xml/Qt5XmlConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Svg/Qt5SvgConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Sql/Qt5SqlConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Network/Qt5NetworkConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Positioning/Qt5PositioningConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5PositioningQuick/Qt5PositioningQuickConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5Concurrent/Qt5ConcurrentConfig.cmake) include(${QT_ANDROID}/lib/cmake/Qt5AndroidExtras/Qt5AndroidExtrasConfig.cmake) else() if(ANDROID) list(APPEND QT_EXTRA_COMPONENTS AndroidExtras) else() list(APPEND QT_EXTRA_COMPONENTS PrintSupport) endif() find_package(Qt5 5.7 REQUIRED COMPONENTS Gui Qml Quick QuickControls2 Xml Svg Sql Network Positioning PositioningQuick Concurrent WebSockets ${QT_EXTRA_COMPONENTS}) endif() else() #find_package(Qt5 5.4 REQUIRED COMPONENTS Gui Qml Quick Xml Sql Svg Network PrintSupport Positioning Concurrent) find_package(Qt5 5.4 REQUIRED COMPONENTS Gui Qml Quick Xml Sql Svg Network PrintSupport Concurrent WebSockets) endif() include(ExternalProject) include(ECMInstallIcons) include(ECMAddAppIcon) include(KDEInstallDirs) include(MacroBoolTo01) include(ECMQtDeclareLoggingCategory) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(FeatureSummary) # Load the frameworks we need if(BUILD_KSTARS_LITE) if("${CMAKE_TOOLCHAIN_FILE}" MATCHES "android.toolchain.cmake$") set(KF5_VERSION 5.28.0) set(KF5_HOST_TOOLING /usr/lib/x86_64-linux-gnu/cmake/) include(${CMAKE_BINARY_DIR}/kf5/kde/install/lib/cmake/KF5Config/KF5ConfigConfig.cmake) include(${CMAKE_BINARY_DIR}/kf5/kde/install/lib/cmake/KF5I18n/KF5I18nConfig.cmake) include(${CMAKE_BINARY_DIR}/kf5/kde/install/lib/cmake/KF5Plotting/KF5PlottingConfig.cmake) else() # Find Optional package NotifyConfig for desktop notifications find_package(KF5 COMPONENTS NotifyConfig) MACRO_BOOL_TO_01(KF5NotifyConfig_FOUND HAVE_NOTIFYCONFIG) find_package(KF5 REQUIRED COMPONENTS Auth Config Crash WidgetsAddons NewStuff I18n KIO XmlGui Plotting Notifications) endif() else(BUILD_KSTARS_LITE) # Find Optional package NotifyConfig for desktop notifications find_package(KF5 COMPONENTS NotifyConfig) MACRO_BOOL_TO_01(KF5NotifyConfig_FOUND HAVE_NOTIFYCONFIG) find_package(KF5 REQUIRED COMPONENTS Auth Config Crash DocTools WidgetsAddons NewStuff I18n KIO XmlGui Plotting Notifications ) endif(BUILD_KSTARS_LITE) ## Eigen3 Library find_package(Eigen3 REQUIRED) add_definitions(${EIGEN_DEFINITIONS}) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR}) ## CFITSIO Library if(ANDROID) if("${CMAKE_TOOLCHAIN_FILE}" MATCHES "android.toolchain.cmake$") set(extra_cmake "-DM_LIB=-lm") endif() find_program(DOS2UNIX dos2unix) if (NOT DOS2UNIX) message(FATAL_ERROR "Could not find dos2unix") endif() externalproject_add(cfitsio SOURCE_DIR "${CMAKE_BINARY_DIR}/packaging/android/3rdparty/cfitsio" URL https://heasarc.gsfc.nasa.gov/FTP/software/fitsio/c/cfitsio3370.tar.gz PATCH_COMMAND bash -c "cd ${CMAKE_BINARY_DIR}/packaging/android/3rdparty/cfitsio && ${DOS2UNIX} ${CMAKE_BINARY_DIR}/packaging/android/3rdparty/cfitsio/CMakeLists.txt && patch -p1 < ${CMAKE_SOURCE_DIR}/packaging/android/3rdparty/cfitsio.patch" CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} ${extra_cmake} BUILD_COMMAND make cfitsio BUILD_IN_SOURCE 1 INSTALL_COMMAND "") set(CFITSIO_FOUND TRUE) set(CFITSIO_INCLUDE_DIR PUBLIC ${CMAKE_BINARY_DIR}/packaging/android/3rdparty/cfitsio) set(CFITSIO_LIBRARIES ${CMAKE_BINARY_DIR}/packaging/android/3rdparty/cfitsio/libcfitsio.a) else() if(BUILD_KSTARS_LITE) find_package(CFitsio REQUIRED) else() find_package(CFitsio) endif() endif() MACRO_BOOL_TO_01(CFITSIO_FOUND HAVE_CFITSIO) set_package_properties(CFitsio PROPERTIES DESCRIPTION "FITS IO Library" URL "http://heasarc.gsfc.nasa.gov/fitsio/fitsio.html" TYPE OPTIONAL PURPOSE "Support for the FITS (Flexible Image Transport System) data format in KStars.") ## INDI Library if (ANDROID) set(QT_ANDROID $ENV{QT_ANDROID}) if("${CMAKE_TOOLCHAIN_FILE}" MATCHES "android.toolchain.cmake$") set(extra_cmake -DCMAKE_AR=${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar) endif() externalproject_add(indi SOURCE_DIR "${CMAKE_BINARY_DIR}/packaging/android/indi" URL https://github.com/indilib/indi/archive/master.zip CONFIGURE_COMMAND cd libindi && cmake . -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} \\ -DINDI_BUILD_POSIX_CLIENT=OFF -DINDI_BUILD_SERVER=OFF -DINDI_BUILD_DRIVERS=OFF -DINDI_BUILD_UNITTESTS=OFF \\ -DINDI_BUILD_DRIVERS=OFF -DINDI_BUILD_QT5_CLIENT=ON -DINDI_CALCULATE_MINMAX=ON \\ -DCFITSIO_DIR=${CMAKE_BINARY_DIR}/packaging/android/3rdparty/cfitsio -DQT_ANDROID=${QT_ANDROID} -DCMAKE_PREFIX_PATH=${QT_ANDROID} ${extra_cmake} BUILD_COMMAND make -C libindi BUILD_IN_SOURCE 1 INSTALL_COMMAND "") set(INDI_FOUND TRUE) set(INDI_INCLUDE_DIR ${CMAKE_BINARY_DIR}/packaging/android/indi/libindi/libs/indibase ${CMAKE_BINARY_DIR}/packaging/android/indi/libindi ${CMAKE_BINARY_DIR}/packaging/android/indi/libindi/libs) set(INDI_CLIENT_ANDROID_LIBRARIES ${CMAKE_BINARY_DIR}/packaging/android/indi/libindi/libindiclientqt.a) else () find_package(INDI 1.7.1) endif () MACRO_BOOL_TO_01(INDI_FOUND HAVE_INDI) set_package_properties(INDI PROPERTIES DESCRIPTION "Astronomical instrumentation control" URL "http://www.indilib.org" TYPE OPTIONAL PURPOSE "Support for controlling astronomical devices on Linux with KStars.") ## Libraw if(BUILD_KSTARS_LITE AND ANDROID) # OpenMP needed for LibRaw set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp -DLIBRAW_USE_OPENMP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp -DLIBRAW_USE_OPENMP") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgomp") externalproject_add(raw SOURCE_DIR "${CMAKE_BINARY_DIR}/packaging/android/3rdparty/libraw" URL https://www.libraw.org/data/LibRaw-0.17.2.tar.gz PATCH_COMMAND bash -c "cd ${CMAKE_BINARY_DIR}/packaging/android/3rdparty/libraw && wget -qO- https://github.com/LibRaw/LibRaw-cmake/archive/master.zip | jar xvf /dev/stdin && rm -rf cmake && mv -f LibRaw-cmake-master/CMakeLists.txt LibRaw-cmake-master/cmake . && patch -p1 < ${CMAKE_SOURCE_DIR}/packaging/android/3rdparty/libraw.patch" CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DENABLE_OPENMP=OFF -DENABLE_LCMS=OFF BUILD_COMMAND make raw BUILD_IN_SOURCE 1 INSTALL_COMMAND "") include_directories(${CMAKE_BINARY_DIR}/packaging/android/3rdparty/libraw) set(LIBRAW_LIBRARIES ${CMAKE_BINARY_DIR}/packaging/android/3rdparty/libraw/libraw.a) set(LibRaw_FOUND true) else() find_package(LibRaw) endif() MACRO_BOOL_TO_01(LibRaw_FOUND HAVE_LIBRAW) set_package_properties(LibRaw PROPERTIES DESCRIPTION "Library for reading RAW files" URL "http://www.libraw.org" TYPE OPTIONAL PURPOSE "Support for reading and displaying RAW files in KStars.") ## WCS Library find_package(WCSLIB) MACRO_BOOL_TO_01(WCSLIB_FOUND HAVE_WCSLIB) set_package_properties(WCSLIB PROPERTIES DESCRIPTION "World Coordinate System library" URL "http://www.atnf.csiro.au/people/mcalabre/WCS" TYPE OPTIONAL PURPOSE "WCS enables KStars to read and process world coordinate systems in FITS header.") ## XPlanet find_package(Xplanet) set_package_properties(Xplanet PROPERTIES DESCRIPTION "Renders an image of all the major planets and most satellites" URL "http://xplanet.sourceforge.net" TYPE RUNTIME PURPOSE "Gives KStars support for xplanet.") # Qt5 Data Visualization find_package(Qt5DataVisualization) MACRO_BOOL_TO_01(Qt5DataVisualization_FOUND HAVE_DATAVISUALIZATION) ## Astrometry.net find_package(AstrometryNet) set_package_properties(AstrometryNet PROPERTIES DESCRIPTION "Astrometrics Library" URL "http://www.astrometry.net" TYPE RUNTIME PURPOSE "Support for plate solving in KStars.") ## Key Chain find_package(Qt5Keychain) MACRO_BOOL_TO_01(Qt5Keychain_FOUND HAVE_KEYCHAIN) ## OpenGL find_package(OpenGL) set_package_properties(OpenGL PROPERTIES DESCRIPTION "Open Graphics Library" URL "http://www.opengl.org" TYPE OPTIONAL PURPOSE "Support for hardware rendering in KStars.") if (${KF5_VERSION} VERSION_GREATER 5.17.0) SET(HAVE_KF5WIT 1) else() SET(HAVE_KF5WIT 0) endif() add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) add_definitions(-DQT_NO_URL_CAST_FROM_STRING) if (CMAKE_BUILD_TYPE STREQUAL "Debug") add_definitions(-DQT_STRICT_ITERATORS) endif() add_definitions(-DQT_NO_CAST_TO_ASCII) # Needed for htmesh, and libraw kde_enable_exceptions() if (UNIX) # TEMPORARY: To disable QCustomPlot warning until 2.0.0 is released which fixes these warnings SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-non-virtual-dtor") # Optimize binary size by dropping unneeded symbols at linking stage if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") endif() endif(UNIX) # Optimize binary size by dropping unneeded symbols at linking stage if (ANDROID) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -fvisibility=hidden -fvisibility-inlines-hidden") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") endif () # Add security (hardening flags) IF (UNIX OR APPLE OR ANDROID) SET(SEC_COMP_FLAGS "-D_FORTIFY_SOURCE=2 -fstack-protector-all -Wcast-align -fPIE") IF (NOT ANDROID AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT APPLE) SET(SEC_COMP_FLAGS "${SEC_COMP_FLAGS} -Wa,--noexecstack") ENDIF () SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${SEC_COMP_FLAGS}") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${SEC_COMP_FLAGS}") SET(SEC_LINK_FLAGS "") IF (NOT APPLE) SET(SEC_LINK_FLAGS "${SEC_LINK_FLAGS} -Wl,-z,nodump -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now") ENDIF () IF (NOT ANDROID AND NOT APPLE) SET(SEC_LINK_FLAGS "${SEC_LINK_FLAGS} -pie") ENDIF () SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${SEC_LINK_FLAGS}") SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${SEC_LINK_FLAGS}") ENDIF () # Clang Format support IF (UNIX OR APPLE) SET(FORMAT_CODE OFF CACHE BOOL "Enable Clang Format") IF (FORMAT_CODE MATCHES ON) FILE(GLOB_RECURSE ALL_SOURCE_FILES *.c *.cpp *.h) FOREACH(SOURCE_FILE ${ALL_SOURCE_FILES}) STRING(FIND ${SOURCE_FILE} ${CMAKE_SOURCE_DIR} DIR_FOUND) IF (NOT ${DIR_FOUND} EQUAL 0) LIST(REMOVE_ITEM ALL_SOURCE_FILES ${SOURCE_FILE}) ENDIF () ENDFOREACH () FIND_PROGRAM(CLANGFORMAT_EXE NAMES clang-format-5.0) IF (CLANGFORMAT_EXE) ADD_CUSTOM_TARGET(clang-format COMMAND ${CLANGFORMAT_EXE} -style=file -i ${ALL_SOURCE_FILES}) ENDIF () ENDIF () ENDIF () SET(FIX_WARNINGS OFF CACHE BOOL "Enable strict compilation mode to turn compiler warnings to errors") # Warning, debug and linker flags IF (UNIX OR APPLE) SET(COMP_FLAGS "") SET(LINKER_FLAGS "") # Verbose warnings and turns all to errors SET(COMP_FLAGS "${COMP_FLAGS} -Wall -Wextra") IF (FIX_WARNINGS) SET(COMP_FLAGS "${COMP_FLAGS} -Werror") ENDIF () # Omit problematic warnings IF (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") SET(COMP_FLAGS "${COMP_FLAGS} -Wno-unused-but-set-variable") ENDIF () IF (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6.9.9) SET(COMP_FLAGS "${COMP_FLAGS} -Wno-format-truncation") ENDIF () IF (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") SET(COMP_FLAGS "${COMP_FLAGS} -Wno-nonnull -Wno-deprecated-declarations") ENDIF () IF (BUILD_KSTARS_LITE AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") SET(COMP_FLAGS "${COMP_FLAGS} -Wno-overloaded-virtual") ENDIF () SET(COMP_FLAGS "${COMP_FLAGS} -g") # Note: The following flags are problematic on older systems with gcc 4.8 IF (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9.9)) IF (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") SET(COMP_FLAGS "${COMP_FLAGS} -Wno-unused-command-line-argument") ENDIF () FIND_PROGRAM(LDGOLD_FOUND ld.gold) SET(LDGOLD_SUPPORT OFF CACHE BOOL "Enable ld.gold support") # Optional ld.gold is 2x faster than normal ld IF (LDGOLD_FOUND AND LDGOLD_SUPPORT MATCHES ON AND NOT APPLE AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES arm) SET(LINKER_FLAGS "${LINKER_FLAGS} -fuse-ld=gold") # We have Gsl library what is a special case for linking: # The gsl library must be linked with cblas. There are two alternatives for this: libcblas or libgslcblas. # For example, CMake gets the GSL_LIBRARIES linking flags from the pkgconfig (gsl.pc) file on Ubuntu. # This file defines -lgsl -lglscblas for linking flags and if KStars is compiled with Clang, the linker # finds out magically that KStars must be linked against glscblas library, but gslcblas is omitted in linking # stage if KStars is built with gcc. The linker must be instructed explicitly to link against all libraries # passed on command line by -Wl,--no-as-needed. SET(LINKER_FLAGS "${LINKER_FLAGS} -Wl,--no-as-needed") # Use Identical Code Folding SET(COMP_FLAGS "${COMP_FLAGS} -ffunction-sections") SET(LINKER_FLAGS "${LINKER_FLAGS} -Wl,--icf=safe") # Compress the debug sections # Note: Before valgrind 3.12.0, patch should be applied for valgrind (https://bugs.kde.org/show_bug.cgi?id=303877) IF (NOT APPLE AND NOT ANDROID AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES arm AND NOT CMAKE_CXX_CLANG_TIDY) SET(COMP_FLAGS "${COMP_FLAGS} -Wa,--compress-debug-sections") SET(LINKER_FLAGS "${LINKER_FLAGS} -Wl,--compress-debug-sections=zlib") ENDIF () ENDIF () ENDIF () # Apply the flags SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMP_FLAGS}") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMP_FLAGS}") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}") SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}") ENDIF () # Sanitizer support SET(SANITIZERS OFF CACHE BOOL "Sanitizer support for gcc and Clang") IF (SANITIZERS AND ((UNIX AND (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR CMAKE_COMPILER_IS_GNUCXX) OR (APPLE AND (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" OR CMAKE_COMPILER_IS_GNUCXX)))) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer") SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address,undefined -fno-omit-frame-pointer") ENDIF () # Unity build support SET(UNITY_BUILD OFF CACHE BOOL "Enable Unity Build") INCLUDE(UnityBuild) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config-kstars.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kstars.h ) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/kstars/version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kstars/version.h ) # let our config.h be found first in any case include_directories (BEFORE ${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(doc) add_subdirectory(kstars) add_subdirectory(datahandlers) IF (NOT ANDROID) IF (BUILD_TESTING) enable_testing(true) add_subdirectory(Tests) ENDIF() # Make it possible to use the po files fetched by the fetch-translations step ki18n_install("${CMAKE_CURRENT_BINARY_DIR}/po") ENDIF () install(FILES org.kde.kstars.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) # Only build k5auth for Linux -if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") - # This helper file is generated to download astrometry.net index files. - add_executable(kauth_kstars_helper kstars/auxiliary/downloadhelper.cpp ${helper_mocs}) - target_link_libraries(kauth_kstars_helper Qt5::Core) - target_link_libraries(kauth_kstars_helper Qt5::Widgets) - target_link_libraries(kauth_kstars_helper KF5::Auth) - install(TARGETS kauth_kstars_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) - kauth_install_helper_files(kauth_kstars_helper org.kde.kf5auth.kstars root) - kauth_install_actions(org.kde.kf5auth.kstars org.kde.kf5auth.kstars.actions) -ENDIF () +# JM 2018-09-26: KAuth is disabled due to reliability issues. +#if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") +# # This helper file is generated to download astrometry.net index files. +# add_executable(kauth_kstars_helper kstars/auxiliary/downloadhelper.cpp ${helper_mocs}) +# target_link_libraries(kauth_kstars_helper Qt5::Core) +# target_link_libraries(kauth_kstars_helper Qt5::Widgets) +# target_link_libraries(kauth_kstars_helper KF5::Auth) +# install(TARGETS kauth_kstars_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) +# kauth_install_helper_files(kauth_kstars_helper org.kde.kf5auth.kstars root) +# kauth_install_actions(org.kde.kf5auth.kstars org.kde.kf5auth.kstars.actions) +#ENDIF () # Final package generation if ("${CMAKE_TOOLCHAIN_FILE}" MATCHES "android.toolchain.cmake$") set(EXPORT_DIR "${CMAKE_BINARY_DIR}/packaging/android/kstars_build_apk/") set(ANDROID_APK_DIR "${CMAKE_SOURCE_DIR}/packaging/android/apk/") set(EXECUTABLE_DESTINATION_PATH "${EXPORT_DIR}/libs/armeabi-v7a/libkstars.so") set(ANDROID_NDK $ENV{CMAKE_ANDROID_NDK}) set(ANDROID_SDK_ROOT $ENV{ANDROID_SDK_ROOT}) set(ANDROID_API_LEVEL $ENV{ANDROID_API_LEVEL}) set(ANDROID_TOOLCHAIN arm-linux-androideabi) set(ANDROID_GCC_VERSION 4.9) set(ANDROID_ABI armeabi-v7a) set(_HOST "${CMAKE_HOST_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}") string(TOLOWER "${_HOST}" _HOST) set(ANDROID_SDK_BUILD_TOOLS_REVISION 21.1.1) set(ANDROID_KEYSTORE $ENV{ANDROID_KEYSTORE}) set(ANDROID_KEYSTORE_ALIAS $ENV{ANDROID_KEYSTORE_ALIAS}) configure_file("${CMAKE_SOURCE_DIR}/packaging/android/deployment-file.json.in" "${CMAKE_BINARY_DIR}/packaging/android/kstars-deployment.json.in") add_custom_target(create-apk-debug-kstars COMMAND cmake -E echo "Generating $ with $/packaging/androiddeployqt" COMMAND cmake -E remove_directory "${EXPORT_DIR}" COMMAND cmake -E copy_directory "${CMAKE_SOURCE_DIR}/packaging/android/apk" "${EXPORT_DIR}" COMMAND cmake -E copy "$" "${EXECUTABLE_DESTINATION_PATH}" COMMAND cmake -DINPUT_FILE="${CMAKE_BINARY_DIR}/packaging/android/kstars-deployment.json.in" -DOUTPUT_FILE="${CMAKE_BINARY_DIR}/packaging/android/kstars-deployment.json" "-DTARGET_DIR=${CMAKE_BINARY_DIR}/kstars" "-DTARGET_NAME=kstars" "-DEXPORT_DIR=${CMAKE_INSTALL_PREFIX}" -P ${CMAKE_SOURCE_DIR}/packaging/android/specifydependencies.cmake COMMAND $/packaging/androiddeployqt --input "${CMAKE_BINARY_DIR}/packaging/android/kstars-deployment.json" --output "${EXPORT_DIR}" --android-platform android-${ANDROID_API_LEVEL} --debug --deployment bundled "\\$(ARGS)" ) add_custom_target(create-apk-release-kstars COMMAND cmake -E echo "Generating $ with $/packaging/androiddeployqt" COMMAND cmake -E remove_directory "${EXPORT_DIR}" COMMAND cmake -E copy_directory "${CMAKE_SOURCE_DIR}/packaging/android/apk" "${EXPORT_DIR}" COMMAND cmake -E copy "$" "${EXECUTABLE_DESTINATION_PATH}" COMMAND cmake -DINPUT_FILE="${CMAKE_BINARY_DIR}/packaging/android/kstars-deployment.json.in" -DOUTPUT_FILE="${CMAKE_BINARY_DIR}/packaging/android/kstars-deployment.json" "-DTARGET_DIR=${CMAKE_BINARY_DIR}/kstars" "-DTARGET_NAME=kstars" "-DEXPORT_DIR=${CMAKE_INSTALL_PREFIX}" -P ${CMAKE_SOURCE_DIR}/packaging/android/specifydependencies.cmake COMMAND $/packaging/androiddeployqt --input "${CMAKE_BINARY_DIR}/packaging/android/kstars-deployment.json" --output "${EXPORT_DIR}" --android-platform android-${ANDROID_API_LEVEL} --release --deployment bundled "\\$(ARGS)" ) add_custom_target(install-apk-debug-kstars COMMAND adb install -r ${CMAKE_BINARY_DIR}/packaging/android/kstars_build_apk//bin/QtApp-debug.apk ) if (ANDROID_KEYSTORE AND ANDROID_KEYSTORE_ALIAS) add_custom_target(sign-apk-kstars COMMAND jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ${ANDROID_KEYSTORE} ${CMAKE_BINARY_DIR}/packaging/android/kstars_build_apk/bin/QtApp-release-unsigned.apk ${ANDROID_KEYSTORE_ALIAS} COMMAND rm -rf ${CMAKE_BINARY_DIR}/packaging/android/kstars_build_apk/bin/kstars-signed.apk COMMAND zipalign -v 4 ${CMAKE_BINARY_DIR}/packaging/android/kstars_build_apk/bin/QtApp-release-unsigned.apk ${CMAKE_BINARY_DIR}/packaging/android/kstars_build_apk/bin/kstars-signed.apk ) endif () endif () diff --git a/kstars/ekos/align/offlineastrometryparser.cpp b/kstars/ekos/align/offlineastrometryparser.cpp index 5144c6381..bea8c68bf 100644 --- a/kstars/ekos/align/offlineastrometryparser.cpp +++ b/kstars/ekos/align/offlineastrometryparser.cpp @@ -1,400 +1,482 @@ /* Astrometry.net Parser Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "offlineastrometryparser.h" #include "align.h" #include "ekos_align_debug.h" #include "ksutils.h" #include "Options.h" +#include "kspaths.h" #include namespace Ekos { OfflineAstrometryParser::OfflineAstrometryParser() : AstrometryParser() { astrometryIndex[2.8] = "index-4200"; astrometryIndex[4.0] = "index-4201"; astrometryIndex[5.6] = "index-4202"; astrometryIndex[8] = "index-4203"; astrometryIndex[11] = "index-4204"; astrometryIndex[16] = "index-4205"; astrometryIndex[22] = "index-4206"; astrometryIndex[30] = "index-4207"; astrometryIndex[42] = "index-4208"; astrometryIndex[60] = "index-4209"; astrometryIndex[85] = "index-4210"; astrometryIndex[120] = "index-4211"; astrometryIndex[170] = "index-4212"; astrometryIndex[240] = "index-4213"; astrometryIndex[340] = "index-4214"; astrometryIndex[480] = "index-4215"; astrometryIndex[680] = "index-4216"; astrometryIndex[1000] = "index-4217"; astrometryIndex[1400] = "index-4218"; astrometryIndex[2000] = "index-4219"; // Reset parity on solver failure connect(this, &OfflineAstrometryParser::solverFailed, this, [&]() { parity = QString(); }); + } bool OfflineAstrometryParser::init() { #ifdef Q_OS_OSX if (Options::astrometryConfFileIsInternal()) KSUtils::configureDefaultAstrometry(); #endif if (astrometryFilesOK) return true; if (astrometryNetOK() == false) { if (align && align->isEnabled()) KMessageBox::information( nullptr, i18n( "Failed to find astrometry.net binaries. Please ensure astrometry.net is installed and try again."), i18n("Missing astrometry files"), "missing_astrometry_binaries_warning"); return false; } astrometryFilesOK = true; QString solverPath; if (Options::astrometrySolverIsInternal()) solverPath = QCoreApplication::applicationDirPath() + "/astrometry/bin/solve-field"; else solverPath = Options::astrometrySolverBinary(); QProcess solveField; solveField.start("bash", QStringList() << "-c" << (solverPath + " --help | grep Revision")); solveField.waitForFinished(); QString output = solveField.readAllStandardOutput(); qCDebug(KSTARS_EKOS_ALIGN) << "solve-field Revision" << output; if (output.isEmpty() == false) { QString version = output.mid(9, 4); align->appendLogText(i18n("Detected Astrometry.net version %1", version)); if (version <= "0.67" && Options::astrometryUseNoFITS2FITS() == false) { Options::setAstrometryUseNoFITS2FITS(true); align->appendLogText(i18n("Setting astrometry option --no-fits2fits")); } else if (version > "0.67" && Options::astrometryUseNoFITS2FITS()) { Options::setAstrometryUseNoFITS2FITS(false); align->appendLogText(i18n("Turning off option --no-fits2fits")); } } return true; } bool OfflineAstrometryParser::astrometryNetOK() { bool solverOK = false, wcsinfoOK = false; if (Options::astrometrySolverIsInternal()) { QFileInfo solverFileInfo(QCoreApplication::applicationDirPath() + "/astrometry/bin/solve-field"); solverOK = solverFileInfo.exists() && solverFileInfo.isFile(); } else { QFileInfo solverFileInfo(Options::astrometrySolverBinary()); solverOK = solverFileInfo.exists() && solverFileInfo.isFile(); + +#ifdef Q_OS_LINUX + QString confPath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Literal("astrometry")+ QLatin1Literal("/astrometry.cfg"); + if (QFileInfo(confPath).exists() == false) + createLocalAstrometryConf(); +#endif } if (Options::astrometryWCSIsInternal()) { QFileInfo wcsFileInfo(QCoreApplication::applicationDirPath() + "/astrometry/bin/wcsinfo"); wcsinfoOK = wcsFileInfo.exists() && wcsFileInfo.isFile(); } else { QFileInfo wcsFileInfo(Options::astrometryWCSInfo()); wcsinfoOK = wcsFileInfo.exists() && wcsFileInfo.isFile(); } return (solverOK && wcsinfoOK); } void OfflineAstrometryParser::verifyIndexFiles(double fov_x, double fov_y) { static double last_fov_x = 0, last_fov_y = 0; if (last_fov_x == fov_x && last_fov_y == fov_y) return; last_fov_x = fov_x; last_fov_y = fov_y; double fov_lower = 0.10 * fov_x; double fov_upper = fov_x; QStringList indexFiles; QString astrometryDataDir; bool indexesOK = true; if (getAstrometryDataDir(astrometryDataDir) == false) return; QStringList nameFilter("*.fits"); QDir directory(astrometryDataDir); QStringList indexList = directory.entryList(nameFilter); + + // JM 2018-09-26: Also add locally stored indexes. +#ifdef Q_OS_LINUX + QDir localAstrometry(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Literal("astrometry")); + indexList << localAstrometry.entryList(nameFilter); +#endif + QString indexSearch = indexList.join(" "); QString startIndex, lastIndex; unsigned int missingIndexes = 0; foreach (float skymarksize, astrometryIndex.keys()) { if (skymarksize >= fov_lower && skymarksize <= fov_upper) { indexFiles << astrometryIndex.value(skymarksize); if (indexSearch.contains(astrometryIndex.value(skymarksize)) == false) { if (startIndex.isEmpty()) startIndex = astrometryIndex.value(skymarksize); lastIndex = astrometryIndex.value(skymarksize); indexesOK = false; missingIndexes++; } } } if (indexesOK == false) { if (missingIndexes == 1) align->appendLogText( i18n("Index file %1 is missing. Astrometry.net would not be able to adequately solve plates until you " "install the missing index files. Download the index files from http://www.astrometry.net", startIndex)); else align->appendLogText(i18n("Index files %1 to %2 are missing. Astrometry.net would not be able to " "adequately solve plates until you install the missing index files. Download the " "index files from http://www.astrometry.net", startIndex, lastIndex)); } } bool OfflineAstrometryParser::getAstrometryDataDir(QString &dataDir) { QString confPath; if (Options::astrometryConfFileIsInternal()) confPath = QCoreApplication::applicationDirPath() + "/astrometry/bin/astrometry.cfg"; else confPath = Options::astrometryConfFile(); QFile confFile(confPath); if (confFile.open(QIODevice::ReadOnly) == false) { - KMessageBox::error(0, i18n("Astrometry configuration file corrupted or missing: %1\nPlease set the " + KMessageBox::error(nullptr, i18n("Astrometry configuration file corrupted or missing: %1\nPlease set the " "configuration file full path in INDI options.", Options::astrometryConfFile())); return false; } QTextStream in(&confFile); QString line; while (!in.atEnd()) { line = in.readLine(); if (line.isEmpty() || line.startsWith('#')) continue; line = line.trimmed(); if (line.startsWith(QLatin1String("add_path"))) { dataDir = line.mid(9).trimmed(); return true; } } - KMessageBox::error(0, i18n("Unable to find data dir in astrometry configuration file.")); + KMessageBox::error(nullptr, i18n("Unable to find data dir in astrometry configuration file.")); return false; } bool OfflineAstrometryParser::startSovler(const QString &filename, const QStringList &args, bool generated) { INDI_UNUSED(generated); QStringList solverArgs = args; // Use parity if it is: 1. Already known from previous solve. 2. This is NOT a blind solve if (Options::astrometryDetectParity() && (parity.isEmpty() == false) && (args.contains("parity") == false) && (args.contains("-3") || args.contains("-L"))) solverArgs << "--parity" << parity; QString confPath; if (Options::astrometryConfFileIsInternal()) confPath = QCoreApplication::applicationDirPath() + "/astrometry/bin/astrometry.cfg"; else + { + // JM 2018-09-26: On Linux, load the local config file. + #ifdef Q_OS_LINUX + confPath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Literal("astrometry")+ QLatin1Literal("/astrometry.cfg"); + #else confPath = Options::astrometryConfFile(); + #endif + } solverArgs << "--config" << confPath; QString solutionFile = QDir::tempPath() + "/solution.wcs"; solverArgs << "-W" << solutionFile << filename; fitsFile = filename; solver.clear(); solver = new QProcess(this); #ifdef Q_OS_OSX QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString path = env.value("PATH", ""); if (Options::astrometrySolverIsInternal()) { env.insert("PATH", QCoreApplication::applicationDirPath() + "/netpbm/bin:" + QCoreApplication::applicationDirPath() + "/python/bin:/usr/local/bin:" + path); env.insert("PYTHONPATH", QCoreApplication::applicationDirPath() + "/python/bin/site-packages"); } else { env.insert("PATH", "/usr/local/bin:" + path); } solver->setProcessEnvironment(env); #endif connect(solver, SIGNAL(finished(int)), this, SLOT(solverComplete(int))); connect(solver, SIGNAL(readyReadStandardOutput()), this, SLOT(logSolver())); #if QT_VERSION > QT_VERSION_CHECK(5, 6, 0) connect(solver.data(), &QProcess::errorOccurred, this, [&]() { align->appendLogText(i18n("Error starting solver: %1", solver->errorString())); emit solverFailed(); }); #else connect(solver, SIGNAL(error(QProcess::ProcessError)), this, SIGNAL(solverFailed())); #endif solverTimer.start(); QString solverPath; if (Options::astrometrySolverIsInternal()) solverPath = QCoreApplication::applicationDirPath() + "/astrometry/bin/solve-field"; else solverPath = Options::astrometrySolverBinary(); solver->start(solverPath, solverArgs); align->appendLogText(i18n("Starting solver...")); if (Options::astrometrySolverVerbose()) { QString command = solverPath + ' ' + solverArgs.join(' '); align->appendLogText(command); } return true; } bool OfflineAstrometryParser::stopSolver() { if (solver.isNull() == false) { solver->terminate(); solver->disconnect(); } return true; } void OfflineAstrometryParser::solverComplete(int exist_status) { solver->disconnect(); // TODO use QTemporaryFile later QString solutionFile = QDir::tempPath() + "/solution.wcs"; QFileInfo solution(solutionFile); if (exist_status != 0 || solution.exists() == false) { align->appendLogText(i18n("Solver failed. Try again.")); emit solverFailed(); return; } connect(&wcsinfo, SIGNAL(finished(int)), this, SLOT(wcsinfoComplete(int))); QString wcsPath; if (Options::astrometryWCSIsInternal()) wcsPath = QCoreApplication::applicationDirPath() + "/astrometry/bin/wcsinfo"; else wcsPath = Options::astrometryWCSInfo(); wcsinfo.start(wcsPath, QStringList(solutionFile)); } void OfflineAstrometryParser::wcsinfoComplete(int exist_status) { wcsinfo.disconnect(); if (exist_status != 0) { align->appendLogText(i18n("WCS header missing or corrupted. Solver failed.")); emit solverFailed(); return; } QString wcsinfo_stdout = wcsinfo.readAllStandardOutput(); QStringList wcskeys = wcsinfo_stdout.split(QRegExp("[\n]")); QStringList key_value; double ra = 0, dec = 0, orientation = 0, pixscale = 0; for (auto &key : wcskeys) { key_value = key.split(' '); if (key_value.size() > 1) { if (key_value[0] == "ra_center") ra = key_value[1].toDouble(); else if (key_value[0] == "dec_center") dec = key_value[1].toDouble(); else if (key_value[0] == "orientation_center") orientation = key_value[1].toDouble(); else if (key_value[0] == "pixscale") pixscale = key_value[1].toDouble(); else if (key_value[0] == "parity") parity = (key_value[1].toInt() == 0) ? "pos" : "neg"; } } - int elapsed = (int)round(solverTimer.elapsed() / 1000.0); + int elapsed = static_cast(round(solverTimer.elapsed() / 1000.0)); align->appendLogText(i18np("Solver completed in %1 second.", "Solver completed in %1 seconds.", elapsed)); emit solverFinished(orientation, ra, dec, pixscale); } void OfflineAstrometryParser::logSolver() { if (Options::astrometrySolverVerbose()) align->appendLogText(solver->readAll().trimmed()); } + +bool OfflineAstrometryParser::createLocalAstrometryConf() +{ + bool rc = false; + + QString confPath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Literal("astrometry") + QLatin1Literal("/astrometry.cfg"); + QString systemConfPath = Options::astrometryConfFile(); + + // Check if directory already exists, if it doesn't create one + QDir writableDir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Literal("astrometry")); + if (writableDir.exists() == false) + { + rc = writableDir.mkdir(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Literal("astrometry")); + + if (rc == false) + { + qCCritical(KSTARS_EKOS_ALIGN) << "Failed to create local astrometry directory"; + return false; + } + } + + // Now copy system astrometry.cfg to local directory + rc = QFile(systemConfPath).copy(confPath); + + if (rc == false) + { + qCCritical(KSTARS_EKOS_ALIGN) << "Failed to copy" << systemConfPath << "to" << confPath; + return false; + } + + QFile localConf(confPath); + + // Open file and add our own path to it + if (localConf.open(QFile::ReadWrite)) + { + QString all = localConf.readAll(); + QStringList lines = all.split("\n"); + for (int i=0; i < lines.count(); i++) + { + if (lines[i].startsWith("add_path")) + { + lines.insert(i+1, QString("add_path %1astrometry").arg(KSPaths::writableLocation(QStandardPaths::GenericDataLocation))); + break; + } + } + + // Clear contents + localConf.resize(0); + + // Now write back all the lines including our own inserted above + QTextStream out(&localConf); + for(const QString &line : lines) + out << line << endl; + localConf.close(); + return true; + } + + qCCritical(KSTARS_EKOS_ALIGN) << "Failed to open local astrometry config" << confPath; + return false; +} } diff --git a/kstars/ekos/align/offlineastrometryparser.h b/kstars/ekos/align/offlineastrometryparser.h index 8f51ef227..73babda6a 100644 --- a/kstars/ekos/align/offlineastrometryparser.h +++ b/kstars/ekos/align/offlineastrometryparser.h @@ -1,62 +1,63 @@ /* Astrometry.net Parser Copyright (C) 2012 Jasem Mutlaq This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "astrometryparser.h" #include #include #include #include namespace Ekos { class Align; /** * @class OfflineAstrometryParser * OfflineAstrometryParser invokes the offline astrometry.net solver to find solutions to captured images. * * @authro Jasem Mutlaq */ class OfflineAstrometryParser : public AstrometryParser { Q_OBJECT public: OfflineAstrometryParser(); virtual ~OfflineAstrometryParser() override = default; virtual void setAlign(Align *_align) override { align = _align; } virtual bool init() override; virtual void verifyIndexFiles(double fov_x, double fov_y) override; virtual bool startSovler(const QString &filename, const QStringList &args, bool generated = true) override; virtual bool stopSolver() override; public slots: void solverComplete(int exist_status); void wcsinfoComplete(int exist_status); void logSolver(); private: bool astrometryNetOK(); + bool createLocalAstrometryConf(); bool getAstrometryDataDir(QString &dataDir); QMap astrometryIndex; QString parity; QPointer solver; QProcess wcsinfo; QTime solverTimer; QString fitsFile; bool astrometryFilesOK { false }; Align *align { nullptr }; }; } diff --git a/kstars/ekos/align/opsastrometryindexfiles.cpp b/kstars/ekos/align/opsastrometryindexfiles.cpp index 3b1583e6f..a1502a323 100644 --- a/kstars/ekos/align/opsastrometryindexfiles.cpp +++ b/kstars/ekos/align/opsastrometryindexfiles.cpp @@ -1,518 +1,528 @@ #include "opsastrometryindexfiles.h" #include "align.h" #include "kstars.h" #include "Options.h" +#include "kspaths.h" -#include -#include #include #include namespace Ekos { OpsAstrometryIndexFiles::OpsAstrometryIndexFiles(Align *parent) : QDialog(KStars::Instance()) { setupUi(this); downloadSpeed = 100; actualdownloadSpeed = downloadSpeed; alignModule = parent; manager = new QNetworkAccessManager(); //Get a pointer to the KConfigDialog // m_ConfigDialog = KConfigDialog::exists( "alignsettings" ); connect(openIndexFileDirectory, SIGNAL(clicked()), this, SLOT(slotOpenIndexFileDirectory())); astrometryIndex[2.8] = "00"; astrometryIndex[4.0] = "01"; astrometryIndex[5.6] = "02"; astrometryIndex[8] = "03"; astrometryIndex[11] = "04"; astrometryIndex[16] = "05"; astrometryIndex[22] = "06"; astrometryIndex[30] = "07"; astrometryIndex[42] = "08"; astrometryIndex[60] = "09"; astrometryIndex[85] = "10"; astrometryIndex[120] = "11"; astrometryIndex[170] = "12"; astrometryIndex[240] = "13"; astrometryIndex[340] = "14"; astrometryIndex[480] = "15"; astrometryIndex[680] = "16"; astrometryIndex[1000] = "17"; astrometryIndex[1400] = "18"; astrometryIndex[2000] = "19"; QList checkboxes = findChildren(); for (auto &checkBox : checkboxes) { connect(checkBox, SIGNAL(clicked(bool)), this, SLOT(downloadOrDeleteIndexFiles(bool))); } QList progressBars = findChildren(); QList qLabels = findChildren(); QList qButtons = findChildren(); for (auto &bar : progressBars) { if(bar->objectName().contains("progress")) { bar->setVisible(false); bar->setTextVisible(false); } } for (auto &button : qButtons) { if(button->objectName().contains("cancel")) { button->setVisible(false); } } for (QLabel * label: qLabels) { if(label->text().contains("info")||label->text().contains("perc")) { label->setVisible(false); } } } void OpsAstrometryIndexFiles::showEvent(QShowEvent *) { slotUpdate(); } void OpsAstrometryIndexFiles::slotUpdate() { double fov_w, fov_h, fov_pixscale; // Values in arcmins. Scale in arcsec per pixel alignModule->getFOVScale(fov_w, fov_h, fov_pixscale); double fov_check = qMax(fov_w, fov_h); FOVOut->setText(QString("%1' x %2'").arg(QString::number(fov_w, 'f', 2), QString::number(fov_h, 'f', 2))); QString astrometryDataDir; if (getAstrometryDataDir(astrometryDataDir) == false) return; indexLocation->setText(astrometryDataDir); QStringList nameFilter("*.fits"); QDir directory(astrometryDataDir); QStringList indexList = directory.entryList(nameFilter); + // JM 2018-09-26: Also add locally stored indexes. +#ifdef Q_OS_LINUX + QDir localAstrometry(KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Literal("astrometry")); + indexList << localAstrometry.entryList(nameFilter); +#endif + for (auto &indexName : indexList) { - if(fileCountMatches(directory,indexName)){ + #ifdef Q_OS_LINUX + if (fileCountMatches(directory,indexName) || fileCountMatches(localAstrometry,indexName)) + #else + if (fileCountMatches(directory,indexName)) + #endif + + { indexName = indexName.replace('-', '_').left(10); QCheckBox *indexCheckBox = findChild(indexName); if (indexCheckBox) indexCheckBox->setChecked(true); } } QList checkboxes = findChildren(); for (auto &checkBox : checkboxes) { checkBox->setIcon(QIcon(":/icons/astrometry-optional.svg")); checkBox->setToolTip(i18n("Optional")); } float last_skymarksize = 2; for (auto &skymarksize : astrometryIndex.keys()) { if ((skymarksize >= 0.40 * fov_check && skymarksize <= 0.9 * fov_check) || (fov_check > last_skymarksize && fov_check < skymarksize)) { QString indexName1 = "index_41" + astrometryIndex.value(skymarksize); QString indexName2 = "index_42" + astrometryIndex.value(skymarksize); QCheckBox *indexCheckBox1 = findChild(indexName1); QCheckBox *indexCheckBox2 = findChild(indexName2); if (indexCheckBox1) { indexCheckBox1->setIcon(QIcon(":/icons/astrometry-required.svg")); indexCheckBox1->setToolTip(i18n("Required")); } if (indexCheckBox2) { indexCheckBox2->setIcon(QIcon(":/icons/astrometry-required.svg")); indexCheckBox2->setToolTip(i18n("Required")); } } else if (skymarksize >= 0.10 * fov_check && skymarksize <= fov_check) { QString indexName1 = "index_41" + astrometryIndex.value(skymarksize); QString indexName2 = "index_42" + astrometryIndex.value(skymarksize); QCheckBox *indexCheckBox1 = findChild(indexName1); QCheckBox *indexCheckBox2 = findChild(indexName2); if (indexCheckBox1) { indexCheckBox1->setIcon(QIcon(":/icons/astrometry-recommended.svg")); indexCheckBox1->setToolTip(i18n("Recommended")); } if (indexCheckBox2) { indexCheckBox2->setIcon(QIcon(":/icons/astrometry-recommended.svg")); indexCheckBox2->setToolTip(i18n("Recommended")); } } last_skymarksize = skymarksize; } } -bool OpsAstrometryIndexFiles::fileCountMatches(QDir directory, QString indexName){ +bool OpsAstrometryIndexFiles::fileCountMatches(QDir directory, QString indexName) +{ QString indexNameMatch = indexName.left(10) + "*.fits"; QStringList list = directory.entryList(QStringList(indexNameMatch)); int count=0; if(indexName.contains("4207")||indexName.contains("4206")||indexName.contains("4205")) count = 12; else if(indexName.contains("4204")||indexName.contains("4203")||indexName.contains("4202")||indexName.contains("4201")||indexName.contains("4200")) count = 48; else count = 1; return list.count()==count; } void OpsAstrometryIndexFiles::slotOpenIndexFileDirectory() { QString astrometryDataDir; if (getAstrometryDataDir(astrometryDataDir) == false) return; QUrl path = QUrl::fromLocalFile(astrometryDataDir); QDesktopServices::openUrl(path); } bool OpsAstrometryIndexFiles::getAstrometryDataDir(QString &dataDir) { QString confPath; if (Options::astrometryConfFileIsInternal()) confPath = QCoreApplication::applicationDirPath() + "/astrometry/bin/astrometry.cfg"; else confPath = Options::astrometryConfFile(); QFile confFile(confPath); if (confFile.open(QIODevice::ReadOnly) == false) { KMessageBox::error(nullptr, i18n("Astrometry configuration file corrupted or missing: %1\nPlease set the " "configuration file full path in INDI options.", Options::astrometryConfFile())); return false; } QTextStream in(&confFile); QString line; while (!in.atEnd()) { line = in.readLine(); if (line.isEmpty() || line.startsWith('#')) continue; line = line.trimmed(); if (line.startsWith(QLatin1String("add_path"))) { dataDir = line.mid(9).trimmed(); return true; } } KMessageBox::error(nullptr, i18n("Unable to find data dir in astrometry configuration file.")); return false; } bool OpsAstrometryIndexFiles::astrometryIndicesAreAvailable() { QNetworkReply *response = manager->get(QNetworkRequest(QUrl("http://broiler.astrometry.net"))); QTimer timeout(this); timeout.setInterval(5000); timeout.setSingleShot(true); timeout.start(); while (!response->isFinished()) { if (!timeout.isActive()) { response->deleteLater(); return false; } qApp->processEvents(); } timeout.stop(); bool wasSuccessful = (response->error() == QNetworkReply::NoError); response->deleteLater(); return wasSuccessful; } void OpsAstrometryIndexFiles::downloadIndexFile(const QString &URL, const QString &fileN, QCheckBox *checkBox, int currentIndex, int maxIndex, double fileSize) { QTime downloadTime; downloadTime.start(); QString indexString = QString::number(currentIndex); if (currentIndex < 10) indexString = '0' + indexString; QString indexSeriesName = checkBox->text().remove('&'); QProgressBar *indexDownloadProgress = findChild(indexSeriesName.replace('-', '_').left(10) + "_progress"); QLabel *indexDownloadInfo = findChild(indexSeriesName.replace('-', '_').left(10) + "_info"); QPushButton *indexDownloadCancel = findChild(indexSeriesName.replace('-', '_').left(10) + "_cancel"); QLabel *indexDownloadPerc = findChild(indexSeriesName.replace('-', '_').left(10) + "_perc"); setDownloadInfoVisible(indexSeriesName, checkBox, true); if(indexDownloadInfo){ if (indexDownloadProgress && maxIndex > 0) indexDownloadProgress->setValue(currentIndex*100 / maxIndex); indexDownloadInfo->setText("(" + QString::number(currentIndex) + '/' + QString::number(maxIndex + 1) + ") "); } QString indexURL = URL; indexURL.replace('*', indexString); QNetworkReply *response = manager->get(QNetworkRequest(QUrl(indexURL))); //Shut it down after too much time elapses. //If the filesize is less than 4 MB, it sets the timeout for 1 minute or 60000 ms. //If it's larger, it assumes a bad download rate of 1 Mbps (100 bytes/ms) //and the calculation estimates the time in milliseconds it would take to download. int timeout=60000; if(fileSize>4000000) timeout=fileSize/downloadSpeed; //qDebug()<<"Filesize: "<< fileSize << ", timeout: " << timeout; QMetaObject::Connection *cancelConnection = new QMetaObject::Connection(); QMetaObject::Connection *replyConnection = new QMetaObject::Connection(); QMetaObject::Connection *percentConnection = new QMetaObject::Connection(); if(indexDownloadPerc){ *percentConnection=connect(response,&QNetworkReply::downloadProgress, [=](qint64 bytesReceived, qint64 bytesTotal){ if (indexDownloadProgress){ indexDownloadProgress->setValue(bytesReceived); indexDownloadProgress->setMaximum(bytesTotal); } indexDownloadPerc->setText(QString::number(bytesReceived * 100 / bytesTotal) + '%'); }); } timeoutTimer.disconnect(); connect(&timeoutTimer, &QTimer::timeout, [&]() { KMessageBox::error(nullptr, i18n("Download Timed out. Either the network is not fast enough, the file is not accessible, or you are not connected.")); disconnectDownload(cancelConnection,replyConnection,percentConnection); if(response){ response->abort(); response->deleteLater(); } setDownloadInfoVisible(indexSeriesName, checkBox, false); }); timeoutTimer.start(timeout); *cancelConnection=connect(indexDownloadCancel, &QPushButton::clicked, [=](){ qDebug() << "Download Cancelled."; timeoutTimer.stop(); disconnectDownload(cancelConnection,replyConnection,percentConnection); if(response){ response->abort(); response->deleteLater(); } setDownloadInfoVisible(indexSeriesName, checkBox, false); }); *replyConnection=connect(response, &QNetworkReply::finished, this, [=]() { timeoutTimer.stop(); if(response){ disconnectDownload(cancelConnection,replyConnection,percentConnection); setDownloadInfoVisible(indexSeriesName, checkBox, false); response->deleteLater(); if (response->error() != QNetworkReply::NoError) return; QByteArray responseData = response->readAll(); QString indexFileN = fileN; indexFileN.replace('*', indexString); QFile file(indexFileN); if (QFileInfo(QFileInfo(file).path()).isWritable()) { if (!file.open(QIODevice::WriteOnly)) { KMessageBox::error(nullptr, i18n("File Write Error")); slotUpdate(); return; } else { file.write(responseData.data(), responseData.size()); file.close(); int downloadedFileSize = QFileInfo(file).size(); int dtime=downloadTime.elapsed(); actualdownloadSpeed=(actualdownloadSpeed+(downloadedFileSize/dtime))/2; qDebug()<<"Filesize: "<< downloadedFileSize<<", time: "<exec()) - { - QMessageBox::information( - this, "Error", - QString("KAuth returned an error code: %1 %2").arg(job->error()).arg(job->errorString())); - slotUpdate(); - return; - } -#endif + KMessageBox::error(nullptr, i18n("Astrometry Folder Permissions Error")); } if (currentIndex == maxIndex) { slotUpdate(); } else downloadIndexFile(URL, fileN, checkBox, currentIndex + 1, maxIndex, fileSize); } }); } void OpsAstrometryIndexFiles::setDownloadInfoVisible(QString indexSeriesName, QCheckBox *checkBox, bool set){ QProgressBar *indexDownloadProgress = findChild(indexSeriesName.replace('-', '_').left(10) + "_progress"); QLabel *indexDownloadInfo = findChild(indexSeriesName.replace('-', '_').left(10) + "_info"); QPushButton *indexDownloadCancel = findChild(indexSeriesName.replace('-', '_').left(10) + "_cancel"); QLabel *indexDownloadPerc = findChild(indexSeriesName.replace('-', '_').left(10) + "_perc"); if (indexDownloadProgress) indexDownloadProgress->setVisible(set); if (indexDownloadInfo) indexDownloadInfo->setVisible(set); if (indexDownloadCancel) indexDownloadCancel->setVisible(set); if (indexDownloadPerc) indexDownloadPerc->setVisible(set); checkBox->setEnabled(!set); } void OpsAstrometryIndexFiles::disconnectDownload(QMetaObject::Connection *cancelConnection,QMetaObject::Connection *replyConnection,QMetaObject::Connection *percentConnection){ if(cancelConnection) disconnect(*cancelConnection); if(replyConnection) disconnect(*replyConnection); if(percentConnection) disconnect(*percentConnection); } void OpsAstrometryIndexFiles::downloadOrDeleteIndexFiles(bool checked) { QCheckBox *checkBox = qobject_cast(QObject::sender()); QString astrometryDataDir; if (getAstrometryDataDir(astrometryDataDir) == false) return; if (checkBox) { QString indexSeriesName = checkBox->text().remove('&'); + #ifdef Q_OS_LINUX + QString filePath = KSPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Literal("astrometry")+ '/' + indexSeriesName; + #else QString filePath = astrometryDataDir + '/' + indexSeriesName; + #endif QString fileNumString = indexSeriesName.mid(8, 2); int indexFileNum = fileNumString.toInt(); if (checked) { checkBox->setChecked(!checked); if (astrometryIndicesAreAvailable()) { QString URL; if (indexSeriesName.startsWith(QLatin1String("index-41"))) URL = "http://broiler.astrometry.net/~dstn/4100/" + indexSeriesName; else if (indexSeriesName.startsWith(QLatin1String("index-42"))) URL = "http://broiler.astrometry.net/~dstn/4200/" + indexSeriesName; int maxIndex = 0; if (indexFileNum < 8 && URL.contains("*")) { maxIndex = 11; if (indexFileNum < 5) maxIndex = 47; } double fileSize=1E11*qPow(astrometryIndex.key(fileNumString),-1.909); //This estimates the file size based on skymark size obtained from the index number. if(maxIndex!=0) fileSize/=maxIndex; //FileSize is divided between multiple files for some index series. downloadIndexFile(URL, filePath, checkBox, 0, maxIndex,fileSize); } else { KMessageBox::sorry(nullptr, i18n("Could not contact Astrometry Index Server: broiler.astrometry.net")); } } else { if (KMessageBox::Continue == KMessageBox::warningContinueCancel( nullptr, i18n("Are you sure you want to delete these index files? %1", indexSeriesName), i18n("Delete File(s)"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "delete_index_files_warning")) { - if (QFileInfo(astrometryDataDir).isWritable()) + bool filesDeleted=false; + // Try to delete local files first { QStringList nameFilter("*.fits"); QDir directory(astrometryDataDir); QStringList indexList = directory.entryList(nameFilter); for (auto &fileName : indexList) { if (fileName.contains(indexSeriesName.left(10))) { if (!directory.remove(fileName)) { KMessageBox::error(nullptr, i18n("File Delete Error")); slotUpdate(); return; } + + filesDeleted = true; } } } - else + + if (filesDeleted) { -#ifdef Q_OS_OSX - KMessageBox::error(0, i18n("Astrometry Folder Permissions Error")); - slotUpdate(); -#else - KAuth::Action action(QStringLiteral("org.kde.kf5auth.kstars.removeindexfileset")); - action.setHelperId(QStringLiteral("org.kde.kf5auth.kstars")); - // Wait 15mins before timing out in the auth dialog. - action.setTimeout(900000); - action.setArguments( - QVariantMap({ { "indexSetName", indexSeriesName }, { "astrometryDataDir", astrometryDataDir } })); - KAuth::ExecuteJob *job = action.execute(); - if (!job->exec()) - QMessageBox::information( - this, "Error", - QString("KAuth returned an error code: %1 %2").arg(job->error()).arg(job->errorString())); -#endif + if (QFileInfo(astrometryDataDir).isWritable()) + { + QStringList nameFilter("*.fits"); + QDir directory(astrometryDataDir); + QStringList indexList = directory.entryList(nameFilter); + for (auto &fileName : indexList) + { + if (fileName.contains(indexSeriesName.left(10))) + { + if (!directory.remove(fileName)) + { + KMessageBox::error(nullptr, i18n("File Delete Error")); + slotUpdate(); + return; + } + } + } + } + else + { + KMessageBox::error(nullptr, i18n("Astrometry Folder Permissions Error")); + slotUpdate(); + } } } } } } }