Compare commits

..
102 Commits
Author SHA1 Message Date
schetnikovich 845315eeb5 Simplified LIBSSH2 CMake listfile 2016-04-05 16:19:42 +03:00
schetnikovich 803306eea5 Default to gnu99 for both GCC and Clang 2016-04-05 14:58:51 +03:00
schetnikovich 94ef0d75d7 Modified gitignore and missed header in OpenSSL 2016-04-05 13:24:37 +03:00
schetnikovich 604d439d57 OpenSSL CMake listfile tweaks 2016-04-05 12:39:25 +03:00
schetnikovich d37f8c7bab OpenSSL 1.0.1e 2016-04-04 21:47:50 +03:00
schetnikovich c7ab94a5a5 Ignore SIGPIPE signal 2016-04-04 20:01:42 +03:00
schetnikovich a6428ce968 Use of LIBSSH2 error codes 2016-04-04 16:31:42 +03:00
schetnikovich 8dd0cd3050 Restructuring of SSH submodule. Updated to the latest version of LIBSSH2. 2016-04-04 15:53:12 +03:00
schetnikovich baede8d8bd SSH tunnel: Log functions moved to separate file II 2016-04-02 13:34:28 +03:00
schetnikovich 2328ec7b31 SSH tunnel: Log functions moved to separate file 2016-04-02 13:34:01 +03:00
schetnikovich 719df7b241 Error handling for the most important operations is restored.
1. First steps to support automatic reconnection for SSH tunnel
2. UI tweaks for Explorer tree items (handling of errors and empty children)
3. Correct handling of invalid mongo::DBClientCursor
4. Null semantic of value type EventError is restored. Was broken by reliance on emptiness of error message string, dah!
5. Error state for several messages is restored. Not for all messages, but still something :)
6. Errors produced by MongoWorker now contains exact error message, instead of constant text.
7. More strict error handling for SSH worker
2016-04-02 01:19:58 +03:00
schetnikovich dff7f0539a Preparation to handle reconnection automatically 2016-03-31 13:56:37 +03:00
schetnikovich f03cf048d8 Minor changes with whitespace characters in the code 2016-03-29 20:43:41 +03:00
schetnikovich 22ffc2ec15 Do not print batch size in the logs 2016-03-29 19:51:49 +03:00
schetnikovich 09a0edb39a Check for availability of "rc" files (this removes unnecessary warnings in logs) 2016-03-29 19:44:10 +03:00
schetnikovich a0a8a7a13e Cleanup of SSH worker thread when no more connected clients 2016-03-29 18:06:05 +03:00
schetnikovich 8f97c06408 Shift + Return when selecting connection in order to enable debug level logging for SSH submodule 2016-03-29 14:57:13 +03:00
schetnikovich af9f8fc6a9 Shift + Connect in order to enable debug level logging for SSH submodule
Added WARN log level
2016-03-29 14:36:38 +03:00
schetnikovich ef6feb2377 Log levels for SSH submodule (ERROR, INFO, DEBUG) 2016-03-28 20:57:22 +03:00
schetnikovich 4e5ffae80c Single log function for SSH submodule 2016-03-28 20:16:45 +03:00
schetnikovich 3597f438cd Handling of different types of sockets moved to separate functions 2016-03-28 19:47:15 +03:00
schetnikovich ad9cc86117 Cleanup of SSH tunnel artifacts: sessions, channels, sockets. 2016-03-28 16:34:25 +03:00
schetnikovich 6656494fda Prefix all global or public C identifiers with "rbm_" 2016-03-27 18:23:57 +03:00
schetnikovich 06ac7dd8f1 Global set of SSH channels replaced with separate channels per each worker thread 2016-03-26 11:57:26 +03:00
schetnikovich 76f4d19b42 Logging to Log panel from SSH tunnel worker 2016-03-25 16:17:25 +03:00
schetnikovich d64a1f704b Real-time logging from SSH Tunnel worker thread. 2016-03-25 12:40:14 +03:00
schetnikovich aa27148868 Detailed errors messages for all system calls when establishing SSH tunnel. 2016-03-24 20:40:12 +03:00
schetnikovich 58c42df669 Better error handling when new connection is made (for normal and via SSH tunnel) 2016-03-24 17:16:10 +03:00
schetnikovich 9c0761826b Automatically bind local SSH tunnel to the first available local port 2016-03-22 23:51:43 +03:00
schetnikovich 63f90dfeb3 SSH tunnel on hard-coded local port 2016-03-22 22:28:40 +03:00
schetnikovich 10b31ef2a9 Two-step connection skeleton (SSH tunnel, and then MongoDB connection) 2016-03-22 20:25:36 +03:00
schetnikovich bb3fd81c4d SSH tunnel worker connection and auth 2016-03-19 20:36:05 +03:00
schetnikovich 2cd6ed2928 SSH public key 2016-03-18 18:42:24 +03:00
schetnikovich fde191f6aa SSH tunnel worker sketch 2016-03-18 18:05:01 +03:00
schetnikovich 1c76852b9c Enable C99 support 2016-03-17 16:27:43 +03:00
schetnikovich 2491f417da Loading and saving of SSH settings 2016-03-17 14:43:18 +03:00
schetnikovich 443b8070f2 SSH tab and settings stub 2016-03-15 22:53:07 +03:00
schetnikovich bb51c1395b Separate SSH channels for different socket connections on the same port
* Additional major fix: restored error handling when connectivity is lost
2016-03-15 20:52:51 +03:00
schetnikovich 3e03cf3d88 Single SSH channel for all socket connections 2016-03-13 21:28:37 +03:00
schetnikovich d6a6bf9719 SSH connection, handshake and authentication routines 2016-03-13 17:43:58 +03:00
schetnikovich 3a2a9d1b7d SSH tunnel config 2016-03-12 20:28:59 +03:00
schetnikovich dda68ff53f Direct port forwarding skeleton 2016-03-11 18:26:45 +03:00
schetnikovich 0ef17c6ad7 Libssh2 compilation 2016-03-11 13:47:29 +03:00
schetnikovich ac58d23713 Libssh2 1.7.0 2016-03-10 23:13:25 +03:00
schetnikovich 5971972525 CHANGELOG updated 2016-03-08 13:58:28 +03:00
schetnikovich 4cc6662837 Merge branch 'master' of github.com:paralect/robomongo 2016-03-06 15:52:50 +03:00
schetnikovich 226cbf48d8 CMake print utils changes 2016-03-06 15:52:01 +03:00
schetnikovich 7d9909c247 #1048: Support for CMake 3.0 2016-03-06 15:50:26 +03:00
Dmitry Schetnikovich 649c096e70 Update BuildingMongoDB.md 2016-03-06 12:47:34 +03:00
Dmitry Schetnikovich ce02d5f5c8 Update BuildingRobomongo.md 2016-03-06 12:45:27 +03:00
Dmitry Schetnikovich 90c667011d Update BuildingMongoDB.md 2016-03-06 12:42:22 +03:00
Dmitry Schetnikovich 0622e85e27 Update BuildingMongoDB.md 2016-03-06 12:41:29 +03:00
Dmitry Schetnikovich 355f01f0a4 Update BuildingMongoDB.md 2016-03-06 12:36:59 +03:00
Dmitry Schetnikovich b0d6cf0842 Update BuildingMongoDB.md 2016-03-06 12:35:35 +03:00
Dmitry Schetnikovich 16b15be81b Update BuildingMongoDB.md 2016-03-06 12:30:43 +03:00
schetnikovich 2b7a8ca14e Version updated to RC7 2016-03-03 18:12:23 +03:00
Dmitry Schetnikovich c338813d1c Update BuildingRobomongoOnWindows.md 2016-03-03 17:48:00 +03:00
schetnikovich 2f38d5b00d #1017: Disable editing in View Mode. Button "Validate" is also now hidden in this mode. 2016-03-03 17:09:17 +03:00
schetnikovich 7c6738e32b Config for "clang-format" tool 2016-03-01 20:32:49 +03:00
schetnikovich 86bfeba68a #929: Show keyboard shortcuts for expand/collapse in context menu 2016-03-01 19:40:37 +03:00
schetnikovich a94aa81c07 #1045: Logs are now shown in the panel at the bottom 2016-02-29 20:50:51 +03:00
schetnikovich 080c1ed865 #1042: Double click on collection should not expand it 2016-02-29 16:49:05 +03:00
schetnikovich c6f13e347c Confirmation dialog for BSON editor when ESC is hit and content is modified
And also clone url in documentation is changed to HTTPS instead of SSH, to allow
allow unauthorized cloning.

# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2016-02-28 22:26:05 +03:00
schetnikovich 3cea361cc9 Merge of PR #1037 2016-02-28 22:16:31 +03:00
schetnikovich f6546fc336 Minor code cleanup 2016-02-28 22:08:35 +03:00
schetnikovich 8d12ab45c9 Mac way to "expand recursively" in a BSON subtree #929. Implemented by Anton Zering 2016-02-28 21:46:39 +03:00
schetnikovich 7dfd9184b7 Fix for broken keyboard shortcut for single document #1039 2016-02-28 21:09:10 +03:00
Anton Zering 0977bc00e3 #929: adds shortcut to expand nodes recursively 2016-02-25 01:13:31 +01:00
Anton Zering 2ea847ce0b #1039: fixes deletion of single documents with shortcut 2016-02-25 00:51:05 +01:00
Anton Zering fb2b5e0487 #1017: document editor ask to save changes when closing the window 2016-02-23 23:56:06 +01:00
Anton Zering cce30bb55b Changed clone url to use HTTPS instead of SSH
Use HTTPS instead of SSH to allow unauthorized cloning
2016-02-23 22:56:41 +01:00
Dmitry Schetnikovich 98af4fc747 CHANGELOG updated 2016-02-13 16:30:59 +03:00
schetnikovich 2c04edb1b3 List of collections is loading much faster now 2016-02-13 16:19:51 +03:00
Dmitry Schetnikovich e4500e0c5c Generate ZIP archives for Windows 2016-02-13 12:56:48 +03:00
Dmitry Schetnikovich 820c4a86f0 Fix for compilation error under Windows 2016-02-13 12:52:49 +03:00
Dmitry Schetnikovich d6253ca194 Merge pull request #1018 from CaptainPatate/CaptainPatate/fix-typo
Fix typo
2016-02-12 00:26:19 +03:00
schetnikovich 33e27917ee Version RC6 2016-02-11 15:20:06 +03:00
schetnikovich cea04368a9 Support for IPv6 addresses with and without square brackets 2016-02-11 15:19:16 +03:00
schetnikovich fe9eed7322 Removed use of monospaced font in Auth tab 2016-02-10 20:24:26 +03:00
Dmitry Schetnikovich 33304e2e7a README updated 2016-02-10 20:17:43 +03:00
schetnikovich 4cd51a7de6 IPv6 support 2016-02-10 20:16:22 +03:00
Amaury Gauthier 5ffd65b1a2 Fix typo 2016-02-09 15:18:04 +01:00
schetnikovich 82b96f4f63 Merge branch 'master' of github.com:paralect/robomongo 2016-02-06 19:00:45 +03:00
schetnikovich e0ea15adb9 Ad-free Robomongo 2016-02-06 19:00:29 +03:00
Dmitry Schetnikovich 209ce0c0a9 README updated 2016-02-03 14:34:01 +03:00
Dmitry Schetnikovich 620488c318 README updated 2016-02-03 14:31:27 +03:00
schetnikovich a270926397 CHANGELOG updated 2016-02-01 23:08:35 +03:00
schetnikovich 8c830b6f1a Robomongo RC4 2016-02-01 20:50:45 +03:00
schetnikovich 33d58189c4 All operations with MongoDB now over DBClientBase, instead of DBClientConnection 2016-02-01 18:28:21 +03:00
schetnikovich 5d8370a970 Support for SCRAM-SHA-1 and MONGODB-CR authentication mechanisms.
1. Ability to select auth mechanisms for each connection (defaults to SCRAM-SHA-1)
2. Diagnostic Dialog also now uses required mechanisms.
2016-02-01 15:17:10 +03:00
schetnikovich fb3d37e641 Message box with description of error when deleting documents. 2016-01-31 19:36:53 +03:00
schetnikovich b27f747ff2 Handling of errors when inserting/updating/removing documents. 2016-01-31 18:47:50 +03:00
schetnikovich 2854783aeb Always allow to read from slaves. 2016-01-31 17:32:28 +03:00
Dmitry Schetnikovich 4b8aa3deaf Shortcuts now will be created on Windows (on desktop and start menu)
1. A bit simplified syntax is used for NSIS commands
2. All Robomongo CMake files start from "Robomongo" (except "find" packages)
3. Clean uninstallation under Windows (only config file is not deleted)
2016-01-30 20:10:09 +03:00
schetnikovich f5397b7c68 CHANGELOG updated 2016-01-29 21:19:45 +03:00
schetnikovich 047620492c Typo fixed 2016-01-29 16:20:18 +03:00
schetnikovich bf51f5d6c9 Gray text color for hey message 2016-01-29 12:47:37 +03:00
schetnikovich d7f27e16fe Hey message added for the time of campaign. 2016-01-29 07:16:51 +03:00
schetnikovich cfb30c69c0 Merge of BSON utils 2016-01-29 05:00:13 +03:00
schetnikovich 8ed0335b7b Use of std::numeric_limits<double>::digits10 to get max float-point precision
Tests returned back.
2016-01-29 04:58:30 +03:00
Dmitry Schetnikovich 313179dfbe Temporary fix for compilation under Windows 2016-01-28 21:42:52 +03:00
schetnikovich 97503f2e81 Version RC3 2016-01-28 21:15:25 +03:00
2770 changed files with 752868 additions and 563 deletions
-7
View File
@@ -19,8 +19,6 @@ libs/
/src/robomongo/robomongo.pro.user.1.3
/src/third-party/mongodb/src/third_party/js-1.7/jswgen
.idea/
# Created by http://www.gitignore.io
@@ -81,11 +79,6 @@ libs/
*.pro.user
*.pro.user.*
*.moc
moc_*.cpp
qrc_*.cpp
ui_*.h
Makefile*
*-build-*
# QtCreator
+42
View File
@@ -1,4 +1,46 @@
0.9.0-RC6 / February 13, 2016
Most important changes: support for MongoDB 3.x and WiredTiger engine. Robomongo
now embeds MongoDB 3.2 shell. It means that you can now use the latest
shell functionality. Both MONGODB-CR and SCRAM-SHA-1 authentication mechanisms are
supprted. Also, we now allow direct connections to Replica Set members and
support IPv6 protocol. Loading of huge list of collections is now faster.
Fixed incorrect representation of floating-point numbers (fixed since RC3).
This bug was a reason of a big number of issues:
#910, #748, #697, #699, #760, #815, #870, #892,
#894, #905, #945, #974, #978, #990, #831, #842
What's new in RC7 (March 4, 2016 )
http://blog.robomongo.org/robomongo-rc7
What's new in RC6 (February 13, 2016 )
http://blog.robomongo.org/ipv6-protocol
What's new in RC5 (February 8, 2016 )
http://blog.robomongo.org/campaign-is-over-robomongo-is-not/
What's new in RC4 (February 1, 2016 )
http://blog.robomongo.org/robomongo-rc4
What's new in RC3 (January 29, 2016 )
http://blog.robomongo.org/robomongo-0-9-0-rc3-released
What's new in RC2 (January 26, 2016)
http://blog.robomongo.org/robomongo-rc2-for-windows-mac-os-x-and-linux
What's new in RC1 (January 17, 2016)
http://blog.robomongo.org/robomongo-rc1-with-full-support-for-mongodb-3
0.8.5 / March 10, 2015
Release 0.8.5 is now deprecated because of a critical bug, that is
described here: http://blog.robomongo.org/robomongo-0-9-0-rc3-released
We recommend to use Robomongo 0.9.0 or newer.
* [Stephen Steneker]
- OS X build fixes for XCode 5 / libc++ (#524)
- OS X specific keyboard shortcut tips (#505)
+15 -11
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1.0)
cmake_minimum_required(VERSION 3.0.0)
project(Robomongo)
# Where to look first for CMake modules, before "${CMAKE_ROOT}/Modules" is checked
@@ -8,20 +8,21 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
set(PROJECT_VERSION_MAJOR "0")
set(PROJECT_VERSION_MINOR "9")
set(PROJECT_VERSION_PATCH "0")
set(PROJECT_VERSION_BUILD "-RC2")
set(PROJECT_VERSION_BUILD "-RC7")
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${PROJECT_VERSION_BUILD}")
# Modules
include(PrintUtils)
include(DefineCMakeDefaults)
include(DefineDefaults)
include(DefineTrashSymbols)
include(TargetArch)
include(QtInstall)
include(PackageRobomongo)
include(RobomongoPrintUtils)
include(RobomongoCMakeDefaults)
include(RobomongoDefaults)
include(RobomongoCommon)
include(RobomongoTrashSymbols)
include(RobomongoTargetArch)
include(RobomongoInstallQt)
include(RobomongoPackage)
# Search for dependencies
find_package(Threads REQUIRED)
find_package(Threading REQUIRED) # Wrapper arround CMake "Threads" module
find_package(Qt5Core REQUIRED)
find_package(Qt5Gui REQUIRED)
find_package(Qt5Widgets REQUIRED)
@@ -33,10 +34,13 @@ if (SYSTEM_MACOSX)
endif()
# Projects
add_subdirectory(src/third-party/openssl)
add_subdirectory(src/third-party/libssh2)
add_subdirectory(src/robomongo/ssh)
add_subdirectory(src/third-party/qjson)
add_subdirectory(src/third-party/qscintilla)
add_subdirectory(src/robomongo)
# Show configuration summary
include(ConfigurationSummary)
include(RobomongoConfigurationSummary)
+3 -9
View File
@@ -1,17 +1,11 @@
Decide the future of Robomongo
==============
![Robomongo mascot](http://app.robomongo.org/images/robomongo-64x64.png)
We've started a campaign on [Indiegogo](https://www.indiegogo.com/projects/save-robomongo) in order to continue development of Robomongo. We want to keep Robomongo free forever. Everyone has a chance to decide the future of the project. Read more on [Robomongo website](http://robomongo.org).
About Robomongo
===============
[Robomongo](http://www.robomongo.org) is a shell-centric cross-platform MongoDB management tool. Unlike most other MongoDB admin UI tools, Robomongo embeds the actual `mongo` shell in a tabbed interface with access to a shell command line as well as GUI interaction.
Note: Robomongo 0.8.x currently embeds the **MongoDB 2.4.0** shell using the SpiderMonkey JavaScript engine. We are working on updating this to the MongoDB 3.0 shell with V8 for the 0.9.x release series.
Starting from version 0.9, Robomongo is compatibile with MongoDB 3.x (including SCRAM-SHA-1 auth and support for WiredTiger storage engine). Robomongo 0.9 embeds the **MongoDB 3.2** shell.
Robomongo 0.8.x embeds the **MongoDB 2.4.0** shell.
Download
+4 -1
View File
@@ -48,6 +48,7 @@ set(MongoDB_INCLUDE_DIRS
${MongoDB_DIR}/src
${MongoDB_DIR}/src/third_party/boost-1.56.0
${MongoDB_DIR}/src/third_party/mozjs-38/include
${MongoDB_DIR}/src/third_party/mozjs-38/mongo_sources
${MongoDB_DIR}/src/third_party/pcre-8.37
${MongoDB_BUILD_DIR}
)
@@ -102,9 +103,11 @@ execute_process(
# all listed variables are TRUE
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MongoDB
FOUND_VAR MongoDB_FOUND # When using CMake 3.0 MONGODB_FOUND variable will be created.
# Make it explicit that variable name is MongoDB_FOUND.
REQUIRED_VARS MongoDB_DIR MongoDB_BUILD_DIR
VERSION_VAR MongoDB_RECENT_TAG
FAIL_MESSAGE "Could not find MongoDB. Make sure that CMAKE_PREFIX_PATH points to MongoDB project root.\n")
FAIL_MESSAGE "Could not find Robomongo Shell (MongoDB fork). Make sure that CMAKE_PREFIX_PATH points to Robomongo Shell project root.\n")
if(MongoDB_FOUND)
set(MongoDB_VERSION ${MongoDB_RECENT_TAG})
+21
View File
@@ -0,0 +1,21 @@
# This wrapper is created in order to support CMake 3.0
# Only starting from CMake 3.1 "Threads" module exports
# "Threads::Threads" target.
find_package(Threads REQUIRED)
# Support for CMake 3.0
# Export "Threads::Threads" imported target if it wasn't exported.
# This lines are taken from the "FindThreads.cmake" of CMake 3.1:
# https://github.com/Kitware/CMake/blob/v3.1.0/Modules/FindThreads.cmake
if(THREADS_FOUND AND NOT TARGET Threads::Threads)
add_library(Threads::Threads INTERFACE IMPORTED)
if(THREADS_HAVE_PTHREAD_ARG)
set_property(TARGET Threads::Threads PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread")
endif()
if(CMAKE_THREAD_LIBS_INIT)
set_property(TARGET Threads::Threads PROPERTY INTERFACE_LINK_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}")
endif()
endif()
@@ -25,8 +25,8 @@ if(NOT CMAKE_BUILD_TYPE)
FORCE)
endif()
# Automatically generate code for Qt moc, uic and rcc files
# Do not use Qt moc, uic and rcc by default
# Since CMake 2.8.6
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC OFF)
set(CMAKE_AUTORCC OFF)
set(CMAKE_AUTOUIC OFF)
+3
View File
@@ -0,0 +1,3 @@
set(ROBOMONGO_DISPLAY_NAME "Robomongo")
set(ROBOMONGO_DISPLAY_FULL_NAME "Robomongo ${PROJECT_VERSION}")
@@ -64,30 +64,41 @@ elseif(SYSTEM_WINDOWS)
set(files_dir "${CMAKE_SOURCE_DIR}/install/windows")
set(exe_name "Robomongo.exe")
set(CPACK_GENERATOR NSIS)
set(CPACK_GENERATOR NSIS ZIP)
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/DESCRIPTION")
# License file will be shown in the installation wizard
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
# Name of the folder where Robomongo files will be installed
set(install_dir "Robomongo ${CPACK_PACKAGE_VERSION}")
# For stable releases (ones that have empty build version)
# use simple name of the folder, without version
if(PROJECT_VERSION_BUILD STREQUAL "")
set(install_dir "Robomongo")
endif()
# Default installation directory (not full path), just the path after
# default "c:\Program Files"
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${install_dir}")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${ROBOMONGO_DISPLAY_FULL_NAME}")
# A path to the executable inside package that contains the installer icon
# A path to the executable inside installed package that contains icon
# This icon is used in Windows "Add or remove programs" control panel
set(CPACK_NSIS_INSTALLED_ICON_NAME "\\\\${exe_name}")
# The title displayed at the top of the installer
set(CPACK_NSIS_PACKAGE_NAME "Robomongo ${CPACK_PACKAGE_VERSION}")
# Create shortcuts on desktop and start-up menu
set(CPACK_NSIS_CREATE_ICONS_EXTRA "
CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${ROBOMONGO_DISPLAY_FULL_NAME}.lnk' '$INSTDIR\\\\${exe_name}'
CreateShortCut '$DESKTOP\\\\${ROBOMONGO_DISPLAY_FULL_NAME}.lnk' '$INSTDIR\\\\${exe_name}'
")
# Installer images
# Cleanup during uninstallation. This cleanup is required because shortcuts
# will not be deleted automatically.
# Without line '!insertmacro...' shortcut in start menu will not be removed
# (i.e. it doesn't work when we referece '$STARTMENU_FOLDER' that is used
# when creating shortcuts). This solution was found somewhere in internet.
set(CPACK_NSIS_DELETE_ICONS_EXTRA "
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete '$DESKTOP\\\\${ROBOMONGO_DISPLAY_FULL_NAME}.lnk'
Delete '$SMPROGRAMS\\\\$MUI_TEMP\\\\${ROBOMONGO_DISPLAY_FULL_NAME}.lnk'
")
# The title displayed at the top of the installer
set(CPACK_NSIS_PACKAGE_NAME "${ROBOMONGO_DISPLAY_FULL_NAME}")
# Installer images (for sidebar and topbar)
set(CPACK_PACKAGE_ICON "${files_dir}\\\\nsis-topbar.bmp")
set(CPACK_NSIS_INSTALLER_MUI_ICON_CODE "!define MUI_WELCOMEFINISHPAGE_BITMAP \\\"${files_dir}\\\\nsis-sidebar.bmp\\\"")
@@ -99,6 +110,11 @@ elseif(SYSTEM_WINDOWS)
# Specify an executable to add an option to run on the finish
# page of the NSIS installer. Without "..\\\\" it dowsn't work for NSIS 2.5
set(CPACK_NSIS_MUI_FINISHPAGE_RUN "..\\\\${exe_name}")
# Trying to set for installer and uninstaller, but this is
# doesn't work and default icons are still used...
set(CPACK_NSIS_MUI_ICON "${files_dir}\\\\robomongo.ico")
set(CPACK_NSIS_MUI_UNIICON "${files_dir}\\\\robomongo.ico")
endif()
include(CPack)
@@ -93,7 +93,7 @@ function(print_target_include_dirs target)
endfunction()
###
# Prints target include directories
# Prints target compile definitions
#
function(print_target_compile_definitions target)
get_property(defs TARGET ${target} PROPERTY INTERFACE_COMPILE_DEFINITIONS)
@@ -102,6 +102,16 @@ function(print_target_compile_definitions target)
endforeach()
endfunction()
###
# Prints target link libraries
#
function(print_target_link_libraries target)
get_property(defs TARGET ${target} PROPERTY INTERFACE_LINK_LIBRARIES)
foreach(def ${defs})
message("${def}")
endforeach()
endfunction()
###
# Prints Qt5 module plugins
#
@@ -1,5 +1,6 @@
if(SYSTEM_LINUX OR SYSTEM_MACOSX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99")
elseif(SYSTEM_WINDOWS)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_UNICODE -DUNICODE -DWIN32 -D_WIN32 -D_CRT_SECURE_NO_WARNINGS -DBOOST_ALL_NO_LIB")
+29 -15
View File
@@ -1,26 +1,40 @@
Building MongoDB
================
Building Robomongo Shell
========================
Linux and Mac
-------------
Robomongo uses [modified version](https://github.com/paralect/robomongo-shell/tree/roboshell-v3.2) of MongoDB that we call Robomongo Shell. Before you can build Robomongo, you have to build Robomongo Shell. The following instructions are applicable only for Mac OS X and Linux. For Windows instructions please check [Building Robomongo on Windows](BuildingRobomongoOnWindows.md).
Clone MongoDB fork and checkout to "roboshell-v3.2" branch:
Build for Mac OS X or Linux
---------------------------
$ git clone git@github.com:paralect/robomongo-shell.git
$ cd robomongo-shell
$ git checkout roboshell-v3.2
Build MongoDB shell in release mode:
1. Clone Robomongo Shell and checkout to `roboshell-v3.2` branch:
$ bin/build
```sh
$ git clone https://github.com/paralect/robomongo-shell.git
$ cd robomongo-shell
$ git checkout roboshell-v3.2
```
Clean build files for release mode:
2. Build Robomongo Shell:
```sh
$ bin/build
```
Done! Now you can continue with [Step 2](BuildingRobomongo.md#step-2) and build Robomongo
with embedded MongoDB 3.2 shell (that you've just built).
<br/>
#### Advanced helper commands
The following commands are needed only if you are planning to develop or deeper understand
Robomongo or Robomongo Shell build processes.
Clean build files for release mode (folder `build/opt` will be removed):
$ bin/clean
#### Advanced
Here is command that is executed by `bin/build` script:
Here is a command that is executed by `bin/build` script:
$ scons mongo -j8 --release --osx-version-min=10.9
+6 -5
View File
@@ -3,8 +3,8 @@ Building Robomongo
#### Step 1.
Download and build Robomongo shell (our fork of MongoDB).
Refer to [Building MongoDB fork](BuildingMongoDB.md) document in this folder.
Download and build Robomongo Shell (our fork of MongoDB).
Refer to [Building Robomongo Shell](BuildingMongoDB.md) document.
#### Step 2.
@@ -13,10 +13,10 @@ Download Robomongo:
$ git clone https://github.com/paralect/robomongo
$ cd robomongo
Set special environment variable `ROBOMONGO_CMAKE_PREFIX_PATH` to point to set of
Set special environment variable `ROBOMONGO_CMAKE_PREFIX_PATH` to point to a set of
directories:
1. Location of Qt SDK
1. Location of Qt 5 SDK
2. Location of Robomongo Shell (built in "Step 1")
Separate directories by semicolon `;` (not colon):
@@ -31,6 +31,7 @@ And finally, build Robomongo:
$ bin/build
<br>
## Helper commands
Execute just built Robomongo:
@@ -48,4 +49,4 @@ Install Robomongo to `build/release/install` folder:
Create package for your OS in `build/release/package` folder:
$ bin/pack
+2 -2
View File
@@ -105,7 +105,7 @@ directories:
1. Location of Qt SDK
2. Location of Robomongo Shell
Separate directories by semicolon `;` (not colon).
Separate directories by semicolon `;` (not colon). You can do this in Command Prompt:
> setx ROBOMONGO_CMAKE_PREFIX_PATH "d:\Qt-5\5.5\msvc2013_64;d:\Projects\robomongo-shell"
@@ -129,4 +129,4 @@ Clean build files (in order to start build from scratch):
Install Robomongo to `build\Release\install` folder:
> bin\install
+61
View File
@@ -0,0 +1,61 @@
# BasedOnStyle: Google
AccessModifierOffset: -4
AlignOperands: false
AlignAfterOpenBracket: true
AlignEscapedNewlinesLeft: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: false
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
ColumnLimit: 100
CommentPragmas: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
IndentCaseLabels: true
IndentFunctionDeclarationAfterType: false
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: true
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 4
UseTab: Never
+17 -5
View File
@@ -1,3 +1,10 @@
# Automatically generate code for Qt moc, uic and rcc files
# Since CMake 2.8.6
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(SOURCES
# Isolated Scope #1
@@ -109,6 +116,10 @@ set(SOURCES
gui/widgets/LogWidget.cpp
gui/MainWindow.cpp
gui/dialogs/SSHTunnelTab.cpp
core/settings/SshSettings.cpp
core/mongodb/SshTunnelWorker.cpp
gui/resources/gui.qrc
)
@@ -122,6 +133,7 @@ target_link_libraries(robomongo
qjson
qscintilla
mongodb
ssh
Threads::Threads) # Target from FindThreads CMake module
target_include_directories(robomongo
@@ -183,15 +195,15 @@ elseif(SYSTEM_LINUX)
endif()
# Install
include(InstallRobomongo)
include(RobomongoInstall)
#
# Helper targets (code below should be moved to separate file)
# Tests targets (code below should be moved to separate file)
#
add_executable(main2 WIN32 EXCLUDE_FROM_ALL app/main_json.cpp gui/editors/JSLexer.cpp)
target_link_libraries(main2 Qt5::Widgets qjson qscintilla mongodb)
target_include_directories(main2
add_executable(tests WIN32 EXCLUDE_FROM_ALL app/main_test.cpp gui/editors/JSLexer.cpp)
target_link_libraries(tests Qt5::Widgets qjson qscintilla mongodb Threads::Threads)
target_include_directories(tests
PRIVATE
${CMAKE_HOME_DIRECTORY}/src)
+42 -10
View File
@@ -2,37 +2,69 @@
#include <QDesktopWidget>
#include <locale.h>
// Header "mongo/util/net/sock" is needed for mongo::enableIPv6()
// Header "mongo/platform/basic" is required by "sock.h" under Windows
#include <mongo/platform/basic.h>
#include <mongo/util/net/sock.h>
#include <mongo/base/initializer.h>
#include "robomongo/gui/MainWindow.h"
#include "robomongo/gui/AppStyle.h"
#include "robomongo/ssh/ssh.h"
int main(int argc, char *argv[], char** envp)
{
if (rbm_ssh_init()) {
return 1;
}
// Please check, do we really need envp for other OSes?
#ifdef Q_OS_WIN
envp = NULL;
#endif
// Support for IPv6 is disabled by default. Enable it.
mongo::enableIPv6(true);
// Initialization routine for MongoDB shell
mongo::runGlobalInitializersOrDie(argc, argv, envp);
// Initialize Qt application
QApplication app(argc, argv);
Robomongo::detail::initStyle();
setlocale(LC_NUMERIC, "C"); // Do not move this line
// On Unix/Linux Qt is configured to use the system locale settings by default.
// This can cause a conflict when using POSIX functions, for instance, when
// converting between data types such as floats and strings, since the notation
// may differ between locales. To get around this problem, call the POSIX
// function setlocale(LC_NUMERIC,"C") right after initializing QApplication or
// QCoreApplication to reset the locale that is used for number formatting to "C"-locale.
// (http://doc.qt.io/qt-5/qcoreapplication.html#locale-settings)
setlocale(LC_NUMERIC, "C");
// Init GUI style
Robomongo::AppStyleUtils::initStyle();
// Application main window
Robomongo::MainWindow win;
// Resize main window. We are trying to keep it "almost" maximized.
QRect screenGeometry = QApplication::desktop()->availableGeometry();
int horizontalMargin = (int)(screenGeometry.width() * 0.1);
int verticalMargin = (int)(screenGeometry.height() * 0.1);
QSize size(screenGeometry.width() - horizontalMargin,
screenGeometry.height() - verticalMargin);
int width = screenGeometry.width() - horizontalMargin;
int height = screenGeometry.height() - verticalMargin;
win.resize(QSize(width, height));
Robomongo::MainWindow win;
win.resize(size);
#if defined(Q_OS_MAC)
win.setUnifiedTitleAndToolBarOnMac(true);
#endif
// Center main window
int x = (screenGeometry.width() - win.width()) / 2;
int y = (screenGeometry.height() - win.height()) / 2;
win.move(x, y);
win.show();
return app.exec();
int rc = app.exec();
rbm_ssh_cleanup();
return rc;
}
-32
View File
@@ -1,32 +0,0 @@
#include <QApplication>
#include <QMainWindow>
#include <parser.h>
#include <serializer.h>
#include <Qsci/qsciscintilla.h>
#include <robomongo/gui/editors/JSLexer.h>
#include <mongo/util/exit_code.h>
namespace mongo {
extern bool isShell;
void logProcessDetailsForLogRotate() {}
void exitCleanly(ExitCode code) {}
}
int main(int argc, char *argv[], char** envp)
{
QApplication app(argc, argv);
QJson::Parser parser;
Robomongo::JSLexer lexer;
Robomongo::JSLexer lexer2z;
QsciScintilla editor;
QMainWindow win;
win.setWindowTitle("Some title");
win.show();
return app.exec();
}
+69
View File
@@ -0,0 +1,69 @@
#undef NDEBUG
#include <sstream>
#include <iostream>
#include <assert.h>
#include <limits>
#include <mongo/util/exit_code.h>
#include <mongo/util/net/hostandport.h>
namespace mongo {
extern bool isShell;
void logProcessDetailsForLogRotate() {}
void exitCleanly(ExitCode code) {}
}
void testHostAndPort() {
mongo::HostAndPort hp = mongo::HostAndPort("127.0.0.1", 20017);
assert(hp.toString() == "127.0.0.1:20017");
hp = mongo::HostAndPort("2a03:b0c0:3:d0::f3:1001", 20017);
assert(hp.toString() == "[2a03:b0c0:3:d0::f3:1001]:20017");
// toString() will wrap IPv6 address in brackets even if they
// are already exist. This is true at least for MongoDB 3.2
hp = mongo::HostAndPort("[2a03:b0c0:3:d0::f3:1001]", 20017);
assert(hp.toString() == "[[2a03:b0c0:3:d0::f3:1001]]:20017");
hp = mongo::HostAndPort("[2a03:b0c0:3:d0::f3:1001", 20017);
assert(hp.toString() == "[[2a03:b0c0:3:d0::f3:1001]:20017");
}
void precisionAssert(const std::string &text, double d) {
std::stringstream s;
s.precision(std::numeric_limits<double>::digits10);
s << d;
std::cout << "Checking " << text << " - ";
assert(text == s.str());
std::cout << "Correct. " << std::endl;
}
void testPrecision() {
precisionAssert("9.8", 9.8);
precisionAssert("9.9", 9.9);
precisionAssert("9.98", 9.98);
precisionAssert("9.987", 9.987);
precisionAssert("9.9876", 9.9876);
precisionAssert("9.98765", 9.98765);
precisionAssert("9.987654", 9.987654);
precisionAssert("9.9876543", 9.9876543);
precisionAssert("9.98765432", 9.98765432);
precisionAssert("9.987654321", 9.987654321);
precisionAssert("9.987654321", 9.9876543210);
precisionAssert("9.98765432109", 9.98765432109);
precisionAssert("9.987654321098", 9.987654321098);
precisionAssert("9.9876543210987", 9.9876543210987);
precisionAssert("9.98765432109876", 9.98765432109876);
precisionAssert("9.98765432109876", 9.987654321098765);
precisionAssert("9.98765432109877", 9.9876543210987654);
precisionAssert("3.1415", 3.1415);
precisionAssert("1.1", 1.1);
precisionAssert("9.7", 9.7);
}
int main(int argc, char *argv[], char** envp)
{
testHostAndPort();
testPrecision();
return 0;
}
+10 -4
View File
@@ -2,12 +2,18 @@
namespace Robomongo
{
EventError::EventError() {}
EventError::EventError() :
_isNull(true) {}
EventError::EventError(const std::string &errorMessage) :
_errorMessage(errorMessage) {}
_errorMessage(errorMessage),
_isNull(false) {}
bool EventError::isNull() const { return _errorMessage.empty(); }
bool EventError::isNull() const {
return _isNull;
}
const std::string &EventError::errorMessage() const { return _errorMessage; }
const std::string &EventError::errorMessage() const {
return _errorMessage;
}
}
+2
View File
@@ -41,5 +41,7 @@ namespace Robomongo
* @brief Error message
*/
const std::string _errorMessage;
bool _isNull;
};
}
+79 -13
View File
@@ -5,6 +5,8 @@
#include "robomongo/core/domain/MongoShell.h"
#include "robomongo/core/domain/MongoCollection.h"
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/mongodb/SshTunnelWorker.h"
#include "robomongo/core/EventBus.h"
#include "robomongo/core/utils/QtUtils.h"
#include "robomongo/core/utils/StdUtils.h"
@@ -31,7 +33,11 @@ namespace Robomongo
}
App::App(EventBus *const bus) : QObject(),
_bus(bus) { }
_bus(bus) {
_bus->subscribe(this, EstablishSshConnectionResponse::Type);
_bus->subscribe(this, ListenSshConnectionResponse::Type);
_bus->subscribe(this, LogEvent::Type);
}
App::~App()
{
@@ -40,22 +46,29 @@ namespace Robomongo
}
/**
* @brief Creates and opens new server connection.
* Creates and opens new server connection.
* @param connection: ConnectionSettings, that will be owned by MongoServer.
* @param visible: should this server be visible in UI (explorer) or not.
*/
MongoServer *App::openServer(ConnectionSettings *connection,
bool visible)
{
MongoServer *server = new MongoServer(connection, visible);
_servers.push_back(server);
MongoServer *App::openServer(ConnectionSettings *connection, bool visible) {
if (visible)
_bus->publish(new ConnectingEvent(this, server));
_bus->publish(new ConnectingEvent(this));
LOG_MSG(QString("Connecting to %1...").arg(QtUtils::toQString(server->connectionRecord()->getFullAddress())), mongo::logger::LogSeverity::Info());
server->tryConnect();
return server;
// When connection is not "visible" or do not have
// ssh settings enabled, continue without SSH Tunnel
if (!visible || !connection->sshSettings()->enabled()) {
return continueOpenServer(connection, visible);
}
// Open SSH channel and only after that open connection
LOG_MSG(QString("Creating SSH tunnel to %1:%2...")
.arg(QtUtils::toQString(connection->sshSettings()->host()))
.arg(connection->sshSettings()->port()), mongo::logger::LogSeverity::Info());
ConnectionSettings* settingsCopy = connection->clone();
SshTunnelWorker* sshWorker = new SshTunnelWorker(settingsCopy);
_bus->send(sshWorker, new EstablishSshConnectionRequest(this, sshWorker, settingsCopy, visible));
return NULL;
}
/**
@@ -102,7 +115,6 @@ namespace Robomongo
MongoShell *shell = new MongoShell(server,scriptInfo);
_shells.push_back(shell);
_bus->publish(new OpeningShellEvent(this, shell));
LOG_MSG("Opening shell...", mongo::logger::LogSeverity::Info());
shell->execute();
return shell;
}
@@ -122,4 +134,58 @@ namespace Robomongo
closeServer(shell->server());
delete shell;
}
MongoServer* App::continueOpenServer(ConnectionSettings *connection, bool visible, int localport) {
ConnectionSettings* settings = connection->clone();
// Modify connection settings when SSH tunnel is used
if (visible && settings->sshSettings()->enabled()) {
settings->setServerHost("127.0.0.1");
settings->setServerPort(localport);
}
MongoServer *server = new MongoServer(settings, visible);
_servers.push_back(server);
server->runWorkerThread();
LOG_MSG(QString("Connecting to %1...").arg(QtUtils::toQString(server->connectionRecord()->getFullAddress())), mongo::logger::LogSeverity::Info());
server->tryConnect();
return server;
}
void App::handle(EstablishSshConnectionResponse *event) {
if (event->isError()) {
_bus->publish(new ConnectionFailedEvent(this, event->error().errorMessage()));
return;
}
LOG_MSG(QString("SSH tunnel created successfully"), mongo::logger::LogSeverity::Info());
_bus->send(event->worker, new ListenSshConnectionRequest(this));
continueOpenServer(event->settings, event->visible, event->localport);
// record event->worker and delete when needed
}
void App::handle(LogEvent *event) {
if (event->level == LogEvent::ERROR) {
LOG_MSG(event->message, mongo::logger::LogSeverity::Error());
} else if (event->level == LogEvent::WARN) {
LOG_MSG(event->message, mongo::logger::LogSeverity::Warning());
} else if (event->level == LogEvent::INFO) {
LOG_MSG(event->message, mongo::logger::LogSeverity::Info());
} else if (event->level == LogEvent::DEBUG) {
LOG_MSG(event->message, mongo::logger::LogSeverity::Log());
}
}
void App::handle(ListenSshConnectionResponse *event) {
if (event->isError()) {
_bus->publish(new ConnectionFailedEvent(this, event->error().errorMessage()));
return;
}
LOG_MSG(QString("SSH tunnel closed."), mongo::logger::LogSeverity::Error());
}
}
+16 -8
View File
@@ -1,6 +1,7 @@
#pragma once
#include <QObject>
#include <vector>
#include <robomongo/core/events/MongoEvents.h>
#include "robomongo/core/domain/ScriptInfo.h"
@@ -12,9 +13,12 @@ namespace Robomongo
class MongoCollection;
class MongoShell;
class MongoDatabase;
class EstablishSshConnectionResponse;
class LogEvent;
namespace detail
{
/**
/**
* @brief Builds single collection query (i.e. db.my_col.find()) from
* string that doesn't contain "db.my_col." prefix.
*
@@ -59,11 +63,11 @@ namespace Robomongo
MongoShell *openShell(MongoServer *server, const QString &script, const std::string &dbName = std::string(),
bool execute = true, const QString &shellName = QString(),
const CursorPosition &cursorPosition = CursorPosition(),const QString &file=QString());
const CursorPosition &cursorPosition = CursorPosition(), const QString &file = QString());
MongoShell *openShell(MongoDatabase *database, const QString &script,
bool execute = true, const QString &shellName = QString(),
const CursorPosition &cursorPosition = CursorPosition(),const QString &filePathToSave=QString());
const CursorPosition &cursorPosition = CursorPosition(), const QString &filePathToSave = QString());
MongoShell *openShell(ConnectionSettings *connection, const ScriptInfo &scriptInfo);
MongoServersContainerType getServers() const {return _servers; };
@@ -74,20 +78,24 @@ namespace Robomongo
*/
void closeShell(MongoShell *shell);
public Q_SLOTS:
void handle(EstablishSshConnectionResponse *event);
void handle(ListenSshConnectionResponse *event);
void handle(LogEvent *event);
private:
MongoServer* continueOpenServer(ConnectionSettings *connection, bool visible, int localport = 0);
/**
* @brief MongoServers, owned by this App.
* MongoServers, owned by this App.
*/
MongoServersContainerType _servers;
/**
* @brief MongoShells, owned by this App.
* MongoShells, owned by this App.
*/
MongoShellsContainerType _shells;
/**
* @brief EventBus
*/
EventBus *const _bus;
};
}
@@ -18,7 +18,7 @@ namespace Robomongo
_system = true;
}
std::string MongoCollection::sizeString() const
/* std::string MongoCollection::sizeString() const
{
return MongoUtils::buildNiceSizeString(_info.sizeBytes()).toStdString();
}
@@ -26,5 +26,5 @@ namespace Robomongo
QString MongoCollection::storageSizeString() const
{
return MongoUtils::buildNiceSizeString(_info.storageSizeBytes());
}
}*/
}
+2 -2
View File
@@ -17,8 +17,8 @@ namespace Robomongo
std::string fullName() const { return _ns.toString(); }
MongoDatabase *database() const { return _database; }
std::string sizeString() const;
QString storageSizeString() const;
// std::string sizeString() const;
// QString storageSizeString() const;
private:
@@ -4,7 +4,9 @@
namespace Robomongo
{
MongoCollectionInfo::MongoCollectionInfo(mongo::BSONObj stats) : _ns(stats.getStringField("ns"))
MongoCollectionInfo::MongoCollectionInfo(const std::string &ns) : _ns(ns) {}
/* MongoCollectionInfo::MongoCollectionInfo(mongo::BSONObj stats) : _ns(stats.getStringField("ns"))
{
// if "size" and "storageSize" are of type Int32 or Int64, they
// will be converted to double by "numberDouble()" function.
@@ -13,6 +15,6 @@ namespace Robomongo
// NumberLong because of mongodb can have very big collections
_count = BsonUtils::getField<mongo::NumberLong>(stats,"count");
}
}*/
}
@@ -7,7 +7,9 @@ namespace Robomongo
class MongoCollectionInfo
{
public:
MongoCollectionInfo(mongo::BSONObj stats);
MongoCollectionInfo() {}
MongoCollectionInfo(const std::string &ns);
// MongoCollectionInfo(mongo::BSONObj stats);
std::string name() const { return _ns.collectionName(); }
std::string fullName() const { return _ns.toString(); }
@@ -18,16 +20,16 @@ namespace Robomongo
* It is double, because db.stats()'s "size" field may be double
* for large values, while Int32 for small.
*/
double sizeBytes() const { return _sizeBytes; }
// double sizeBytes() const { return _sizeBytes; }
/**
* @brief Storage size in bytes
* It is double, because db.stats()'s "storageSize" field may be double
* for large values, while Int32 for small.
*/
double storageSizeBytes() const { return _storageSizeBytes; }
// double storageSizeBytes() const { return _storageSizeBytes; }
long long count() const { return _count; }
// long long count() const { return _count; }
private:
MongoNamespace _ns;
+12 -9
View File
@@ -8,7 +8,6 @@
namespace Robomongo
{
R_REGISTER_EVENT(MongoDatabaseCollectionListLoadedEvent)
R_REGISTER_EVENT(MongoDatabaseUsersLoadedEvent)
R_REGISTER_EVENT(MongoDatabaseFunctionsLoadedEvent)
@@ -21,9 +20,7 @@ namespace Robomongo
_system(name == "admin" || name == "local"),
_server(server),
_bus(AppRegistry::instance().bus()),
_name(name)
{
}
_name(name) {}
MongoDatabase::~MongoDatabase()
{
@@ -99,8 +96,10 @@ namespace Robomongo
void MongoDatabase::handle(LoadCollectionNamesResponse *loaded)
{
if (loaded->isError())
if (loaded->isError()) {
_bus->publish(new MongoDatabaseCollectionListLoadedEvent(this, loaded->error()));
return;
}
clearCollections();
const std::vector<MongoCollectionInfo> &colectionsInfos = loaded->collectionInfos();
@@ -122,17 +121,21 @@ namespace Robomongo
void MongoDatabase::handle(LoadUsersResponse *event)
{
if (event->isError())
if (event->isError()) {
_bus->publish(new MongoDatabaseUsersLoadedEvent(this, event->error()));
return;
}
_bus->publish(new MongoDatabaseUsersLoadedEvent(this, this, event->users()));
}
void MongoDatabase::handle(LoadFunctionsResponse *event)
{
if (event->isError())
if (event->isError()) {
_bus->publish(new MongoDatabaseFunctionsLoadedEvent(this, event->error()));
return;
}
_bus->publish(new MongoDatabaseFunctionsLoadedEvent(this, this, event->functions()));
}
@@ -86,6 +86,9 @@ namespace Robomongo
Event(sender),
collections(list) { }
MongoDatabaseCollectionListLoadedEvent(QObject *sender, const EventError &error) :
Event(sender, error) {}
std::vector<MongoCollection *> collections;
};
@@ -98,6 +101,9 @@ namespace Robomongo
_users(list),
_database(database) {}
MongoDatabaseUsersLoadedEvent(QObject *sender, const EventError &error) :
Event(sender, error) {}
std::vector<MongoUser> users() const { return _users; }
MongoDatabase *database() const { return _database; }
@@ -115,6 +121,9 @@ namespace Robomongo
_functions(list),
_database(database) {}
MongoDatabaseFunctionsLoadedEvent(QObject *sender, const EventError &error) :
Event(sender, error) {}
std::vector<MongoFunction> functions() const { return _functions; }
MongoDatabase *database() const { return _database; }
+83 -75
View File
@@ -2,75 +2,71 @@
#include "robomongo/core/domain/MongoDatabase.h"
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/settings/SettingsManager.h"
#include "robomongo/core/mongodb/MongoWorker.h"
#include "robomongo/core/mongodb/SshTunnelWorker.h"
#include "robomongo/core/AppRegistry.h"
#include "robomongo/core/EventBus.h"
#include "robomongo/core/utils/QtUtils.h"
namespace Robomongo
{
namespace Robomongo {
R_REGISTER_EVENT(MongoServerLoadingDatabasesEvent)
MongoServer::MongoServer(ConnectionSettings *connectionRecord, bool visible) : QObject(),
MongoServer::MongoServer(ConnectionSettings *settings, bool visible) : QObject(),
_version(0.0f),
_visible(visible),
_client(new MongoWorker(connectionRecord->clone(),AppRegistry::instance().settingsManager()->loadMongoRcJs(),AppRegistry::instance().settingsManager()->batchSize())),
_isConnected(false)
{
}
_client(NULL),
_isConnected(false),
_settings(settings),
_bus(AppRegistry::instance().bus()) { }
bool MongoServer::isConnected() const
{
bool MongoServer::isConnected() const {
return _isConnected;
}
ConnectionSettings *MongoServer::connectionRecord() const
{
return _client->connectionRecord();
ConnectionSettings *MongoServer::connectionRecord() const {
return _settings;
}
MongoServer::~MongoServer()
{
MongoServer::~MongoServer() {
clearDatabases();
_client->stopAndDelete();
if (_client != NULL) {
_client->stopAndDelete();
}
// MongoWorker "_client" does not deleted here, because it is now owned by
// another thread (call to moveToThread() made in MongoWorker constructor).
// It will be deleted by this thread by means of "deleteLater()", which
// is also specified in MongoWorker constructor.
delete _settings;
}
/**
* @brief Try to connect to MongoDB server.
* @throws MongoException, if fails
*/
void MongoServer::tryConnect()
{
AppRegistry::instance().bus()->send(_client,new EstablishConnectionRequest(this,
"_connectionRecord->databaseName()",
"_connectionRecord->userName()",
"_connectionRecord->userPassword()"));
void MongoServer::tryConnect() {
_bus->send(_client, new EstablishConnectionRequest(this));
}
QStringList MongoServer::getDatabasesNames() const
{
QStringList MongoServer::getDatabasesNames() const {
QStringList result;
for (DatabasesContainerType::const_iterator it = _databases.begin(); it != _databases.end(); ++it) {
for (QList<MongoDatabase *>::const_iterator it = _databases.begin(); it != _databases.end(); ++it) {
MongoDatabase *datab = *it;
result.append(QtUtils::toQString(datab->name()));
}
return result;
}
void MongoServer::createDatabase(const std::string &dbName)
{
AppRegistry::instance().bus()->send(_client,new CreateDatabaseRequest(this, dbName));
void MongoServer::createDatabase(const std::string &dbName) {
_bus->send(_client,new CreateDatabaseRequest(this, dbName));
}
MongoDatabase *MongoServer::findDatabaseByName(const std::string &dbName) const
{
for (DatabasesContainerType::const_iterator it = _databases.begin(); it != _databases.end(); ++it) {
MongoDatabase *MongoServer::findDatabaseByName(const std::string &dbName) const {
for (QList<MongoDatabase *>::const_iterator it = _databases.begin(); it != _databases.end(); ++it) {
MongoDatabase *datab = *it;
if (datab->name() == dbName) {
return datab;
@@ -79,79 +75,82 @@ namespace Robomongo
return NULL;
}
void MongoServer::dropDatabase(const std::string &dbName)
{
AppRegistry::instance().bus()->send(_client,new DropDatabaseRequest(this, dbName));
void MongoServer::dropDatabase(const std::string &dbName) {
_bus->send(_client,new DropDatabaseRequest(this, dbName));
}
void MongoServer::insertDocuments(const std::vector<mongo::BSONObj> &objCont, const MongoNamespace &ns)
{
void MongoServer::insertDocuments(const std::vector<mongo::BSONObj> &objCont,
const MongoNamespace &ns) {
for (std::vector<mongo::BSONObj>::const_iterator it = objCont.begin(); it != objCont.end(); it++) {
insertDocument(*it, ns);
}
}
void MongoServer::insertDocument(const mongo::BSONObj &obj, const MongoNamespace &ns)
{
AppRegistry::instance().bus()->send(_client,new InsertDocumentRequest(this, obj, ns));
void MongoServer::insertDocument(const mongo::BSONObj &obj, const MongoNamespace &ns) {
_bus->send(_client,new InsertDocumentRequest(this, obj, ns));
}
void MongoServer::saveDocuments(const std::vector<mongo::BSONObj> &objCont, const MongoNamespace &ns)
{
void MongoServer::saveDocuments(const std::vector<mongo::BSONObj> &objCont, const MongoNamespace &ns) {
for (std::vector<mongo::BSONObj>::const_iterator it = objCont.begin(); it != objCont.end(); it++) {
saveDocument(*it,ns);
}
}
void MongoServer::saveDocument(const mongo::BSONObj &obj, const MongoNamespace &ns)
{
AppRegistry::instance().bus()->send(_client, new InsertDocumentRequest(this, obj, ns, true));
void MongoServer::saveDocument(const mongo::BSONObj &obj, const MongoNamespace &ns) {
_bus->send(_client, new InsertDocumentRequest(this, obj, ns, true));
}
void MongoServer::removeDocuments(mongo::Query query, const MongoNamespace &ns, bool justOne)
{
AppRegistry::instance().bus()->send(_client, new RemoveDocumentRequest(this, query, ns, justOne));
void MongoServer::removeDocuments(mongo::Query query, const MongoNamespace &ns, bool justOne) {
_bus->send(_client, new RemoveDocumentRequest(this, query, ns, justOne));
}
void MongoServer::loadDatabases()
{
AppRegistry::instance().bus()->publish(new MongoServerLoadingDatabasesEvent(this));
AppRegistry::instance().bus()->send(_client, new LoadDatabaseNamesRequest(this));
void MongoServer::loadDatabases() {
_bus->publish(new MongoServerLoadingDatabasesEvent(this));
_bus->send(_client, new LoadDatabaseNamesRequest(this));
}
void MongoServer::clearDatabases()
{
void MongoServer::clearDatabases() {
qDeleteAll(_databases);
_databases.clear();
}
void MongoServer::addDatabase(MongoDatabase *database)
{
void MongoServer::addDatabase(MongoDatabase *database) {
_databases.append(database);
}
void MongoServer::handle(EstablishConnectionResponse *event)
{
void MongoServer::handle(EstablishConnectionResponse *event) {
if (event->isError()) {
_isConnected = false;
std::stringstream ss;
ss << "Cannot connect to the MongoDB at " << connectionRecord()->getFullAddress()
<< ".\n\nError:\n" << event->error().errorMessage();
_bus->publish(new ConnectionFailedEvent(this, ss.str()));
return;
}
const ConnectionInfo &info = event->info();
_isConnected = !event->isError();
if (event->isError()) {
AppRegistry::instance().bus()->publish(new ConnectionFailedEvent(this, event->error()));
} else if (_visible) {
AppRegistry::instance().bus()->publish(new ConnectionEstablishedEvent(this));
clearDatabases();
for(std::vector<std::string>::const_iterator it = info._databases.begin(); it != info._databases.end(); ++it) {
const std::string &name = *it;
MongoDatabase *db = new MongoDatabase(this, name);
addDatabase(db);
}
}
_version = info._version;
_isConnected = true;
// Do nothing if this is not "visible" connection
if (!_visible) {
return;
}
_bus->publish(new ConnectionEstablishedEvent(this));
clearDatabases();
for(std::vector<std::string>::const_iterator it = info._databases.begin(); it != info._databases.end(); ++it) {
const std::string &name = *it;
MongoDatabase *db = new MongoDatabase(this, name);
addDatabase(db);
}
}
void MongoServer::handle(LoadDatabaseNamesResponse *event)
{
void MongoServer::handle(LoadDatabaseNamesResponse *event) {
if (event->isError()) {
AppRegistry::instance().bus()->publish(new ConnectionFailedEvent(this, event->error()));
_bus->publish(new DatabaseListLoadedEvent(this, event->error()));
return;
}
@@ -162,11 +161,20 @@ namespace Robomongo
addDatabase(db);
}
AppRegistry::instance().bus()->publish(new DatabaseListLoadedEvent(this, _databases));
_bus->publish(new DatabaseListLoadedEvent(this, _databases));
}
void MongoServer::handle(InsertDocumentResponse *event)
{
AppRegistry::instance().bus()->publish(new InsertDocumentResponse(event->sender(), event->error()));
void MongoServer::handle(InsertDocumentResponse *event) {
_bus->publish(new InsertDocumentResponse(event->sender(), event->error()));
}
void MongoServer::handle(RemoveDocumentResponse *event) {
_bus->publish(new RemoveDocumentResponse(event->sender(), event->error()));
}
void MongoServer::runWorkerThread() {
_client = new MongoWorker(_settings->clone(),
AppRegistry::instance().settingsManager()->loadMongoRcJs(),
AppRegistry::instance().settingsManager()->batchSize());
}
}
+8 -3
View File
@@ -11,6 +11,7 @@ namespace Robomongo
class EstablishConnectionResponse;
class LoadDatabaseNamesResponse;
class InsertDocumentResponse;
class EventBus;
/**
* @brief MongoServer represents active connection to MongoDB server.
@@ -28,10 +29,11 @@ namespace Robomongo
* @param visible
* @param defaultDatabase
*/
typedef QList<MongoDatabase *> DatabasesContainerType;
MongoServer(ConnectionSettings *connectionRecord, bool visible);
~MongoServer();
void runWorkerThread();
/**
* @brief Try to connect to MongoDB server.
* @throws MongoException, if fails
@@ -68,18 +70,21 @@ namespace Robomongo
void handle(EstablishConnectionResponse *event);
void handle(LoadDatabaseNamesResponse *event);
void handle(InsertDocumentResponse *event);
void handle(RemoveDocumentResponse *event);
private:
void clearDatabases();
void addDatabase(MongoDatabase *database);
MongoWorker *const _client;
MongoWorker* _client;
ConnectionSettings* _settings;
EventBus* _bus;
float _version;
bool _visible;
bool _isConnected;
DatabasesContainerType _databases;
QList<MongoDatabase *> _databases;
};
class MongoServerLoadingDatabasesEvent : public Event
+17 -1
View File
@@ -62,7 +62,8 @@ namespace Robomongo
void MongoShell::stop()
{
mongo::Scope::setInterruptFlag(true);
// _server->client()->interrupt();
// mongo::Scope::setInterruptFlag(true);
}
bool MongoShell::loadFromFile()
@@ -82,16 +83,31 @@ namespace Robomongo
void MongoShell::handle(ExecuteQueryResponse *event)
{
if (event->isError()) {
AppRegistry::instance().bus()->publish(new DocumentListLoadedEvent(this, event->error()));
return;
}
AppRegistry::instance().bus()->publish(new DocumentListLoadedEvent(this, event->resultIndex, event->queryInfo, query(), event->documents));
}
void MongoShell::handle(ExecuteScriptResponse *event)
{
if (event->isError()) {
AppRegistry::instance().bus()->publish(new ScriptExecutedEvent(this, event->error()));
return;
}
AppRegistry::instance().bus()->publish(new ScriptExecutedEvent(this, event->result, event->empty));
}
void MongoShell::handle(AutocompleteResponse *event)
{
if (event->isError()) {
AppRegistry::instance().bus()->publish(new AutocompleteResponse(this, event->error()));
return;
}
AppRegistry::instance().bus()->publish(new AutocompleteResponse(this, event->list, event->prefix));
}
}
+31
View File
@@ -11,6 +11,7 @@
#include "robomongo/core/utils/BsonUtils.h"
#include "robomongo/core/settings/SettingsManager.h"
#include "robomongo/core/domain/MongoServer.h"
#include "robomongo/core/events/MongoEvents.h"
#include "robomongo/shell/db/ptimeutil.h"
@@ -92,6 +93,7 @@ namespace Robomongo
{
QWidget *wid = dynamic_cast<QWidget*>(_observer);
AppRegistry::instance().bus()->subscribe(this, InsertDocumentResponse::Type);
AppRegistry::instance().bus()->subscribe(this, RemoveDocumentResponse::Type);
_deleteDocumentAction = new QAction("Delete Document...", wid);
VERIFY(connect(_deleteDocumentAction, SIGNAL(triggered()), SLOT(onDeleteDocument())));
@@ -195,9 +197,37 @@ namespace Robomongo
void Notifier::handle(InsertDocumentResponse *event)
{
if (event->isError()) {
QMessageBox::warning(NULL, "Database Error", QString::fromStdString(event->error().errorMessage()));
return;
}
_shell->query(0, _queryInfo);
}
void Notifier::handle(RemoveDocumentResponse *event)
{
if (event->isError()) {
// Commented because when you'll delete multiple documents,
// we'll receive this event as many times as many selected
// documents you have. This is incorrect and remove of
// multiple documents should be a single command, instead
// of many. Error message should be printed in the logs.
// QMessageBox::warning(NULL, "Database Error", QString::fromStdString(event->error().errorMessage()));
return;
}
}
void Notifier::handleDeleteCommand()
{
if (_observer->selectedIndexes().count() > 1) {
onDeleteDocuments();
} else {
onDeleteDocument();
}
}
void Notifier::onDeleteDocuments()
{
if (!_queryInfo._info.isValid())
@@ -206,6 +236,7 @@ namespace Robomongo
QModelIndexList selectedIndexes = _observer->selectedIndexes();
if (!detail::isMultiSelection(selectedIndexes))
return;
int answer = QMessageBox::question(dynamic_cast<QWidget*>(_observer), "Delete", QString("Do you want to delete %1 selected documents?").arg(selectedIndexes.count()));
if (answer == QMessageBox::Yes) {
std::vector<BsonTreeItem*> items;
+4 -1
View File
@@ -14,6 +14,7 @@ namespace Robomongo
class MongoShell;
class BsonTreeItem;
class InsertDocumentResponse;
class RemoveDocumentResponse;
namespace detail
{
@@ -45,8 +46,9 @@ namespace Robomongo
void initMultiSelectionMenu(QMenu *const menu);
void deleteDocuments(std::vector<BsonTreeItem*> items, bool force);
void handleDeleteCommand();
public Q_SLOTS:
public Q_SLOTS:
void onDeleteDocument();
void onDeleteDocuments();
void onEditDocument();
@@ -56,6 +58,7 @@ namespace Robomongo
void onCopyTimestamp();
void onCopyJson();
void handle(InsertDocumentResponse *event);
void handle(RemoveDocumentResponse *event);
private:
QAction *_deleteDocumentAction;
+27 -23
View File
@@ -49,11 +49,8 @@ namespace
namespace mongo {
extern bool isShell;
void logProcessDetailsForLogRotate() {}
void exitCleanly(ExitCode code) {}
}
namespace Robomongo
@@ -85,7 +82,7 @@ namespace Robomongo
connectDatabase = _connection->primaryCredential()->databaseName();
std::stringstream ss;
ss << "db = connect('" << _connection->serverHost() << ":" << _connection->serverPort() << "/" << connectDatabase;
ss << "db = connect('" << _connection->info().toString() << "/" << connectDatabase;
// v0.9
// ss << "db = connect('" << _connection->serverHost() << ":" << _connection->serverPort() << _connection->sslInfo() << _connection->sshInfo() << "/" << connectDatabase;
@@ -114,18 +111,18 @@ namespace Robomongo
_engine = mongo::globalScriptEngine;
// Load '.mongorc.js' from user's home directory
// We are not checking whether file exists, because it will be
// checked by 'Scope::execFile'.
if (isLoadMongoRcJs) {
std::string mongorcPath = QtUtils::toStdString(QString("%1/.mongorc.js").arg(QDir::homePath()));
scope->execFile(mongorcPath, false, false);
QString mongorcPath = QString("%1/.mongorc.js").arg(QDir::homePath());
if (QFile::exists(mongorcPath)) {
scope->execFile(QtUtils::toStdString(mongorcPath), false, false);
}
}
// Load '.robomongorc.js'
// Alexander: branding very usfull see Chromium and his brand Chrome, in Chrome some features private
// Dmitry: I agree, but we still need to support ".robomongorc.js" even when name of project will change
std::string roboMongorcPath = QtUtils::toStdString(QString("%1/.robomongorc.js").arg(QDir::homePath()));
scope->execFile(roboMongorcPath, false, false);
QString robomongorcPath = QString("%1/.robomongorc.js").arg(QDir::homePath());
if (QFile::exists(robomongorcPath)) {
scope->execFile(QtUtils::toStdString(robomongorcPath), false, false);
}
}
// Esprima ECMAScript parser: http://esprima.org/
@@ -193,8 +190,9 @@ namespace Robomongo
try {
QElapsedTimer timer;
timer.start();
if ( _scope->exec( statement , "(shell)" , false , true , false, 3600000 ) )
_scope->exec( "__robomongoLastRes = __lastres__; shellPrintHelper( __lastres__ );" , "(shell2)" , true , true , false, 3600000);
if ( _scope->exec( statement , "(shell)" , false , true , false, 0 ) ) {
_scope->exec( "__robomongoLastRes = __lastres__; shellPrintHelper( __lastres__ );" , "(shell2)" , true , true , false, 0);
}
qint64 elapsed = timer.elapsed();
@@ -207,7 +205,7 @@ namespace Robomongo
results.push_back(prepareResult(type, answer, docs, elapsed));
}
catch (const std::exception &e) {
std::cout << "error:" << e.what() << endl;
std::cout << "error:" << e.what() << std::endl;
}
}
}
@@ -217,7 +215,8 @@ namespace Robomongo
void ScriptEngine::interrupt()
{
_engine->interruptAll();
// This operation crash Robomongo
// static_cast<mongo::mozjs::MozJSImplScope*>(_scope)->kill();
// v0.9
//mongo::Scope::_interruptFlag = true;
@@ -228,10 +227,15 @@ namespace Robomongo
QMutexLocker lock(&_mutex);
if (!dbName.empty()) {
// switch to database
char useDb[1024]={0};
sprintf(useDb,"shellHelper.use('%s');",dbName.c_str());
_scope->exec(useDb, "(usedb)", false, true, false);
std::stringstream ss;
// Switch to database
ss << "shellHelper.use('" << dbName << "');" << std::endl;
// Always allow to read from slave
ss << "rs.slaveOk();" << std::endl;
_scope->exec(ss.str(), "(usedb)", false, true, false);
}
}
@@ -239,10 +243,10 @@ namespace Robomongo
{
QMutexLocker lock(&_mutex);
char buff[64]={0};
sprintf(buff,"DBQuery.shellBatchSize = %d",batchSize);
char buff[64] = {0};
sprintf(buff, "DBQuery.shellBatchSize = %d", batchSize);
_scope->exec(buff, "(shellBatchSize)", true, true, true);
_scope->exec(buff, "(shellBatchSize)", false, true, true);
}
void ScriptEngine::ping()
@@ -60,4 +60,10 @@ namespace Robomongo
R_REGISTER_EVENT(DropFunctionRequest)
R_REGISTER_EVENT(DropFunctionResponse)
R_REGISTER_EVENT(QueryWidgetUpdatedEvent)
R_REGISTER_EVENT(EstablishSshConnectionRequest)
R_REGISTER_EVENT(EstablishSshConnectionResponse)
R_REGISTER_EVENT(ListenSshConnectionRequest)
R_REGISTER_EVENT(ListenSshConnectionResponse)
R_REGISTER_EVENT(LogEvent)
R_REGISTER_EVENT(StopScriptRequest)
}
+132 -25
View File
@@ -19,6 +19,8 @@ namespace Robomongo
class MongoShell;
class MongoDatabase;
class MongoWorker;
class ConnectionSettings;
class SshTunnelWorker;
/**
* @brief EstablishConnection
@@ -28,16 +30,8 @@ namespace Robomongo
{
R_EVENT
EstablishConnectionRequest(QObject *sender, const QString &databaseName,
const QString &userName, const QString &userPassword) :
Event(sender),
databaseName(databaseName),
userName(userName),
userPassword(userPassword) {}
QString databaseName;
QString userName;
QString userPassword;
EstablishConnectionRequest(QObject *sender) :
Event(sender) {}
};
class EstablishConnectionResponse : public Event
@@ -141,7 +135,11 @@ namespace Robomongo
{
R_EVENT
public:
LoadCollectionIndexesResponse(QObject *sender, const std::vector<EnsureIndexInfo> &indexes) :Event(sender), _indexes(indexes) {}
LoadCollectionIndexesResponse(QObject *sender, const std::vector<EnsureIndexInfo> &indexes) :
Event(sender), _indexes(indexes) {}
LoadCollectionIndexesResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
std::vector<EnsureIndexInfo> indexes() const { return _indexes; }
private:
std::vector<EnsureIndexInfo> _indexes;
@@ -185,11 +183,15 @@ namespace Robomongo
R_EVENT
public:
DeleteCollectionIndexResponse(QObject *sender, const MongoCollectionInfo &collection,const std::string &index) :
Event(sender),_collection(collection),_index(index) {}
Event(sender),_collection(collection),_index(index) {}
DeleteCollectionIndexResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
MongoCollectionInfo collection() const { return _collection; }
std::string index() const {return _index;}
private:
const MongoCollectionInfo _collection;
MongoCollectionInfo _collection;
std::string _index;
};
@@ -809,6 +811,9 @@ namespace Robomongo
list(list),
prefix(prefix) {}
AutocompleteResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
QStringList list;
std::string prefix;
};
@@ -855,11 +860,8 @@ namespace Robomongo
{
R_EVENT
ConnectingEvent(QObject *sender, MongoServer *server) :
Event(sender),
server(server) { }
MongoServer *server;
ConnectingEvent(QObject *sender) :
Event(sender) { }
};
class OpeningShellEvent : public Event
@@ -877,15 +879,14 @@ namespace Robomongo
{
R_EVENT
ConnectionFailedEvent(MongoServer *server) :
Event((QObject *)server),
server(server) { }
ConnectionFailedEvent(QObject *sender, const std::string& message) :
Event(sender),
message(message) {}
ConnectionFailedEvent(MongoServer *server, const EventError &error) :
Event((QObject *)server, error),
server(server) {}
ConnectionFailedEvent(QObject *sender, const EventError &error) :
Event(sender, error) {}
MongoServer *server;
std::string message;
};
// class ScriptExecute
@@ -909,6 +910,9 @@ namespace Robomongo
Event(sender),
list(list) { }
DatabaseListLoadedEvent(QObject *sender, const EventError &error) :
Event(sender, error) {}
QList<MongoDatabase *> list;
};
@@ -924,6 +928,9 @@ namespace Robomongo
_query(query),
_documents(docs) { }
DocumentListLoadedEvent(QObject *sender, const EventError &error) :
Event(sender, error) {}
int resultIndex() const { return _resultIndex; }
MongoQueryInfo queryInfo() const { return _queryInfo; }
std::vector<MongoDocumentPtr> documents() const { return _documents; }
@@ -946,6 +953,9 @@ namespace Robomongo
_result(result),
_empty(empty) { }
ScriptExecutedEvent(QObject *sender, const EventError &error) :
Event(sender, error) {}
MongoShellExecResult result() const { return _result; }
bool empty() const { return _empty; }
@@ -977,4 +987,101 @@ namespace Robomongo
private:
int _numOfResults;
};
/**
* @brief Establish SSH Connection
*/
class EstablishSshConnectionRequest : public Event
{
R_EVENT
EstablishSshConnectionRequest(QObject *sender, SshTunnelWorker* worker, ConnectionSettings* settings, bool visible) :
Event(sender),
worker(worker),
settings(settings),
visible(visible) {}
ConnectionSettings* settings;
bool visible;
SshTunnelWorker* worker;
};
class EstablishSshConnectionResponse : public Event
{
R_EVENT
EstablishSshConnectionResponse(QObject *sender, SshTunnelWorker* worker, ConnectionSettings* settings, bool visible, int localport) :
Event(sender),
worker(worker),
settings(settings),
visible(visible),
localport(localport) {}
EstablishSshConnectionResponse(QObject *sender, const EventError &error, SshTunnelWorker* worker, ConnectionSettings* settings, bool visible) :
Event(sender, error),
worker(worker),
settings(settings),
visible(visible) {}
ConnectionSettings* settings;
bool visible;
SshTunnelWorker* worker;
int localport;
};
/**
* @brief Start listening on all opened sockets (SSH tunnel)
*/
class ListenSshConnectionRequest : public Event
{
R_EVENT
ListenSshConnectionRequest(QObject *sender) :
Event(sender) {}
};
class ListenSshConnectionResponse : public Event
{
R_EVENT
ListenSshConnectionResponse(QObject *sender, ConnectionSettings* settings) :
Event(sender),
settings(settings) {}
ListenSshConnectionResponse(QObject *sender, const EventError &error, ConnectionSettings* settings) :
Event(sender, error),
settings(settings) {}
ConnectionSettings* settings;
};
class LogEvent : public Event
{
R_EVENT
enum LogLevel {
ERROR = 1,
WARN = 2,
INFO = 3,
DEBUG = 100 // log as much as possible
};
LogEvent(QObject *sender, const std::string& message, LogLevel level) :
Event(sender),
message(message),
level(level) {}
std::string message;
LogLevel level;
};
class StopScriptRequest : public Event
{
R_EVENT
StopScriptRequest(QObject *sender) :
Event(sender) {}
};
}
+43 -4
View File
@@ -34,7 +34,7 @@ namespace
namespace Robomongo
{
MongoClient::MongoClient(mongo::DBClientConnection *const dbclient) :
MongoClient::MongoClient(mongo::DBClientBase *const dbclient) :
_dbclient(dbclient) { }
std::vector<std::string> MongoClient::getCollectionNames(const std::string &dbname) const
@@ -79,6 +79,11 @@ namespace Robomongo
std::vector<MongoUser> users;
std::unique_ptr<mongo::DBClientCursor> cursor(_dbclient->query(ns.toString(), mongo::Query()));
// Cursor may be NULL, it means we have connectivity problem
if (!cursor)
throw mongo::DBException("Network error while attempting to load list of users", 0);
float ver = getVersion();
while (cursor->more()) {
mongo::BSONObj bsonObj = cursor->next();
@@ -126,6 +131,9 @@ namespace Robomongo
std::unique_ptr<mongo::DBClientCursor> cursor(_dbclient->query(ns.toString(), mongo::Query().sort("_id")));
// Cursor may be NULL, it means we have connectivity problem
if (!cursor)
throw mongo::DBException("Network error while attempting to load list of functions.", 0);
while (cursor->more()) {
mongo::BSONObj bsonObj = cursor->next();
@@ -381,18 +389,28 @@ namespace Robomongo
_dbclient->createCollection(to.toString());
std::unique_ptr<mongo::DBClientCursor> cursor(_dbclient->query(from.toString(), mongo::Query()));
// Cursor may be NULL, it means we have connectivity problem
if (!cursor)
throw mongo::DBException("Network error while attempting to run query", 0);
while (cursor->more()) {
mongo::BSONObj bsonObj = cursor->next();
_dbclient->insert(to.toString(), bsonObj);
}
}
void MongoClient::copyCollectionToDiffServer(mongo::DBClientConnection *const fromServ,const MongoNamespace &from, const MongoNamespace &to)
void MongoClient::copyCollectionToDiffServer(mongo::DBClientBase *const fromServ,const MongoNamespace &from, const MongoNamespace &to)
{
if (!_dbclient->exists(to.toString()))
_dbclient->createCollection(to.toString());
std::unique_ptr<mongo::DBClientCursor> cursor(fromServ->query(from.toString(), mongo::Query()));
// Cursor may be NULL, it means we have connectivity problem
if (!cursor)
throw mongo::DBException("Network error while attempting to run query", 0);
while (cursor->more()) {
mongo::BSONObj bsonObj = cursor->next();
_dbclient->insert(to.toString(), bsonObj);
@@ -407,11 +425,11 @@ namespace Robomongo
void MongoClient::insertDocument(const mongo::BSONObj &obj, const MongoNamespace &ns)
{
_dbclient->insert(ns.toString(), obj);
checkLastErrorAndThrow(ns.databaseName());
}
void MongoClient::saveDocument(const mongo::BSONObj &obj, const MongoNamespace &ns)
{
mongo::BSONElement id = obj.getField("_id");
mongo::BSONObjBuilder builder;
builder.append(id);
@@ -419,12 +437,13 @@ namespace Robomongo
mongo::Query query(bsonQuery);
_dbclient->update(ns.toString(), query, obj, true, false);
//_dbclient->save(ns.toString().toStdString(), obj);
checkLastErrorAndThrow(ns.databaseName());
}
void MongoClient::removeDocuments(const MongoNamespace &ns, mongo::Query query, bool justOne /*= true*/)
{
_dbclient->remove(ns.toString(), query, justOne);
checkLastErrorAndThrow(ns.databaseName());
}
std::vector<MongoDocumentPtr> MongoClient::query(const MongoQueryInfo &info)
@@ -442,6 +461,10 @@ namespace Robomongo
ns.toString(), info._query, info._limit, info._skip,
info._fields.nFields() ? &info._fields : 0, info._options, info._batchSize);
// DBClientBase::query may return nullptr
if (!cursor)
throw mongo::DBException("Network error while attempting to run query", 0);
while (cursor->more()) {
mongo::BSONObj bsonObj = cursor->next();
MongoDocumentPtr doc(new MongoDocument(bsonObj.getOwned()));
@@ -453,6 +476,10 @@ namespace Robomongo
MongoCollectionInfo MongoClient::runCollStatsCommand(const std::string &ns)
{
MongoCollectionInfo info(ns);
return info;
/* // Commented for now, to speedup load of collection names
MongoNamespace mongons(ns);
mongo::BSONObjBuilder command; // { collStats: "db.collection", scale : 1 }
@@ -464,6 +491,7 @@ namespace Robomongo
std::string isCV = result.toString();
MongoCollectionInfo newInfo(result);
return newInfo;
*/
}
std::vector<MongoCollectionInfo> MongoClient::runCollStatsCommand(const std::vector<std::string> &namespaces)
@@ -483,4 +511,15 @@ namespace Robomongo
// do nothing here, because we are not using ScopedDbConnection now
//_scopedConnection->done();
}
void MongoClient::checkLastErrorAndThrow(const std::string &db)
{
std::string lastError = _dbclient->getLastError(db);
// Nothing to do when there is no error
if (lastError.empty())
return;
throw mongo::DBException(lastError, mongo::ErrorCodes::InternalError);
}
}
+4 -3
View File
@@ -12,7 +12,7 @@ namespace Robomongo
class MongoClient
{
public:
MongoClient(mongo::DBClientConnection *const scopedConnection);
MongoClient(mongo::DBClientBase *const scopedConnection);
std::vector<std::string> getCollectionNames(const std::string &dbname) const;
std::vector<std::string> getDatabaseNames() const;
@@ -41,7 +41,7 @@ namespace Robomongo
void renameCollection(const MongoNamespace &ns, const std::string &newCollectionName);
void duplicateCollection(const MongoNamespace &ns, const std::string &newCollectionName);
void dropCollection(const MongoNamespace &ns);
void copyCollectionToDiffServer(mongo::DBClientConnection *const,const MongoNamespace &from, const MongoNamespace &to);
void copyCollectionToDiffServer(mongo::DBClientBase *const,const MongoNamespace &from, const MongoNamespace &to);
void insertDocument(const mongo::BSONObj &obj, const MongoNamespace &ns);
void saveDocument(const mongo::BSONObj &obj, const MongoNamespace &ns);
@@ -54,6 +54,7 @@ namespace Robomongo
void done();
private:
mongo::DBClientConnection *const _dbclient;
mongo::DBClientBase *const _dbclient;
void checkLastErrorAndThrow(const std::string &db);
};
}
+90 -61
View File
@@ -17,7 +17,7 @@
namespace Robomongo
{
MongoWorker::MongoWorker(ConnectionSettings *connection,bool isLoadMongoRcJs, int batchSize, QObject *parent) : QObject(parent),
MongoWorker::MongoWorker(ConnectionSettings *connection, bool isLoadMongoRcJs, int batchSize, QObject *parent) : QObject(parent),
_connection(connection),
_scriptEngine(NULL),
_dbclient(NULL),
@@ -37,8 +37,7 @@ namespace Robomongo
void MongoWorker::timerEvent(QTimerEvent *event)
{
if (_timerId==event->timerId())
{
if (_timerId == event->timerId()) {
keepAlive();
}
}
@@ -76,8 +75,18 @@ namespace Robomongo
_scriptEngine->use(_connection->defaultDatabase());
_scriptEngine->setBatchSize(_batchSize);
_timerId = startTimer(pingTimeMs);
} catch (const std::exception &ex) {
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
catch (const std::exception &ex) {
}
void MongoWorker::interrupt() {
try {
if (_isQuiting || !_scriptEngine)
return;
_scriptEngine->interrupt();
} catch(const mongo::DBException &ex) {
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -110,29 +119,30 @@ namespace Robomongo
mongo::DBClientBase *conn = getConnection();
bool hasPrimary = _connection->hasEnabledPrimaryCredential();
if (hasPrimary) {
std::string errmsg;
bool ok = conn->auth(
_connection->primaryCredential()->databaseName(),
_connection->primaryCredential()->userName(),
_connection->primaryCredential()->userPassword(), errmsg);
CredentialSettings *credentials = _connection->primaryCredential();
if (!ok) {
throw std::runtime_error("Unable to authorize");
}
// Building BSON object:
mongo::BSONObj authParams(mongo::BSONObjBuilder()
.append("user", credentials->userName())
.append("db", credentials->databaseName())
.append("pwd", credentials->userPassword())
.append("mechanism", credentials->mechanism())
.obj());
conn->auth(authParams);
// If authentication succeed and database name is 'admin' -
// then user is admin, otherwise user is not admin
std::string dbName = _connection->primaryCredential()->databaseName();
std::string dbName = credentials->databaseName();
std::transform(dbName.begin(), dbName.end(), dbName.begin(), ::tolower);
if (dbName.compare("admin") != 0) // dbName is NOT "admin"
_isAdmin = false;
}
boost::scoped_ptr<MongoClient> client(getClient());
//conn->done();
std::vector<std::string> dbNames = getDatabaseNamesSafe();
reply(event->sender(), new EstablishConnectionResponse(this, ConnectionInfo(_connection->getFullAddress(), dbNames, client->getVersion()) ));
} catch(const std::exception &ex) {
reply(event->sender(), new EstablishConnectionResponse(this, EventError("Unable to connect to MongoDB")));
reply(event->sender(), new EstablishConnectionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -158,10 +168,8 @@ namespace Robomongo
try {
boost::scoped_ptr<MongoClient> client(getClient());
result = client->getDatabaseNames();
}
catch(const std::exception &ex)
{
if(!authBase.empty())
} catch(const std::exception &ex) {
if (!authBase.empty())
result.push_back(authBase);
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
@@ -173,27 +181,32 @@ namespace Robomongo
*/
void MongoWorker::handle(LoadDatabaseNamesRequest *event)
{
// If user not an admin - he doesn't have access to mongodb 'listDatabases' command
// Non admin user has access only to the single database he specified while performing auth.
std::vector<std::string> dbNames = getDatabaseNamesSafe();
try {
// If user not an admin - he doesn't have access to mongodb 'listDatabases' command
// Non admin user has access only to the single database he specified while performing auth.
std::vector<std::string> dbNames = getDatabaseNamesSafe();
// Remove from list of created databases existing databases
for(std::vector<std::string>::iterator it = dbNames.begin(); it != dbNames.end(); ++it) {
std::unordered_set<std::string>::const_iterator exists = _createdDbs.find(*it);
if (exists != _createdDbs.end()) {
_createdDbs.erase(*it);
// Remove from list of created databases existing databases
for (std::vector<std::string>::iterator it = dbNames.begin(); it != dbNames.end(); ++it) {
std::unordered_set<std::string>::const_iterator exists = _createdDbs.find(*it);
if (exists != _createdDbs.end()) {
_createdDbs.erase(*it);
}
}
}
// Merge with list of created databases
for(std::unordered_set<std::string>::iterator it = _createdDbs.begin(); it != _createdDbs.end(); ++it) {
dbNames.push_back(*it);
}
// Merge with list of created databases
for (std::unordered_set<std::string>::iterator it = _createdDbs.begin(); it != _createdDbs.end(); ++it) {
dbNames.push_back(*it);
}
if(dbNames.size()){
reply(event->sender(), new LoadDatabaseNamesResponse(this, dbNames));
}else{
reply(event->sender(), new LoadDatabaseNamesResponse(this, EventError("Unable to load database names.")));
if (dbNames.size()) {
reply(event->sender(), new LoadDatabaseNamesResponse(this, dbNames));
} else {
reply(event->sender(), new LoadDatabaseNamesResponse(this, EventError("Unable to load database names.")));
}
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadDatabaseNamesResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -211,7 +224,7 @@ namespace Robomongo
reply(event->sender(), new LoadCollectionNamesResponse(this, event->databaseName(), infos));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadCollectionNamesResponse(this, EventError("Unable to load list of collections.")));
reply(event->sender(), new LoadCollectionNamesResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -225,7 +238,7 @@ namespace Robomongo
reply(event->sender(), new LoadUsersResponse(this, event->databaseName(), users));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadUsersResponse(this, EventError("Unable to load list of users.")));
reply(event->sender(), new LoadUsersResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -239,7 +252,7 @@ namespace Robomongo
reply(event->sender(), new LoadCollectionIndexesResponse(this, ind));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadCollectionIndexesResponse(this, std::vector<EnsureIndexInfo>()));
reply(event->sender(), new LoadCollectionIndexesResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -256,7 +269,7 @@ namespace Robomongo
reply(event->sender(), new LoadCollectionIndexesResponse(this, ind));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadCollectionIndexesResponse(this, std::vector<EnsureIndexInfo>()));
reply(event->sender(), new LoadCollectionIndexesResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -269,7 +282,7 @@ namespace Robomongo
client->done();
reply(event->sender(), new DeleteCollectionIndexResponse(this, event->collection(), event->name()));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DeleteCollectionIndexResponse(this, event->collection(), std::string() ));
reply(event->sender(), new DeleteCollectionIndexResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -284,7 +297,7 @@ namespace Robomongo
reply(event->sender(), new LoadCollectionIndexesResponse(this, ind));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadCollectionIndexesResponse(this, std::vector<EnsureIndexInfo>()));
reply(event->sender(), new LoadCollectionIndexesResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -298,7 +311,7 @@ namespace Robomongo
reply(event->sender(), new LoadFunctionsResponse(this, event->databaseName(), funs));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadFunctionsResponse(this, EventError("Unable to load list of functions.")));
reply(event->sender(), new LoadFunctionsResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -317,7 +330,8 @@ namespace Robomongo
reply(event->sender(), new InsertDocumentResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new InsertDocumentResponse(this, EventError("Unable to insert document.")));
EventError error = EventError("Error when saving document: " + ex.toString());
reply(event->sender(), new InsertDocumentResponse(this, error));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -332,7 +346,8 @@ namespace Robomongo
reply(event->sender(), new RemoveDocumentResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new RemoveDocumentResponse(this, EventError("Unable to remove documents.")));
EventError error = EventError("Error when deleting document: " + ex.toString());
reply(event->sender(), new RemoveDocumentResponse(this, error));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -346,7 +361,7 @@ namespace Robomongo
reply(event->sender(), new ExecuteQueryResponse(this, event->resultIndex(), event->queryInfo(), docs));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new ExecuteQueryResponse(this, EventError("Unable to complete query.")));
reply(event->sender(), new ExecuteQueryResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -360,7 +375,19 @@ namespace Robomongo
MongoShellExecResult result = _scriptEngine->exec(event->script, event->databaseName);
reply(event->sender(), new ExecuteScriptResponse(this, result, event->script.empty()));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new ExecuteScriptResponse(this, EventError("Unable to complete query.")));
reply(event->sender(), new ExecuteScriptResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
/**
* @brief Interrupt javascript execution
*/
void MongoWorker::handle(StopScriptRequest *)
{
try {
_scriptEngine->interrupt();
} catch(const mongo::DBException &ex) {
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -371,7 +398,7 @@ namespace Robomongo
QStringList list = _scriptEngine->complete(event->prefix, event->mode);
reply(event->sender(), new AutocompleteResponse(this, list, event->prefix));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new ExecuteScriptResponse(this, EventError("Unable to autocomplete query.")));
reply(event->sender(), new AutocompleteResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -388,7 +415,7 @@ namespace Robomongo
reply(event->sender(), new CreateDatabaseResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new CreateDatabaseResponse(this, EventError("Unable to create database.")));
reply(event->sender(), new CreateDatabaseResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -405,7 +432,7 @@ namespace Robomongo
reply(event->sender(), new DropDatabaseResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DropDatabaseResponse(this, EventError("Unable to drop database.")));
reply(event->sender(), new DropDatabaseResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -419,7 +446,7 @@ namespace Robomongo
reply(event->sender(), new CreateCollectionResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new CreateCollectionResponse(this, EventError("Unable to create collection.")));
reply(event->sender(), new CreateCollectionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -433,7 +460,7 @@ namespace Robomongo
reply(event->sender(), new DropCollectionResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DropCollectionResponse(this, EventError("Unable to drop collection.")));
reply(event->sender(), new DropCollectionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -447,7 +474,7 @@ namespace Robomongo
reply(event->sender(), new RenameCollectionResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new RenameCollectionResponse(this, EventError("Unable to rename collection.")));
reply(event->sender(), new RenameCollectionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -461,7 +488,7 @@ namespace Robomongo
reply(event->sender(), new DuplicateCollectionResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DuplicateCollectionResponse(this, EventError("Unable to duplicate collection.")));
reply(event->sender(), new DuplicateCollectionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -476,7 +503,7 @@ namespace Robomongo
reply(event->sender(), new CopyCollectionToDiffServerResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new CopyCollectionToDiffServerResponse(this, EventError("Unable to copy collection.")));
reply(event->sender(), new CopyCollectionToDiffServerResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -490,7 +517,7 @@ namespace Robomongo
reply(event->sender(), new CreateUserResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new CreateUserResponse(this, EventError("Unable to create/ovewrite user.")));
reply(event->sender(), new CreateUserResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -504,7 +531,7 @@ namespace Robomongo
reply(event->sender(), new DropUserResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DropUserResponse(this, EventError("Unable to drop user.")));
reply(event->sender(), new DropUserResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -518,7 +545,7 @@ namespace Robomongo
reply(event->sender(), new CreateFunctionResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new CreateFunctionResponse(this, EventError("Unable to create/ovewrite function.")));
reply(event->sender(), new CreateFunctionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
@@ -532,15 +559,17 @@ namespace Robomongo
reply(event->sender(), new DropFunctionResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DropFunctionResponse(this, EventError("Unable to drop function.")));
reply(event->sender(), new DropFunctionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
mongo::DBClientConnection *MongoWorker::getConnection()
mongo::DBClientBase *MongoWorker::getConnection()
{
if (!_dbclient) {
mongo::DBClientConnection *conn = new mongo::DBClientConnection(true);
// Timeout for operations is 10 seconds.
// Connect timeout is fixed, but short, at 5 seconds (see headers for DBClientConnection)
mongo::DBClientConnection *conn = new mongo::DBClientConnection(true, 10);
conn->connect(_connection->info());
_dbclient = conn;
}
+4 -4
View File
@@ -16,7 +16,6 @@ namespace Robomongo
class MongoClient;
class ScriptEngine;
class ConnectionSettings;
class RDBClientConnection;
class MongoWorker : public QObject
{
@@ -25,9 +24,9 @@ namespace Robomongo
public:
typedef std::vector<std::string> DatabasesContainerType;
explicit MongoWorker(ConnectionSettings *connection, bool isLoadMongoRcJs, int batchSize, QObject *parent = NULL);
ConnectionSettings *connectionRecord() const {return _connection;}
~MongoWorker();
enum { pingTimeMs = 60 * 1000 };
void interrupt();
void stopAndDelete();
protected Q_SLOTS: // handlers:
@@ -102,6 +101,7 @@ namespace Robomongo
* @brief Execute javascript
*/
void handle(ExecuteScriptRequest *event);
void handle(StopScriptRequest *event);
void handle(AutocompleteRequest *event);
void handle(CreateDatabaseRequest *event);
@@ -130,8 +130,8 @@ namespace Robomongo
DatabasesContainerType getDatabaseNamesSafe();
std::string getAuthBase() const;
mongo::DBClientConnection *_dbclient;
mongo::DBClientConnection *getConnection();
mongo::DBClientBase *_dbclient;
mongo::DBClientBase *getConnection();
MongoClient *getClient();
/**
@@ -0,0 +1,198 @@
#include "robomongo/core/mongodb/SshTunnelWorker.h"
#include <QThread>
#include "robomongo/core/utils/QtUtils.h"
#include "robomongo/core/utils/Logger.h"
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/AppRegistry.h"
#include "robomongo/core/EventBus.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/events/MongoEvents.h"
#include "robomongo/ssh/ssh.h"
#include "robomongo/core/domain/App.h"
namespace Robomongo
{
SshTunnelWorker::SshTunnelWorker(ConnectionSettings *settings) : QObject(),
_settings(settings),
_sshConfig(NULL),
_sshSession(NULL)
{
_thread = new QThread();
moveToThread(_thread);
VERIFY(connect(_thread, SIGNAL(started()), this, SLOT(init())));
VERIFY(connect(_thread, SIGNAL(finished()), _thread, SLOT(deleteLater())));
VERIFY(connect(_thread, SIGNAL(finished()), this, SLOT(deleteLater())));
_thread->start();
}
void SshTunnelWorker::init()
{
try {
SshSettings *ssh = _settings->sshSettings();
// We are going to access raw char* buffers, so prepare copies
// of strings (and rest of the things for symmetry)
_sshhost = ssh->host();
_sshport = ssh->port();
_remotehost = _settings->serverHost();
_remoteport = _settings->serverPort();
_localip = "127.0.0.1";
_localport = 27040;
_userName = ssh->userName();
_userPassword = ssh->userPassword();
_privateKeyFile = ssh->privateKeyFile();
_publicKeyFile = ssh->publicKeyFile();
_passphrase = ssh->passphrase();
_authMethod = ssh->authMethod(); // "password" or "publickey"
}
catch (const std::exception &ex) {
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
}
SshTunnelWorker::~SshTunnelWorker()
{
// QThread "_thread" and MongoWorker itself will be deleted later
// (see MongoWorker() constructor)
delete _settings;
delete _sshConfig;
}
void SshTunnelWorker::stopAndDelete() {
_isQuiting = 1;
_thread->quit();
}
void SshTunnelWorker::handle(EstablishSshConnectionRequest *event) {
try {
if (_isQuiting)
return;
_sshConfig = new rbm_ssh_tunnel_config;
_sshConfig->sshserverip = const_cast<char*>(_sshhost.c_str());
_sshConfig->sshserverport = static_cast<unsigned int>(_sshport);
_sshConfig->remotehost = const_cast<char*>(_remotehost.c_str());
_sshConfig->remoteport = static_cast<unsigned int>(_remoteport);
_sshConfig->localip = const_cast<char*>(_localip.c_str());
_sshConfig->localport = static_cast<unsigned int>(_localport);
_sshConfig->username = const_cast<char*>(_userName.c_str());
_sshConfig->password = const_cast<char*>(_userPassword.c_str());
_sshConfig->privatekeyfile = const_cast<char*>(_privateKeyFile.c_str());
_sshConfig->publickeyfile = _publicKeyFile.empty() ? NULL : const_cast<char*>(_publicKeyFile.c_str());
_sshConfig->passphrase = const_cast<char*>(_passphrase.c_str());
_sshConfig->authtype = (_authMethod == "publickey") ? RBM_SSH_AUTH_TYPE_PUBLICKEY : RBM_SSH_AUTH_TYPE_PASSWORD;
_sshConfig->logcontext = this;
_sshConfig->logcallback = &SshTunnelWorker::logCallbackHandler;
_sshConfig->loglevel = (rbm_ssh_log_type) _settings->sshSettings()->logLevel(); // RBM_SSH_LOG_TYPE_DEBUG;
if ((_sshSession = rbm_ssh_session_create(_sshConfig)) == 0) {
// Cleanup config structure
delete _sshConfig;
_sshConfig = NULL;
// Not much we can say about this error
throw std::runtime_error("Failed to create SSH session");
}
if (rbm_ssh_session_setup(_sshSession) == -1) {
// Prepare copy of error message (if any)
std::string error(_sshSession->lasterror);
std::stringstream ss;
ss << "Failed to create SSH tunnel to "
<< _settings->sshSettings()->host() << ":"
<< _settings->sshSettings()->port() << ".\n\nError:\n"
<< error;
// Cleanup session
rbm_ssh_session_close(_sshSession);
_sshSession = NULL;
// Cleanup config
delete _sshConfig;
_sshConfig = NULL;
throw std::runtime_error(ss.str());
}
reply(event->sender(), new EstablishSshConnectionResponse(
this, event->worker, event->settings, event->visible, _sshConfig->localport));
} catch (const std::exception& ex) {
reply(event->sender(),
new EstablishSshConnectionResponse(this, EventError(ex.what()),
event->worker, event->settings, event->visible));
// In case of error in connection, we should cleanup SshTunnelWorker
stopAndDelete();
}
}
void SshTunnelWorker::handle(ListenSshConnectionRequest *event) {
// do it here, i.e. cycle
try {
if (_isQuiting)
return;
if (_sshSession == NULL || _sshConfig == NULL)
return;
// This function will block until all TCP connections disconnects.
// Initially, it will wait for at least one such connection.
if (rbm_ssh_open_tunnel(_sshSession) != 0) {
// Prepare copy of error message (if any)
std::string error(_sshSession->lasterror);
std::stringstream ss;
ss << "You are disconnected from SSH tunnel ("
<< _settings->sshSettings()->host() << ":"
<< _settings->sshSettings()->port() << "). "
<< "Please initiate a new connection and reopen all tabs.\n\nError:\n"
<< error;
throw std::runtime_error(ss.str());
}
log("SSH tunnel stopped normally.", false);
} catch (const std::exception& ex) {
reply(event->sender(),
new ListenSshConnectionResponse(this, EventError(ex.what()), _settings));
}
// When we done with SSH (i.e. rbm_ssh_open_tunnel exits) regardless
// of the error or success state, we should cleanup SshTunnelWorker.
stopAndDelete();
}
/**
* @brief Send reply event to object 'receiver'
*/
void SshTunnelWorker::reply(QObject *receiver, Event *event)
{
if (_isQuiting)
return;
AppRegistry::instance().bus()->send(receiver, event);
}
void SshTunnelWorker::log(const std::string &message, int level) {
if (_isQuiting)
return;
AppRegistry::instance().bus()->send(
AppRegistry::instance().app(),
new LogEvent(this, message, (LogEvent::LogLevel)level));
}
void SshTunnelWorker::logCallbackHandler(void *context, char *message, int level) {
static_cast<SshTunnelWorker*>(context)->log(message, level);
}
}
@@ -0,0 +1,62 @@
#pragma once
#include <QObject>
#include <robomongo/ssh/ssh.h>
#include "robomongo/core/events/MongoEvents.h"
QT_BEGIN_NAMESPACE
class QThread;
QT_END_NAMESPACE
struct rbm_ssh_tunnel_config;
struct rbm_ssh_session;
namespace Robomongo
{
class ConnectionSettings;
class EstablishSshConnectionRequest;
class ListenSshConnectionRequest;
class SshTunnelWorker : public QObject
{
Q_OBJECT
public:
explicit SshTunnelWorker(ConnectionSettings *settings);
~SshTunnelWorker();
static void logCallbackHandler(void* context, char *message, int level);
protected:
void stopAndDelete();
protected Q_SLOTS: // handlers:
void init();
void handle(EstablishSshConnectionRequest *event);
void handle(ListenSshConnectionRequest *event);
private:
void reply(QObject *receiver, Event *event);
void log(const std::string& message, int level = 3);
QThread *_thread;
QAtomicInteger<int> _isQuiting;
ConnectionSettings* _settings;
rbm_ssh_tunnel_config* _sshConfig;
rbm_ssh_session* _sshSession;
std::string _sshhost;
int _sshport;
std::string _remotehost;
int _remoteport;
std::string _localip;
int _localport;
std::string _userName;
std::string _userPassword;
std::string _privateKeyFile;
std::string _publicKeyFile;
std::string _passphrase;
std::string _authMethod; // "password" or "publickey"
};
}
@@ -3,6 +3,7 @@
#include <stdio.h>
#include "robomongo/core/settings/CredentialSettings.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/utils/QtUtils.h"
namespace
@@ -16,58 +17,47 @@ namespace Robomongo
{
/**
* @brief Creates ConnectionSettings with default values
* Creates ConnectionSettings with default values
*/
ConnectionSettings::ConnectionSettings() : QObject(),
_connectionName(defaultNameConnection),
_host(defaultServerHost),
_port(port)
{
_port(port),
_sshSettings(new SshSettings()) { }
}
void ConnectionSettings::fromVariant(const QVariantMap &map) {
setConnectionName(QtUtils::toStdString(map.value("connectionName").toString()));
setServerHost(QtUtils::toStdString(map.value("serverHost").toString()));
setServerPort(map.value("serverPort").toInt());
setDefaultDatabase(QtUtils::toStdString(map.value("defaultDatabase").toString()));
ConnectionSettings::ConnectionSettings(QVariantMap map) : QObject(),
_connectionName(QtUtils::toStdString(map.value("connectionName").toString())),
_host(QtUtils::toStdString(map.value("serverHost").toString())),
_port(map.value("serverPort").toInt())
#ifdef MONGO_SSL
,SSLInfo(map.value("sslEnabled").toBool(),QtUtils::toStdString(map.value("sslPemKeyFile").toString()))
#endif
#ifdef SSH_SUPPORT_ENABLED
,SSHInfo()
#endif
,_defaultDatabase(QtUtils::toStdString(map.value("defaultDatabase").toString()))
{
#ifdef SSH_SUPPORT_ENABLED
SSHInfo inf;
inf._hostName = QtUtils::toStdString(map.value("sshHost").toString());
inf._userName = QtUtils::toStdString(map.value("sshUserName").toString());
inf._port = map.value("sshPort").toInt();
inf._password = QtUtils::toStdString(map.value("sshUserPassword").toString());
inf._publicKey._publicKey = QtUtils::toStdString(map.value("sshPublicKey").toString());
inf._publicKey._privateKey = QtUtils::toStdString(map.value("sshPrivateKey").toString());
inf._publicKey._passphrase = QtUtils::toStdString(map.value("sshPassphrase").toString());
inf._currentMethod = static_cast<SSHInfo::SupportedAuthenticationMetods>(map.value("sshAuthMethod").toInt());
setSshInfo(inf);
#endif
QVariantList list = map.value("credentials").toList();
for(QVariantList::const_iterator it = list.begin(); it != list.end(); ++it) {
QVariant var = *it;
CredentialSettings *credential = new CredentialSettings(var.toMap());
addCredential(credential);
}
if (map.contains("ssh")) {
_sshSettings->fromVariant(map.value("ssh").toMap());
}
//#ifdef MONGO_SSL
// ,SSLInfo(map.value("sslEnabled").toBool(),QtUtils::toStdString(map.value("sslPemKeyFile").toString()))
//#endif
}
/**
* @brief Cleanup used resources
* Cleanup used resources
*/
ConnectionSettings::~ConnectionSettings()
{
clearCredentials();
delete _sshSettings;
}
/**
* @brief Creates completely new ConnectionSettings by cloning this record.
* Creates completely new ConnectionSettings by cloning this record.
*/
ConnectionSettings *ConnectionSettings::clone() const
{
@@ -77,7 +67,7 @@ namespace Robomongo
}
/**
* @brief Discards current state and applies state from 'source' ConnectionSettings.
* Discards current state and applies state from 'source' ConnectionSettings.
*/
void ConnectionSettings::apply(const ConnectionSettings *source)
{
@@ -85,18 +75,19 @@ namespace Robomongo
setServerHost(source->serverHost());
setServerPort(source->serverPort());
setDefaultDatabase(source->defaultDatabase());
#ifdef MONGO_SSL
setSslInfo(source->sslInfo());
#endif
#ifdef SSH_SUPPORT_ENABLED
setSshInfo(source->sshInfo());
#endif
clearCredentials();
QList<CredentialSettings *> cred = source->credentials();
for (QList<CredentialSettings *>::iterator it = cred.begin(); it != cred.end(); ++it) {
addCredential((*it)->clone());
}
// Clone SSH settings
_sshSettings = source->sshSettings()->clone();
//#ifdef MONGO_SSL
// setSslInfo(source->sslInfo());
//#endif
}
/**
@@ -113,17 +104,6 @@ namespace Robomongo
SSLInfo infl = _info.sslInfo();
map.insert("sslEnabled", infl._sslSupport);
map.insert("sslPemKeyFile", QtUtils::toQString(infl._sslPEMKeyFile));
#endif
#ifdef SSH_SUPPORT_ENABLED
SSHInfo inf = _info.sshInfo();
map.insert("sshHost", QtUtils::toQString(inf._hostName));
map.insert("sshUserName", QtUtils::toQString(inf._userName));
map.insert("sshPort", inf._port);
map.insert("sshUserPassword", QtUtils::toQString(inf._password));
map.insert("sshPublicKey", QtUtils::toQString(inf._publicKey._publicKey));
map.insert("sshPrivateKey", QtUtils::toQString(inf._publicKey._privateKey));
map.insert("sshPassphrase", QtUtils::toQString(inf._publicKey._passphrase));
map.insert("sshAuthMethod", inf._currentMethod);
#endif
QVariantList list;
for(QList<CredentialSettings *>::const_iterator it = _credentials.begin(); it != _credentials.end(); ++it) {
@@ -132,6 +112,8 @@ namespace Robomongo
}
map.insert("credentials", list);
map.insert("ssh", _sshSettings->toVariant());
return map;
}
@@ -191,8 +173,9 @@ namespace Robomongo
std::string ConnectionSettings::getFullAddress() const
{
char buff[1024] = {0};
sprintf(buff, "%s:%u", _host.c_str(), _port);
return buff;
return info().toString();
}
}
@@ -4,10 +4,12 @@
#include <QVariant>
#include <QVariantMap>
#include <mongo/client/dbclientinterface.h>
#include <boost/algorithm/string.hpp>
namespace Robomongo
{
class CredentialSettings;
class SshSettings;
/**
* @brief Represents connection record
*/
@@ -42,6 +44,7 @@ namespace Robomongo
* @brief Converts to QVariantMap
*/
QVariant toVariant() const;
void fromVariant(const QVariantMap &map);
/**
* @brief Name of connection
@@ -121,24 +124,34 @@ namespace Robomongo
return _connectionName;
}
mongo::HostAndPort info() const {return mongo::HostAndPort(_host, _port);}
#ifdef MONGO_SSL
SSLInfo sslInfo() const {return _info.sslInfo(); }
void setSslInfo(const SSLInfo &info) {_info.setSslInfo(info);}
#endif // MONGO_SSL
#ifdef SSH_SUPPORT_ENABLED
SSHInfo sshInfo() const {return _info.sshInfo(); }
void setSshInfo(const SSHInfo &info) {_info.setSshInfo(info);}
#endif // SSH_SUPPORT_ENABLED
mongo::HostAndPort info() const {
// If it doesn't look like IPv6 address,
// treat it like IPv4 or literal hostname
if (_host.find(':') == std::string::npos) {
return mongo::HostAndPort(_host, _port);
}
// The following code assumes, that it is IPv6 address
// If address contains square brackets ("["), remove them:
std::string hostCopy = _host;
if (_host.find('[') != std::string::npos) {
boost::erase_all(hostCopy, "[");
boost::erase_all(hostCopy, "]");
}
return mongo::HostAndPort(hostCopy, _port);
}
SshSettings *sshSettings() const { return _sshSettings; }
private:
CredentialSettings *findCredential(const std::string &databaseName) const;
std::string _connectionName;
std::string _host;
int _port;
//mongo::HostAndPort _info;
std::string _defaultDatabase;
QList<CredentialSettings *> _credentials;
SshSettings *_sshSettings;
};
}
@@ -8,6 +8,7 @@ namespace Robomongo
_userName(),
_userPassword(),
_databaseName(),
_mechanism(),
_enabled(false)
{
@@ -16,6 +17,7 @@ namespace Robomongo
_userName(QtUtils::toStdString(map.value("userName").toString())),
_userPassword(QtUtils::toStdString(map.value("userPassword").toString())),
_databaseName(QtUtils::toStdString(map.value("databaseName").toString())),
_mechanism(QtUtils::toStdString(map.value("mechanism").toString())),
_enabled(map.value("enabled").toBool())
{
}
@@ -35,6 +37,7 @@ namespace Robomongo
map.insert("userName", QtUtils::toQString(userName()));
map.insert("userPassword", QtUtils::toQString(userPassword()));
map.insert("databaseName", QtUtils::toQString(databaseName()));
map.insert("mechanism", QtUtils::toQString(mechanism()));
map.insert("enabled", enabled());
return map;
}
@@ -40,6 +40,12 @@ namespace Robomongo
std::string databaseName() const { return _databaseName.empty() ? "admin" : _databaseName; }
void setDatabaseName(const std::string &databaseName) { _databaseName = databaseName; }
/**
* @brief Authentication mechanism (SCRAM-SHA-1 or MONGODB-CR)
*/
std::string mechanism() const { return _mechanism.empty() ? "SCRAM-SHA-1" : _mechanism; }
void setMechanism(const std::string &mechanism) { _mechanism = mechanism; }
/**
* @brief Flag, indecating whether we should use this
* credentials to perform authentication, or not.
@@ -51,6 +57,7 @@ namespace Robomongo
std::string _userName;
std::string _userPassword;
std::string _databaseName;
std::string _mechanism; // authentication mechanism
/**
* @brief Flag, indecating whether we should use this
@@ -56,7 +56,7 @@ namespace Robomongo
SettingsManager::~SettingsManager()
{
std::for_each(_connections.begin(),_connections.end(),stdutils::default_delete<ConnectionSettings *>());
std::for_each(_connections.begin() ,_connections.end(), stdutils::default_delete<ConnectionSettings *>());
}
/**
@@ -184,7 +184,8 @@ namespace Robomongo
QVariantList list = map.value("connections").toList();
for (QVariantList::iterator it = list.begin(); it != list.end(); ++it) {
ConnectionSettings *record = new ConnectionSettings((*it).toMap());
ConnectionSettings *record = new ConnectionSettings();
record->fromVariant((*it).toMap());
_connections.push_back(record);
}
@@ -0,0 +1,44 @@
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/utils/QtUtils.h"
namespace Robomongo
{
SshSettings::SshSettings() :
_port(22),
_authMethod("publickey"),
_enabled(false) {
}
SshSettings *SshSettings::clone() const {
SshSettings *cloned = new SshSettings(*this);
return cloned;
}
QVariant SshSettings::toVariant() const {
QVariantMap map;
map.insert("host", QtUtils::toQString(host()));
map.insert("port", port());
map.insert("userName", QtUtils::toQString(userName()));
map.insert("userPassword", QtUtils::toQString(userPassword()));
map.insert("privateKeyFile", QtUtils::toQString(privateKeyFile()));
map.insert("publicKeyFile", QtUtils::toQString(publicKeyFile()));
map.insert("passphrase", QtUtils::toQString(passphrase()));
map.insert("method", QtUtils::toQString(authMethod()));
map.insert("enabled", enabled());
return map;
}
void SshSettings::fromVariant(const QVariantMap &map) {
setHost(QtUtils::toStdString(map.value("host").toString()));
setPort(map.value("port").toInt());
setUserName(QtUtils::toStdString(map.value("userName").toString()));
setUserPassword(QtUtils::toStdString(map.value("userPassword").toString()));
setPrivateKeyFile(QtUtils::toStdString(map.value("privateKeyFile").toString()));
setPublicKeyFile(QtUtils::toStdString(map.value("publicKeyFile").toString()));
setPassphrase(QtUtils::toStdString(map.value("passphrase").toString()));
setAuthMethod(QtUtils::toStdString(map.value("method").toString()));
setEnabled(map.value("enabled").toBool());
}
}
+76
View File
@@ -0,0 +1,76 @@
#pragma once
#include <QString>
#include <QVariant>
#include <QVariantMap>
namespace Robomongo
{
class SshSettings
{
public:
SshSettings();
/**
* Clones credential settings.
*/
SshSettings *clone() const;
/**
* Converts to QVariantMap
*/
QVariant toVariant() const;
void fromVariant(const QVariantMap &map);
std::string host() const { return _host; }
void setHost(const std::string &host) { _host = host; }
int port() const { return _port; }
void setPort(const int port) { _port = port; }
std::string userName() const { return _userName; }
void setUserName(const std::string &userName) { _userName = userName; }
std::string userPassword() const { return _userPassword; }
void setUserPassword(const std::string &userPassword) { _userPassword = userPassword; }
std::string privateKeyFile() const { return _privateKeyFile; }
void setPrivateKeyFile(const std::string &path) { _privateKeyFile = path; }
std::string publicKeyFile() const { return _publicKeyFile; }
void setPublicKeyFile(const std::string &path) { _publicKeyFile = path; }
std::string passphrase() const { return _passphrase; }
void setPassphrase(const std::string &passphrase) { _passphrase = passphrase; }
std::string authMethod() const { return _authMethod.empty() ? "publickey" : _authMethod; }
void setAuthMethod(const std::string &method) { _authMethod = method; }
/**
* @brief Flag, indecating whether we should use this
* credentials to perform authentication, or not.
*/
bool enabled() const { return _enabled; }
void setEnabled(bool enabled) { _enabled = enabled; }
int logLevel() const { return _logLevel; }
void setLogLevel(const int logLevel) { _logLevel = logLevel; }
private:
std::string _host;
int _port;
std::string _userName;
std::string _userPassword;
std::string _privateKeyFile;
std::string _publicKeyFile;
std::string _passphrase;
std::string _authMethod; // "password" or "publickey"
/**
* Flag, indicating whether we should use
* this SSH tunnel or not.
*/
bool _enabled;
int _logLevel; // this property is not persisted
};
}
+10 -7
View File
@@ -135,7 +135,9 @@ namespace Robomongo
int sign=0;
if ( elem.number() >= -std::numeric_limits< double >::max() &&
elem.number() <= std::numeric_limits< double >::max() ) {
s.precision( 16 );
// was 16
s.precision(std::numeric_limits<double>::digits10);
s << elem.number();
}
else if (std::isnan(elem.number()) ) {
@@ -573,10 +575,10 @@ namespace Robomongo
{
case NumberDouble:
{
const int maxSize = 32;
char buf[maxSize] = {0};
snprintf(buf, maxSize, "%.16g", elem.Double());
con.append(buf);
std::stringstream s;
s.precision(std::numeric_limits<double>::digits10);
s << elem.Double();
con.append(s.str());
}
break;
case String:
@@ -727,5 +729,6 @@ namespace Robomongo
}
return i;
}
}
}
} // BsonUtils
} // Robomongo
+2 -2
View File
@@ -46,7 +46,7 @@ namespace Robomongo
{
// v0.9
// LOG(level) << "[" PROJECT_NAME_TITLE "] " << QtUtils::toStdString(mess) << std::endl;
// if (notify)
// emit printed(mess, level);
if (notify)
emit printed(mess, level);
}
}
+4 -3
View File
@@ -11,15 +11,16 @@
namespace Robomongo
{
class Logger
: public QObject, public Patterns::LazySingleton<Logger>
class Logger : public QObject, public Patterns::LazySingleton<Logger>
{
friend class Patterns::LazySingleton<Logger>;
Q_OBJECT
friend class Patterns::LazySingleton<Logger>;
public:
void print(const char *mess, mongo::logger::LogSeverity level, bool notify);
void print(const std::string &mess, mongo::logger::LogSeverity level, bool notify);
void print(const QString &mess, mongo::logger::LogSeverity level, bool notify);
Q_SIGNALS:
void printed(const QString &mess, mongo::logger::LogSeverity level);
+14 -15
View File
@@ -1,24 +1,25 @@
#include "robomongo/gui/AppStyle.h"
#include <QApplication>
#include <QStyleFactory>
#include "robomongo/core/AppRegistry.h"
#include "robomongo/core/settings/SettingsManager.h"
#include <QApplication>
#include <QStyleFactory>
namespace Robomongo
{
const QString AppStyle::StyleName = "Native";
namespace detail
namespace AppStyleUtils
{
void applyStyle(const QString &styleName)
{
if (styleName == "Native") {
QApplication::setStyle(new AppStyle);
return;
}
else {
QApplication::setStyle(QStyleFactory::create(styleName));
}
QApplication::setStyle(QStyleFactory::create(styleName));
}
QStringList getSupportedStyles()
@@ -35,25 +36,23 @@ namespace Robomongo
}
}
void AppStyle::drawControl(ControlElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget) const
void AppStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
return OsStyle::drawControl(element, option, painter, widget);
return QProxyStyle::drawControl(element, option, painter, widget);
}
void AppStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
#ifdef Q_OS_WIN
if(element==QStyle::PE_FrameFocusRect)
if (element == QStyle::PE_FrameFocusRect)
return;
#endif
#endif // Q_OS_WIN
return OsStyle::drawPrimitive(element, option, painter, widget);
return QProxyStyle::drawPrimitive(element, option, painter, widget);
}
QRect AppStyle::subElementRect( SubElement element, const QStyleOption * option, const QWidget * widget /*= 0 */ ) const
QRect AppStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget /*= 0 */) const
{
return OsStyle::subElementRect(element, option, widget);
return QProxyStyle::subElementRect(element, option, widget);
}
}
+4 -5
View File
@@ -2,21 +2,20 @@
#include <QStyle>
#include <QProxyStyle>
typedef QProxyStyle OsStyle;
namespace Robomongo
{
namespace detail
namespace AppStyleUtils
{
void initStyle();
void applyStyle(const QString &styleName);
QStringList getSupportedStyles();
void initStyle();
}
class AppStyle
:public OsStyle
class AppStyle : public QProxyStyle
{
Q_OBJECT
public:
static const QString StyleName;
virtual void drawControl(ControlElement element, const QStyleOption * option, QPainter * painter, const QWidget * widget) const;
+27 -26
View File
@@ -19,6 +19,7 @@
#include <mongo/logger/log_severity.h>
#include "robomongo/core/settings/SettingsManager.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/domain/MongoServer.h"
#include "robomongo/core/domain/App.h"
#include "robomongo/core/AppRegistry.h"
@@ -97,17 +98,13 @@ namespace Robomongo
_workArea(NULL),
_connectionsMenu(NULL)
{
AppRegistry::instance().bus()->subscribe(this, ConnectionFailedEvent::Type);
AppRegistry::instance().bus()->subscribe(this, ScriptExecutedEvent::Type);
AppRegistry::instance().bus()->subscribe(this, ScriptExecutingEvent::Type);
AppRegistry::instance().bus()->subscribe(this, QueryWidgetUpdatedEvent::Type);
QColor background = palette().window().color();
QString controlKey = "Ctrl";
#if defined(Q_OS_MAC)
QString explorerColor = "#EFEFEF"; // was #CED6DF"
controlKey = QChar(0x2318); // "Command" key aka Cauliflower
setUnifiedTitleAndToolBarOnMac(true);
#elif defined(Q_OS_LINUX)
QString explorerColor = background.darker(103).name();
#else
@@ -491,6 +488,7 @@ namespace Robomongo
_execToolBar->setVisible(true /*toolbarsSettings["exec"].toBool()*/);
setToolBarIconSize(_execToolBar);
addToolBar(_execToolBar);
_toolbarsMenu->addAction(_execToolBar->toggleViewAction());
VERIFY(connect(_execToolBar->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onExecToolbarVisibilityChanged(bool))));
@@ -501,13 +499,18 @@ namespace Robomongo
QTimer::singleShot(0, this, SLOT(manageConnections()));
updateMenus();
AppRegistry::instance().bus()->subscribe(this, ConnectionFailedEvent::Type);
AppRegistry::instance().bus()->subscribe(this, ScriptExecutedEvent::Type);
AppRegistry::instance().bus()->subscribe(this, ScriptExecutingEvent::Type);
AppRegistry::instance().bus()->subscribe(this, QueryWidgetUpdatedEvent::Type);
}
void MainWindow::createStylesMenu()
{
_viewMenu->addSeparator();
QMenu *styles = _viewMenu->addMenu("Theme");
QStringList supportedStyles = detail::getSupportedStyles();
QStringList supportedStyles = AppStyleUtils::getSupportedStyles();
QActionGroup *styleGroup = new QActionGroup(this);
VERIFY(connect(styleGroup, SIGNAL(triggered(QAction *)), this, SLOT(changeStyle(QAction *))));
const QString &currentStyle = AppRegistry::instance().settingsManager()->currentStyle();
@@ -525,7 +528,7 @@ namespace Robomongo
{
QColor windowColor = palette().window().color();
QColor buttonBgColor = windowColor.lighter(105);
QColor buttonBorderBgColor = windowColor.darker(120);
QColor buttonBorderBgColor = windowColor.darker(112);
QColor buttonPressedColor = windowColor.darker(102);
QToolButton *log = new QToolButton(this);
@@ -537,9 +540,9 @@ namespace Robomongo
" background-color: %1;"
" border-style: outset;"
" border-width: 1px;"
" border-radius: 3px;"
" border-radius: 4px;"
" border-color: %2;"
" padding: 1px 20px 1px 20px;"
" padding: 1px 10px 1px 10px;"
"} \n"
""
"QToolButton:checked, QToolButton:pressed {"
@@ -557,7 +560,7 @@ namespace Robomongo
void MainWindow::changeStyle(QAction *ac)
{
const QString &text = ac->text();
detail::applyStyle(text);
AppStyleUtils::applyStyle(text);
AppRegistry::instance().settingsManager()->setCurrentStyle(text);
AppRegistry::instance().settingsManager()->save();
}
@@ -648,6 +651,17 @@ namespace Robomongo
if (result == QDialog::Accepted) {
ConnectionSettings *selected = dialog.selectedConnection();
selected->sshSettings()->setLogLevel(1);
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
// Enable debug level of logging for SSH
if (selected->sshSettings()->enabled()) {
selected->sshSettings()->setLogLevel(100);
}
// Show log pannel
toggleLogs(true);
}
try {
_app->openServer(selected, true);
} catch(const std::exception &) {
@@ -872,16 +886,15 @@ namespace Robomongo
_app->openServer(ptr, true);
}
catch(const std::exception &) {
QString message = QString("Cannot connect to MongoDB (%1)").arg(QtUtils::toQString(ptr->getFullAddress()));
QString message = QString("Cannot connect to the MongoDB at %1.")
.arg(QtUtils::toQString(ptr->getFullAddress()));
QMessageBox::information(this, "Error", message);
}
}
void MainWindow::handle(ConnectionFailedEvent *event)
{
ConnectionSettings *connection = event->server->connectionRecord();
QString message = QString("Cannot connect to MongoDB (%1),\nerror: %2").arg(QtUtils::toQString(connection->getFullAddress())).arg(QtUtils::toQString(event->error().errorMessage()));
QMessageBox::information(this, "Error", message);
QMessageBox::information(this, "Error", QtUtils::toQString(event->message));
}
void MainWindow::handle(ScriptExecutingEvent *)
@@ -938,19 +951,13 @@ namespace Robomongo
_logDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea);
_logDock->setWidget(log);
_logDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
_logDock->setVisible(false);
// Prior to v0.9 it was:
// _logDock->setVisible(AppRegistry::instance().settingsManager()->toolbars()["logs"].toBool());
QAction *action = _logDock->toggleViewAction();
// Adjust any parameter you want.
action->setText(QString("&Logs"));
action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L));
//action->setStatusTip(QString("Press to show/hide Logs panel.")); //commented for now because this message hides Logs button in status bar :)
action->setChecked(_logDock->isVisible());
VERIFY(connect(action, SIGNAL(triggered(bool)), this, SLOT(onLogsVisibilityChanged(bool))));
// Install action in the menu.
_viewMenu->addAction(action);
@@ -1006,10 +1013,4 @@ namespace Robomongo
AppRegistry::instance().settingsManager()->setToolbarSettings("explorer", isVisible);
AppRegistry::instance().settingsManager()->save();
}
void MainWindow::onLogsVisibilityChanged(bool isVisible)
{
AppRegistry::instance().settingsManager()->setToolbarSettings("logs", isVisible);
AppRegistry::instance().settingsManager()->save();
}
}
+1 -2
View File
@@ -76,8 +76,7 @@ namespace Robomongo
void onOpenSaveToolbarVisibilityChanged(bool isVisisble);
void onExecToolbarVisibilityChanged(bool isVisisble);
void onExplorerVisibilityChanged(bool isVisisble);
void onLogsVisibilityChanged(bool isVisible);
private:
QDockWidget *_logDock;
+1 -1
View File
@@ -46,7 +46,7 @@ namespace Robomongo
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
QPushButton *closeButton = buttonBox->button(QDialogButtonBox::Close);
buttonBox->addButton(closeButton, QDialogButtonBox::ButtonRole(QDialogButtonBox::RejectRole | QDialogButtonBox::AcceptRole));
VERIFY(connect(buttonBox , SIGNAL(rejected()), this, SLOT(reject())));
VERIFY(connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())));
QIcon icon = GuiRegistry::instance().mainWindowIcon();
QPixmap iconPixmap = icon.pixmap(128, 128);
@@ -5,6 +5,7 @@
#include <QLabel>
#include <QCheckBox>
#include <QPushButton>
#include <QComboBox>
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/settings/CredentialSettings.h"
@@ -16,8 +17,8 @@ namespace Robomongo
_settings(settings)
{
_databaseNameDescriptionLabel = new QLabel(
"<nobr>The <code>admin</code> database is unique in MongoDB.</nobr> Users with normal access "
"to the <code>admin</code> database have read and write access to <b>all "
"<nobr>The admin database is unique in MongoDB.</nobr> Users with normal access "
"to the admin database have read and write access to <b>all "
"databases</b>.");
_databaseNameDescriptionLabel->setWordWrap(true);
@@ -27,12 +28,17 @@ namespace Robomongo
_userName = new QLineEdit();
_userNameLabel = new QLabel("User Name");
_mechanismLabel = new QLabel("Auth Mechanism");
_userPassword = new QLineEdit();
_userPassword->setEchoMode(QLineEdit::Password);
_userPasswordLabel = new QLabel("Password");
_databaseName = new QLineEdit("admin");
_databaseNameLabel = new QLabel("Database");
_mechanismComboBox = new QComboBox();
_mechanismComboBox->addItem("SCRAM-SHA-1");
_mechanismComboBox->addItem("MONGODB-CR");
_useAuth = new QCheckBox("Perform authentication");
_useAuth->setStyleSheet("margin-bottom: 7px");
VERIFY(connect(_useAuth, SIGNAL(toggled(bool)), this, SLOT(authChecked(bool))));
@@ -48,6 +54,7 @@ namespace Robomongo
_userName->setText(QtUtils::toQString(primaryCredential->userName()));
_userPassword->setText(QtUtils::toQString(primaryCredential->userPassword()));
_databaseName->setText(QtUtils::toQString(primaryCredential->databaseName()));
_mechanismComboBox->setCurrentText(QtUtils::toQString(primaryCredential->mechanism()));
}
QGridLayout *_authLayout = new QGridLayout;
@@ -60,6 +67,8 @@ namespace Robomongo
_authLayout->addWidget(_userPasswordLabel, 4, 0);
_authLayout->addWidget(_userPassword, 4, 1);
_authLayout->addWidget(_echoModeButton, 4, 2);
_authLayout->addWidget(_mechanismLabel, 5, 0);
_authLayout->addWidget(_mechanismComboBox, 5, 1, 1, 2);
_authLayout->setAlignment(Qt::AlignTop);
QVBoxLayout *mainLayout = new QVBoxLayout;
@@ -82,6 +91,7 @@ namespace Robomongo
credential->setUserName(QtUtils::toStdString(_userName->text()));
credential->setUserPassword(QtUtils::toStdString(_userPassword->text()));
credential->setDatabaseName(QtUtils::toStdString(_databaseName->text()));
credential->setMechanism(QtUtils::toStdString(_mechanismComboBox->currentText()));
_settings->addCredential(credential);
}
@@ -103,6 +113,9 @@ namespace Robomongo
_userPasswordLabel->setDisabled(!checked);
_echoModeButton->setDisabled(!checked);
_mechanismLabel->setDisabled(!checked);
_mechanismComboBox->setDisabled(!checked);
if (checked)
_databaseName->setFocus();
}
@@ -6,6 +6,7 @@ class QLineEdit;
class QLabel;
class QCheckBox;
class QPushButton;
class QComboBox;
QT_END_NAMESPACE
namespace Robomongo
@@ -35,6 +36,9 @@ namespace Robomongo
QCheckBox *_useAuth;
QPushButton *_echoModeButton;
QLabel *_mechanismLabel;
QComboBox *_mechanismComboBox;
ConnectionSettings *const _settings;
};
}
@@ -22,7 +22,7 @@ namespace Robomongo
connectionDescriptionLabel->setContentsMargins(0, -2, 0, 20);
QLabel *serverDescriptionLabel = new QLabel(
"Specify host and port of MongoDB server. Host can be either IP or domain name.");
"Specify host and port of MongoDB server. Host can be either IPv4, IPv6 or domain name.");
serverDescriptionLabel->setWordWrap(true);
serverDescriptionLabel->setContentsMargins(0, -2, 0, 20);
@@ -151,16 +151,20 @@ namespace Robomongo
try {
if (_connection->hasEnabledPrimaryCredential())
{
CredentialSettings *credential = _connection->primaryCredential();
std::string database = credential->databaseName();
std::string username = credential->userName();
std::string password = credential->userPassword();
CredentialSettings *credentials = _connection->primaryCredential();
std::string errmsg;
bool ok = connection->auth(database, username, password, errmsg);
emit authStatus("", ok);
// Building BSON object:
mongo::BSONObj authParams(mongo::BSONObjBuilder()
.append("user", credentials->userName())
.append("db", credentials->databaseName())
.append("pwd", credentials->userPassword())
.append("mechanism", credentials->mechanism())
.obj());
connection->auth(authParams);
emit authStatus("", true);
}
} catch (const mongo::UserException &) {
} catch (const mongo::UserException &ex) {
emit authStatus("", false);
}
+6 -15
View File
@@ -13,9 +13,8 @@
#include "robomongo/gui/dialogs/ConnectionBasicTab.h"
#include "robomongo/gui/dialogs/ConnectionAdvancedTab.h"
#include "robomongo/gui/dialogs/ConnectionSslTab.h"
#ifdef SSH_SUPPORT_ENABLED
#include "robomongo/gui/dialogs/ConnectionSslTab.h"
#include "robomongo/gui/dialogs/SSHTunnelTab.h"
#endif
#include "robomongo/gui/dialogs/ConnectionDiagnosticDialog.h"
#include "robomongo/core/utils/QtUtils.h"
@@ -51,24 +50,18 @@ namespace Robomongo
_basicTab = new ConnectionBasicTab(_connection);
_authTab = new ConnectionAuthTab(_connection);
_advancedTab = new ConnectionAdvancedTab(_connection);
_sshTab = new SshTunnelTab(_connection);
// v0.9
//_sslTab = new ConnectionSslTab(_connection);
#ifdef SSH_SUPPORT_ENABLED
// v0.9
//_sshTab = new SshTunnelTab(_connection);
#endif
tabWidget->addTab(_basicTab, "Connection");
tabWidget->addTab(_authTab, "Authentication");
tabWidget->addTab(_advancedTab, "Advanced");
tabWidget->addTab(_sshTab, "SSH");
// v0.9
//tabWidget->addTab(_sslTab, "SSL");
#ifdef SSH_SUPPORT_ENABLED
// v0.9
//tabWidget->addTab(_sshTab, "SSH");
#endif
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(tabWidget);
@@ -83,7 +76,7 @@ namespace Robomongo
*/
void ConnectionDialog::accept()
{
if(validateAndApply()){
if (validateAndApply()){
QDialog::accept();
}
}
@@ -100,12 +93,10 @@ namespace Robomongo
_basicTab->accept();
_authTab->accept();
_advancedTab->accept();
_sshTab->accept();
// v0.9
// _sslTab->accept();
#ifdef SSH_SUPPORT_ENABLED
_sshTab->accept();
#endif
return true;
}
@@ -43,6 +43,7 @@ namespace Robomongo
ConnectionAuthTab *_authTab;
ConnectionBasicTab *_basicTab;
ConnectionAdvancedTab *_advancedTab;
SshTunnelTab *_sshTab;
// v0.9
//ConnectionSslTab *_sslTab;
@@ -8,6 +8,7 @@
#include <QHeaderView>
#include <QDialogButtonBox>
#include <QTreeWidgetItem>
#include <QKeyEvent>
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/settings/CredentialSettings.h"
@@ -339,6 +340,29 @@ namespace Robomongo
_connectionItems.push_back(item);
}
void ConnectionsDialog::keyPressEvent(QKeyEvent *event) {
if (event->key() == Qt::Key_E && (event->modifiers() & Qt::ControlModifier)) {
edit();
return;
}
if (event->key() == Qt::Key_W && (event->modifiers() & Qt::ControlModifier)) {
close();
return;
}
// Shift + Return also accepts connection (this shortcut is handled
// to support DEBUG level logging)
if (event->key() == Qt::Key_Return && (event->modifiers() & Qt::ShiftModifier)) {
accept();
return;
}
QDialog::keyPressEvent(event);
}
ConnectionsTreeWidget::ConnectionsTreeWidget()
{
setDragDropMode(QAbstractItemView::InternalMove);
@@ -72,6 +72,8 @@ namespace Robomongo
*/
void listWidget_layoutChanged();
void keyPressEvent(QKeyEvent* event);
private:
/**
* @brief ConnectionSettings, that was selected after pressing on
@@ -53,6 +53,9 @@ namespace Robomongo
_queryText = new FindFrame(this);
_configureQueryText();
_queryText->sciScintilla()->setText(json);
// clear modification state after setting the content
_queryText->sciScintilla()->setModified(false);
VERIFY(connect(_queryText->sciScintilla(), SIGNAL(textChanged()), this, SLOT(onQueryTextChanged())));
QHBoxLayout *hlayout = new QHBoxLayout();
@@ -84,8 +87,11 @@ namespace Robomongo
layout->addLayout(bottomlayout);
setLayout(layout);
if (_readonly)
if (_readonly) {
validate->hide();
buttonBox->button(QDialogButtonBox::Save)->hide();
_queryText->sciScintilla()->setReadOnly(true);
}
}
QString DocumentTextEditor::jsonText() const
@@ -106,18 +112,40 @@ namespace Robomongo
QDialog::accept();
}
void DocumentTextEditor::reject()
{
if (_queryText->sciScintilla()->isModified()) {
int ret = QMessageBox::warning(this, tr("Robomongo"),
tr("The document has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Save | QMessageBox::Discard
| QMessageBox::Cancel,
QMessageBox::Save);
if (ret == QMessageBox::Save) {
this->accept();
} else if (ret == QMessageBox::Discard) {
QDialog::reject();
}
return;
}
QDialog::reject();
}
bool DocumentTextEditor::validate(bool silentOnSuccess /* = true */)
{
QString text = jsonText();
int len = 0;
try {
try {
std::string textString = QtUtils::toStdString(text);
const char *json = textString.c_str();
int jsonLen = textString.length();
int offset = 0;
_obj.clear();
while(offset!=jsonLen)
{
{
mongo::BSONObj doc = mongo::Robomongo::fromjson(json+offset,&len);
_obj.push_back(doc);
offset+=len;
@@ -29,6 +29,7 @@ namespace Robomongo
public Q_SLOTS:
virtual void accept();
virtual void reject();
bool validate(bool silentOnSuccess = true);
private Q_SLOTS:
@@ -77,7 +77,7 @@ namespace Robomongo
QLabel *stylesLabel = new QLabel("Styles:");
stylesLayout->addWidget(stylesLabel);
_stylesComboBox = new QComboBox();
_stylesComboBox->addItems(detail::getSupportedStyles());
_stylesComboBox->addItems(AppStyleUtils::getSupportedStyles());
stylesLayout->addWidget(_stylesComboBox);
layout->addLayout(stylesLayout);
@@ -117,7 +117,7 @@ namespace Robomongo
AppRegistry::instance().settingsManager()->setLoadMongoRcJs(_loadMongoRcJsCheckBox->isChecked());
AppRegistry::instance().settingsManager()->setDisableConnectionShortcuts(_disabelConnectionShortcutsCheckBox->isChecked());
Robomongo::AppRegistry::instance().settingsManager()->setCurrentStyle(_stylesComboBox->currentText());
detail::applyStyle(_stylesComboBox->currentText());
AppStyleUtils::applyStyle(_stylesComboBox->currentText());
Robomongo::AppRegistry::instance().settingsManager()->save();
return BaseClass::accept();
+45 -33
View File
@@ -1,5 +1,6 @@
#include "robomongo/gui/dialogs/SSHTunnelTab.h"
#include <QApplication>
#include <QLabel>
#include <QLineEdit>
#include <QGridLayout>
@@ -13,21 +14,22 @@
#include "robomongo/core/utils/QtUtils.h"
#include "robomongo/gui/utils/ComboBoxUtils.h"
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/settings/SshSettings.h"
namespace Robomongo
{
SshTunnelTab::SshTunnelTab(ConnectionSettings *settings) :
_settings(settings)
{
SSHInfo info = _settings->sshInfo();
SshSettings *info = settings->sshSettings();
_sshSupport = new QCheckBox("Use SSH tunnel");
_sshSupport->setStyleSheet("margin-bottom: 7px");
_sshSupport->setChecked(info.isValid());
_sshSupport->setChecked(info->enabled());
_sshHostName = new QLineEdit(QtUtils::toQString(info._hostName));
_userName = new QLineEdit(QtUtils::toQString(info._userName));
_sshHostName = new QLineEdit(QtUtils::toQString(info->host()));
_userName = new QLineEdit(QtUtils::toQString(info->userName()));
_sshPort = new QLineEdit(QString::number(info._port));
_sshPort = new QLineEdit(QString::number(info->port()));
_sshPort->setFixedWidth(80);
QRegExp rx("\\d+");//(0-65554)
_sshPort->setValidator(new QRegExpValidator(rx, this));
@@ -36,14 +38,14 @@ namespace Robomongo
_security->addItems(QStringList() << "Password" << "Private Key");
VERIFY(connect(_security, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(securityChange(const QString&))));
_passwordBox = new QLineEdit(QtUtils::toQString(info._password));
_passwordBox = new QLineEdit(QtUtils::toQString(info->userPassword()));
_passwordBox->setEchoMode(QLineEdit::Password);
_passwordEchoModeButton = new QPushButton(tr("Show"));
VERIFY(connect(_passwordEchoModeButton, SIGNAL(clicked()), this, SLOT(togglePasswordEchoMode())));
_privateKeyBox = new QLineEdit(QtUtils::toQString(info._publicKey._privateKey));
_privateKeyBox = new QLineEdit(QtUtils::toQString(info->privateKeyFile()));
_passphraseBox = new QLineEdit(QtUtils::toQString(info._publicKey._passphrase));
_passphraseBox = new QLineEdit(QtUtils::toQString(info->passphrase()));
_passphraseBox->setEchoMode(QLineEdit::Password);
_passphraseEchoModeButton = new QPushButton(tr("Show"));
VERIFY(connect(_passphraseEchoModeButton, SIGNAL(clicked()), this, SLOT(togglePassphraseEchoMode())));
@@ -92,7 +94,7 @@ namespace Robomongo
connectionLayout->addWidget(_passwordEchoModeButton, 5, 2);
_selectPrivateFileButton = new QPushButton("...");
_selectPrivateFileButton->setFixedSize(20, 20);
_selectPrivateFileButton->setMaximumWidth(50);
connectionLayout->addWidget(_sshPrivateKeyLabel, 7, 0);
connectionLayout->addWidget(_privateKeyBox, 7, 1);
@@ -107,7 +109,7 @@ namespace Robomongo
mainLayout->addLayout(connectionLayout);
setLayout(mainLayout);
if (info.authMethod() == SSHInfo::PUBLICKEY) {
if (info->authMethod() == "publickey") {
utils::setCurrentText(_security, "Private Key");
} else {
utils::setCurrentText(_security, "Password");
@@ -148,9 +150,9 @@ namespace Robomongo
_passwordLabel->setEnabled(value);
}
void SshTunnelTab::securityChange(const QString&)
void SshTunnelTab::securityChange(const QString& method)
{
bool isKey = selectedAuthMethod() == SSHInfo::PUBLICKEY;
bool isKey = method == "Private Key";
_sshPrivateKeyLabel->setVisible(isKey);
_privateKeyBox->setVisible(isKey);
@@ -167,8 +169,18 @@ namespace Robomongo
void SshTunnelTab::setPrivateFile()
{
// Default location
QString sshDir = QString("%1/.ssh").arg(QDir::homePath());
QString filepath = QFileDialog::getOpenFileName(this, "Select private key file",
_privateKeyBox->text(), QObject::tr("Private key files (*.*)"));
sshDir, QObject::tr("Private key files (*)"));
// Some strange behaviour at least on Mac happens when you
// close QFileDialog. Focus switched to a different modal
// dialog, not the one that was active before openning QFileDialog.
// http://stackoverflow.com/questions/17998811/window-modal-qfiledialog-pushing-parent-to-background-after-exec
QApplication::activeModalWidget()->raise();
QApplication::activeModalWidget()->activateWindow();
if (filepath.isNull())
return;
@@ -176,32 +188,32 @@ namespace Robomongo
_privateKeyBox->setText(filepath);
}
SSHInfo::SupportedAuthenticationMetods SshTunnelTab::selectedAuthMethod()
{
if (_security->currentText() == "Private Key")
return SSHInfo::PUBLICKEY;
return SSHInfo::PASSWORD;
}
// SSHInfo::SupportedAuthenticationMetods SshTunnelTab::selectedAuthMethod()
// {
// if (_security->currentText() == "Private Key")
// return SSHInfo::PUBLICKEY;
//
// return SSHInfo::PASSWORD;
// }
void SshTunnelTab::accept()
{
SSHInfo info = _settings->sshInfo();
info._hostName = QtUtils::toStdString(_sshHostName->text());
info._userName = QtUtils::toStdString(_userName->text());
info._port = _sshPort->text().toInt();
info._password = QtUtils::toStdString(_passwordBox->text());
info._publicKey._publicKey = "";
info._publicKey._privateKey = QtUtils::toStdString(_privateKeyBox->text());
info._publicKey._passphrase = QtUtils::toStdString(_passphraseBox->text());
SshSettings *info = _settings->sshSettings();
info->setHost(QtUtils::toStdString(_sshHostName->text()));
info->setPort(_sshPort->text().toInt());
info->setUserName(QtUtils::toStdString(_userName->text()));
info->setUserPassword(QtUtils::toStdString(_passwordBox->text()));
// info->setPublicKeyFile("");
info->setPrivateKeyFile(QtUtils::toStdString(_privateKeyBox->text()));
info->setPassphrase(QtUtils::toStdString(_passphraseBox->text()));
if (_sshSupport->isChecked()) {
info._currentMethod = selectedAuthMethod();
if (_security->currentText() == "Private Key") {
info->setAuthMethod("publickey");
} else {
info._currentMethod = SSHInfo::UNKNOWN;
info->setAuthMethod("password");
}
_settings->setSshInfo(info);
info->setEnabled(_sshSupport->isChecked());
}
void SshTunnelTab::togglePasswordEchoMode()
+1 -1
View File
@@ -34,7 +34,7 @@ namespace Robomongo
void togglePassphraseEchoMode();
private:
SSHInfo::SupportedAuthenticationMetods selectedAuthMethod();
// SSHInfo::SupportedAuthenticationMetods selectedAuthMethod();
private:
QCheckBox *_sshSupport;
+2
View File
@@ -31,6 +31,8 @@
<file>icons/execute_24x24.png</file>
<file>icons/main_window_icon_old.png</file>
<file>icons/logo-256x256.png</file>
<file>icons/logo-20x20.png</file>
<file>icons/handsup-20x20.png</file>
<file>icons/text_highlighted_16x16.png</file>
<file>icons/tree_highlighted_16x16.png</file>
<file>icons/maximize_highlighted_16x16.png</file>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

+29 -4
View File
@@ -36,11 +36,36 @@ namespace Robomongo
void LogWidget::addMessage(const QString &message, mongo::logger::LogSeverity level)
{
// Print time
QTime time = QTime::currentTime();
QDate date = QDate::currentDate();
//_logTextEdit->appendHtml(QString(level == mongo::logger::LogSeverity::Error() ? "<font color=red>%1 %2</font>" : "<font color=black>%1 %2</font>").arg(time.toString("h:mm:ss AP:")).arg(message.toHtmlEscaped()));
_logTextEdit->setTextColor(level == mongo::logger::LogSeverity::Error() ? QColor(Qt::red):QColor(Qt::black));
_logTextEdit->append(date.toString("yyyy-MM-dd ") + time.toString("hh:mm:ss: ") + message);
_logTextEdit->moveCursor (QTextCursor::End);
_logTextEdit->setTextColor(QColor("#aaaaaa"));
_logTextEdit->insertPlainText(time.toString("h:mm:ss AP") + "\t");
// Print message
_logTextEdit->moveCursor (QTextCursor::End);
// Nice color for the future: "#CD9800" :)
QColor textColor = QColor(Qt::black);
if (level == mongo::logger::LogSeverity::Error())
textColor = QColor("#CD0000");
else if (level == mongo::logger::LogSeverity::Log())
textColor = QColor("#777777");
else if (level == mongo::logger::LogSeverity::Warning())
textColor = QColor("#CD9800");
_logTextEdit->setTextColor(textColor);
const int maxLength = 500;
if (message.length() <= maxLength) {
_logTextEdit->insertPlainText(message.trimmed() + "\n");
} else {
_logTextEdit->insertPlainText(QString("(truncated) ") + message.left(maxLength).trimmed() + "...\n");
}
// Scroll to the bottom
QScrollBar *sb = _logTextEdit->verticalScrollBar();
sb->setValue(sb->maximum());
}
@@ -273,7 +273,7 @@ namespace Robomongo
_indexDir = new ExplorerCollectionDirIndexesTreeItem(this);
addChild(_indexDir);
setToolTip(0, buildToolTip(collection));
// setToolTip(0, buildToolTip(collection));
setExpanded(false);
setChildIndicatorPolicy(QTreeWidgetItem::ShowIndicator);
@@ -281,8 +281,25 @@ namespace Robomongo
void ExplorerCollectionTreeItem::handle(LoadCollectionIndexesResponse *event)
{
if (event->isError()) {
_indexDir->setText(0, "Indexes");
_indexDir->setExpanded(false);
QtUtils::clearChildItems(_indexDir);
std::stringstream ss;
ss << "Cannot load list of indexes.\n\nError:\n" << event->error().errorMessage();
QMessageBox::information(NULL, "Error", QtUtils::toQString(ss.str()));
return;
}
QtUtils::clearChildItems(_indexDir);
const std::vector<EnsureIndexInfo> &indexes = event->indexes();
// Do not expand, when we do not have functions
if (indexes.size() == 0)
_indexDir->setExpanded(false);
for (std::vector<EnsureIndexInfo>::const_iterator it = indexes.begin(); it!=indexes.end(); ++it) {
_indexDir->addChild(new ExplorerCollectionIndexesTreeItem(_indexDir,*it));
}
@@ -291,6 +308,10 @@ namespace Robomongo
void ExplorerCollectionTreeItem::handle(DeleteCollectionIndexResponse *event)
{
if (event->isError()) {
return;
}
if (!event->index().empty()) {
int itemCount = _indexDir->childCount();
QString eventIndex = QtUtils::toQString(event->index());
@@ -329,8 +350,9 @@ namespace Robomongo
QString ExplorerCollectionTreeItem::buildToolTip(MongoCollection *collection)
{
// This function does not used now
char buff[2048]={0};
sprintf(buff,tooltipTemplate,collection->name().c_str(),collection->info().count(),collection->sizeString().c_str());
// sprintf(buff,tooltipTemplate,collection->name().c_str(),collection->info().count(),collection->sizeString().c_str());
return buff;
}
@@ -55,7 +55,7 @@ namespace Robomongo
QAction *dbStats = new QAction("Database Statistics", this);
VERIFY(connect(dbStats, SIGNAL(triggered()), SLOT(ui_dbStatistics())));
QAction *dbDrop = new QAction("Drop Database..", this);
QAction *dbDrop = new QAction("Drop Database...", this);
VERIFY(connect(dbDrop, SIGNAL(triggered()), SLOT(ui_dbDrop())));
QAction *dbRepair = new QAction("Repair Database...", this);
@@ -137,11 +137,28 @@ namespace Robomongo
void ExplorerDatabaseTreeItem::handle(MongoDatabaseCollectionListLoadedEvent *event)
{
if (event->isError()) {
_collectionFolderItem->setText(0, "Collections");
_collectionFolderItem->setExpanded(false);
std::stringstream ss;
ss << "Cannot load list of collections.\n\nError:\n" << event->error().errorMessage();
QMessageBox::information(NULL, "Error", QtUtils::toQString(ss.str()));
return;
}
std::vector<MongoCollection *> collections = event->collections;
int count = collections.size();
_collectionFolderItem->setText(0, detail::buildName("Collections",count));
QtUtils::clearChildItems(_collectionFolderItem);
// Do not expand, when we do not have collections
if (count == 0) {
_collectionFolderItem->setExpanded(false);
return;
}
_collectionSystemFolderItem = new ExplorerTreeItem(_collectionFolderItem);
_collectionSystemFolderItem->setIcon(0, GuiRegistry::instance().folderIcon());
_collectionSystemFolderItem->setText(0, "System");
@@ -162,10 +179,25 @@ namespace Robomongo
void ExplorerDatabaseTreeItem::handle(MongoDatabaseUsersLoadedEvent *event)
{
if (event->isError()) {
_usersFolderItem->setText(0, "Users");
_usersFolderItem->setExpanded(false);
std::stringstream ss;
ss << "Cannot load list of users.\n\nError:\n" << event->error().errorMessage();
QMessageBox::information(NULL, "Error", QtUtils::toQString(ss.str()));
return;
}
std::vector<MongoUser> users = event->users();
int count = users.size();
_usersFolderItem->setText(0, detail::buildName("Users",count));
// Do not expand, when we do not have users
if (count == 0)
_usersFolderItem->setExpanded(false);
QtUtils::clearChildItems(_usersFolderItem);
for (int i = 0; i < users.size(); ++i) {
@@ -176,10 +208,25 @@ namespace Robomongo
void ExplorerDatabaseTreeItem::handle(MongoDatabaseFunctionsLoadedEvent *event)
{
if (event->isError()) {
_javascriptFolderItem->setText(0, "Functions");
_javascriptFolderItem->setExpanded(false);
std::stringstream ss;
ss << "Cannot load list of functions.\n\nError:\n" << event->error().errorMessage();
QMessageBox::information(NULL, "Error", QtUtils::toQString(ss.str()));
return;
}
std::vector<MongoFunction> functions = event->functions();
int count = functions.size();
_javascriptFolderItem->setText(0, detail::buildName("Functions",count));
// Do not expand, when we do not have functions
if (count == 0)
_javascriptFolderItem->setExpanded(false);
QtUtils::clearChildItems(_javascriptFolderItem);
for (int i = 0; i < functions.size(); ++i) {
@@ -2,6 +2,7 @@
#include <QAction>
#include <QMenu>
#include <QMessageBox>
#include "robomongo/core/domain/MongoServer.h"
#include "robomongo/core/domain/MongoDatabase.h"
@@ -116,6 +117,25 @@ namespace Robomongo
void ExplorerServerTreeItem::handle(DatabaseListLoadedEvent *event)
{
if (event->isError()) {
setText(0, buildServerName(0)); // Restore normal name (without "...")
setExpanded(false); // Collapse
// We should clear all child items, but we are not
// doing this, because of incorrect "senders" for messages
// that are going to Worker thread. Yes, some guy specified UI objects
// as senders. If we delete these UI objects, response will crash the app
// (because QObject::thread() is used to get thread of the QObject)
// QtUtils::clearChildItems(this);
std::stringstream ss;
ss << "Cannot load list of databases.\n\nError:\n"
<< event->error().errorMessage();
QMessageBox::information(NULL, "Error", QtUtils::toQString(ss.str()));
return;
}
databaseRefreshed(event->list);
}
@@ -18,6 +18,7 @@ namespace Robomongo
setIndentation(15);
setHeaderHidden(true);
setSelectionMode(QAbstractItemView::SingleSelection);
setExpandsOnDoubleClick(false);
}
void ExplorerTreeWidget::contextMenuEvent(QContextMenuEvent *event)
@@ -127,6 +127,10 @@ namespace Robomongo
ExplorerCollectionTreeItem *collectionItem = dynamic_cast<ExplorerCollectionTreeItem *>(item);
if (collectionItem) {
AppRegistry::instance().app()->openShell(collectionItem->collection());
return;
}
// Toggle expanded state
item->setExpanded(!item->isExpanded());
}
}
@@ -12,7 +12,7 @@
namespace Robomongo
{
BsonTreeView::BsonTreeView(MongoShell *shell, const MongoQueryInfo &queryInfo, QWidget *parent)
BsonTreeView::BsonTreeView(MongoShell *shell, const MongoQueryInfo &queryInfo, QWidget *parent)
: BaseClass(parent),_notifier(this,shell,queryInfo)
{
#if defined(Q_OS_MAC)
@@ -25,9 +25,11 @@ namespace Robomongo
VERIFY(connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showContextMenu(const QPoint&))));
_expandRecursive = new QAction("Expand Recursively", this);
_expandRecursive->setShortcut(QKeySequence(Qt::ALT + Qt::Key_Right));
VERIFY(connect(_expandRecursive, SIGNAL(triggered()), SLOT(onExpandRecursive())));
_collapseRecursive = new QAction(tr("Collapse Recursively"), this);
_collapseRecursive->setShortcut(QKeySequence(Qt::ALT + Qt::Key_Left));
VERIFY(connect(_collapseRecursive, SIGNAL(triggered()), SLOT(onCollapseRecursive())));
setStyleSheet("QTreeView { border-left: 1px solid #c7c5c4; border-top: 1px solid #c7c5c4; }");
@@ -81,9 +83,25 @@ namespace Robomongo
void BsonTreeView::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Delete) {
_notifier.onDeleteDocuments();
switch (event->key()) {
case Qt::Key_Delete:
_notifier.handleDeleteCommand();
break;
case Qt::Key_Backspace:
// Cmd/Ctrl + Backspace
if (event->modifiers() & Qt::ControlModifier)
_notifier.handleDeleteCommand();
break;
case Qt::Key_Right:
if (event->modifiers() & Qt::AltModifier)
this->onExpandRecursive();
break;
case Qt::Key_Left:
if (event->modifiers() & Qt::AltModifier)
this->onCollapseRecursive();
break;
}
return BaseClass::keyPressEvent(event);
}
@@ -4,6 +4,7 @@
#include <QLabel>
#include <QFileInfo>
#include <QVBoxLayout>
#include <QMessageBox>
#include <Qsci/qsciscintilla.h>
#include <Qsci/qscilexerjavascript.h>
#include <mongo/client/dbclientinterface.h>
@@ -193,14 +194,30 @@ namespace Robomongo
void QueryWidget::handle(DocumentListLoadedEvent *event)
{
hideProgress();
hideProgress();
if (event->isError()) {
QString message = QString("Failed to load documents.\n\nError:\n%1")
.arg(QtUtils::toQString(event->error().errorMessage()));
QMessageBox::information(this, "Error", message);
return;
}
_viewer->updatePart(event->resultIndex(), event->queryInfo(), event->documents()); // this should be in viewer, subscribed to ScriptExecutedEvent
}
void QueryWidget::handle(ScriptExecutedEvent *event)
{
hideProgress();
_currentResult = event->result();
if (event->isError()) {
QString message = QString("Failed to execute script.\n\nError:\n%1")
.arg(QtUtils::toQString(event->error().errorMessage()));
QMessageBox::information(this, "Error", message);
return;
}
_currentResult = event->result();
updateCurrentTab();
displayData(event->result().results(), event->empty());
@@ -216,6 +233,11 @@ namespace Robomongo
void QueryWidget::handle(AutocompleteResponse *event)
{
if (event->isError()) {
// Do not show error message (error should be already logged)
return;
}
_scriptWidget->showAutocompletion(event->list, QtUtils::toQString(event->prefix) );
}
+60
View File
@@ -0,0 +1,60 @@
include(CheckIncludeFiles)
include(CheckSymbolExists)
## Platform checks
check_include_files(inttypes.h HAVE_INTTYPES_H)
check_include_files(unistd.h HAVE_UNISTD_H)
check_include_files(stdlib.h HAVE_STDLIB_H)
check_include_files(sys/select.h HAVE_SYS_SELECT_H)
check_include_files(sys/socket.h HAVE_SYS_SOCKET_H)
check_include_files(sys/time.h HAVE_SYS_TIME_H)
check_include_files(arpa/inet.h HAVE_ARPA_INET_H)
check_include_files(netinet/in.h HAVE_NETINET_IN_H)
check_include_files(winsock2.h HAVE_WINSOCK2_H)
check_symbol_exists(strcasecmp strings.h HAVE_STRCASECMP)
check_symbol_exists(_stricmp string.h HAVE__STRICMP)
check_symbol_exists(snprintf stdio.h HAVE_SNPRINTF)
check_symbol_exists(_snprintf stdio.h HAVE__SNPRINTF)
check_symbol_exists(__func__ "" HAVE___FUNC__)
check_symbol_exists(__FUNCTION__ "" HAVE___FUNCTION__)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/libssh2_config.h.in
${CMAKE_CURRENT_BINARY_DIR}/libssh2_config.h)
# Direct-tcpip sample
add_library(ssh ssh.c log.c array.c)
target_link_libraries(ssh
PUBLIC
libssh2)
target_include_directories(ssh
PUBLIC
${CMAKE_BINARY_DIR}/src
${CMAKE_SOURCE_DIR}/src)
# Server
add_executable(server server.c)
target_link_libraries(server
PRIVATE
ssh)
target_include_directories(server
PRIVATE
${CMAKE_BINARY_DIR}/src
${CMAKE_SOURCE_DIR}/src)
# Tests
add_executable(ssh_test test.c)
target_link_libraries(ssh_test
PRIVATE
ssh)
target_include_directories(ssh_test
PRIVATE
${CMAKE_BINARY_DIR}/src
${CMAKE_SOURCE_DIR}/src)
+20
View File
@@ -0,0 +1,20 @@
SSH tunnel submodule
====================
Implemented in vanilla C99 and uses LIBSSH2 library.
Notes
-----
1. Users of this module should include *only* `ssh.h`. Do not pollute it
with unnecessary includes or implementation specific declarations.
1. Implementation files include `private.h`, which is also includes
`ssh.h` automatically.
1. The preferred use of `*` is adjacent to the data name or function name
and not adjacent to the type name (i.e. `char *data`, not `char* data`)
1. Public functions and types always should be prefixed with `rbm_ssh_`.
It means that all declarations in `ssh.h` should use this prefix.
1. Private functions and types always should be prefixed with just
`rbm_`. You may declare file-scoped objects without this prefix, but still
prefer to always use `rbm_` prefix.
+57
View File
@@ -0,0 +1,57 @@
#include "robomongo/ssh/private.h"
#include <stdlib.h>
/*
* Adds pointer to array.
* You should pass your array (sometype**) and size of it (int) by reference.
* Array can be NULL, in this case new array will be allocated
* @array Address of (void **) array
* @currentsize Address of size
* @data Pointer that should be added to array
*/
int rbm_array_add(void ***array, int *currentsize, void *data) {
if (!*array && *currentsize)
return RBM_ERROR;
int newsize = *currentsize + 1;
// Acts like malloc when (*array == NULL)
void **newarray = realloc(*array, newsize * sizeof(void*));
if (!newarray)
return RBM_ERROR;
newarray[*currentsize] = data;
*array = newarray;
*currentsize = newsize;
return RBM_SUCCESS;
}
int rbm_array_remove(void ***array, int *currentsize, void *data) {
int newsize = *currentsize - 1;
void **newarray;
for (int i = 0; i < *currentsize; i++) {
if ((*array)[i] != data)
continue;
if (*currentsize == 1) {
newarray = NULL;
} else {
newarray = malloc(newsize * sizeof(void*));
if (!newarray)
return RBM_ERROR;
memcpy(newarray, *array, i * sizeof(void*));
if (i + 1 < *currentsize)
memcpy(newarray + i, *array + i + 1, (*currentsize - i - 1) * sizeof(void*));
}
free(*array);
*currentsize = newsize;
*array = newarray;
return RBM_SUCCESS;
}
return RBM_ERROR;
}
+72
View File
@@ -0,0 +1,72 @@
/* Copyright (c) 2014 Alexander Lamaison <alexander.lamaison@gmail.com>
*
* 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.
*
* Neither the name of the copyright holder nor the names
* of any other contributors may be used to endorse or
* promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "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 COPYRIGHT OWNER 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.
*/
/* Headers */
#cmakedefine HAVE_UNISTD_H
#cmakedefine HAVE_INTTYPES_H
#cmakedefine HAVE_STDLIB_H
#cmakedefine HAVE_SYS_SELECT_H
#cmakedefine HAVE_SYS_SOCKET_H
#cmakedefine HAVE_SYS_TIME_H
#cmakedefine HAVE_ARPA_INET_H
#cmakedefine HAVE_NETINET_IN_H
#cmakedefine HAVE_WINSOCK2_H
/* Functions */
#cmakedefine HAVE_STRCASECMP
#cmakedefine HAVE__STRICMP
#cmakedefine HAVE_SNPRINTF
#cmakedefine HAVE__SNPRINTF
/* Workaround for platforms without POSIX strcasecmp (e.g. Windows) */
#ifndef HAVE_STRCASECMP
# ifdef HAVE__STRICMP
# define strcasecmp _stricmp
# define HAVE_STRCASECMP
# endif
#endif
/* Symbols */
#cmakedefine HAVE___FUNC__
#cmakedefine HAVE___FUNCTION__
/* Workaround for platforms without C90 __func__ */
#ifndef HAVE___FUNC__
# ifdef HAVE___FUNCTION__
# define __func__ __FUNCTION__
# define HAVE___FUNC__
# endif
#endif
+127
View File
@@ -0,0 +1,127 @@
#include "robomongo/ssh/private.h"
#include <errno.h>
#include <stdio.h>
int log_error(const char *format, ...)
{
int errsave = errno;
const int buf_size = 1024;
char buf[buf_size];
va_list args;
va_start(args, format);
vsnprintf(buf, buf_size, format, args);
if (errsave) {
fprintf(stderr, "Error (%d): %s. %s\n", errno, strerror(errsave), buf);
} else {
fprintf(stderr, "Error: %s\n", buf);
}
va_end(args);
return 1;
}
int log_msg(const char *format, ...)
{
const int buf_size = 1024;
char buf[buf_size];
va_list args;
va_start(args, format);
vsnprintf(buf, buf_size, format, args);
printf("%s\n", buf);
va_end(args);
return 1;
}
/*
* errsave: use 0, if you are not logging errors (i.e. errno == 0).
*/
void ssh_log_v(struct rbm_session* session, enum rbm_ssh_log_type type, const char *format, va_list args, int errsave) {
const size_t bufsize = 2000;
char buf[bufsize];
vsnprintf(buf, bufsize, format, args);
if (type == RBM_SSH_LOG_TYPE_ERROR ||
type == RBM_SSH_LOG_TYPE_WARN) {
if (errsave) {
sprintf(session->lasterror, "%s. %s. (Error #%d)", strerror(errsave), buf, errsave);
} else {
sprintf(session->lasterror, "%s", buf);
}
fprintf(stderr, "%s\n", session->lasterror);
if (session->config->logcontext)
session->config->logcallback(session->config->logcontext, session->lasterror, type);
return;
}
if (type != RBM_SSH_LOG_TYPE_INFO &&
type != RBM_SSH_LOG_TYPE_DEBUG)
return;
if (type > session->config->loglevel)
return;
printf("%s\n", buf);
if (session->config->logcontext)
session->config->logcallback(session->config->logcontext, buf, type);
}
void ssh_log_msg(struct rbm_session* session, const char *format, ...) {
const int type = RBM_SSH_LOG_TYPE_INFO;
// For performance reasons, return as quick as possible,
// if this level of logging is not enabled
if (type > session->config->loglevel)
return;
va_list args;
va_start(args, format);
ssh_log_v(session, type, format, args, 0);
va_end(args);
}
// When you faced with an error that you are planning to overcome or handle,
// log it as a warning. If you do not have plan how to proceed further, log
// as an error.
void ssh_log_warn(struct rbm_session* session, const char *format, ...) {
int errsave = errno;
const int type = RBM_SSH_LOG_TYPE_WARN;
// For performance reasons, return as quick as possible,
// if this level of logging is not enabled
if (type > session->config->loglevel)
return;
va_list args;
va_start(args, format);
ssh_log_v(session, type, format, args, errsave);
va_end(args);
}
void ssh_log_debug(struct rbm_session* session, const char *format, ...) {
const int type = RBM_SSH_LOG_TYPE_DEBUG;
// For performance reasons, return as quick as possible,
// if this level of logging is not enabled
if (type > session->config->loglevel)
return;
va_list args;
va_start(args, format);
ssh_log_v(session, type, format, args, 0);
va_end(args);
}
void ssh_log_error(struct rbm_session* session, const char *format, ...) {
int errsave = errno;
va_list args;
va_start(args, format);
ssh_log_v(session, RBM_SSH_LOG_TYPE_ERROR, format, args, errsave);
va_end(args);
}
+94
View File
@@ -0,0 +1,94 @@
#ifndef ROBOMONGO_SSH_PRIVATE_H
#define ROBOMONGO_SSH_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
#include <libssh2.h>
#include <stdarg.h> // for va_list in log functions
#include "robomongo/ssh/ssh.h"
#ifdef _WIN32
#include "robomongo/ssh/win.h"
#else
#include "robomongo/ssh/unix.h"
#endif
//===----------------------------------------------------------------------===//
// Common constants
//===----------------------------------------------------------------------===//
enum {
RBM_SUCCESS = 0,
RBM_ERROR = -1,
RBM_BUFSIZE = 16384, // Size of the in/out buffers
};
//===----------------------------------------------------------------------===//
// Data structures
//===----------------------------------------------------------------------===//
struct rbm_channel {
struct rbm_session* session;
LIBSSH2_CHANNEL *channel;
rbm_socket_t socket;
char *inbuf;
char *outbuf;
int bufmaxsize;
};
struct rbm_session {
rbm_socket_t localsocket;
rbm_socket_t sshsocket;
LIBSSH2_SESSION *sshsession;
struct rbm_ssh_tunnel_config *config;
struct rbm_channel **channels; // array of channels
int channelssize; // number of channels
struct rbm_ssh_session *publicsession;
char lasterror[2048];
};
// Channels
struct rbm_channel *rbm_channel_create(struct rbm_session* session, rbm_socket_t socket, LIBSSH2_CHANNEL *lchannel);
void rbm_channel_close(struct rbm_channel *channel);
struct rbm_channel *rbm_channel_find_by_socket(struct rbm_session *session, rbm_socket_t socket);
void rbm_session_cleanup(struct rbm_session *session);
int rbm_open_tunnel(struct rbm_session *connection);
int rbm_ssh_setup(struct rbm_session *session);
static rbm_socket_t socket_connect(struct rbm_session* session, char *ip, int port);
LIBSSH2_SESSION *ssh_connect(struct rbm_session *rsession, rbm_socket_t sock, enum rbm_ssh_auth_type type, char *username, char *password,
char *publickeypath, char *privatekeypath, char *passphrase);
rbm_socket_t socket_listen(struct rbm_session *rsession, char *ip, int *port);
//===----------------------------------------------------------------------===//
// Logging
//===----------------------------------------------------------------------===//
int log_error(const char *format, ...);
int log_msg(const char *format, ...);
void ssh_log_v(struct rbm_session *session, enum rbm_ssh_log_type type, const char *format, va_list args, int errsave);
void ssh_log_msg(struct rbm_session *session, const char *format, ...);
void ssh_log_warn(struct rbm_session *session, const char *format, ...);
void ssh_log_debug(struct rbm_session *session, const char *format, ...);
void ssh_log_error(struct rbm_session *session, const char *format, ...);
//===----------------------------------------------------------------------===//
// Utils
//===----------------------------------------------------------------------===//
int rbm_array_add(void ***array, int *currentsize, void *data);
int rbm_array_remove(void ***array, int *currentsize, void *data);
#ifdef __cplusplus
}
#endif
#endif // ROBOMONGO_SSH_PRIVATE_H
+54
View File
@@ -0,0 +1,54 @@
#include "ssh.h"
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
struct rbm_ssh_tunnel_config config;
config.authtype = RBM_SSH_AUTH_TYPE_PUBLICKEY;
config.localip = "127.0.0.1";
config.localport = 27040;
config.username = "dmitry";
config.password = "";
config.privatekeyfile = "/Users/dmitry/.ssh/ubuntik";
config.publickeyfile = NULL; //"/Users/dmitry/.ssh/ubuntik.pub";
config.passphrase = "";
config.sshserverip = "198.61.166.171";
config.sshserverport = 22;
config.remotehost = "localhost";
config.remoteport = 27017;
config.logcontext = NULL;
config.loglevel = RBM_SSH_LOG_TYPE_DEBUG;
if (rbm_ssh_init())
return 1;
struct rbm_ssh_session *session = NULL;
session = rbm_ssh_session_create(&config);
if (!session) {
printf("Unable to create session. Shutdown");
return 1;
}
// rbm_ssh_session_close(session);
printf("II About to open tunnel...\n");
if (rbm_ssh_session_setup(session) == -1) {
printf("Setup failed. Shutdowning...");
rbm_ssh_session_close(session);
return 1;
}
// rbm_ssh_session_close(session);
//
printf("About to open tunnel...\n");
if (rbm_ssh_open_tunnel(session) != 0) {
printf("Tunnel stopped because of error.");
return 1;
}
//
// printf("Planned shutdown of the tunnel.");
rbm_ssh_cleanup();
return 0;
}
+731
View File
@@ -0,0 +1,731 @@
#include "robomongo/ssh/private.h"
#include <stdlib.h>
#include <errno.h>
#include <unistd.h> // close()
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <signal.h>
static int handle_new_client_connections(struct rbm_session *connection, int *fdmax, fd_set *masterset);
static int handle_ssh_connections(struct rbm_session *connection, fd_set *masterset);
static int handle_client_connections(struct rbm_session *connection, rbm_socket_t i, fd_set *masterset);
//===----------------------------------------------------------------------===//
// Public API
//===----------------------------------------------------------------------===//
/*
* Initialize SSH submodule, should be called only once
* Returns 0 if succeeded, or a negative value for error.
*/
int rbm_ssh_init() {
// Ignore SIGPIPE signal. If we will not do that, an attempt to send/write
// to the socket that do not have readers will force OS to generate SIGPIPE
// signal. Default handler for this signal is a termination of program.
// While signal is ignored, send/write will still return error code, that
// should be handled by the code.
signal(SIGPIPE, SIG_IGN);
int err = libssh2_init(0);
if (err) {
log_error("Initialization of LIBSSH2 failed with error code: (%d)", err);
return RBM_ERROR;
}
return RBM_SUCCESS;
}
/*
* Cleanup SSH submodule, should be called only once
*/
void rbm_ssh_cleanup() {
libssh2_exit();
}
/*
* Returns NULL on error, valid rbm_ssh_session* if no errors
*/
struct rbm_ssh_session *rbm_ssh_session_create(struct rbm_ssh_tunnel_config *config) {
struct rbm_session *session = (struct rbm_session *) malloc(sizeof(struct rbm_session));
if (!session)
return NULL;
session->localsocket = rbm_socket_invalid;
session->sshsocket = rbm_socket_invalid;
session->sshsession = NULL;
session->config = config;
session->channels = NULL;
session->channelssize = 0;
session->lasterror[0] = '\0';
// Check that loglevel is valid
if (config->loglevel != RBM_SSH_LOG_TYPE_ERROR &&
config->loglevel != RBM_SSH_LOG_TYPE_WARN &&
config->loglevel != RBM_SSH_LOG_TYPE_INFO &&
config->loglevel != RBM_SSH_LOG_TYPE_DEBUG) {
log_error("Invalid log level for SSH submodule");
return NULL;
}
struct rbm_ssh_session *publicsession = (struct rbm_ssh_session *) malloc(sizeof(struct rbm_ssh_session));
if (!publicsession)
return NULL;
publicsession->lasterror = session->lasterror;
// Point to each other
publicsession->handle = session;
session->publicsession = publicsession;
return publicsession;
}
void rbm_ssh_session_close(struct rbm_ssh_session *sshsession) {
struct rbm_session *session = (struct rbm_session*)sshsession->handle;
if (session->localsocket != rbm_socket_invalid) {
ssh_log_debug(session, "Closing local accept socket");
close(session->localsocket);
session->localsocket = rbm_socket_invalid;
}
rbm_session_cleanup(session);
ssh_log_debug(session, "SSH tunnel successfully closed.");
free(session);
free(sshsession);
}
int rbm_ssh_open_tunnel(struct rbm_ssh_session *sshsession) {
struct rbm_session *connection = (struct rbm_session*)sshsession->handle;
const int ERROR = -1;
const int SUCCESS = 0;
int rc = 0;
while (1) {
rc = rbm_open_tunnel(connection);
if (rc == 0)
break;
// Cleanup SSH connection we hope that local connection
// will not break so often
rbm_session_cleanup(connection);
ssh_log_warn(connection, "Reconnecting SSH tunnel...");
if (rbm_ssh_setup(connection) == -1) {
rbm_ssh_session_close(sshsession);
return ERROR;
}
}
return SUCCESS;
}
// Returns -1 on error, 0 when otherwise
int rbm_ssh_session_setup(struct rbm_ssh_session *sshsession) {
struct rbm_session *session = (struct rbm_session*)sshsession->handle;
const int ERROR = -1;
const int SUCCESS = 0;
struct rbm_ssh_tunnel_config *config = session->config;
if (rbm_ssh_setup(session) == -1)
return ERROR;
session->localsocket = socket_listen(session, config->localip, (int *) &config->localport);
if (session->localsocket == -1) {
session->localsocket = rbm_socket_invalid;
return ERROR; // errors are already logged by socket_listen
}
ssh_log_debug(session, "Waiting for TCP connection on %s:%d...", config->localip, config->localport);
return SUCCESS;
}
//===----------------------------------------------------------------------===//
// Private API
//===----------------------------------------------------------------------===//
// Returns NULL on error, or valid rbm_ssh_channel otherwise.
struct rbm_channel *rbm_channel_create(struct rbm_session* session, rbm_socket_t socket, LIBSSH2_CHANNEL *lchannel) {
struct rbm_channel *channel = malloc(sizeof(struct rbm_channel));
if (!channel)
return NULL;
char *inbuf = malloc(sizeof(char) * RBM_BUFSIZE);
if (!inbuf)
return NULL;
char *outbuf = malloc(sizeof(char) * RBM_BUFSIZE);
if (!outbuf)
return NULL;
channel->session = session;
channel->channel = lchannel;
channel->socket = socket;
channel->inbuf = inbuf;
channel->outbuf = outbuf;
channel->bufmaxsize = RBM_BUFSIZE;
if (rbm_array_add((void ***)&session->channels, &session->channelssize, channel))
return NULL;
return channel;
}
void rbm_channel_close(struct rbm_channel *channel) {
struct rbm_session *session = channel->session;
if (rbm_array_remove((void ***)&session->channels, &session->channelssize, channel))
return;
// 3. Close socket
if (channel->socket != rbm_socket_invalid) {
close(channel->socket);
channel->socket = rbm_socket_invalid;
}
// 1. Free libssh2 channel
if (channel->channel) {
libssh2_channel_free(channel->channel);
channel->channel = NULL;
}
// 2. Free input/output buffers
free(channel->inbuf);
free(channel->outbuf);
channel->inbuf = NULL;
channel->outbuf = NULL;
// 4. Free channel struct
free(channel);
ssh_log_debug(session, "Channel closed");
}
/*
* Returns channel for specified socket, or NULL otherwise
*/
struct rbm_channel *rbm_channel_find_by_socket(struct rbm_session *session, rbm_socket_t socket) {
for (int i = 0; i < session->channelssize; i++)
if (session->channels[i]->socket == socket)
return session->channels[i];
return NULL;
}
// Returns -1 on error, 0 when otherwise
int rbm_ssh_setup(struct rbm_session *session) {
const int ERROR = -1;
const int SUCCESS = 0;
struct rbm_ssh_tunnel_config *config = session->config;
ssh_log_debug(session, "Connecting to SSH server (%s:%d)...", config->sshserverip, config->sshserverport);
session->sshsocket = socket_connect(session, config->sshserverip, config->sshserverport);
if (session->sshsocket == -1) {
session->sshsocket = rbm_socket_invalid;
return ERROR; // errors are already logged by socket_connect
}
session->sshsession = ssh_connect(session, session->sshsocket, config->authtype, config->username, config->password,
config->publickeyfile, config->privatekeyfile, config->passphrase);
if (session->sshsession == 0) {
return ERROR; // errors are already logged by ssh_connect
}
// Must use non-blocking IO hereafter due to the current libssh3 API
libssh2_session_set_blocking(session->sshsession, 0);
return SUCCESS;
}
void rbm_session_cleanup(struct rbm_session *session) {
if (session == NULL)
return;
// Order is important:
// 1. free libssh2 channels (and client sockets)
// 2. free libssh2 session
// 3. close ssh socket
// 1.
ssh_log_debug(session, "Closing channels");
while (session->channelssize > 0) {
rbm_channel_close(session->channels[0]);
}
// 2.
if (session->sshsession) {
ssh_log_debug(session, "Closing SSH session");
libssh2_session_disconnect(session->sshsession, "Client disconnecting normally");
libssh2_session_free(session->sshsession);
session->sshsession = NULL;
}
// 3.
if (session->sshsocket != rbm_socket_invalid) {
ssh_log_debug(session, "Closing SSH socket");
close(session->sshsocket);
session->sshsocket = rbm_socket_invalid;
}
}
int rbm_open_tunnel(struct rbm_session *connection) {
const int ERROR = -1;
const int SUCCESS = 0;
rbm_socket_t local_socket = connection->localsocket;
rbm_socket_t ssh_socket = connection->sshsocket;
const int maxerrors = 25; // number of serial errors, when we probably should stop the loop
int errors = 0; // counter for serial errors
int rc = 0;
int fdmax; // maximum socket (file descriptor) number
int isocket; // index for traversing sockets
fd_set masterset, readset, clearset;
FD_ZERO(&masterset);
FD_ZERO(&clearset);
// Add the listener to the master set
FD_SET(local_socket, &masterset);
FD_SET(ssh_socket, &masterset);
// Keep track of the biggest file descriptor
fdmax = local_socket > ssh_socket ? local_socket : ssh_socket;
while (errors < maxerrors) {
readset = masterset; // copy set
// If readset has no descriptors, it means that
// session is closed and we should stop our work
if (!memcmp(&readset, &clearset, sizeof(fd_set)))
break;
ssh_log_debug(connection, "* Okay, we are ready to select.");
if (select(fdmax + 1, &readset, NULL, NULL, NULL) == -1) {
ssh_log_error(connection, "Error on select()");
break;
}
ssh_log_debug(connection, "* Selected!");
// Run through the existing connections looking for data to read
for(isocket = 0; isocket <= fdmax; isocket++) {
// Skip connections that are not available for reading
if (!FD_ISSET(isocket, &readset))
continue;
if (isocket == local_socket) {
rc = handle_new_client_connections(connection, &fdmax, &masterset);
goto next;
}
if (isocket == ssh_socket) {
rc = handle_ssh_connections(connection, &masterset);
goto next;
}
rc = handle_client_connections(connection, isocket, &masterset);
next:
// Increment "errors" counter, if we found an error,
// or zero it, if not.
errors = (rc == -2) ? errors + 1 : 0;
if (errors > 0) {
ssh_log_warn(connection, "*** COLLECTED %d AGAIN ***", errors);
}
if (rc == -1) {
ssh_log_warn(connection, "SSH tunnel shutdown because of error");
return ERROR;
}
}
}
if (errors >= maxerrors) {
ssh_log_warn(connection, "SSH tunnel shutdown because of series of successive EAGAIN errors");
return ERROR;
}
rbm_ssh_session_close(connection->publicsession);
return SUCCESS;
}
// Return -1 on error. 0 otherwise.
static int handle_new_client_connections(struct rbm_session *connection, int *fdmax, fd_set *masterset) {
rbm_socket_t local_socket = connection->localsocket;
rbm_socket_t ssh_socket = connection->sshsocket;
LIBSSH2_SESSION* session = connection->sshsession;
struct rbm_ssh_tunnel_config* config = connection->config;
rbm_socket_t newfd = rbm_socket_invalid;
const int ERROR = -1;
const int SUCCESS = 0;
ssh_log_debug(connection, "Data on accept socket is available");
struct sockaddr_in remoteaddr;
socklen_t slen = sizeof(remoteaddr);
// handle new connections
if ((newfd = accept(local_socket, (struct sockaddr *) &remoteaddr, &slen)) == -1) {
ssh_log_error(connection, "Error on accept()");
return ERROR;
} else {
FD_SET(newfd, masterset); // add to master set
if (newfd > *fdmax) { // keep track of the maximum
*fdmax = newfd;
}
ssh_log_debug(connection, "New connection from %s on socket %d", inet_ntoa(remoteaddr.sin_addr), newfd);
}
LIBSSH2_CHANNEL *channel = NULL;
int maxattempts = 25; //45;
int attempts = 0;
while (attempts < maxattempts) {
++attempts;
channel = libssh2_channel_direct_tcpip_ex(session, config->remotehost, config->remoteport,
config->localip, config->localport);
int errsave = errno;
if (!channel) {
int lerr = libssh2_session_last_error(connection->sshsession, NULL, NULL, 0);
ssh_log_warn(connection, "Could not open the direct TCP/IP channel (%d)", lerr);
// Error 35: Resource temporarily unavailable.
if (lerr == LIBSSH2_ERROR_EAGAIN) {
usleep(200 * 1000);
continue;
}
break;
}
ssh_log_debug(connection, "Channel successfully created!");
break;
}
if (!channel) {
ssh_log_error(connection, "Failed to create SSH channel");
return ERROR;
}
if (rbm_channel_create(connection, newfd, channel) == NULL) {
return ERROR;
}
return SUCCESS;
}
// 0: success
// -1:
static int handle_ssh_connections(struct rbm_session *connection, fd_set *masterset) {
const int AGAIN = -2;
const int ERROR = -1;
const int SUCCESS = 0;
struct rbm_ssh_tunnel_config* config = connection->config;
ssh_log_debug(connection, "Data on SSH socket is available");
ssh_log_debug(connection, "[%d] <- Number of channels", connection->channelssize);
if (connection->channelssize == 0) {
FD_CLR(connection->localsocket, masterset); // remove from master set
FD_CLR(connection->sshsocket, masterset); // remove from master set
return SUCCESS;
}
int s = 0;
int result = SUCCESS;
int eagain = 0;
while (s < connection->channelssize) {
struct rbm_channel *context = connection->channels[s];
++s;
int firstflag = 1;
while (1) {
int len;
len = libssh2_channel_read(context->channel, context->outbuf, context->bufmaxsize);
if (len == LIBSSH2_ERROR_EAGAIN) {
if (firstflag) {
++eagain;
if (eagain == connection->channelssize) {
result = AGAIN;
ssh_log_warn(connection, "All channels are in a non ready state (EAGAIN)");
}
}
// Proceed with the next channel
break;
} else if (len < 0) {
// ETIMEDOUT (60) Connection timed out
// We need to reconnect
// if (errno == 60) {
// return 2;
// }
// Endless cycle:
// Network is down. libssh2_channel_read: -43. (Error #50)
result = ERROR;
ssh_log_error(connection, "libssh2_channel_read: %d", len);
return ERROR;
}
firstflag = 0;
ssh_log_debug(connection, "Received %d bytes from tunnel", len);
int wr = 0;
int rc = 0; // result
while (wr < len) {
rc = send(context->socket, context->outbuf + wr, len - wr, 0);
if (rc <= 0) {
result = ERROR;
ssh_log_error(connection, "Failure to write data to client");
break;
}
wr += rc;
}
if (libssh2_channel_eof(context->channel)) {
result = SUCCESS;
ssh_log_debug(connection, "The server at %s:%d disconnected!\n",
config->remotehost, config->remoteport);
break;
}
}
}
return result;
}
static int handle_client_connections(struct rbm_session *connection, rbm_socket_t i, fd_set *masterset) {
const int ERROR = -1;
const int SUCCESS = 0;
int result = SUCCESS;
ssh_log_debug(connection, "Data on client socket is available");
struct rbm_channel *context = rbm_channel_find_by_socket(connection, i);
if (!context) {
close(i); // bye!
FD_CLR(i, masterset); // remove from master set
return ERROR;
}
// Read data from a client
int nbytes = recv(context->socket, context->inbuf, context->bufmaxsize, 0);
if (nbytes <= 0) {
if (nbytes == 0) {
// Normal situation
result = SUCCESS;
ssh_log_debug(connection, "Client disconnected");
} else {
// Got error
result = ERROR;
ssh_log_error(connection, "Error when recv()");
}
// In both these cases, close and cleanup connection
// close(context->socket); // bye!
FD_CLR(context->socket, masterset); // remove from master set
rbm_channel_close(context);
if (connection->channelssize == 0) {
FD_CLR(connection->localsocket, masterset); // remove from master set
FD_CLR(connection->sshsocket, masterset); // remove from master set
}
return result;
}
ssh_log_debug(connection, "Received %d bytes from client", nbytes);
// Write data to ssh tunnel
const int againmax = 100;
int again = 0;
int wr = 0;
int rc = 0; // result
while (wr < nbytes) {
rc = libssh2_channel_write(context->channel, context->inbuf + wr, nbytes - wr);
if (LIBSSH2_ERROR_EAGAIN == rc) {
++again;
if (again > againmax) {
ssh_log_warn(connection, "Number of attempts to libssh2_channel_write exсeed max value");
return ERROR;
}
continue;
}
if (rc < 0) {
ssh_log_error(connection, "Failed to write to SSH channel");
return ERROR;
}
wr += rc;
}
ssh_log_debug(connection, "Written %d bytes to tunnel", wr);
return SUCCESS;
}
/*
* Returns socket if succeed, otherwise -1 on error
*/
static rbm_socket_t socket_connect(struct rbm_session* session, char *ip, int port) {
rbm_socket_t sock;
struct sockaddr_in sin;
/* Connect to SSH server */
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == rbm_socket_invalid) {
ssh_log_error(session, "Failed to open socket");
return -1;
}
sin.sin_family = AF_INET;
if (INADDR_NONE == (sin.sin_addr.s_addr = inet_addr(ip))) {
ssh_log_error(session, "Call to inet_addr failed");
return -1;
}
sin.sin_port = htons(port);
if (connect(sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in)) != 0) {
ssh_log_error(session, "Failed to connect to %s:%d", ip, port);
return -1;
}
return sock;
}
/*
* Returns socket (binded and in listen state) if succeed, otherwise (rbm_socket_invalid) on error
*/
rbm_socket_t socket_listen(struct rbm_session *rsession, char *ip, int *port) {
rbm_socket_t listensock;
struct sockaddr_in sin;
int sockopt;
socklen_t sinlen;
listensock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listensock == rbm_socket_invalid) {
ssh_log_error(rsession, "Failed to open socket");
return rbm_socket_invalid;
}
sin.sin_family = AF_INET;
sin.sin_port = htons(0); // Bind to any available port (htons is not needed, but still it's here)
if (INADDR_NONE == (sin.sin_addr.s_addr = inet_addr(ip))) {
ssh_log_error(rsession, "inet_addr");
return rbm_socket_invalid;
}
sockopt = 1;
setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt));
sinlen = sizeof(sin);
if (-1 == bind(listensock, (struct sockaddr *)&sin, sinlen)) {
ssh_log_error(rsession, "Cannot bind to port %d", port);
return rbm_socket_invalid;
}
if (-1 == listen(listensock, 2)) {
ssh_log_error(rsession, "Failed to listen opened socket");
return rbm_socket_invalid;
}
if (getsockname(listensock, (struct sockaddr *)&sin, &sinlen) == -1) {
ssh_log_error(rsession, "Failed to get socket address");
return rbm_socket_invalid;
}
*port = ntohs(sin.sin_port);
return listensock;
}
/*
* Returns 0 if error.
*/
LIBSSH2_SESSION *ssh_connect(struct rbm_session *rsession, rbm_socket_t sock, enum rbm_ssh_auth_type type, char *username, char *password,
char *publickeypath, char *privatekeypath, char *passphrase) {
int rc, i, auth = RBM_SSH_AUTH_TYPE_NONE;
LIBSSH2_SESSION *session;
const char *fingerprint;
char *userauthlist;
ssh_log_debug(rsession, "ssh_connect: username: %s", username);
ssh_log_debug(rsession, "ssh_connect: password: %s", password);
ssh_log_debug(rsession, "ssh_connect: privatekeyfile: %s", privatekeypath);
ssh_log_debug(rsession, "ssh_connect: publickeyfile: %s", publickeypath);
/* Create a session instance */
session = libssh2_session_init();
if (!session) {
ssh_log_error(rsession, "Could not initialize SSH session");
return 0;
}
/* ... start it up. This will trade welcome banners, exchange keys,
* and setup crypto, compression, and MAC layers
*/
rc = libssh2_session_handshake(session, sock);
if (rc) {
ssh_log_error(rsession, "Error when starting up SSH session: %d\n", rc);
return 0;
}
/* At this point we havn't yet authenticated. The first thing to do
* is check the hostkey's fingerprint against our known hosts Your app
* may have it hard coded, may go to a file, may present it to the
* user, that's your call
*/
fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
/*
fprintf(stderr, "Fingerprint: ");
for(i = 0; i < 20; i++)
fprintf(stderr, "%02X ", (unsigned char)fingerprint[i]);
fprintf(stderr, "\n");
*/
/* check what authentication methods are available */
userauthlist = libssh2_userauth_list(session, username, strlen(username));
ssh_log_debug(rsession, "Authentication methods: %s", userauthlist);
if (strstr(userauthlist, "password"))
auth |= RBM_SSH_AUTH_TYPE_PASSWORD;
if (strstr(userauthlist, "publickey"))
auth |= RBM_SSH_AUTH_TYPE_PUBLICKEY;
// If authentication by password is available
// and was chosen by the user, then use it
if (auth & RBM_SSH_AUTH_TYPE_PASSWORD && type == RBM_SSH_AUTH_TYPE_PASSWORD) {
if (libssh2_userauth_password(session, username, password)) {
ssh_log_error(rsession, "Authentication by password failed");
return 0;
}
ssh_log_debug(rsession, "Authentication by password succeeded.");
// If authentication by key is available
// and was chosen by the user, then use it
} else if (auth & RBM_SSH_AUTH_TYPE_PUBLICKEY && type == RBM_SSH_AUTH_TYPE_PUBLICKEY) {
rc = libssh2_userauth_publickey_fromfile(session, username, publickeypath, privatekeypath, passphrase);
if (rc) {
ssh_log_error(rsession, "Authentication by key (%s) failed (Error %d)", privatekeypath, rc);
return 0;
}
ssh_log_debug(rsession, "Authentication by key succeeded.");
} else {
ssh_log_error(rsession, "No supported authentication methods found");
return 0;
}
return session;
}
+71
View File
@@ -0,0 +1,71 @@
#ifndef ROBOMONGO_SSH_H
#define ROBOMONGO_SSH_H
#ifdef __cplusplus
extern "C" {
#endif
enum rbm_ssh_log_type {
RBM_SSH_LOG_TYPE_ERROR = 1,
RBM_SSH_LOG_TYPE_WARN = 2,
RBM_SSH_LOG_TYPE_INFO = 3,
RBM_SSH_LOG_TYPE_DEBUG = 100 // log as much as possible
};
enum rbm_ssh_auth_type {
RBM_SSH_AUTH_TYPE_NONE = 0,
RBM_SSH_AUTH_TYPE_PASSWORD = 1,
RBM_SSH_AUTH_TYPE_PUBLICKEY = 2
};
/*
* SSH Tunnel configuration
*/
struct rbm_ssh_tunnel_config {
enum rbm_ssh_auth_type authtype;
// Keys and optional passphrase
char *privatekeyfile;
char *publickeyfile;
char *passphrase; // May be NULL or ""
// Local IP and port to bind and listen to
char *localip;
unsigned int localport;
// Remote host and port to connect to
char *remotehost; // Resolved by the remote server
unsigned int remoteport;
// Username and password of remote user
char *username;
char *password; // May be NULL or ""
// Remote IP and (host, port) in remote network
char *sshserverip;
unsigned int sshserverport; // SSH port
// Logging facilities
enum rbm_ssh_log_type loglevel;
void *logcontext; // Pointer to user-defined data
void (*logcallback)(void *logcontext, char *message, int level);
};
struct rbm_ssh_session {
char *lasterror;
void *handle; // opaque pointer to rbm_session
};
int rbm_ssh_init();
void rbm_ssh_cleanup();
struct rbm_ssh_session* rbm_ssh_session_create(struct rbm_ssh_tunnel_config *config);
int rbm_ssh_open_tunnel(struct rbm_ssh_session *connection);
int rbm_ssh_session_setup(struct rbm_ssh_session *session);
void rbm_ssh_session_close(struct rbm_ssh_session *session);
#ifdef __cplusplus
}
#endif
#endif // ROBOMONGO_SSH_H
+338
View File
@@ -0,0 +1,338 @@
#include "libssh2_config.h"
#include <libssh2.h>
#ifdef WIN32
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#endif
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/types.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifndef INADDR_NONE
#define INADDR_NONE (in_addr_t)-1
#endif
const char *keyfile1 = "/Users/dmitry/.ssh/ubuntik.pub";
const char *keyfile2 = "/Users/dmitry/.ssh/ubuntik";
const char *username = "username";
const char *password = "";
const char *server_ip = "127.0.0.1";
const char *local_listenip = "127.0.0.1";
unsigned int local_listenport = 2222;
const char *remote_desthost = "localhost"; /* resolved by the server */
unsigned int remote_destport = 22;
enum {
AUTH_NONE = 0,
AUTH_PASSWORD,
AUTH_PUBLICKEY
};
int main(int argc, char *argv[])
{
int rc, i, auth = AUTH_NONE;
struct sockaddr_in sin;
socklen_t sinlen;
const char *fingerprint;
char *userauthlist;
LIBSSH2_SESSION *session;
LIBSSH2_CHANNEL *channel = NULL;
const char *shost;
unsigned int sport;
fd_set fds;
struct timeval tv;
ssize_t len, wr;
char buf[16384];
#ifdef WIN32
char sockopt;
SOCKET sock = INVALID_SOCKET;
SOCKET listensock = INVALID_SOCKET, forwardsock = INVALID_SOCKET;
WSADATA wsadata;
int err;
err = WSAStartup(MAKEWORD(2,0), &wsadata);
if (err != 0) {
fprintf(stderr, "WSAStartup failed with error: %d\n", err);
return 1;
}
#else
int sockopt, sock = -1;
int listensock = -1, forwardsock = -1;
#endif
if (argc > 1)
server_ip = argv[1];
if (argc > 2)
username = argv[2];
if (argc > 3)
password = argv[3];
if (argc > 4)
local_listenip = argv[4];
if (argc > 5)
local_listenport = atoi(argv[5]);
if (argc > 6)
remote_desthost = argv[6];
if (argc > 7)
remote_destport = atoi(argv[7]);
rc = libssh2_init (0);
if (rc != 0) {
fprintf (stderr, "libssh2 initialization failed (%d)\n", rc);
return 1;
}
/* Connect to SSH server */
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
#ifdef WIN32
if (sock == INVALID_SOCKET) {
fprintf(stderr, "failed to open socket!\n");
return -1;
}
#else
if (sock == -1) {
perror("socket");
return -1;
}
#endif
sin.sin_family = AF_INET;
if (INADDR_NONE == (sin.sin_addr.s_addr = inet_addr(server_ip))) {
perror("inet_addr");
return -1;
}
sin.sin_port = htons(22);
if (connect(sock, (struct sockaddr*)(&sin),
sizeof(struct sockaddr_in)) != 0) {
fprintf(stderr, "failed to connect!\n");
return -1;
}
/* Create a session instance */
session = libssh2_session_init();
if(!session) {
fprintf(stderr, "Could not initialize SSH session!\n");
return -1;
}
/* ... start it up. This will trade welcome banners, exchange keys,
* and setup crypto, compression, and MAC layers
*/
rc = libssh2_session_handshake(session, sock);
if(rc) {
fprintf(stderr, "Error when starting up SSH session: %d\n", rc);
return -1;
}
/* At this point we havn't yet authenticated. The first thing to do
* is check the hostkey's fingerprint against our known hosts Your app
* may have it hard coded, may go to a file, may present it to the
* user, that's your call
*/
fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
fprintf(stderr, "Fingerprint: ");
for(i = 0; i < 20; i++)
fprintf(stderr, "%02X ", (unsigned char)fingerprint[i]);
fprintf(stderr, "\n");
/* check what authentication methods are available */
userauthlist = libssh2_userauth_list(session, username, strlen(username));
fprintf(stderr, "Authentication methods: %s\n", userauthlist);
if (strstr(userauthlist, "password"))
auth |= AUTH_PASSWORD;
if (strstr(userauthlist, "publickey"))
auth |= AUTH_PUBLICKEY;
/* check for options */
if(argc > 8) {
if ((auth & AUTH_PASSWORD) && !strcasecmp(argv[8], "-p"))
auth = AUTH_PASSWORD;
if ((auth & AUTH_PUBLICKEY) && !strcasecmp(argv[8], "-k"))
auth = AUTH_PUBLICKEY;
}
if (auth & AUTH_PASSWORD) {
if (libssh2_userauth_password(session, username, password)) {
fprintf(stderr, "Authentication by password failed.\n");
goto shutdown;
}
} else if (auth & AUTH_PUBLICKEY) {
fprintf(stderr, "\tPassword: %s\n", password);
if (libssh2_userauth_publickey_fromfile(session, username, keyfile1,
keyfile2, password)) {
fprintf(stderr, "\tAuthentication by public key failed!\n");
goto shutdown;
}
fprintf(stderr, "\tAuthentication by public key succeeded.\n");
} else {
fprintf(stderr, "No supported authentication methods found!\n");
goto shutdown;
}
listensock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
#ifdef WIN32
if (listensock == INVALID_SOCKET) {
fprintf(stderr, "failed to open listen socket!\n");
return -1;
}
#else
if (listensock == -1) {
perror("socket");
return -1;
}
#endif
sin.sin_family = AF_INET;
sin.sin_port = htons(local_listenport);
if (INADDR_NONE == (sin.sin_addr.s_addr = inet_addr(local_listenip))) {
perror("inet_addr");
goto shutdown;
}
sockopt = 1;
setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt));
sinlen=sizeof(sin);
if (-1 == bind(listensock, (struct sockaddr *)&sin, sinlen)) {
perror("bind");
goto shutdown;
}
if (-1 == listen(listensock, 2)) {
perror("listen");
goto shutdown;
}
fprintf(stderr, "Waiting for TCP connection on %s:%d...\n",
inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
forwardsock = accept(listensock, (struct sockaddr *)&sin, &sinlen);
#ifdef WIN32
if (forwardsock == INVALID_SOCKET) {
fprintf(stderr, "failed to accept forward socket!\n");
goto shutdown;
}
#else
if (forwardsock == -1) {
perror("accept");
goto shutdown;
}
#endif
shost = inet_ntoa(sin.sin_addr);
sport = local_listenport; // ntohs(sin.sin_port);
fprintf(stderr, "Forwarding connection from %s:%d here to remote %s:%d\n",
shost, sport, remote_desthost, remote_destport);
channel = libssh2_channel_direct_tcpip_ex(session, remote_desthost,
remote_destport, shost, sport);
if (!channel) {
fprintf(stderr, "Could not open the direct-tcpip channel!\n"
"(Note that this can be a problem at the server!"
" Please review the server logs.)\n");
goto shutdown;
}
/* Must use non-blocking IO hereafter due to the current libssh2 API */
libssh2_session_set_blocking(session, 0);
while (1) {
FD_ZERO(&fds);
FD_SET(forwardsock, &fds);
tv.tv_sec = 0;
tv.tv_usec = 100000;
rc = select(forwardsock + 1, &fds, NULL, NULL, &tv);
if (-1 == rc) {
perror("select");
goto shutdown;
}
if (rc && FD_ISSET(forwardsock, &fds)) {
len = recv(forwardsock, buf, sizeof(buf), 0);
if (len < 0) {
perror("read");
goto shutdown;
} else if (0 == len) {
fprintf(stderr, "The client at %s:%d disconnected!\n", shost,
sport);
goto shutdown;
}
wr = 0;
while(wr < len) {
i = libssh2_channel_write(channel, buf + wr, len - wr);
if (LIBSSH2_ERROR_EAGAIN == i) {
continue;
}
if (i < 0) {
fprintf(stderr, "libssh2_channel_write: %d\n", i);
goto shutdown;
}
wr += i;
}
}
while (1) {
len = libssh2_channel_read(channel, buf, sizeof(buf));
if (LIBSSH2_ERROR_EAGAIN == len)
break;
else if (len < 0) {
fprintf(stderr, "libssh2_channel_read: %d", (int)len);
goto shutdown;
}
wr = 0;
while (wr < len) {
i = send(forwardsock, buf + wr, len - wr, 0);
if (i <= 0) {
perror("write");
goto shutdown;
}
wr += i;
}
if (libssh2_channel_eof(channel)) {
fprintf(stderr, "The server at %s:%d disconnected!\n",
remote_desthost, remote_destport);
goto shutdown;
}
}
}
shutdown:
#ifdef WIN32
closesocket(forwardsock);
closesocket(listensock);
#else
close(forwardsock);
close(listensock);
#endif
if (channel)
libssh2_channel_free(channel);
libssh2_session_disconnect(session, "Client disconnecting normally");
libssh2_session_free(session);
#ifdef WIN32
closesocket(sock);
#else
close(sock);
#endif
libssh2_exit();
return 0;
}
+12
View File
@@ -0,0 +1,12 @@
#include "robomongo/ssh/ssh.h"
#include <stdarg.h>
int log_error(const char *format, ...);
int log_msg(const char *format, ...);
void ssh_log_v(rbm_ssh_session* session, enum rbm_ssh_log_type type, const char *format, va_list args, int errsave);
void ssh_log_msg(rbm_ssh_session* session, const char *format, ...);
void ssh_log_warn(rbm_ssh_session* session, const char *format, ...);
void ssh_log_debug(rbm_ssh_session* session, const char *format, ...);
void ssh_log_error(rbm_ssh_session* session, const char *format, ...);
+759
View File
@@ -0,0 +1,759 @@
#include "robomongo/ssh/ssh.h"
#include "robomongo/ssh/internal.h"
#include "robomongo/ssh/libssh2_config.h"
#ifdef WIN32
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#endif
#include <fcntl.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/types.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifndef INADDR_NONE
#define INADDR_NONE (in_addr_t)-1
#endif
/*
* Initialises Sockets and Libssh
* Returns 0 if initialization succeeds
*/
int rbm_ssh_init() {
int err;
#ifdef WIN32
WSADATA wsadata;
#endif
#ifdef WIN32
err = WSAStartup(MAKEWORD(2,0), &wsadata);
if (err != 0) {
log_error("WSAStartup failed with error: %d", err);
return 1;
}
#endif
err = libssh2_init (0);
if (err != 0) {
log_error("libssh2 initialization failed (%d)", err);
return 1;
}
return 0;
}
/*
* Cleanups Sockets and Libssh
*/
void rbm_ssh_cleanup() {
libssh2_exit();
#ifdef WIN32
WSACleanup();
#endif
}
/*
* Returns socket if succeed, otherwise -1 on error
*/
static rbm_socket_t socket_connect(rbm_ssh_session* session, char *ip, int port) {
rbm_socket_t sock;
struct sockaddr_in sin;
/* Connect to SSH server */
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == rbm_socket_invalid) {
ssh_log_error(session, "Failed to open socket");
return -1;
}
sin.sin_family = AF_INET;
if (INADDR_NONE == (sin.sin_addr.s_addr = inet_addr(ip))) {
ssh_log_error(session, "Call to inet_addr failed");
return -1;
}
sin.sin_port = htons(port);
if (connect(sock, (struct sockaddr*)(&sin), sizeof(struct sockaddr_in)) != 0) {
ssh_log_error(session, "Failed to connect to %s:%d", ip, port);
return -1;
}
return sock;
}
/*
* Returns socket (binded and in listen state) if succeed, otherwise (rbm_socket_invalid) on error
*/
rbm_socket_t socket_listen(rbm_ssh_session *rsession, char *ip, int *port) {
rbm_socket_t listensock;
struct sockaddr_in sin;
int sockopt;
socklen_t sinlen;
listensock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listensock == rbm_socket_invalid) {
ssh_log_error(rsession, "Failed to open socket");
return rbm_socket_invalid;
}
sin.sin_family = AF_INET;
sin.sin_port = htons(0); // Bind to any available port (htons is not needed, but still it's here)
if (INADDR_NONE == (sin.sin_addr.s_addr = inet_addr(ip))) {
ssh_log_error(rsession, "inet_addr");
return rbm_socket_invalid;
}
sockopt = 1;
setsockopt(listensock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt));
sinlen = sizeof(sin);
if (-1 == bind(listensock, (struct sockaddr *)&sin, sinlen)) {
ssh_log_error(rsession, "Cannot bind to port %d", port);
return rbm_socket_invalid;
}
if (-1 == listen(listensock, 2)) {
ssh_log_error(rsession, "Failed to listen opened socket");
return rbm_socket_invalid;
}
if (getsockname(listensock, (struct sockaddr *)&sin, &sinlen) == -1) {
ssh_log_error(rsession, "Failed to get socket address");
return rbm_socket_invalid;
}
*port = ntohs(sin.sin_port);
return listensock;
}
/*
* Returns 0 if error.
*/
LIBSSH2_SESSION *ssh_connect(rbm_ssh_session *rsession, rbm_socket_t sock, enum rbm_ssh_auth_type type, char *username, char *password,
char *publickeypath, char *privatekeypath, char *passphrase) {
int rc, i, auth = RBM_SSH_AUTH_TYPE_NONE;
LIBSSH2_SESSION *session;
const char *fingerprint;
char *userauthlist;
ssh_log_debug(rsession, "ssh_connect: username: %s", username);
ssh_log_debug(rsession, "ssh_connect: password: %s", password);
ssh_log_debug(rsession, "ssh_connect: privatekeyfile: %s", privatekeypath);
ssh_log_debug(rsession, "ssh_connect: publickeyfile: %s", publickeypath);
/* Create a session instance */
session = libssh2_session_init();
if (!session) {
ssh_log_error(rsession, "Could not initialize SSH session");
return 0;
}
/* ... start it up. This will trade welcome banners, exchange keys,
* and setup crypto, compression, and MAC layers
*/
rc = libssh2_session_handshake(session, sock);
if (rc) {
ssh_log_error(rsession, "Error when starting up SSH session: %d\n", rc);
return 0;
}
/* At this point we havn't yet authenticated. The first thing to do
* is check the hostkey's fingerprint against our known hosts Your app
* may have it hard coded, may go to a file, may present it to the
* user, that's your call
*/
fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
/*
fprintf(stderr, "Fingerprint: ");
for(i = 0; i < 20; i++)
fprintf(stderr, "%02X ", (unsigned char)fingerprint[i]);
fprintf(stderr, "\n");
*/
/* check what authentication methods are available */
userauthlist = libssh2_userauth_list(session, username, strlen(username));
ssh_log_debug(rsession, "Authentication methods: %s", userauthlist);
if (strstr(userauthlist, "password"))
auth |= RBM_SSH_AUTH_TYPE_PASSWORD;
if (strstr(userauthlist, "publickey"))
auth |= RBM_SSH_AUTH_TYPE_PUBLICKEY;
// If authentication by password is available
// and was chosen by the user, then use it
if (auth & RBM_SSH_AUTH_TYPE_PASSWORD && type == RBM_SSH_AUTH_TYPE_PASSWORD) {
if (libssh2_userauth_password(session, username, password)) {
ssh_log_error(rsession, "Authentication by password failed");
return 0;
}
ssh_log_debug(rsession, "Authentication by password succeeded.");
// If authentication by key is available
// and was chosen by the user, then use it
} else if (auth & RBM_SSH_AUTH_TYPE_PUBLICKEY && type == RBM_SSH_AUTH_TYPE_PUBLICKEY) {
rc = libssh2_userauth_publickey_fromfile(session, username, publickeypath, privatekeypath, passphrase);
if (rc) {
ssh_log_error(rsession, "Authentication by key (%s) failed (Error %d)", privatekeypath, rc);
return 0;
}
ssh_log_debug(rsession, "Authentication by key succeeded.");
} else {
ssh_log_error(rsession, "No supported authentication methods found");
return 0;
}
return session;
}
// Returns NULL on error, or valid rbm_ssh_channel otherwise.
rbm_ssh_channel *ssh_channel_create(rbm_ssh_session* session, rbm_socket_t socket, LIBSSH2_CHANNEL *lchannel) {
const int bufsize = 16384;
rbm_ssh_channel **channels;
rbm_ssh_channel *channel = malloc(sizeof(rbm_ssh_channel));
channel->session = session;
channel->channel = lchannel;
channel->socket = socket;
channel->inbuf = malloc(sizeof(char) * bufsize);
channel->outbuf = malloc(sizeof(char) * bufsize);
channel->bufmaxsize = bufsize;
channels = (rbm_ssh_channel**) realloc (session->channels, (session->channelssize + 1) * sizeof(rbm_ssh_channel*)); // acts like malloc when (session->channels == NULL)
if (!channels) {
ssh_log_error(session, "Not enough memory (call to realloc)");
return NULL;
}
channels[session->channelssize] = channel;
session->channels = channels;
++session->channelssize;
return channel;
}
void ssh_channel_close(rbm_ssh_channel* channel) {
int i;
rbm_ssh_session* session;
rbm_ssh_channel** channels;
session = channel->session;
for (i = 0; i < session->channelssize; i++) {
if (session->channels[i] != channel) {
continue;
}
if (session->channelssize == 1) {
channels = NULL;
} else {
channels = (rbm_ssh_channel**) malloc((session->channelssize - 1) * sizeof(rbm_ssh_channel*));
memcpy(channels, session->channels, i * sizeof(rbm_ssh_channel*));
if (i + 1 < session->channelssize) {
memcpy(channels + i, session->channels + i + 1, (session->channelssize - i - 1) * sizeof(rbm_ssh_channel*));
}
}
free(session->channels);
session->channels = channels;
--session->channelssize;
// 1. Free libssh2 channel
if (channel->channel) {
libssh2_channel_free(channel->channel);
channel->channel = NULL;
}
// 2. Free input/output buffers
free(channel->inbuf);
channel->inbuf = NULL;
free(channel->outbuf);
channel->outbuf = NULL;
// 3. Close socket
if (channel->socket != rbm_socket_invalid) {
close(channel->socket);
channel->socket = rbm_socket_invalid;
}
// 4. Free channel struct
free(channel);
ssh_log_debug(session, "Channel closed");
break;
}
}
rbm_ssh_channel* ssh_channel_find_by_socket(rbm_ssh_session* session, rbm_socket_t socket) {
for (int i = 0; i < session->channelssize; i++) {
if (session->channels[i]->socket == socket) {
return session->channels[i];
}
}
return NULL;
}
// Return -1 on error. 0 otherwise.
static int handle_new_client_connections(rbm_ssh_session *connection, int *fdmax, fd_set *masterset) {
rbm_socket_t local_socket = connection->localsocket;
rbm_socket_t ssh_socket = connection->sshsocket;
LIBSSH2_SESSION* session = connection->sshsession;
struct rbm_ssh_tunnel_config* config = connection->config;
rbm_socket_t newfd = rbm_socket_invalid;
const int ERROR = -1;
const int SUCCESS = 0;
ssh_log_debug(connection, "Data on accept socket is available");
struct sockaddr_in remoteaddr;
socklen_t slen = sizeof(remoteaddr);
// handle new connections
if ((newfd = accept(local_socket, (struct sockaddr *) &remoteaddr, &slen)) == -1) {
ssh_log_error(connection, "Error on accept()");
return ERROR;
} else {
FD_SET(newfd, masterset); // add to master set
if (newfd > *fdmax) { // keep track of the maximum
*fdmax = newfd;
}
ssh_log_debug(connection, "New connection from %s on socket %d", inet_ntoa(remoteaddr.sin_addr), newfd);
}
LIBSSH2_CHANNEL *channel = NULL;
int maxattempts = 25; //45;
int attempts = 0;
while (attempts < maxattempts) {
++attempts;
channel = libssh2_channel_direct_tcpip_ex(session, config->remotehost, config->remoteport,
config->localip, config->localport);
int errsave = errno;
if (!channel) {
ssh_log_warn(connection, "Could not open the direct TCP/IP channel (%d)", channel);
// Error 35: Resource temporarily unavailable.
if (errsave == 35) {
usleep(200 * 1000);
continue;
}
break;
}
ssh_log_debug(connection, "Channel successfully created!");
break;
}
if (!channel) {
ssh_log_error(connection, "Failed to create SSH channel");
return ERROR;
}
if (ssh_channel_create(connection, newfd, channel) == NULL) {
return ERROR;
}
return SUCCESS;
}
// 0: success
// -1:
static int handle_ssh_connections(rbm_ssh_session *connection, fd_set *masterset) {
const int AGAIN = -2;
const int ERROR = -1;
const int SUCCESS = 0;
struct rbm_ssh_tunnel_config* config = connection->config;
ssh_log_debug(connection, "Data on SSH socket is available");
ssh_log_debug(connection, "[%d] <- Number of channels", connection->channelssize);
if (connection->channelssize == 0) {
FD_CLR(connection->localsocket, masterset); // remove from master set
FD_CLR(connection->sshsocket, masterset); // remove from master set
return SUCCESS;
}
int s = 0;
int result = SUCCESS;
int eagain = 0;
while (s < connection->channelssize) {
rbm_ssh_channel *context = connection->channels[s];
++s;
int firstflag = 1;
while (1) {
int len;
len = libssh2_channel_read(context->channel, context->outbuf, context->bufmaxsize);
if (len == LIBSSH2_ERROR_EAGAIN) {
if (firstflag) {
++eagain;
if (eagain == connection->channelssize) {
result = AGAIN;
ssh_log_warn(connection, "All channels are in a non ready state (EAGAIN)");
}
}
// Proceed with the next channel
break;
} else if (len < 0) {
// ETIMEDOUT (60) Connection timed out
// We need to reconnect
// if (errno == 60) {
// return 2;
// }
// Endless cycle:
// Network is down. libssh2_channel_read: -43. (Error #50)
result = ERROR;
ssh_log_error(connection, "libssh2_channel_read: %d", len);
break;
}
firstflag = 0;
ssh_log_debug(connection, "Received %d bytes from tunnel", len);
int wr = 0;
int rc = 0; // result
while (wr < len) {
rc = send(context->socket, context->outbuf + wr, len - wr, 0);
if (rc <= 0) {
result = ERROR;
ssh_log_error(connection, "Failure to write data to client");
break;
}
wr += rc;
}
if (libssh2_channel_eof(context->channel)) {
result = SUCCESS;
ssh_log_debug(connection, "The server at %s:%d disconnected!\n",
config->remotehost, config->remoteport);
break;
}
}
}
return result;
}
static int handle_client_connections(rbm_ssh_session *connection, rbm_socket_t i, fd_set *masterset) {
const int ERROR = -1;
const int SUCCESS = 0;
int result = SUCCESS;
ssh_log_debug(connection, "Data on client socket is available");
rbm_ssh_channel *context = ssh_channel_find_by_socket(connection, i);
if (!context) {
close(i); // bye!
FD_CLR(i, masterset); // remove from master set
return ERROR;
}
// Read data from a client
int nbytes = recv(context->socket, context->inbuf, context->bufmaxsize, 0);
if (nbytes <= 0) {
if (nbytes == 0) {
// Normal situation
result = SUCCESS;
ssh_log_debug(connection, "Client disconnected");
} else {
// Got error
result = ERROR;
ssh_log_error(connection, "Error when recv()");
}
// In both these cases, close and cleanup connection
close(context->socket); // bye!
FD_CLR(context->socket, masterset); // remove from master set
ssh_channel_close(context);
if (connection->channelssize == 0) {
FD_CLR(connection->localsocket, masterset); // remove from master set
FD_CLR(connection->sshsocket, masterset); // remove from master set
}
return result;
}
ssh_log_debug(connection, "Received %d bytes from client", nbytes);
// Write data to ssh tunnel
const int againmax = 100;
int again = 0;
int wr = 0;
int rc = 0; // result
while (wr < nbytes) {
rc = libssh2_channel_write(context->channel, context->inbuf + wr, nbytes - wr);
if (LIBSSH2_ERROR_EAGAIN == rc) {
++again;
if (again > againmax) {
ssh_log_warn(connection, "Number of attempts to libssh2_channel_write exсeed max value");
return ERROR;
}
continue;
}
if (rc < 0) {
ssh_log_error(connection, "Failed to write to SSH channel");
return ERROR;
}
wr += rc;
}
ssh_log_debug(connection, "Written %d bytes to tunnel", wr);
return SUCCESS;
}
int rbm_ssh_open_tunnel_ex(rbm_ssh_session *connection) {
const int ERROR = -1;
const int SUCCESS = 0;
rbm_socket_t local_socket = connection->localsocket;
rbm_socket_t ssh_socket = connection->sshsocket;
const int maxerrors = 25; // number of serial errors, when we probably should stop the loop
int errors = 0; // counter for serial errors
int rc = 0;
int fdmax; // maximum socket (file descriptor) number
int isocket; // index for traversing sockets
fd_set masterset, readset, clearset;
FD_ZERO(&masterset);
FD_ZERO(&clearset);
// Add the listener to the master set
FD_SET(local_socket, &masterset);
FD_SET(ssh_socket, &masterset);
// Keep track of the biggest file descriptor
fdmax = local_socket > ssh_socket ? local_socket : ssh_socket;
while (errors < maxerrors) {
readset = masterset; // copy set
// If readset has no descriptors, it means that
// session is closed and we should stop our work
if (!memcmp(&readset, &clearset, sizeof(fd_set)))
break;
ssh_log_debug(connection, "* Okay, we are ready to select.");
if (select(fdmax + 1, &readset, NULL, NULL, NULL) == -1) {
ssh_log_error(connection, "Error on select()");
break;
}
ssh_log_debug(connection, "* Selected!");
// Run through the existing connections looking for data to read
for(isocket = 0; isocket <= fdmax; isocket++) {
// Skip connections that are not available for reading
if (!FD_ISSET(isocket, &readset))
continue;
if (isocket == local_socket) {
rc = handle_new_client_connections(connection, &fdmax, &masterset);
goto next;
}
if (isocket == ssh_socket) {
rc = handle_ssh_connections(connection, &masterset);
goto next;
}
rc = handle_client_connections(connection, isocket, &masterset);
next:
// Increment "errors" counter, if we found an error,
// or zero it, if not.
errors = (rc == -2) ? errors + 1 : 0;
if (errors > 0) {
ssh_log_warn(connection, "*** COLLECTED %d AGAIN ***", errors);
}
if (rc == -1) {
ssh_log_warn(connection, "SSH tunnel shutdown because of error");
return ERROR;
}
}
}
if (errors >= maxerrors) {
ssh_log_warn(connection, "SSH tunnel shutdown because of series of successive EAGAIN errors");
return ERROR;
}
rbm_ssh_session_close(connection);
return SUCCESS;
}
void rbm_ssh_session_cleanup(rbm_ssh_session *session);
int rbm_ssh_open_tunnel(rbm_ssh_session *connection) {
const int ERROR = -1;
const int SUCCESS = 0;
int rc = 0;
while (1) {
rc = rbm_ssh_open_tunnel_ex(connection);
if (rc == 0)
break;
// Cleanup SSH connection we hope that local connection
// will not break so often
rbm_ssh_session_cleanup(connection);
ssh_log_warn(connection, "STARTING AGAIN!!!!!!!!!111");
if (rbm_ssh_setup(connection) == -1) {
rbm_ssh_session_close(connection);
return ERROR;
}
}
return SUCCESS;
}
void rbm_ssh_session_cleanup(rbm_ssh_session *session) {
if (session == NULL) {
return;
}
// Order is important:
// 1. close accept socket
// 2. free libssh2 channels (and client sockets)
// 3. free libssh2 session
// 4. close ssh socket
// 2.
ssh_log_debug(session, "Closing channels");
while (session->channelssize > 0) {
ssh_channel_close(session->channels[0]);
ssh_log_debug(session, "Channel closed");
}
// 3.
if (session->sshsession) {
ssh_log_debug(session, "Closing SSH session");
libssh2_session_disconnect(session->sshsession, "Client disconnecting normally");
libssh2_session_free(session->sshsession);
session->sshsession = NULL;
}
// 4.
if (session->sshsocket != rbm_socket_invalid) {
ssh_log_debug(session, "Closing SSH socket");
close(session->sshsocket);
session->sshsocket = rbm_socket_invalid;
}
}
void rbm_ssh_session_close(rbm_ssh_session *session) {
if (session->localsocket != rbm_socket_invalid) {
ssh_log_debug(session, "Closing local accept socket");
close(session->localsocket);
session->localsocket = rbm_socket_invalid;
}
rbm_ssh_session_cleanup(session);
ssh_log_debug(session, "SSH tunnel successfully closed.");
free(session);
}
// Returns 0 on error, valid rbm_ssh_session* if no errors
rbm_ssh_session* rbm_ssh_session_create(struct rbm_ssh_tunnel_config *config) {
rbm_ssh_session *session = (rbm_ssh_session *) malloc(sizeof(rbm_ssh_session));
if (!session) {
return NULL;
}
session->localsocket = rbm_socket_invalid;
session->sshsocket = rbm_socket_invalid;
session->sshsession = 0;
session->config = config;
session->channelssize = 0;
session->channels = NULL;
session->lasterror[0] = '\0';
// Check that loglevel is valid
if (config->loglevel != RBM_SSH_LOG_TYPE_ERROR &&
config->loglevel != RBM_SSH_LOG_TYPE_WARN &&
config->loglevel != RBM_SSH_LOG_TYPE_INFO &&
config->loglevel != RBM_SSH_LOG_TYPE_DEBUG) {
log_error("Invalid log level for SSH submodule");
return NULL;
}
return session;
}
// Returns -1 on error, 0 when otherwise
int rbm_ssh_setup(rbm_ssh_session *session) {
const int ERROR = -1;
const int SUCCESS = 0;
rbm_ssh_tunnel_config *config = session->config;
ssh_log_debug(session, "Connecting to SSH server (%s:%d)...", config->sshserverip, config->sshserverport);
session->sshsocket = socket_connect(session, config->sshserverip, config->sshserverport);
if (session->sshsocket == -1) {
session->sshsocket = rbm_socket_invalid;
return ERROR; // errors are already logged by socket_connect
}
session->sshsession = ssh_connect(session, session->sshsocket, config->authtype, config->username, config->password,
config->publickeyfile, config->privatekeyfile, config->passphrase);
if (session->sshsession == 0) {
return ERROR; // errors are already logged by ssh_connect
}
// Must use non-blocking IO hereafter due to the current libssh3 API
libssh2_session_set_blocking(session->sshsession, 0);
return SUCCESS;
}
// Returns -1 on error, 0 when otherwise
int rbm_ssh_session_setup(rbm_ssh_session *session) {
const int ERROR = -1;
const int SUCCESS = 0;
rbm_ssh_tunnel_config *config = session->config;
if (rbm_ssh_setup(session) == -1)
return ERROR;
session->localsocket = socket_listen(session, config->localip, (int *) &config->localport);
if (session->localsocket == -1) {
session->localsocket = rbm_socket_invalid;
return ERROR; // errors are already logged by socket_listen
}
ssh_log_debug(session, "Waiting for TCP connection on %s:%d...", config->localip, config->localport);
return SUCCESS;
}
+99
View File
@@ -0,0 +1,99 @@
#ifndef ROBOMONGO_SSH_H
#define ROBOMONGO_SSH_H
#include <libssh2.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
# define rbm_socket_t SOCKET
# define rbm_socket_invalid INVALID_SOCKET
#else
# define rbm_socket_t int
# define rbm_socket_invalid (-1)
#endif
enum rbm_ssh_log_type {
RBM_SSH_LOG_TYPE_ERROR = 1,
RBM_SSH_LOG_TYPE_WARN = 2,
RBM_SSH_LOG_TYPE_INFO = 3,
RBM_SSH_LOG_TYPE_DEBUG = 100 // log as much as possible
};
enum rbm_ssh_auth_type {
RBM_SSH_AUTH_TYPE_NONE = 0,
RBM_SSH_AUTH_TYPE_PASSWORD = 1,
RBM_SSH_AUTH_TYPE_PUBLICKEY = 2
};
struct rbm_ssh_session;
/*
* SSH Tunnel configuration
*/
typedef struct rbm_ssh_tunnel_config {
enum rbm_ssh_auth_type authtype;
enum rbm_ssh_log_type loglevel;
// Keys and optional passphrase
char *privatekeyfile;
char *publickeyfile;
char *passphrase; // May be NULL or ""
// Local IP and port to bind and listen to
char *localip;
unsigned int localport;
// Remote host and port to connect to
char *remotehost; // Resolved by the remote server
unsigned int remoteport;
// Username and password of remote user
char *username;
char *password; // May be NULL or ""
// Remote IP and (host, port) in remote network
char *sshserverip;
unsigned int sshserverport; // SSH port
void *context; // pointer to user-defined data
void (*logcallback)(struct rbm_ssh_session* session, char *message, int level);
} rbm_ssh_tunnel_config;
typedef struct rbm_ssh_channel {
struct rbm_ssh_session* session;
LIBSSH2_CHANNEL *channel;
rbm_socket_t socket;
char *inbuf;
char *outbuf;
int bufmaxsize;
} rbm_ssh_channel;
typedef struct rbm_ssh_session {
rbm_socket_t localsocket;
rbm_socket_t sshsocket;
LIBSSH2_SESSION *sshsession;
rbm_ssh_tunnel_config *config;
rbm_ssh_channel **channels;
int channelssize; // number of channels
char lasterror[2048];
} rbm_ssh_session;
int rbm_ssh_init();
void rbm_ssh_cleanup();
rbm_ssh_session* rbm_ssh_session_create(struct rbm_ssh_tunnel_config *config);
int rbm_ssh_session_setup(rbm_ssh_session *session);
void rbm_ssh_session_close(rbm_ssh_session *session);
int rbm_ssh_open_tunnel(struct rbm_ssh_session *connection);
#ifdef __cplusplus
}
#endif
#endif // ROBOMONGO_SSH_H

Some files were not shown because too many files have changed in this diff Show More