Compare commits
102
Commits
v0.9.0-rc3
..
ssh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
845315eeb5 | ||
|
|
803306eea5 | ||
|
|
94ef0d75d7 | ||
|
|
604d439d57 | ||
|
|
d37f8c7bab | ||
|
|
c7ab94a5a5 | ||
|
|
a6428ce968 | ||
|
|
8dd0cd3050 | ||
|
|
baede8d8bd | ||
|
|
2328ec7b31 | ||
|
|
719df7b241 | ||
|
|
dff7f0539a | ||
|
|
f03cf048d8 | ||
|
|
22ffc2ec15 | ||
|
|
09a0edb39a | ||
|
|
a0a8a7a13e | ||
|
|
8f97c06408 | ||
|
|
af9f8fc6a9 | ||
|
|
ef6feb2377 | ||
|
|
4e5ffae80c | ||
|
|
3597f438cd | ||
|
|
ad9cc86117 | ||
|
|
6656494fda | ||
|
|
06ac7dd8f1 | ||
|
|
76f4d19b42 | ||
|
|
d64a1f704b | ||
|
|
aa27148868 | ||
|
|
58c42df669 | ||
|
|
9c0761826b | ||
|
|
63f90dfeb3 | ||
|
|
10b31ef2a9 | ||
|
|
bb3fd81c4d | ||
|
|
2cd6ed2928 | ||
|
|
fde191f6aa | ||
|
|
1c76852b9c | ||
|
|
2491f417da | ||
|
|
443b8070f2 | ||
|
|
bb51c1395b | ||
|
|
3e03cf3d88 | ||
|
|
d6a6bf9719 | ||
|
|
3a2a9d1b7d | ||
|
|
dda68ff53f | ||
|
|
0ef17c6ad7 | ||
|
|
ac58d23713 | ||
|
|
5971972525 | ||
|
|
4cc6662837 | ||
|
|
226cbf48d8 | ||
|
|
7d9909c247 | ||
|
|
649c096e70 | ||
|
|
ce02d5f5c8 | ||
|
|
90c667011d | ||
|
|
0622e85e27 | ||
|
|
355f01f0a4 | ||
|
|
b0d6cf0842 | ||
|
|
16b15be81b | ||
|
|
2b7a8ca14e | ||
|
|
c338813d1c | ||
|
|
2f38d5b00d | ||
|
|
7c6738e32b | ||
|
|
86bfeba68a | ||
|
|
a94aa81c07 | ||
|
|
080c1ed865 | ||
|
|
c6f13e347c | ||
|
|
3cea361cc9 | ||
|
|
f6546fc336 | ||
|
|
8d12ab45c9 | ||
|
|
7dfd9184b7 | ||
|
|
0977bc00e3 | ||
|
|
2ea847ce0b | ||
|
|
fb2b5e0487 | ||
|
|
cce30bb55b | ||
|
|
98af4fc747 | ||
|
|
2c04edb1b3 | ||
|
|
e4500e0c5c | ||
|
|
820c4a86f0 | ||
|
|
d6253ca194 | ||
|
|
33e27917ee | ||
|
|
cea04368a9 | ||
|
|
fe9eed7322 | ||
|
|
33304e2e7a | ||
|
|
4cd51a7de6 | ||
|
|
5ffd65b1a2 | ||
|
|
82b96f4f63 | ||
|
|
e0ea15adb9 | ||
|
|
209ce0c0a9 | ||
|
|
620488c318 | ||
|
|
a270926397 | ||
|
|
8c830b6f1a | ||
|
|
33d58189c4 | ||
|
|
5d8370a970 | ||
|
|
fb3d37e641 | ||
|
|
b27f747ff2 | ||
|
|
2854783aeb | ||
|
|
4b8aa3deaf | ||
|
|
f5397b7c68 | ||
|
|
047620492c | ||
|
|
bf51f5d6c9 | ||
|
|
d7f27e16fe | ||
|
|
cfb30c69c0 | ||
|
|
8ed0335b7b | ||
|
|
313179dfbe | ||
|
|
97503f2e81 |
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
Decide the future of Robomongo
|
||||
==============
|
||||

|
||||
|
||||
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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,5 +41,7 @@ namespace Robomongo
|
||||
* @brief Error message
|
||||
*/
|
||||
const std::string _errorMessage;
|
||||
|
||||
bool _isNull;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ¤tStyle = 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Robomongo
|
||||
void togglePassphraseEchoMode();
|
||||
|
||||
private:
|
||||
SSHInfo::SupportedAuthenticationMetods selectedAuthMethod();
|
||||
// SSHInfo::SupportedAuthenticationMetods selectedAuthMethod();
|
||||
|
||||
private:
|
||||
QCheckBox *_sshSupport;
|
||||
|
||||
@@ -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 |
@@ -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) );
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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, ...);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user