Compare commits

...
273 Commits
Author SHA1 Message Date
Gokhan Simsek a7d32806f3 Changes for patch version 1.0.2 to support for ES6 (ECMAScript2015) features 2017-04-27 18:45:41 +03:00
Gökhan Simsek 18951047eb Update CHANGELOG 2017-04-27 14:47:37 +03:00
Gokhan Simsek 1124ec22c6 Merge branch 'master' into Rebranding 2017-04-26 11:01:25 +03:00
Gokhan Simsek ab367fc890 Change to fix a merge error 2017-04-26 11:01:04 +03:00
Gokhan Simsek 22c632794f Merge branch 'master' of https://github.com/paralect/robomongo 2017-04-26 10:40:39 +03:00
Gökhan Simsek 0fefd6d443 Update README.md
For version 1.0
2017-04-21 17:15:29 +03:00
Gokhan Simsek 89f24ea07c Updating changelog for 1.0 release 2017-04-21 09:04:07 +03:00
Gokhan Simsek d2a11bc0f2 UpdatesBar: Making the height of bar %30 bigger for MAC 2017-04-20 15:08:21 +03:00
Gokhan Simsek 575455b10e Removing the changes done in the previous commit "e858c68: Setting unified light blue color for all links in the application" 2017-04-20 12:45:58 +03:00
Gokhan Simsek e858c68fa6 Setting unified light blue color for all links in the application 2017-04-20 10:55:49 +03:00
Gokhan Simsek 2df26740c6 WelcomeTab: Changing background color from grey to base (white) 2017-04-20 10:52:15 +03:00
Gokhan Simsek 39209ee2b8 WelcomeTab: Making left (What's New) section's header dynamically read from remote server. 2017-04-20 10:50:31 +03:00
Gokhan Simsek 3e9cd11aa3 WelcomeTab: Fix to solve low resolution image problem 2017-04-12 09:43:54 +03:00
Gokhan Simsek f91b2c4a2f UpdatesBar: Adding fix for the problem where some menu items were still shown when focus changes to welcome tab from other shell tabs. Updating softwareID to 8 to receive Robomongo related updates. 2017-04-11 16:02:18 +03:00
Gökhan Simsek a2b1a07a4f Update RuntimeDependencies.md
Adding more debugging commands and options for Linux
2017-04-05 11:25:50 +03:00
Gokhan Simsek 82b5f434f9 Adding fix to solve problem where program was failing to start due to relocation errors during runtime shared library search on Linux 2017-04-05 09:33:46 +03:00
Gokhan Simsek a55b7398da EulaDialog: Adding first launch size which is almost the half of primary screen width and height 2017-03-30 12:54:44 +03:00
Gokhan Simsek d9b37b2094 Rebranding from RoboM to Robomongo 2017-03-30 11:26:50 +03:00
Gokhan Simsek 230c5f37bc ConnectionBasicTab: Adding fix for the cosmetic issue when creating new replica set connection, plus and minus buttons alignment issue for MAC and Linux. 2017-03-29 16:08:18 +03:00
Gokhan Simsek 561bdbf2b4 Changing shortcut for "Open/Refresh Welcome Tab" to "CTRL+SHIFT+W". Disabling File, Open, Save, Save As menu items when there is only Welcome Tab. 2017-03-27 09:29:10 +03:00
Gokhan Simsek 0bafec7ce5 To fix the vendor string on Windows programs list 2017-03-27 09:23:28 +03:00
Gokhan Simsek 34dca72a09 Fix: #501: Display Dates in - Local Timezone shows wrong date 2017-03-27 09:22:36 +03:00
Gokhan Simsek 428b576acf Fix: #1309: Time zone shown as "000:00" when UTC +00:00 is used as local time zone 2017-03-23 14:26:06 +03:00
Gokhan Simsek f26a08d9bc Fix: #1159: Timestamp value is represented incorrectly 2017-03-23 14:17:39 +03:00
Gokhan Simsek 92ae9affd6 ReplicaSets: #1302: Some enhancements and more informational error messages for the issue where same set name is used with different members. 2017-03-22 12:06:30 +03:00
Gokhan Simsek d52cd2e0c0 ReplicaSets: Adding "Set Name" option into connection basic tab UI and "setNameUserEntered" parameter into config file. 2017-03-21 18:41:32 +03:00
Gokhan Simsek e11cbd1a26 ReplicaSets: Renaming config file parameter "setName" as "cachedSetName" 2017-03-21 14:53:00 +03:00
Gokhan Simsek 4e58a6a6b4 EulaDialog: Re-enabling first license page for windows, disabling second email form page for linux 2017-03-21 09:44:32 +03:00
Gokhan Simsek e01de676fa ReplicaSets: #1302 - To avoid the problem when same set name is used with different members, which is not supported configuration, Robomongo might connect to wrong set. 2017-03-20 09:35:25 +03:00
Gokhan Simsek 9e43802e2a WelcomeTab: Using http for file downloads on Linux due to crashes and unstable behaviors 2017-03-17 10:09:12 +03:00
Gokhan Simsek f02f4f6116 Re-enabling first start connections dialog which was disabled for development purposes 2017-03-16 19:03:39 +03:00
Gokhan Simsek 306689f30d EulaDialog: Removing email form page for Linux 2017-03-16 18:39:43 +03:00
Gökhan Simsek 43bf28968b Update BuildingRobomongo.md 2017-03-15 13:13:13 +03:00
Gökhan Simsek df6e9a57d8 Updating Centos build steps 2017-03-13 11:51:31 +03:00
Gokhan Simsek cbcafd28c9 Minor re-factoring 2017-03-10 14:57:55 +03:00
Gokhan Simsek 4f181ec460 UpdatesBar: Adding functionality to auto-adjust height for updates label to fix issues with set word wrap problems. EulaDialog: Adding one space before main body text. 2017-03-10 11:39:27 +03:00
Gokhan Simsek a72583df76 Code Review: All changes between 1.0.0 (this commit) and 1.0.0-RC1 (commit 496f5c2, 1 Feb 2017). No behavioral changes. 2017-03-09 18:27:45 +03:00
Gokhan Simsek 8f508e5e0f Removing connection setting's program-lifetime uniqueID solution which was replaced with persistent (config. file) UUID solution 2017-03-06 19:49:38 +03:00
Gokhan Simsek 536f75fdb4 WelcomeTab: Making 'all logs button' fully left aligned for MAC 2017-03-06 16:00:13 +03:00
Gokhan Simsek f37a070eda WelcomeTab: Showing blog section widgets after loading what's new section widgets. 2017-03-06 12:47:41 +03:00
Gokhan Simsek 51c1267bf4 WelcomeTab: Disabling Recent Connections feature on UI, settings file and code. 2017-03-04 16:48:28 +03:00
Gokhan Simsek 6a0b972aac WelcomeTab: Adding 'Connect' tool button plus the clear all action instead of clear all button 2017-03-04 15:05:07 +03:00
Gokhan Simsek 94168f02ca Rebranding: Renaming config folder to robom and config file to robom.json 2017-03-03 19:51:48 +03:00
Gokhan Simsek 88c8dfa26e WelcomeTab: Fixing major problem which was causing cache files not to be saved 2017-03-03 19:49:58 +03:00
Gokhan Simsek c8050f0edd Rebranding: Major renaming to RoboM and 3T Software Labs Ltd 2017-03-03 19:04:16 +03:00
Gokhan Simsek e3d90e63e8 CallHome: Adding "os" parameter to send operating system information 2017-03-03 15:19:14 +03:00
Gokhan Simsek ec20a3e4c9 EulaDialog: Adding code for posting email, name and last name. Removing two checkboxes. 2017-03-03 15:01:34 +03:00
Gokhan Simsek 83bac6b8aa EulaDialog: Not showing first page(license) which is already shown in installer and hiding some buttons on second page on Windows.
Updates Bar: Not showing the bar if the string sent from server is empty.
2017-03-01 15:59:35 +03:00
Gokhan Simsek 34025743a1 Some refactoring for 1.0.0 changes, no functional changes. 2017-03-01 11:26:37 +03:00
Gokhan Simsek ff02a303c7 SettingsManager: Change of config. file path due to re-branding to 3T. 2017-02-28 14:10:53 +03:00
Gokhan Simsek 4552576fa3 Minor change: Fixing MAC build error. Disabling debug code. 2017-02-28 11:33:25 +03:00
Gokhan Simsek cc2c834359 WelcomeTab: Adding dynamic resizing functionality for text, image and blogs sections which are shown 60, 50 and 28 percent of tab respectively. Resizing happens any time tab or main window size is changed by user. 2017-02-28 10:35:45 +03:00
Gokhan Simsek b746aeb729 WelcomeTab: Adding code to show main text and image 50-60 percent and blogs section 30 percent of whole tab width which is calculated and adjusted every time the tab is opened or refreshed. 2017-02-27 20:16:20 +03:00
Gokhan Simsek 686fc08cef Not showing main window updates bar if there is network error. Disabling welcome tab's right click menu. 2017-02-27 15:18:16 +03:00
Gokhan Simsek 9d74aae3f5 WelcomeTab: Polishing left layout when there is no image or/and text is available (no cache, no internet). Not showing "What's New" header when there is no image and no text. 2017-02-27 11:14:51 +03:00
Gokhan Simsek b3fd99eb1b WelcomeTab: Fixing cropped image and text layout problems. Refactoring picture download handler. 2017-02-25 15:43:07 +03:00
Gokhan Simsek bf111c97f1 AboutDialog: Fixing broken robomongo link 2017-02-25 15:40:13 +03:00
Gokhan Simsek ac8d4cf899 Disabling welcome tab's icon for MAC. Adding initial size for setup wizard. 2017-02-24 16:41:33 +03:00
Gokhan Simsek 04bd7fbffa WelcomeTab: Adding a better adjusted icon. 2017-02-24 12:18:48 +03:00
Gokhan Simsek 8167e8d944 Adding functionality import connections to version 1.0.0 2017-02-24 11:49:57 +03:00
Gokhan Simsek 6c3174599a WelcomeTab: Adding functionality when 'Open/Refresh Welcome Tab' menu item is clicked, show/focus and reload welcome tab if tab is not closed; if tab is closed, insert it as first tab. 2017-02-24 11:28:23 +03:00
Gokhan Simsek 76aca637e8 Adding config file directory automatically created with PROJECT_VERSION.
WelcomeTab: Adding load/save to cache for RSS feed XML.
2017-02-24 09:05:39 +03:00
Gokhan Simsek 832d843ea1 WelcomeTab: To achieve much faster loading, adding cache mechanism for what's new section's main picture and text 2017-02-23 11:50:06 +03:00
Gokhan Simsek 9aa702efcd WelcomeTab: Adding underline on mouse hover functionality for blog links 2017-02-22 12:20:04 +03:00
Gokhan Simsek cde6d9eaae Doing unzipping/reading file content in-memory instead of extracting/reading from file. Fixing build error related to qzipreader.h for MAC/Linux. 2017-02-22 10:41:01 +03:00
Gokhan Simsek 91db033e3a WelcomeTab: Adding menu action 'Window > Open Welcome Tab' to be able to re-open/refresh welcome tab when closed. 2017-02-21 16:51:51 +03:00
Gokhan Simsek 7d6c0af7e7 WelcomeTab: Adding functionality to be able to download/load tab's main text, picture and blogs info rss from network. Changing maximum shown blog count to 10. 2017-02-21 16:29:23 +03:00
Gokhan Simsek d0c2345044 CallHome: Adding functionality to unzip 3T zip files (more specifically *.dat files) 2017-02-21 15:27:33 +03:00
Gokhan Simsek 51c4b93bc7 Adding one space at the beginning of updates bar's text. Making updates bar non-movable. Minor white space change in about dialog text. 2017-02-20 12:44:15 +03:00
Gokhan Simsek 242acbcd9d CallHome: Adding "checkForUpdates" flag into config file. Adding related "Check For Udates" menu action into main window "Options" menu. Not checking updates if the related flag is false. 2017-02-19 01:53:36 +03:00
Gokhan Simsek 3079801e0f CallHome: Adding code for first update check (30secs after program start) and hourly periodic update checks. 2017-02-19 01:24:11 +03:00
Gokhan Simsek fdc02400b9 CallHome: Adding updates bar into main window 2017-02-19 00:52:38 +03:00
Gokhan Simsek 499b0da006 CallHome: Adding functionality to find & save AnonymousID from the settings files inside .3T folder 2017-02-18 19:01:18 +03:00
Gokhan Simsek a7caf02ec6 Adding Qt XML module into the project 2017-02-18 18:57:03 +03:00
Gokhan Simsek 70b819700d CallHome: Renaming uuid to anonymousID in config file 2017-02-18 14:24:56 +03:00
Gokhan Simsek b1aca90888 SetupWizard: Adding acceptedEulaVersions array of strings into the config file. and the related functionality. 2017-02-17 18:34:08 +03:00
Gokhan Simsek 73ccc5d3ad SetupWizard: Converting eula dialog into a two page-setup wizard. Minor changes on about dialog. 2017-02-17 10:56:40 +03:00
Gokhan Simsek 47d3fd7db9 AboutDialog: Adding GNU license agreement tab. Updating names according to rebranding. 2017-02-15 17:33:30 +03:00
Gokhan Simsek 56d2d7cb12 Rebranding: Changing executable name to "studio-3t-robo" for Windows and Linux. Updating bin/run for Linux. 2017-02-15 11:55:22 +03:00
Gokhan Simsek ac3c078141 Rebranding: Name changes for MAC build effecting run, install and packing. 2017-02-15 09:54:09 +03:00
Gokhan Simsek 0110a0df8d Rebranding: Renaming from "Studio 3T Robo" and "3T Software Labs" in UI and cmake files. 2017-02-14 16:56:40 +03:00
Gokhan Simsek c1d6b51075 WelcomeTab: Minor change to fix a build error for MAC/Linux. 2017-02-14 14:02:58 +03:00
Gokhan Simsek bc6c6a36da WelcomeTab: Adding left and right vertical layouts. Moving blog posts section to the right side of the page. 2017-02-14 10:45:04 +03:00
Gokhan Simsek 806d76d9a2 WelcomeTab: For much faster image loading, adding capability to save/use cached image files instead of downloading from internet. 2017-02-13 20:16:55 +03:00
Gokhan Simsek a0a476b57d Minor change to fix MAC/Linux build errors. 2017-02-13 19:00:22 +03:00
Gokhan Simsek d5dd24fa51 WelcomeTab: Adding functionality to be able to delete recent connections one by one. 2017-02-12 19:41:08 +03:00
Gokhan Simsek 42561be37e WelcomeTab: Adding underline on mouse hover feature for recent connection items only. 2017-02-09 20:09:55 +03:00
Gokhan Simsek c2fdee9cf8 WelcomeTab: Adding functionality where removing a connection from connections list dialog will remove related recent connection label and related entry from config. file. 2017-02-09 14:02:06 +03:00
Gokhan Simsek 9972ee4113 Improving connection setting's program-lifetime uniqueID by replacing with persistent (config. file) UUID solution used in WelcomeTab and replica set cached set name feature. 2017-02-09 12:45:30 +03:00
Gokhan Simsek 846b0ce9fd WelcomeTab: Adding functionality to load/save recent connections from/into config. file. 2017-02-08 19:57:43 +03:00
Gokhan Simsek fe78fe4416 WelcomeTab: Adding first working version of Recent Connections feature without saving into config. file. 2017-02-08 12:33:54 +03:00
Gokhan Simsek 54d03ae788 WelcomeTab: Adding and showing an image downloaded from an internet URL. 2017-02-06 20:59:55 +03:00
Gokhan Simsek 55f895a7f5 Adding Qt Network module into cmake project to use in Welcome Tab. 2017-02-06 20:55:32 +03:00
Gokhan Simsek 22b557605b WelcomeTab: Adding first basic working version with static data and with 'What's New' and 'Blog Posts' sections 2017-02-06 12:25:45 +03:00
Gokhan Simsek 16ed6e844a EulaDialog: Adding UUID generation, eulaAccepted flag & UUID string into config file and related functionality. 2017-02-04 14:29:05 +03:00
Gokhan Simsek d96b3de97a Updating version to 1.0.0 and main window title to 1.0 2017-02-04 10:30:20 +03:00
Gokhan Simsek bd0436ffa0 EulaDialog: Adding first basic working version 2017-02-04 10:21:16 +03:00
Gokhan Simsek 496f5c256f Minor update for change log. 2017-02-01 16:43:51 +03:00
Gökhan Simsek 6617d07e1e Update README.md 2017-02-01 16:42:20 +03:00
Gökhan Simsek 99837f0b8f Update README.md 2017-02-01 16:39:53 +03:00
Gokhan Simsek 6ed27b7b83 Updating change log for 1.0-RC1 2017-02-01 15:24:05 +03:00
Gokhan Simsek 3247fd77d6 Replica Set: To fix the problem where shell tab does not update primary address when checked after 10 minutes primary changed. 2017-02-01 13:21:29 +03:00
Gokhan Simsek f9b9679b5e ReplicaSet: Minor text adjustment for test connection (diagnostics) dialog 2017-01-30 11:56:32 +03:00
Gokhan Simsek 685d264ab7 Updating icon for 'Open Shell' menu item for MAC due to alignment problems. 2017-01-26 18:03:18 +03:00
Gokhan Simsek 2355f0cb54 Updating main window title into short version "Robomongo 1.0-RC1" 2017-01-26 11:28:29 +03:00
Gokhan Simsek 68674fb1c2 ReplicaSet: Updating member add/remove buttons to support MAC style, making them absolute right aligned, decreasing vertical spacing between columns for better look. 2017-01-25 13:25:25 +03:00
Gokhan Simsek 0e1a942ecd Removing one improvement because it breaks 'show log' functionality. 2017-01-24 11:28:34 +03:00
Gokhan Simsek d01ce11e47 ReplicaSet: Clearing cached set name when a connections settings is changed & saved. Adding set's first member's hostname & port info into 'test' connection dialog. 2017-01-24 09:13:25 +03:00
Gokhan Simsek eee9e1cbd4 ReplicaSet: Always expand replica set folder, if an operation failed due unreachable primary. 2017-01-22 18:43:53 +03:00
Gokhan Simsek 54e2da3dc4 ReplicaSet: Adding additional check to detect & give error if any of the replica set member items does not contain ":" character between hostname and port after user clicks save on Connection Settings dialog. 2017-01-20 19:06:10 +03:00
Gokhan Simsek 6887850eae ReplicaSet: For cloned connections from connections dialog, cached set name should be set to empty string. 2017-01-20 18:50:35 +03:00
Gokhan Simsek ab16885112 ReplicaSet: Set replica set folder not-expanded after first connection, not-expanded after connection refresh, expanded after replica set folder user-manual-extending and after replica set folder context menu 'refresh' action. 2017-01-20 18:33:48 +03:00
Gokhan Simsek 5f3ff06403 Code Review: All changes between 1.0.0-RC1 (this commit) and 0.9.0 Final (commit 0786489, 15 october). No behavioral changes. 2017-01-20 16:35:06 +03:00
Gokhan Simsek 81f872fdaf Disabling unfinished export URI connection string feature 2017-01-17 18:38:55 +03:00
Gokhan Simsek 0ec739a2b7 Fix for the problem where all delete/remove document related operations were causing all related/unrelated tabs to re-run. 2017-01-17 18:09:11 +03:00
Gokhan Simsek 752bc51452 Implementing more maintainable solution to handle import using const static list of config file & import function pairs. 2017-01-17 17:37:29 +03:00
Gokhan Simsek 3a23c62070 Changing import behavior to find & import only from the latest version instead of importing from all old versions one by one. 2017-01-17 17:30:50 +03:00
Gokhan Simsek 4512b4c998 ReplicaSet: Adding more handlers for replica set item edit. Auto-remove white space; auto-delete item from widget if item text is empty; auto-remove non-numeric characters from port sub string; if there is no numeric character in port string, auto-add 27017 as default port number. 2017-01-17 10:39:26 +03:00
Gokhan Simsek 684d2abcdc ReplicaSet: Adding duplicate member checker & error message and adding plus and minus icons for replica set member add/remove. Adding 'Attributes' header into connections list dialog. Removing find set button and read preference from connection basic tab. 2017-01-16 16:16:39 +03:00
Gokhan Simsek db3eb8872c Updating Robomongo version to 1.0.0-RC1. Adding plus and minus sign icons for replica set member add/remove. 2017-01-16 15:29:29 +03:00
Gokhan Simsek 885f388201 Minor changes to clear MAC build warnings. 2017-01-12 11:17:53 +03:00
Gokhan Simsek 9431f7f2a8 ReplicaSet: Minor changes to fix some build errors for Linux. 2017-01-11 10:52:37 +03:00
Gokhan Simsek f7752fd2b7 ReplicaSet: Handle long lasting (e.g. overnight) connections which causes replica set monitor to be null. 2017-01-10 11:53:49 +03:00
Gokhan Simsek e4b2c2262e ReplicaSet: Minor change to fix a build error on MAC and Linux. 2017-01-06 23:16:51 +03:00
Gokhan Simsek 9a31b6259d Improving 'remove document' related operations triggered both from explorer and from result widget: Implementing ONE, MULTI and ALL logic, single error window and better logging. Improving Insert Document operation. 2017-01-05 16:38:08 +03:00
Gokhan Simsek 995abe9401 ReplicaSet: Improving re-run of an existing tab for set primary changes. In some cases, need to find the error manually and return error. 2017-01-05 10:10:49 +03:00
Gokhan Simsek a13b39907b ReplicaSet: Improving handler for 'Remove All Documents' and 'Remove Document' for single server and replica sets. Improved logging for fail and success cases. 2017-01-05 10:08:06 +03:00
Gökhan Simsek c64fec22ee Update BuildingRobomongo.md 2017-01-04 10:40:59 +03:00
Gokhan Simsek dd1d835dfe ReplicaSet refactoring on various modules. No behavioral changes. 2017-01-03 12:25:25 +03:00
Gokhan Simsek 5a78becc6f ReplicaSet refactoring: Restructuring mainly MongoWorker and some other modules. No behavioral changes. 2017-01-02 21:33:46 +03:00
Gokhan Simsek f598e62d1c ReplicaSet refactoring: Restructuring MongoServer for better readability. Adding new modules 'string_operations' and 'common' as utility libraries. 2017-01-02 13:16:06 +03:00
Gokhan Simsek b8ae4d0fc5 ReplicaSet: Implementing cached set name feature for 2 times faster first connections. 2017-01-02 10:35:10 +03:00
Gokhan Simsek 5403c16f48 ReplicaSet: Fix for an issue related to saving empty replica set member list. Fix for the issue where connections list was still showing 'Replica Set' after changing connection type from replica set to single server. 2016-12-30 11:13:13 +03:00
Gokhan Simsek 3d162032f4 ReplicaSet: Make replica set member addition smarter than expected by auto incrementing the port and using the last entered hostname by user. 2016-12-30 10:47:09 +03:00
Gokhan Simsek c591a7a91e ReplicaSet: Adding 'keep alive' functionality for replica set connections. 2016-12-29 11:44:34 +03:00
Gokhan Simsek fd2c476ae5 Disabling new create connection dialog, reverting back to original. 2016-12-28 11:48:43 +03:00
Gokhan Simsek 42bf58a0b5 ReplicaSet: Improvement for faster connection refresh by disabling unnecessary replica set refresh. Disabling settings save in MongoServer. Decreasing replica set refresh time to 2 seconds. 2016-12-23 11:19:00 +03:00
Gokhan Simsek 4d09e1b5a5 Fix for the problem where connections list comes empty if config file or it's directory do not exist (connections were imported only after second program opening) 2016-12-21 15:09:16 +03:00
Gokhan Simsek d825b065f2 ReplicaSet: Fix for the problem where primary unreachable case for one replica set connection was causing other unrelated replica set connections to go offline. 2016-12-21 14:25:51 +03:00
Gokhan Simsek 13428b5ed6 ReplicaSet: Removing unused context menu item "Refresh" for replica set member items on explorer widget. 2016-12-21 14:23:59 +03:00
Gokhan Simsek 521d1789bc Improved logging for connection establish & refresh server fail and success cases. 2016-12-21 10:20:46 +03:00
Gokhan Simsek f64257ae86 Improving handler for 'Create Database' for single server and replica sets.
Moving 'refresh databases' from event request to event response.
Improved logging for fail and success cases.
2016-12-19 20:53:38 +03:00
Gokhan Simsek d228ca1c4b Minor refactoring due to failing MAC build. 2016-12-19 19:33:05 +03:00
Gokhan Simsek 690f21e9ae Improving handler for 'Drop Database' for single server and replica sets. For replica set connections, it triggers replica set refresh on failure.
Moving 'refresh databases' from event request to event response. Improved logging for fail and success cases.
2016-12-19 18:23:14 +03:00
Gokhan Simsek 743710a9f0 Improving handler for 'Refresh Users' for single server and replica sets. For replica set connections, it triggers replica set refresh on failure.
Improved logging for fail and success cases.
2016-12-19 11:46:09 +03:00
Gokhan Simsek ba29971e20 Improving handler for 'Refresh Functions' for single server and replica sets. For replica set connections, it triggers replica set refresh on failure.
Improved logging for fail and success cases.
2016-12-19 11:37:17 +03:00
Gokhan Simsek f49e492a92 Improving handler for 'Refresh Collections' for single server and replica sets. For replica set connections, it triggers replica set refresh on failure.
Improved logging for fail and success cases.
2016-12-19 11:27:05 +03:00
Gokhan Simsek 1c62abaec0 Adding response handler for 'Drop User' for single server and replica sets. For replica set connections, it triggers replica set refresh on failure.
Moving 'refresh users' from event request to event response. Improved logging for fail and success cases.
2016-12-19 11:01:32 +03:00
Gokhan Simsek 1940a03a61 Improving handler for 'Create User' and 'Edit User' operations for single server and replica sets. For replica set connections, it triggers replica set refresh on failure.
Moving 'refresh users' from event request to event response. Improved logging for fail and success cases.
2016-12-19 09:59:18 +03:00
Gokhan Simsek 9bee2f443f C++11 transition, automatic clean up for possible memory leak. 2016-12-16 10:10:24 +03:00
Gokhan Simsek 1cf331f83c Fix for SSH connection issue. 2016-12-16 09:59:27 +03:00
Gokhan Simsek 685b835886 Adding response handler for 'Drop Function' for single server and replica sets. For replica set connections, it triggers replica set refresh on failure.
Moving 'refresh functions' from event request to event response. Improved logging for fail and success cases.
2016-12-15 17:48:32 +03:00
Gokhan Simsek ab2494a6ba Adding response handler for 'Create Function' and 'Update Function' operations for single server and replica sets. For replica set connections, it triggers replica set refresh on failure.
Moving 'refresh functions' from event request to event response. Improved logging for fail and success cases.
2016-12-15 17:21:11 +03:00
Gokhan Simsek 3ae0d61bd5 Improving 'Create Collection' for single server and replica sets. For replica set connections, it triggers replica set refresh on failure.
Handling create attempt of already-existing collection names.
Moving 'refresh collections' from event request to event response.
Improved logging for fail and success cases.
2016-12-14 11:53:53 +03:00
Gokhan Simsek f4e11b623f Improving response handler for 'DropCollection' which for replica set connections triggers replica set refresh on failure. Adding code to handle drop attempt of non-existing collections.
Moving 'refresh collections' from event request to event response.
Improved logging for fail and success cases.
2016-12-11 19:54:40 +03:00
Gokhan Simsek 938479e43f DuplicateCollection: Adding code to handle and show error for the case where duplicating a collection with a collection name which already exists finishes silently. 2016-12-11 18:16:26 +03:00
Gokhan Simsek 31ec650659 Implementing response handler for 'Duplicate Collection' which works for single server connections expectedly and for replica set connections triggers replica set refresh on failure.
Moving 'refresh collections' from event request to event response.
Improved logging for fail and success cases.
2016-12-11 18:09:15 +03:00
Gokhan Simsek 87c13ff74a Implementing global response handler for 'Rename Collection' which works for single server connections expectedly and for replica set connections triggers replica set refresh on failure.
Moving 'refresh collections' from event request to event response.
Improved logging for fail and success cases.
2016-12-11 15:50:48 +03:00
Gokhan Simsek 2c820daae4 #1256: Fix for in-progress widget not centered for "Update Documents/Remove Documents" context menu actions 2016-12-09 12:39:25 +03:00
Gokhan Simsek 3b0cdc0caa #1236: Fix for the two global problems where insert or update documents in one tab was causing auto reloading of other tabs and also multiple error windows to popup for each tab opened from same root connection.
ReplicaSets: Adding better handling for insert document failures for both originated from explorer collection context menu and from output document results context menu.
2016-12-08 20:15:23 +03:00
Gokhan Simsek 85570dd61d Adding adjusted log level prefix strings into logger. 2016-12-06 14:59:27 +03:00
Gokhan Simsek 400d3670be ReplicaSets: Adding refresh replica set on error feature for insert document operation. 2016-12-06 14:55:52 +03:00
Gokhan Simsek 45c93a7f3e ReplicaSets: Fixing an error and improving connection between tabs and explorerServerItem's mongo server in order to receive ReplicaSetRefreshed events correctly. 2016-12-06 10:26:20 +03:00
Gokhan Simsek 6ca3f492fe Adding code to handle global problem where re-run of existing shell tab with reachable server fails due to null mongo scope which is result when new shell opened before when server is unreachable. 2016-12-04 21:43:46 +03:00
Gokhan Simsek dee3768d34 ReplicaSets: Adding automatic replica set refresh for failed script queries when set's primary unreachable cases. 2016-12-04 16:57:33 +03:00
Gökhan Simsek d2570f9c44 Update README.md 2016-12-03 12:49:57 +03:00
Gokhan Simsek a630fa9f98 ReplicaSets: Reverting ExplorerServerTreeItem expand() for single server back to its original version. Fixing small error related to [SSL] [SSH] texts. 2016-11-30 00:20:41 +03:00
Gokhan Simsek 875d4248f5 ReplicaSets: Better error messages for failed connections and failed refresh events for SSL & replica sets and single server cases. Fixing a problem related to replica set refresh folder event. 2016-11-29 21:34:49 +03:00
Gokhan Simsek 9523ff2472 ReplicaSets: Adding refresh status of set on expand feature for explorer feature replica set folder 2016-11-28 09:39:35 +03:00
Gokhan Simsek cf92bda8db ReplicaSets: Fixing a potential problem by setting cached set name to empty when connection failed to all nodes. 2016-11-27 01:55:55 +03:00
Gokhan Simsek c73b5ed7dc ReplicaSets: Changes to achieve 3 times faster first connection to replica set. 2016-11-27 01:24:12 +03:00
Gokhan Simsek ceb32f63ad ReplicaSets: Adding new feature to be able to check replica set status even when primary is unreachable and there is at least one reachable secondary. Disabling some context menu items for ServerTreeItem and ReplicaSetFolderItem for the no primary case and all nodes unreachable case. 2016-11-25 15:07:15 +03:00
Gokhan Simsek 4065193dd8 ReplicaSets: Implementing slightly different design for "create collection" for replica set connections. Adding an using new event ReplicaSetFolderLoading. 2016-11-23 13:53:37 +03:00
Gokhan Simsek b4d7ae2d3e Renaming MongoServer::_client to _worker. 2016-11-21 20:30:13 +03:00
Gokhan Simsek 140dce8dd2 ReplicaSets: Fixing issue related to create database and drop database actions. 2016-11-21 16:34:26 +03:00
Gokhan Simsek e80fd77299 ReplicaSets: Major changes, re-implementing connection to replica set, refresh server for replica set and refresh for replica set folder. 2016-11-21 15:56:29 +03:00
Gokhan Simsek 7e552a4db0 ReplicaSets: Updating replica set member icons with transparent background. 2016-11-18 23:14:24 +03:00
Gokhan Simsek 7d28eec8d0 ReplicaSets: Updating replica set icon with transparent background. Adding replica set offline icon. 2016-11-18 23:09:10 +03:00
Gokhan Simsek c29f8bf2f4 Adding save/restore window size feature for Edit, View and Insert Document dialogs 2016-11-17 19:27:56 +03:00
Gokhan Simsek f76433729c Correcting typo for namespace HighDpiConstants 2016-11-16 23:55:15 +03:00
Gokhan Simsek db630fc549 Updating Robomongo version to 1.0.0 2016-11-15 22:14:52 +03:00
Gokhan Simsek 4eb13e84cc Adding first working import connections from 0.9 to 1.0 and from 0.8.5 to 1.0. Updating default config path to /.config/robomongo/1.0/ 2016-11-14 22:16:23 +03:00
Gokhan Simsek 16b8c8efea ConnectionString: Adding generate button, include passwords check box and related functionalities. Adding tooltip popup for copy to clipboard button. 2016-11-13 02:30:51 +03:00
Gokhan Simsek 9b2c72e4a2 ReplicaSets: Adding first working version of export URI connection string feature into ConnectionSettings advanced tab. Refactoring ConnectionSettings info() to hostAndPort(). 2016-11-11 21:53:31 +03:00
Gokhan Simsek b3b9c52144 Temporarily disabling export/import feature 2016-11-10 13:05:01 +03:00
Gokhan Simsek 4bc319e0bb Minor refactorings 2016-11-10 12:45:06 +03:00
Gökhan Simsek 50071a2980 Moving gitter badge next to "About Robomongo" text 2016-11-10 11:15:48 +03:00
Gökhan Simsek 2036ce4ba1 Merge pull request #1207 from revolter/patch-1
Move Gitter badge to the top
2016-11-10 10:14:28 +02:00
Gokhan Simsek f2624fea03 ReplicaSets: Better handling of server item's refresh and replica set folder item's refresh for set's unreachable cases. Adding code to show more correct error string for replica set connections. Updating script engine's dbname dynamically. 2016-11-09 14:56:45 +03:00
Gokhan Simsek 950bc3daaa ReplicaSets: Handling of cases where there is no reachable primary with reachable secondaries or no reachable nodes at all. 2016-11-07 02:18:13 +03:00
Gokhan Simsek 39b6779a8c ReplicaSets: Better and faster handling of set status change for "Replica Set" folder item. Adding new RefreshReplicaSet* event group. Adding new function MongoSever::tryRefreshReplicaSet(). 2016-11-06 14:51:30 +03:00
Gokhan Simsek 743486d88b ReplicaSets: Adding functionality to dynamically update script engine with correct primary node to be able to re-run an existing open tab even when primary changed. Adding ReplicaSet struct as an info class. 2016-11-04 19:21:19 +03:00
Gokhan Simsek 50b0e90495 ReplicaSets: Moving replica set primary update for script engine inside replica set if condition. 2016-11-02 18:38:31 +03:00
Gokhan Simsek a8b049686a Using ConnectionSettings pointer itself instead of deep copy in ConnectionsDialog and some const refactoring. 2016-11-02 18:36:57 +03:00
Gokhan Simsek 23f133dbbe ReplicaSets: Adding functionality to auto-find and auto-configure replica set (and settings) for the case at least one reachable set member is entered by user. 2016-10-31 13:24:02 +03:00
Gokhan Simsek b033375491 ReplicaSets: In order to be able to save some settings changes (i,e, replica set name) after an established connection, passing the original connection settings object to MongoServer instead of the clone. 2016-10-30 23:38:41 +02:00
Gokhan Simsek ca4060473d ReplicaSets: Updating script engine to work with replica sets. Adding remove set item button functionality. 2016-10-30 23:26:44 +02:00
Gokhan Simsek 472b6a8c46 ReplicaSets: Adding replica set info into ConnectionDiagnosticDialog and main window log widget. 2016-10-29 18:17:17 +03:00
Gokhan Simsek 0947bac63c ReplicaSets: Adding setName into ReplicaSetSettings. If set name comes from settings (URI), using it instead of trying to get set name with connecting to nodes. Adding "Topology" column into ConnectionsDialog. 2016-10-29 01:47:03 +03:00
Gokhan Simsek 1a414a2723 ReplicaSets: Adding server icons to represent primary & secondary replica nodes (draft versions) 2016-10-28 18:35:12 +03:00
Gokhan Simsek d7bbf37cbb ReplicaSets: Adding code to extract SSL and credentials information from mongo URI. 2016-10-28 18:21:28 +03:00
Gokhan Simsek 6063ff90eb ReplicaSets: Adding code to obtain replica set name needed by constructor of mongo::DBClientReplicaSet 2016-10-28 16:33:01 +03:00
Gokhan Simsek 20dd2eb564 ReplicaSets: Change to fix build error on MAC 2016-10-27 12:29:59 +03:00
Gokhan Simsek 4a80853be6 ReplicaSets: Adding link from CreateConnectionDialog "Create connection manually" label to ConnectionsDialog addManually() function 2016-10-24 01:28:39 +03:00
Gokhan Simsek b6cd30e22d Adding "Security" header column into ConnectionsDialog 2016-10-24 00:15:12 +03:00
Gokhan Simsek ea59ca59d3 ReplicaSets: Adding new dialog "create connection"; Adding parsing ability from MongoURI to ConnectionSettings; Adding ability to create new connection from mongo connection string URI. 2016-10-23 14:42:24 +03:00
Gokhan Simsek c67d8a942b ReplicaSets: If replica set item widget is down, disabling iteim, it's context menu and double click event; Adding replica set name into replicaSetFolderItem. 2016-10-20 10:33:29 +03:00
Gokhan Simsek 244d94b17d Export_Import_Temp: Removing merge conflicts 2016-10-14 01:02:13 +03:00
Gokhan Simsek b05b348198 Merge branch 'master' into Export_Import_Temp 2016-10-14 00:50:43 +03:00
Gokhan Simsek 38a8595d15 ReplicaSets: Showing grey server icon when a replica member is offline 2016-10-14 00:47:24 +03:00
Gokhan Simsek f2226c92b3 Export_Import: Cancelling some customizations done for the design attempt of export dialog with custom explorer tree items 2016-10-13 22:58:25 +03:00
Gokhan Simsek 466709df23 Export_Import: Cancelling tree widget structure due to unnecessary complexity created; Enabling export button only a collection is selected; Adding "show details" button and feature. 2016-10-13 12:01:17 +03:00
Gokhan Simsek 702459918b ReplicaSet: Adding functionality refresh replica set view via "Refresh" context menu items both on Server and Replica Set Folder explorer tree items. Adding ExplorerReplicaSetFolderItem as a new specialized class. Adding new connection type "ConnectionRefresh" and function MongoServer::tryRefresh function to use updating whole connection view. 2016-10-12 15:50:58 +03:00
Gokhan Simsek 4efbd8b90a Export_Import: Adding support for multiple simultaneous exports in auto mode as well; adding enableDisableWidgets function to disable some widgets during export operation 2016-10-10 02:22:52 +03:00
Gokhan Simsek 655bfcba0a Export_Import: Adding support for multiple simultaneous exports in manual mode; Making export dialog mode-less 2016-10-09 18:27:54 +03:00
Gokhan Simsek 041356ebac Export_Import: Adding progress dialog for long lasting exports for auto mode 2016-10-09 13:52:35 +03:00
Gokhan Simsek 32e9ceb741 Export_Import: Appending mongo export output into auto mode result dialog 2016-10-09 01:00:29 +03:00
Gokhan Simsek 1aeb0e42dc Export_Import: Adding manual export mode 2016-10-08 23:30:33 +03:00
Gokhan Simsek 9c419b1f2e ReplicaSet: Enabling this feature which was disabled code due to release 0.9.0 Final 2016-10-07 19:15:14 +03:00
Gökhan Simsek 0786489924 Update CHANGELOG 2016-10-05 17:28:02 +03:00
Iulian Onofrei 2835aa1685 Move Gitter badge to the top
This is quite a preferred position for most projects and it helps to be this visible. I personally thought Robomongo is not using Gitter at first.
2016-10-05 11:33:19 +03:00
Gokhan Simsek 9cc34df185 Fixing the problem where create collection dialog was too large after switching into non-advanced mode from non-option tabs in advanced mode 2016-10-04 22:19:15 +03:00
Gokhan Simsek d5adcdf988 Fixing not working paralect link in about menu; Hiding validate json button when create collection dialog is in basic mode. 2016-10-04 21:41:08 +03:00
Gokhan Simsek 837c0e1a17 MinimizeToTray: Fixing the problem where incorrect context menu was shown when user minimize and right-click on tray icon. 2016-10-04 21:16:46 +03:00
Gökhan Simsek d458f43b63 Update README.md 2016-10-04 20:58:16 +03:00
Gokhan Simsek 1cfb1b6287 Export_Import: Adding code to support query argument; Moving all changes to _mongoExportArgs into one location inside accept() function. 2016-10-02 22:23:49 +03:00
Gokhan Simsek 6f5124616a Export_Import: Adding support to export in CSV format 2016-10-02 21:40:21 +03:00
Gokhan Simsek 3ba2f8ae93 Export_Import: Working export getting arguments from UI; adding smart filename and path creation when collection selected; adding browse directory functionality. 2016-10-01 18:17:44 +03:00
Gokhan Simsek b6d87141f2 Export_Import: First working mongo export with local executable directory and static arguments 2016-09-30 22:56:31 +03:00
Gokhan Simsek 993e9355bb Export_Import: Not creating "Functions", "User" and "Indexes" folders if items are created inside exp/imp dialog 2016-09-30 22:01:52 +03:00
Gokhan Simsek be655902b4 Updating CHANGELOG for 0.9.0 Final 2016-09-30 19:37:26 +03:00
Gökhan Simsek f9332befb3 Update README.md for 0.9.0 Final. 2016-09-30 19:19:49 +03:00
Gökhan Simsek 66728df978 Update README.md 2016-09-30 18:32:26 +03:00
Gokhan Simsek 60dc85c2fc To fix the problem where "View Documents" window cannot be brought into front from the behind of floating output window 2016-09-29 12:38:36 +03:00
Gökhan Simsek 4b4eb623b9 Update README.md 2016-09-28 19:21:20 +03:00
Gokhan Simsek d2c98bf559 DockableOutputWindow: Adding code to support this feature after toggle orientation; updating tool tip for maximize output result button 2016-09-27 12:57:09 +03:00
Gokhan Simsek 9a31503996 DockableOutputWindow: Fixing problem where dock/undock icon wasn't updated after re-running existing query tab with undocked output window 2016-09-23 13:09:15 +03:00
Gokhan Simsek cb4622f61f Adding protection for all tray icon code which is Windows only 2016-09-22 14:55:21 +03:00
Gokhan Simsek 1ce1091eca Minor change due to MAC build error 2016-09-22 14:23:23 +03:00
Gokhan Simsek 4546eef1e3 Refactoring some functions to avoid access to private members for better encapsulation 2016-09-22 14:05:53 +03:00
Gokhan Simsek 1d8f24ced2 Code Review: Refactoring and non-major changes for commits from RC10 release (33c89ea) to this commit. 2016-09-22 13:32:40 +03:00
Gokhan Simsek 19a61ba517 Code Review: For pull request #1163(minimize to tray) - More minor refactoring and style changes. 2016-09-21 11:56:28 +03:00
Gokhan Simsek 2b377e35f1 Code Review: For pull request #1163(minimize to tray) - Adding fix for the problem when tray icon double clicked event was not caught and so context menu was showing wrong text. 2016-09-20 21:38:06 +03:00
Gokhan Simsek f5011630a3 Code review: For pull request #1127 (copy path & copy name), constness and access level edits, applying Robomongo Coding Style. 2016-09-20 20:26:54 +03:00
Gokhan Simsek 8967a84f12 DockableOutputWindow: Fix for possible Qt bug (MAC only) where dock window is not brought to front when connected tab is selected 2016-09-20 17:08:23 +03:00
Gokhan Simsek 38bed5e4eb Code review : MinimizeToTray pull request minor changes 2016-09-19 01:32:10 +03:00
Gokhan Simsek 5370ecedd8 ReplicaSet: Temporarily disable Replica Set feature - still under development 2016-09-15 13:18:53 +03:00
Gökhan Simsek 0e8cd5cefc Merge pull request #1163 from JoshJ1091/master
Add system tray icon and implement ability to close to system tray (Windows)
2016-09-14 13:37:10 +02:00
Gokhan Simsek e3c3649b0f Robomongo 0.9.0 2016-09-14 13:06:50 +03:00
Gokhan Simsek 19f953fa54 RepSet: Removing QShortCut due to failed build on Ubuntu 2016-09-14 12:10:28 +03:00
Gokhan Simsek ad1b5e35c7 Merge branch 'DockableOutputWindow' 2016-09-13 19:26:43 +03:00
Gokhan Simsek ff2eb0fef6 DockableOutputWindow: Updating clean versions of maximize and minimize icons; Minor change on ReplicaSetSettings due to failed build on MAC 2016-09-13 19:10:21 +03:00
Gokhan Simsek d356c14f87 DockableOutputWindow: Increasing size of dock/undock button icons 2016-09-12 00:57:14 +03:00
Gokhan Simsek 829cbf890b DockableOutputWindow: Adding new icons for maximize/minimize button 2016-09-11 23:40:12 +03:00
Gokhan Simsek 5ae52d4b4f DockableOutputWindow: Fix to solve crash when floating dock window hovered over bottom part of the maximized query widget 2016-09-11 18:50:18 +03:00
Gokhan Simsek 4608c6b7ee DockableOutputWindow: No maximize (via double click or any other source) if there is only one query result 2016-09-11 16:44:00 +03:00
Gokhan Simsek 7c7a818ca0 Merge branch 'DockableOutputWindow' 2016-09-11 15:59:32 +03:00
Gokhan Simsek 08c6d44185 DockableOutputWindow: Removing the additional horizontal line on bottom of tab title that is seen after undock 2016-09-11 15:56:50 +03:00
Gokhan Simsek dc8495b147 DockableOutputWindow: Adding code for frameless query results dock window; adding functionality to dock/undock from OutputItemHeaderWidget; adding dock/undock buttons and icons into OutputItemHeaderWidget; not showing OutputItemHeaderWidget maximize button if there is one query result; showing only one undock button if there are more than one query results. 2016-09-11 15:16:44 +03:00
Gokhan Simsek e2f5765a87 DockableOutputWindow: Adding code to show OutputWidget maximize button only if there are multiple query results 2016-09-10 16:24:46 +03:00
Gokhan Simsek 7ebc059430 Export_Import: Adding function to be able to customize ExplorerDatabaseTreeItem in order to use it inside Export Dialog 2016-09-09 19:52:46 +03:00
Gokhan Simsek 0d3fda185f Export_Import: Adding new dialog/class ExportDialog and it's first visual template; adding import action into main window 2016-09-09 13:41:25 +03:00
Gokhan Simsek 38df06261b New branch Export_Import; adding export tool bar item into main windows; adding icons for export and import 2016-09-08 14:09:11 +03:00
Gokhan Simsek 0086df54f0 DockableOutputWindow: Focus on active shell query tab when related dock output window is selected 2016-09-07 20:05:06 +03:00
Gokhan Simsek ddc0c910c8 DockableOutputWindow: Removing unused signal-slot connection 2016-09-07 14:02:04 +03:00
Gokhan Simsek 31f74baef7 DockableOutputWindow: Adding close button on dock output window with custom close event (dock instead of close) 2016-09-07 13:41:39 +03:00
Gokhan Simsek 8cd4f50304 DockableOutputWindow: Fix to solve crash on "Update Documents" 2016-09-07 11:06:55 +03:00
Gokhan Simsek 48f6abb361 DockableOutputWindow: Fix to solve un-selectable query window text issue when output window is floating. 2016-09-06 19:28:34 +03:00
Gokhan Simsek 47039d8685 DockableOutputWindow: Adding code to support full window query editor when output window is docked 2016-09-06 02:51:25 +03:00
Gokhan Simsek bc9aded3b7 DockableOutputWindow: Bring the output dock window front when tabs changed on main window 2016-09-04 21:50:18 +03:00
Gokhan Simsek f919544d1e DockableOutputWindow: Adding docked and undocked(full screen space for queries) modes for query window (ScriptWidget). 2016-09-04 18:59:16 +03:00
Gokhan Simsek ea49d0508a DockableOutputWindow: Adding dock capability for query result window (OutputWidget) 2016-09-03 21:57:08 +03:00
Josh 578447ecc3 Add system tray icon and implement ability to close to system tray
(Windows)
2016-08-18 16:52:26 +01:00
146 changed files with 11850 additions and 3790 deletions
+143 -48
View File
@@ -1,61 +1,156 @@
Latest Release:
Robomongo 0.9.0 RC10 (Aug 8, 2016)
http://blog.robomongo.org/robomongo-rc10/
Latest Version:
Robomongo 1.0 (21 Apr, 2017)
http://blog.robomongo.org/robomongo-1-0
Highlights from Robomongo 0.9.0 RCs:
Major Features:
- Support for MongoDB 3.x and WiredTiger engine. Robomongo now embeds MongoDB 3.2 shell.
(Ability to use the latest shell functionality)
- SSH tunnel support for connections added.
- Support for SSL connections added.
- MongoDB Atlas (Cloud MongoDB) support added.
- Both MONGODB-CR and SCRAM-SHA-1 authentication mechanisms are supported.
- Allow direct connections to Replica Set members and support IPv6 protocol.
- Loading of huge list of collections is now faster.
- Create Collection dialog is now expandable to support all advanced options.
(Supporting all major mongoDB versions 3.2, 3.0, 2.6 and all storage engine types (WiredTiger
and MMAPV1) is added.)
- Windows High DPI support.
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
- SSH connection failure to servers on Ubuntu and OS X with the following error:
"Resource temporarily unavailable. Error when starting up SSH session: -8" (#1090)
Robomongo 1.x:
Major Features:
- Replica set support.
- Update Notifications Bar
- Welcome Tab
- New operation logging for almost all operations.
Major Fixes/Improvements:
- Improved error handling (no double error windows) for almost all operations.
- Timestamp value is represented incorrectly (https://github.com/Studio3T/robomongo/issues/1159)
- Local Timezone shows wrong date (https://github.com/Studio3T/robomongo/issues/501)
* What's new in patch 1.0.2 (28 Apr , 2017)
- Support for ES6 (ECMAScript2015) added.
* What's new in Robomongo 1.0 (21 Apr , 2017)
http://blog.robomongo.org/robomongo-1-0
New Features:
- Update Notifications Bar
- Welcome Tab
- Welcome Wizard
Improvements:
- Replica set name configurable on UI
- Configuration file system location change
- "About Robomongo" dialog changes
Fixes:
- Fix to avoid the same set name problem (https://github.com/Studio3T/robomongo/issues/1302)
- Timestamp value is represented incorrectly (https://github.com/Studio3T/robomongo/issues/1159)
- Local Timezone shows wrong date (https://github.com/Studio3T/robomongo/issues/501)
- Time zone shown as "000:00" when UTC +00:00 is used as local timezone(https://github.com/Studio3T/robomongo/issues/1309)
* What's new in Robomongo 1.0-RC1 (1 Feb, 2017)
http://blog.robomongo.org/robomongo-1-rc1/
New Features:
- Replica set support.
- New 'Attributes' column in connections list window showing connection properties such as 'Replica Set', 'SSL' and 'SSH'.
- New operation logging for almost all operations.
Improvements:
- Improved error handling (no double error windows) for all fail and success conditions for the following operations:
. Create, Drop Database
. Create, Edit User
. Create, Drop, Duplicate, Rename Collection
. Insert Document
. Remove document, Delete document, Remove all documents (triggered both from explorer and from result widget)
. Refresh Users
. Refresh Functions
. Refresh Collections
. Connection establish & refresh server fail and success cases.
- Windows size is saved and restored for Edit, View and Insert Document dialogs.
- Connections are automatically imported from the latest Robomongo version.
- Better error reporting/handling and logging have been added for the operations below:
. Create, Drop Function
. Drop User
- More informative and adjusted log level prefix strings haven added for log window. (Error, Warn, Info etc..).
- After a new connection is created, now the focus goes to the newly created connection (end of list) instead of the
beginning of connections list. And when Robomongo is opened, the focus goes to the end of the connection list instead
of beginning.
- Error messages added and error handling for 'remove document', 'remove all documents' operations triggered explorer
and single or multi 'delete document' from results window have been re-implemented and improved.
- Relevant errors are shown for the following cases now:
. Drop attempt of non-existing collection.
. Duplicating a collection with a collection name which already exists finishes silently.
Fixes:
- Fix for the problem where the connections list comes empty if the Robomongo config file or it's directory do not exist.
- Fix for the problem where 'in-progress bar' widget was not centered on main window for "Update Documents/Remove Documents"
operations. https://github.com/paralect/robomongo/issues/1256
- Fix for two problems where the operations 'insert or update documents' in one tab was causing auto reloading of other
unrelated tabs and also multiple error windows to popup. https://github.com/paralect/robomongo/issues/1236
- Fix for the problem where the operations 'remove document', 'remove all documents' triggered explorer and single or
multi 'delete document' from results window were causing auto reloading of other unrelated tabs.
Robomongo 0.9:
Major Features:
- Support for MongoDB 3.x and WiredTiger engine. Robomongo now embeds MongoDB 3.2 shell.
(Ability to use the latest shell functionality)
- SSH tunnel support for connections
- SSL support
- MongoDB Atlas (Cloud MongoDB) support
- Both MONGODB-CR and SCRAM-SHA-1 authentication mechanisms are supported.
- Allow direct connections to Replica Set members and support IPv6 protocol.
- Loading of huge list of collections is now faster.
- Create Collection dialog is now expandable to support all advanced options.
(Supporting all major mongoDB versions 3.2, 3.0, 2.6 and all storage engine types (WiredTiger
and MMAPV1) is added.)
- Windows High DPI support.
- Full size editor with dockable output window.
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
- SSH connection failure to servers on Ubuntu and OS X with the following error:
"Resource temporarily unavailable. Error when starting up SSH session: -8" (#1090)
What's new in 0.9.0 Final (30 Sep, 2016)
http://blog.robomongo.org/robomongo-0-9-0-final/
New Features:
- Full size editor with dockable output window.
- New JSON object context menu items: "Copy Name" and "Copy Path" are added.
- New database context menu items: "Current Operations" and "Kill Operation" are added.
- Minimize to Tray Icon for Windows.
Other Changes
- Cloud platforms Compose, mLab and Amazon EC2 were tested and added as supported platforms.
- Maximize/Restore result button behavior and icon have been improved.
- For better code quality and code reviews, Robomongo Code Quality and Robomongo Code Review pages have been created.
https://github.com/paralect/robomongo/wiki/Robomongo-Code-Review
https://github.com/paralect/robomongo/wiki/Robomongo-Code-Quality
What's new in RC10 (Aug 8, 2016)
http://blog.robomongo.org/robomongo-rc10/
New Features:
- SSL connections with advanced options are now supported.
- MongoDB Atlas (Cloud MongoDB) is supported.
- Cross Platform High DPI enhancements.
Enhancements:
- For better security, OpenSSL has been upgraded to OpenSSL 1.0.1p.
- Qt has been updated from Qt 5.5 to latest Qt 5.7. With this update there are many
enhancements including cross platform High DPI support.
New Features:
- SSL connections with advanced options are now supported.
- MongoDB Atlas (Cloud MongoDB) is supported.
- Cross Platform High DPI enhancements.
Enhancements:
- For better security, OpenSSL has been upgraded to OpenSSL 1.0.1p.
- Qt has been updated from Qt 5.5 to latest Qt 5.7. With this update there are many
enhancements including cross platform High DPI support.
What's new in RC9 (May 30, 2016)
http://blog.robomongo.org/robomongo-rc9/
New Features:
- Create Collection dialog with advanced options.
- New feature has been added to save/restore window sizes. (#1102)
- Connection settings from previous versions of Robomongo are
now automatically imported starting from Robomongo 0.9.0 RC9. (#1086)
- Support for querying documents by UUIDs is added, both in legacy and new encoding. (#432)
Fixed:
- SSH connection failure to servers on Ubuntu and OS X with the following error:
"Resource temporarily unavailable. Error when starting up SSH session: -8" (#1090)
- Correct handling of create/drop of databases and collections. (#1083)
New Features:
- Create Collection dialog with advanced options.
- New feature has been added to save/restore window sizes. (#1102)
- Connection settings from previous versions of Robomongo are
now automatically imported starting from Robomongo 0.9.0 RC9. (#1086)
- Support for querying documents by UUIDs is added, both in legacy and new encoding. (#432)
Fixed:
- SSH connection failure to servers on Ubuntu and OS X with the following error:
"Resource temporarily unavailable. Error when starting up SSH session: -8" (#1090)
- Correct handling of create/drop of databases and collections. (#1083)
What's new in RC8 (April 14, 2016)
http://blog.robomongo.org/robomongo-rc8/
What's new in RC7 (March 4, 2016)
http://blog.robomongo.org/robomongo-rc7
@@ -130,7 +225,7 @@ http://blog.robomongo.org/robomongo-rc10/
* [Andres Kievsky]
- Added new app icon in a variety of png and iDraw formats (#693)
* [Michael Steinacher]
- Retina text display support (#724)
- Retina text display support (#724)
0.8.4 / November 27, 2013
+6 -4
View File
@@ -5,10 +5,10 @@ project(Robomongo)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
# Project version
set(PROJECT_VERSION_MAJOR "0")
set(PROJECT_VERSION_MINOR "9")
set(PROJECT_VERSION_PATCH "0")
set(PROJECT_VERSION_BUILD "-RC10")
set(PROJECT_VERSION_MAJOR "1")
set(PROJECT_VERSION_MINOR "0")
set(PROJECT_VERSION_PATCH "2")
set(PROJECT_VERSION_BUILD "")
set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}${PROJECT_VERSION_BUILD}")
# Modules
@@ -27,6 +27,8 @@ find_package(Qt5Core REQUIRED)
find_package(Qt5Gui REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5PrintSupport REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5Xml REQUIRED)
find_package(MongoDB REQUIRED)
find_package(OpenSSL REQUIRED)
+1 -1
View File
@@ -1,6 +1,6 @@
Robomongo, shell-centric cross-platform MongoDB management tool.
Copyright 2013, Paralect.
Copyright 2014-2017 3T Software Labs Ltd.
The Robomongo is released under the terms of the GNU General Public
License, version 3.
+33 -33
View File
@@ -1,4 +1,4 @@
About Robomongo
About Robomongo [![Join the chat at https://gitter.im/paralect/robomongo](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/paralect/robomongo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
===============
[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.
@@ -7,31 +7,34 @@ Starting from version 0.9, Robomongo is compatibile with MongoDB 3.x (including
Robomongo 0.8.x embeds the **MongoDB 2.4.0** shell.
What's new in latest Robomongo 0.9.0 RC10?
What's new in latest Robomongo 1.0?
====================================
Latest release of Robomongo 0.9.0 RC10 includes:
- Support for SSL
- MongoDB Atlas Support
- HiDPI enhancements
Central feature of this release is full support for TSL/SSL protocol with advanced options. This version also brings support to MongoDB Atlas cloud service and includes enhancements for HiDPI displays.
Latest release of Robomongo 1.0 includes:
- Update Notifications Bar
- Welcome Tab
- Welcome Wizard
- Replica set name configurable on UI
- Fixes to avoid same replica set name problem
- Fixes to solve various problems regarding Data and Timestamp data types
Blog: http://blog.robomongo.org/robomongo-rc10
Blog: http://blog.robomongo.org/robomongo-1-0/
Download: https://robomongo.org/download
Watch: [Robomongo Youtube channel](https://www.youtube.com/channel/UCM_7WAseRWeeiBikExppstA)
Follow: https://twitter.com/Robomongo
What's Planned for the Next Release?
What's Planned for the Next Releases?
====================================
We are currently working towards [Robomongo 0.9.0 RC11 milestone](https://github.com/paralect/robomongo/milestone/16).
We are currently working towards [Robomongo 2.0 milestone](https://github.com/paralect/robomongo/milestone/21).
Currently in progress:
- Replica Sets Support
- MongoDB 3.4 Support
Plans for Future:
- User Roles
- Support execution of multiple simultaneous queries [#1161](https://github.com/paralect/robomongo/issues/1161)
- Enhancements for stability, running without crashes
- Selectable mongodb engines [#1249](https://github.com/paralect/robomongo/issues/1249)
- Enhancements for stability, running without crashes ([Stability Milestone](https://github.com/paralect/robomongo/milestone/15))
- More Documentation
- Unit Tests
- Refactoring Debug Log module
@@ -39,20 +42,19 @@ Plans for Future:
Supported Platforms
===============
**Supported MongoDBs:**
| MongoDB Versions |
| :-------------------- |
| 3.2 |
| 3.0 |
| 2.6 |
| MongoDB Versions | MongoDB Cloud |
| :-------------------- | :------------ |
| 3.2 | MongoDB Atlas |
| 3.0 | Compose |
| 2.6 | mLab |
| | ObjectRocket |
**Note**: Currently Robomongo supports connection to single server of a replica set. Support for connection to **Replica Sets** is one of the most important features of next Robomongo release plan.
**Supported OS Platforms:**
| MongoDB Cloud Platforms|
| :------------ |
| MongoDB Atlas |
| Compose |
| mLab |
| ObjectRocket |
| Amazon EC2 |
| Windows | Mac | Linux |
|:---------------------- | :---------------| :---------------------------|
@@ -63,14 +65,13 @@ Supported Platforms
\* latest stable build
Download
========
You can download tested install packages for OS X, Windows, and Linux from our site: [www.robomongo.org](http://www.robomongo.org).
The latest release candidate is currently **Robomongo 0.9.0 RC10**. (http://blog.robomongo.org/robomongo-rc10/).
The latest stable release is currently **Robomongo 0.8.5**. (http://blog.robomongo.org/whats-new-in-robomongo-0-8-5/)
The latest stable release is currently [**Robomongo 1.0**](http://blog.robomongo.org/robomongo-1-0/).
Support
=======
@@ -81,7 +82,7 @@ You can:
- [Create a new issue in the Github issue queue](https://github.com/paralect/robomongo/issues)
- [Join developer discussion on Gitter](https://gitter.im/paralect/robomongo) [![Join the chat at https://gitter.im/paralect/robomongo](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/paralect/robomongo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
- [Join developer discussion on Gitter](https://gitter.im/paralect/robomongo)
Build
=====
@@ -96,6 +97,7 @@ Contribute!
### Suggest Features
New feature suggestions or UI improvements are always welcome.
[Create a new feature request on github](https://github.com/paralect/robomongo/issues/new)
This project is powered by open source volunteers, so we have a limited amount of development resource to address all requests. We will certainly make best efforts to progress (particularly for those with strong community upvotes).
@@ -107,8 +109,6 @@ Code contributions are always welcome! Just try to follow our pre-commit checks
If you plan to contribute, please create a Github issue (or comment on the relevant existing issue) so we can help coordinate with upcoming release plans.
For a general workflow, see Github's guide to [Fork a Repo](https://help.github.com/articles/fork-a-repo/).
Pull requests (PRs) should generally be for discrete issues (i.e. one issue per PR please) and be clean to merge against the current master branch. It would also be helpful if you can confirm what testing has been done (specific O/S targets and MongoDB versions if applicable).
A usual naming approach for feature branches is `issue-###`. Include the issue number in your commit message / pull request description to link the PR to the original issue.
@@ -128,7 +128,7 @@ There are a number of issues we could use help with reproducing. They are marked
License
=======
Copyright (C) 2013-2016 [Paralect, Inc](http://www.paralect.com)
Copyright 2014-2017 [3T Software Labs Ltd](https://studio3t.com/). All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 3 as
@@ -8,6 +8,8 @@ message("| Qt5 Core: ${Qt5Core_VERSION} ${Qt5Core_DIR}")
message("| Qt5 Gui: ${Qt5Gui_VERSION} ${Qt5Gui_DIR}")
message("| Qt5 Widgets: ${Qt5Widgets_VERSION} ${Qt5Widgets_DIR}")
message("| Qt5 PrintSupport: ${Qt5Widgets_VERSION} ${Qt5PrintSupport_DIR}")
message("| Qt5 Network: ${Qt5Network_VERSION} ${Qt5Network_DIR}")
message("| Qt5 Xml: ${Qt5Xml_VERSION} ${Qt5Xml_DIR}")
message("| MongoDB: ${MongoDB_VERSION} ${MongoDB_BUILD_DIR}")
message("| OpenSSL: ${OPENSSL_VERSION} ${OpenSSL_DIR}")
message("|")
+6 -4
View File
@@ -24,7 +24,7 @@ if(SYSTEM_LINUX)
set(qt_conf_dir ${bin_dir})
set(qt_conf_plugins "../lib")
elseif(SYSTEM_MACOSX)
set(bundle_name Robomongo.app)
set(bundle_name "Robomongo.app")
set(contents_path ${bundle_name}/Contents)
set(bin_dir ${contents_path}/MacOS)
@@ -72,7 +72,9 @@ elseif(SYSTEM_MACOSX)
elseif(SYSTEM_LINUX)
install(
FILES
"${OpenSSL_DIR}/libssl.so.1.0.0"
"${OpenSSL_DIR}/libssl.so"
"${OpenSSL_DIR}/libssl.so.1.0.0"
"${OpenSSL_DIR}/libcrypto.so"
"${OpenSSL_DIR}/libcrypto.so.1.0.0"
DESTINATION ${lib_dir})
endif()
@@ -93,7 +95,7 @@ install(
DESTINATION ${license_dir})
# Install common dependencies
install_qt_lib(Core Gui Widgets PrintSupport)
install_qt_lib(Core Gui Widgets PrintSupport Network Xml)
install_qt_plugins(QGifPlugin QICOPlugin)
install_icu_libs()
@@ -111,7 +113,7 @@ elseif(SYSTEM_MACOSX)
# Install icon
install(
FILES "${CMAKE_SOURCE_DIR}/install/macosx/Robomongo.icns"
FILES "${CMAKE_SOURCE_DIR}/install/macosx/robomongo.icns"
DESTINATION "${resources_dir}")
elseif(SYSTEM_WINDOWS)
install_qt_plugins(
+4 -4
View File
@@ -15,7 +15,7 @@ execute_process(
string(TIMESTAMP timestamp "%Y-%m-%d")
# Package name (as it should appear in UI)
set(CPACK_PACKAGE_NAME Robomongo)
set(CPACK_PACKAGE_NAME robomongo)
# Version of the package
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
@@ -34,8 +34,8 @@ set(CPACK_MONOLITHIC_INSTALL ON)
set(CPACK_STRIP_FILES ON)
# Additional information
set(CPACK_PACKAGE_VENDOR Paralect)
set(CPACK_PACKAGE_CONTACT robomongo@paralect.com)
set(CPACK_PACKAGE_VENDOR "3T Software Labs Ltd")
set(CPACK_PACKAGE_CONTACT robomongo@3t.io)
set(CPACK_PACKAGE_DESCRIPTION "Shell-centric cross-platform MongoDB management tool.")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Robomongo is a shell-centric cross-platform MongoDB management tool.")
@@ -62,7 +62,7 @@ elseif(SYSTEM_MACOSX)
elseif(SYSTEM_WINDOWS)
set(files_dir "${CMAKE_SOURCE_DIR}/install/windows")
set(exe_name "Robomongo.exe")
set(exe_name "robomongo.exe")
set(CPACK_GENERATOR NSIS ZIP)
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/DESCRIPTION")
+3 -3
View File
@@ -18,9 +18,9 @@ endif()
set(PROJECT_NAME "Robomongo")
set(PROJECT_NAME_TITLE ${PROJECT_NAME})
set(PROJECT_DOMAIN "www.robomongo.org")
set(PROJECT_COMPANYNAME "Paralect")
set(PROJECT_COPYRIGHT "Copyright (C) 2013-2015 ${PROJECT_COMPANYNAME} All Rights Reserved.")
set(PROJECT_COMPANYNAME_DOMAIN "www.paralect.com")
set(PROJECT_COMPANYNAME "3T Software Labs Ltd")
set(PROJECT_COPYRIGHT "Copyright (C) 2014-2017 ${PROJECT_COMPANYNAME} All Rights Reserved.")
set(PROJECT_COMPANYNAME_DOMAIN "https://studio3t.com/")
set(PROJECT_GITHUB_FORK "www.github.com/paralect/robomongo")
set(PROJECT_GITHUB_ISSUES "www.github.com/paralect/robomongo/issues")
+12 -1
View File
@@ -125,4 +125,15 @@ Install Robomongo to `build/release/install` folder:
Create package for your OS in `build/release/package` folder:
$ bin/pack
**Important Notes**
- For Ubuntu 16.04 builds, it has been reported that Robomongo /bin/configure step might fail and installing mesa-common-dev package solves it. Details: https://github.com/paralect/robomongo/issues/1268
- For Centos builds, Robomongo /bin/configure step might fail due to error: `failed to find gl/gl.h`. In this case, the following packages must be installed:
```sh
$ sudo yum install mesa-libGL
$ sudo yum install mesa-libGL-devel
```
+32 -5
View File
@@ -12,19 +12,46 @@ Runtime dependencies that are not privided by platforms but required by Robomong
Diagnostic of dependencies
--------------------------
##### Linux
#### Linux
View dependencies of executable or shared library:
a. View dependencies of executable or shared library:
$ ldd robomongo
View RPATH or RUNPATH records of executable or shared library:
$ readelf -d robomongo
b. View dependency tree of executable or shared library:
$ lddtree robomongo
$ ldd -v robomongo
c. View RPATH or RUNPATH records of executable or shared library:
$ objdump -x bin/robomongo | grep "RPATH\|RUNPATH"
$ readelf -d bin/robomongo | grep RPATH
For Robomongo binary it should be `$ORIGIN/../lib`
##### Mac OS X
d. Enable extensive logging for run-time shared library search
$ export LD_DEBUG=files
$ bin/robomongo
Source:
http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html
e. Modify RPATH using `chrpath` and the dynamic linker and RPATH using paths `patchelf`
$ patchelf --set-rpath '$ORIGIN/../lib' robo
More: https://nixos.org/patchelf.html
$ chrpath -r "/somepath/lib/" robomongo
More:
https://linux.die.net/man/1/chrpath
http://www.programering.com/a/MTOwcDNwATU.html // examples
#### Mac OS X
Show dependencies of executable or shared library:

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 827 B

After

Width:  |  Height:  |  Size: 827 B

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

+2 -2
View File
@@ -26,10 +26,10 @@ IDI_ICON1 ICON "@windows_icon@"
BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "Paralect, Inc\0"
VALUE "CompanyName", "3T Software Labs Ltd\0"
VALUE "FileDescription", "Robomongo, MongoDB management tool\0"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "LegalCopyright", "Copyright Paralect, 2013 - 2016\0"
VALUE "LegalCopyright", "Copyright 3T Software Labs Ltd, 2014 - 2017\0"
VALUE "ProductName", "Robomongo\0"
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
VALUE "OriginalFilename", "Robomongo.exe\0"
+1 -1
View File
@@ -1,6 +1,6 @@
Robomongo shortcut key reference
================================
"3T", "Robomongo"
### Windows/Linux users
Ctrl+Enter or F5 or Ctrl+R
+20 -6
View File
@@ -53,12 +53,16 @@ set(SOURCES
core/domain/App.cpp
core/mongodb/MongoClient.cpp
core/mongodb/MongoWorker.cpp
core/mongodb/ReplicaSet.cpp
core/settings/SettingsManager.cpp
core/AppRegistry.cpp
utils/string_operations.cpp
utils/common.cpp
# Isolated Scope #3
gui/GuiRegistry.cpp
gui/dialogs/AboutDialog.cpp
gui/dialogs/EulaDialog.cpp
gui/dialogs/ConnectionAdvancedTab.cpp
gui/dialogs/ConnectionAuthTab.cpp
gui/dialogs/ConnectionBasicTab.cpp
@@ -75,6 +79,8 @@ set(SOURCES
# Isolated scope #4
gui/dialogs/PreferencesDialog.cpp
gui/dialogs/ConnectionsDialog.cpp
gui/dialogs/CreateConnectionDialog.cpp
gui/dialogs/ExportDialog.cpp
# Isolated scope #5
gui/editors/PlainJavaScriptEditor.cpp
@@ -86,6 +92,7 @@ set(SOURCES
# Isolated scope #6
gui/widgets/explorer/ExplorerCollectionTreeItem.cpp
gui/widgets/explorer/ExplorerTreeItem.cpp
gui/widgets/explorer/ExplorerReplicaSetFolderItem.cpp
gui/widgets/explorer/ExplorerReplicaSetTreeItem.cpp
gui/widgets/explorer/ExplorerDatabaseTreeItem.cpp
gui/widgets/explorer/ExplorerDatabaseCategoryTreeItem.cpp
@@ -117,6 +124,7 @@ set(SOURCES
gui/widgets/workarea/QueryWidget.cpp
gui/widgets/workarea/WorkAreaTabBar.cpp
gui/widgets/workarea/WorkAreaTabWidget.cpp
gui/widgets/workarea/WelcomeTab.cpp
# Final scope
gui/widgets/LogWidget.cpp
@@ -126,9 +134,10 @@ set(SOURCES
gui/dialogs/SSLTab.cpp
core/settings/SshSettings.cpp
core/settings/SslSettings.cpp
core/settings/ReplicaSetSettings.cpp
core/settings/ReplicaSetSettings.cpp
core/mongodb/SshTunnelWorker.cpp
resources/robo.qrc
gui/resources/gui.qrc
)
@@ -139,6 +148,8 @@ add_executable(robomongo MACOSX_BUNDLE WIN32 app/main.cpp ${SOURCES})
target_link_libraries(robomongo
PRIVATE
Qt5::Widgets
Qt5::Network
Qt5::Xml
qjson
qscintilla
mongodb
@@ -160,6 +171,8 @@ target_compile_definitions(robomongo
PROJECT_GITHUB_FORK="${PROJECT_GITHUB_FORK}"
PROJECT_GITHUB_ISSUES="${PROJECT_GITHUB_ISSUES}"
PROJECT_VERSION="${PROJECT_VERSION}"
PROJECT_VERSION_SHORT="${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
PROJECT_QT_VERSION="${Qt5Core_VERSION}"
PROJECT_NAME_LOWERCASE="${PROJECT_NAME_LOWERCASE}")
if(SYSTEM_WINDOWS)
@@ -190,17 +203,18 @@ elseif(SYSTEM_MACOSX)
OUTPUT_NAME "Robomongo"
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_SOURCE_DIR}/install/macosx/Info.plist.in"
MACOSX_BUNDLE_BUNDLE_NAME "Robomongo"
MACOSX_BUNDLE_GUI_IDENTIFIER "com.paralect.Robomongo"
MACOSX_BUNDLE_INFO_STRING "Robomongo ${PROJECT_VERSION}. Copyright Paralect, 2013 - 2016"
MACOSX_BUNDLE_GUI_IDENTIFIER "com.3tsoftwarelabs.robomongo"
MACOSX_BUNDLE_INFO_STRING "Robomongo ${PROJECT_VERSION}. Copyright 3T Software Labs Ltd, 2014 - 2017"
MACOSX_BUNDLE_SHORT_VERSION "${PROJECT_VERSION}"
MACOSX_BUNDLE_LONG_VERSION "${PROJECT_VERSION}"
MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}"
MACOSX_BUNDLE_COPYRIGHT "Copyright Paralect, 2013 - 2016"
MACOSX_BUNDLE_ICON_FILE "Robomongo")
MACOSX_BUNDLE_COPYRIGHT "Copyright 3T Software Labs Ltd, 2014 - 2017"
MACOSX_BUNDLE_ICON_FILE "robomongo")
elseif(SYSTEM_LINUX)
set_target_properties(robomongo PROPERTIES
INSTALL_RPATH "$ORIGIN/../lib")
INSTALL_RPATH "$ORIGIN/../lib"
OUTPUT_NAME "robomongo")
endif()
# Install
+19 -4
View File
@@ -10,16 +10,18 @@
#include <mongo/base/initializer.h>
#include <mongo/util/net/ssl_options.h>
#include "robomongo/core/AppRegistry.h"
#include "robomongo/core/settings/SettingsManager.h"
#include "robomongo/gui/MainWindow.h"
#include "robomongo/gui/AppStyle.h"
#include "robomongo/gui/dialogs/EulaDialog.h"
#include "robomongo/ssh/ssh.h"
int main(int argc, char *argv[], char** envp)
{
if (rbm_ssh_init()) {
if (rbm_ssh_init())
return 1;
}
// Please check, do we really need envp for other OSes?
#ifdef Q_OS_WIN
@@ -54,12 +56,25 @@ int main(int argc, char *argv[], char** envp)
app.setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
// EULA License Agreement
auto const settingsManager = Robomongo::AppRegistry::instance().settingsManager();
if (!settingsManager->acceptedEulaVersions().contains(PROJECT_VERSION)) {
Robomongo::EulaDialog eulaDialog;
if (eulaDialog.exec() == QDialog::Rejected) {
rbm_ssh_cleanup();
return 1;
}
// EULA accepted
settingsManager->addAcceptedEulaVersion(PROJECT_VERSION);
settingsManager->save();
}
// Init GUI style
Robomongo::AppStyleUtils::initStyle();
// Application main window
Robomongo::MainWindow win;
win.show();
Robomongo::MainWindow mainWindow;
mainWindow.show();
int rc = app.exec();
rbm_ssh_cleanup();
+9 -7
View File
@@ -32,16 +32,18 @@ namespace Robomongo
typedef boost::shared_ptr<MongoDocument> MongoDocumentPtr;
enum ConnectionType {
// This type of connection is shown in Explorer and also
// opens SSH tunnel for Secondary connections (if needed)
// This type of connection is shown in Explorer and also opens SSH tunnel for secondary
// connections (if needed)
ConnectionPrimary = 0,
// Never shown in Explorer and uses SSH tunnel from
// Primary connection (if needed)
// Never shown in Explorer and uses SSH tunnel from primary connection (if needed)
ConnectionSecondary = 1,
// The same as Primary, but is specifically for testing
// connections.
ConnectionTest = 2
// The same as Primary, but is specifically for testing connections.
ConnectionTest = 2,
// Never shown in Explorer and can be used to refresh (via reconnecting) current connection view
// (i.e. db version, storage engine, current replica set primary, status of replica set etc...)
ConnectionRefresh = 3
};
}
+20 -4
View File
@@ -1,19 +1,35 @@
#include "robomongo/core/EventError.h"
#include "robomongo/utils/string_operations.h"
namespace Robomongo
{
EventError::EventError() :
_isNull(true) {}
EventError::EventError(const std::string &errorMessage) :
_errorMessage(errorMessage),
EventError::EventError(const std::string &errorMessage, ErrorCode errorCode /*= Unknown*/,
bool showErrorWindow /* = true */) :
_errorMessage(captilizeFirstChar(errorMessage)),
_errorCode(errorCode),
_showErrorWindow(showErrorWindow),
_isNull(false) {}
bool EventError::isNull() const {
EventError::EventError(const std::string &errorMessage, ReplicaSet const& replicaSetInfo,
bool showErrorWindow /* = true */ ) :
_errorMessage(errorMessage),
_errorCode(SetPrimaryUnreachable),
_replicaSetInfo(replicaSetInfo),
_showErrorWindow(showErrorWindow),
_isNull(false) {}
bool EventError::isNull() const
{
return _isNull;
}
const std::string &EventError::errorMessage() const {
const std::string &EventError::errorMessage() const
{
return _errorMessage;
}
}
+23 -4
View File
@@ -1,6 +1,8 @@
#pragma once
#include <string>
#include "robomongo/core/mongodb/ReplicaSet.h"
namespace Robomongo
{
/**
@@ -10,7 +12,11 @@ namespace Robomongo
*/
class EventError
{
public:
enum ErrorCode { Unknown, SetPrimaryUnreachable, ServerHasDifferentMembers };
/**
* @brief Creates "null" (or, in other words, empty) error.
* Subsequent call of isNull() on this object will return true.
@@ -21,7 +27,14 @@ namespace Robomongo
* @brief Creates error object with specified error message.
* Subsequent call of isNull() on this object will return false.
*/
explicit EventError(const std::string &errorMessage);
explicit EventError(const std::string &errorMessage, ErrorCode errorCode = Unknown,
bool showErrorWindow = true);
/**
* @brief Creates error object only for replica set connection failures
*/
explicit EventError(const std::string &errorMessage, ReplicaSet const& replicaSetInfo,
bool showErrorWindow = true);
/**
* @brief Tests whether error is "null" (or, in other words, empty).
@@ -35,13 +48,19 @@ namespace Robomongo
* @brief Returns error message that describes this error.
*/
const std::string &errorMessage() const;
ErrorCode errorCode() const { return _errorCode; }
ReplicaSet replicaSetInfo() const { return _replicaSetInfo; }
bool showErrorWindow() const { return _showErrorWindow; }
private:
/**
* @brief Error message
*/
const std::string _errorMessage;
bool _isNull;
std::string const _errorMessage;
ErrorCode const _errorCode = Unknown;
ReplicaSet const _replicaSetInfo;
bool const _showErrorWindow = true;
bool const _isNull;
};
}
+38 -22
View File
@@ -6,6 +6,7 @@
#include "robomongo/core/domain/MongoShell.h"
#include "robomongo/core/domain/MongoCollection.h"
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/settings/ReplicaSetSettings.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/settings/SslSettings.h"
#include "robomongo/core/mongodb/SshTunnelWorker.h"
@@ -105,7 +106,7 @@ namespace Robomongo
* @param connection: ConnectionSettings, that will be owned by MongoServer.
* @param visible: should this server be visible in UI (explorer) or not.
*/
MongoServer *App::openServerInternal(ConnectionSettings const* connection, ConnectionType type) {
MongoServer *App::openServerInternal(ConnectionSettings* connSettings, ConnectionType type) {
++_lastServerHandle;
if (type == ConnectionPrimary)
@@ -113,16 +114,16 @@ namespace Robomongo
// When connection is SECONDARY or do not have
// ssh settings enabled, continue without SSH Tunnel
if (type == ConnectionSecondary || !connection->sshSettings()->enabled()) {
return continueOpenServer(_lastServerHandle, connection, type);
if (type == ConnectionSecondary || !connSettings->sshSettings()->enabled()) {
return continueOpenServer(_lastServerHandle, connSettings, type);
}
// 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());
.arg(QtUtils::toQString(connSettings->sshSettings()->host()))
.arg(connSettings->sshSettings()->port()), mongo::logger::LogSeverity::Info());
ConnectionSettings* settingsCopy = connection->clone();
ConnectionSettings* settingsCopy = connSettings->clone();
SshTunnelWorker* sshWorker = new SshTunnelWorker(settingsCopy);
_bus->send(sshWorker, new EstablishSshConnectionRequest(this, _lastServerHandle, sshWorker, settingsCopy, type));
return NULL;
@@ -134,7 +135,8 @@ namespace Robomongo
*/
void App::closeServer(MongoServer *server)
{
_servers.erase(std::remove_if(_servers.begin(), _servers.end(), stdutils::RemoveIfFound<MongoServer*>(server)), _servers.end());
_servers.erase(std::remove_if(_servers.begin(), _servers.end(), stdutils::RemoveIfFound<MongoServer*>(server)),
_servers.end());
}
void App::openShell(MongoCollection *collection, const QString &filePathToSave)
@@ -142,7 +144,8 @@ namespace Robomongo
ConnectionSettings *connection = collection->database()->server()->connectionRecord();
connection->setDefaultDatabase(collection->database()->name());
QString script = detail::buildCollectionQuery(collection->name(), "find({})");
openShell(connection, ScriptInfo(script, true, CursorPosition(0, -2), QtUtils::toQString(collection->database()->name()), filePathToSave));
openShell(collection->database()->server(), connection, ScriptInfo(script, true, CursorPosition(0, -2),
QtUtils::toQString(collection->database()->name()), filePathToSave));
}
void App::openShell(MongoServer *server, const QString &script, const std::string &dbName,
@@ -154,7 +157,7 @@ namespace Robomongo
if (!dbName.empty())
connection->setDefaultDatabase(dbName);
openShell(connection, ScriptInfo(script, execute, cursorPosition, shellName, filePathToSave));
openShell(server, connection, ScriptInfo(script, execute, cursorPosition, shellName, filePathToSave));
}
void App::openShell(MongoDatabase *database, const QString &script,
@@ -163,16 +166,19 @@ namespace Robomongo
{
ConnectionSettings *connection = database->server()->connectionRecord();
connection->setDefaultDatabase(database->name());
openShell(connection, ScriptInfo(script, execute, cursorPosition, shellName, filePathToSave));
openShell(database->server(), connection, ScriptInfo(script, execute, cursorPosition,
shellName, filePathToSave));
}
void App::openShell(ConnectionSettings const* connection, const ScriptInfo &scriptInfo)
void App::openShell(MongoServer* server, ConnectionSettings* connection, const ScriptInfo &scriptInfo)
{
MongoServer *server = openServerInternal(connection, ConnectionSecondary);
if (!server)
MongoServer *serverClone = openServerInternal(connection, ConnectionSecondary);
if (!serverClone || !server)
return;
MongoShell *shell = new MongoShell(server, scriptInfo);
MongoShell *shell = new MongoShell(serverClone, scriptInfo);
// Connection between explorer's server and tab's MongoShells
_bus->subscribe(server, ReplicaSetRefreshed::Type, shell);
_shells.push_back(shell);
_bus->publish(new OpeningShellEvent(this, shell));
shell->execute();
@@ -195,22 +201,33 @@ namespace Robomongo
delete shell;
}
MongoServer *App::continueOpenServer(int serverHandle, ConnectionSettings const* connection, ConnectionType type, int localport) {
ConnectionSettings* settings = connection->clone();
MongoServer *App::continueOpenServer(int serverHandle, ConnectionSettings* connSettings, ConnectionType type,
int localport)
{
ConnectionSettings* connSettingsClone = connSettings->clone();
// Modify connection settings when SSH tunnel is used
if ((type == ConnectionPrimary || type == ConnectionTest)
&& settings->sshSettings()->enabled()) {
settings->setServerHost("127.0.0.1");
settings->setServerPort(localport);
&& connSettingsClone->sshSettings()->enabled()) {
connSettingsClone->setServerHost("127.0.0.1");
connSettingsClone->setServerPort(localport);
}
MongoServer *server = new MongoServer(serverHandle, settings, type);
MongoServer *server = new MongoServer(serverHandle, connSettingsClone, type);
_servers.push_back(server);
server->runWorkerThread();
LOG_MSG(QString("Connecting to %1...").arg(QtUtils::toQString(server->connectionRecord()->getFullAddress())), mongo::logger::LogSeverity::Info());
auto replicaSetStr = QString::fromStdString(connSettings->connectionName()) + " [Replica Set]";
replicaSetStr = (connSettings->replicaSetSettings()->members().size() > 0)
? replicaSetStr + QString::fromStdString(connSettings->replicaSetSettings()->members()[0])
: replicaSetStr + "";
QString serverAddress = connSettings->isReplicaSet()
? replicaSetStr
: QString::fromStdString(connSettings->getFullAddress());
LOG_MSG(QString("Connecting to %1...").arg(serverAddress), mongo::logger::LogSeverity::Info());
server->tryConnect();
return server;
}
@@ -256,7 +273,6 @@ namespace Robomongo
_bus->publish(new ConnectionFailedEvent(this, serverHandle, type, errormsg, reason));
}
bool App::askSslPassphrasePromptDialog(ConnectionSettings *connSettings) const
{
auto sslSettings = connSettings->sslSettings();
+7 -3
View File
@@ -70,7 +70,11 @@ namespace Robomongo
bool execute = true, const QString &shellName = QString(),
const CursorPosition &cursorPosition = CursorPosition(), const QString &filePathToSave = QString());
void openShell(ConnectionSettings const* connection, const ScriptInfo &scriptInfo);
/**
* @brief Open new shell using explorer's MongoServer (ExplorerServerTreeItem's _server)
*/
void openShell(MongoServer* server, ConnectionSettings* connSettings, const ScriptInfo &scriptInfo);
MongoServersContainerType getServers() const { return _servers; };
/**
@@ -89,8 +93,8 @@ namespace Robomongo
void handle(LogEvent *event);
private:
MongoServer *openServerInternal(ConnectionSettings const* connection, ConnectionType type);
MongoServer *continueOpenServer(int serverHandle, ConnectionSettings const *connection, ConnectionType type, int localport = 0);
MongoServer *openServerInternal(ConnectionSettings* connSettings, ConnectionType type);
MongoServer *continueOpenServer(int serverHandle, ConnectionSettings* connSettings, ConnectionType type, int localport = 0);
/**
* @brief Create prompt dialog to enter SSL PEM key passphrase and save passphrase into SSL settings
+143 -35
View File
@@ -5,6 +5,8 @@
#include "robomongo/core/mongodb/MongoWorker.h"
#include "robomongo/core/AppRegistry.h"
#include "robomongo/core/EventBus.h"
#include "robomongo/core/utils/Logger.h"
#include "robomongo/utils/common.h"
namespace Robomongo
{
@@ -37,115 +39,148 @@ namespace Robomongo
void MongoDatabase::loadCollections()
{
_bus->publish(new MongoDatabaseCollectionsLoadingEvent(this));
_bus->send(_server->client(), new LoadCollectionNamesRequest(this, _name));
_bus->send(_server->worker(), new LoadCollectionNamesRequest(this, _name));
}
void MongoDatabase::loadUsers()
{
_bus->publish(new MongoDatabaseUsersLoadingEvent(this));
_bus->send(_server->client(), new LoadUsersRequest(this, _name));
_bus->send(_server->worker(), new LoadUsersRequest(this, _name));
}
void MongoDatabase::loadFunctions()
{
_bus->publish(new MongoDatabaseFunctionsLoadingEvent(this));
_bus->send(_server->client(), new LoadFunctionsRequest(this, _name));
_bus->send(_server->worker(), new LoadFunctionsRequest(this, _name));
}
void MongoDatabase::createCollection(const std::string &collection, long long size, bool capped, int maxDocNum, const mongo::BSONObj& extraOptions)
{
_bus->send(_server->client(),
_bus->send(_server->worker(),
new CreateCollectionRequest(this, MongoNamespace(_name, collection), extraOptions, size, capped, maxDocNum));
}
void MongoDatabase::dropCollection(const std::string &collection)
{
_bus->send(_server->client(), new DropCollectionRequest(this, MongoNamespace(_name, collection)));
_bus->send(_server->worker(), new DropCollectionRequest(this, MongoNamespace(_name, collection)));
}
void MongoDatabase::renameCollection(const std::string &collection, const std::string &newCollection)
{
_bus->send(_server->client(), new RenameCollectionRequest(this, MongoNamespace(_name, collection), newCollection));
_bus->send(_server->worker(), new RenameCollectionRequest(this, MongoNamespace(_name, collection),
newCollection));
}
void MongoDatabase::duplicateCollection(const std::string &collection, const std::string &newCollection)
{
_bus->send(_server->client(), new DuplicateCollectionRequest(this, MongoNamespace(_name, collection), newCollection));
_bus->send(_server->worker(), new DuplicateCollectionRequest(this, MongoNamespace(_name, collection), newCollection));
}
void MongoDatabase::copyCollection(MongoServer *server, const std::string &sourceDatabase, const std::string &collection)
{
_bus->send(_server->client(), new CopyCollectionToDiffServerRequest(this, server->client(), sourceDatabase, collection, _name));
_bus->send(_server->worker(), new CopyCollectionToDiffServerRequest(this, server->worker(), sourceDatabase, collection, _name));
}
void MongoDatabase::createUser(const MongoUser &user, bool overwrite)
{
_bus->send(_server->client(), new CreateUserRequest(this, _name, user, overwrite));
_bus->send(_server->worker(), new CreateUserRequest(this, _name, user, overwrite));
}
void MongoDatabase::dropUser(const mongo::OID &id)
void MongoDatabase::dropUser(const mongo::OID &id, std::string const& userName)
{
_bus->send(_server->client(), new DropUserRequest(this, _name, id));
_bus->send(_server->worker(), new DropUserRequest(this, _name, id, userName));
}
void MongoDatabase::createFunction(const MongoFunction &fun)
{
_bus->send(_server->client(), new CreateFunctionRequest(this, _name, fun));
_bus->send(_server->worker(), new CreateFunctionRequest(this, _name, fun));
}
void MongoDatabase::updateFunction(const std::string &name, const MongoFunction &fun)
{
_bus->send(_server->client(), new CreateFunctionRequest(this, _name, fun, name));
_bus->send(_server->worker(), new CreateFunctionRequest(this, _name, fun, name));
}
void MongoDatabase::dropFunction(const std::string &name)
{
_bus->send(_server->client(), new DropFunctionRequest(this, _name, name));
_bus->send(_server->worker(), new DropFunctionRequest(this, _name, name));
}
void MongoDatabase::handle(LoadCollectionNamesResponse *loaded)
void MongoDatabase::handle(LoadCollectionNamesResponse *event)
{
if (loaded->isError()) {
_bus->publish(new MongoDatabaseCollectionListLoadedEvent(this, loaded->error()));
if (event->isError()) {
if (_server->connectionRecord()->isReplicaSet()) // replica set
handleIfReplicaSetUnreachable(event);
else // single server
_bus->publish(new MongoDatabaseCollectionListLoadedEvent(this, event->error()));
genericEventErrorHandler(event, "Failed to refresh 'Collections'.", _bus, this);
return;
}
clearCollections();
const std::vector<MongoCollectionInfo> &colectionsInfos = loaded->collectionInfos();
for (std::vector<MongoCollectionInfo>::const_iterator it = colectionsInfos.begin(); it != colectionsInfos.end(); ++it) {
const MongoCollectionInfo &info = *it;
MongoCollection *collection = new MongoCollection(this, info);
addCollection(collection);
}
for (auto const& collectionInfo : event->collectionInfos())
addCollection(new MongoCollection(this, collectionInfo));
_bus->publish(new MongoDatabaseCollectionListLoadedEvent(this, _collections));
LOG_MSG("'Collections' refreshed.", mongo::logger::LogSeverity::Info());
}
void MongoDatabase::handle(CreateFunctionResponse *event)
{
if (event->isError()) {
handleIfReplicaSetUnreachable(event);
genericEventErrorHandler(event, "Failed to create function \'" + event->functionName + "\'.", _bus, this);
}
else {
loadFunctions();
LOG_MSG("Function \'" + event->functionName + "\' created.", mongo::logger::LogSeverity::Info());
}
}
void MongoDatabase::handle(CreateUserResponse *event)
{
if (event->isError())
return;
if (event->isError()) {
handleIfReplicaSetUnreachable(event);
genericEventErrorHandler(event, "Failed to create user \'" + event->userName + "\'.", _bus, this);
}
else {
loadUsers();
LOG_MSG("User \'" + event->userName + "\' created.", mongo::logger::LogSeverity::Info());
}
}
void MongoDatabase::handle(LoadUsersResponse *event)
{
if (event->isError()) {
_bus->publish(new MongoDatabaseUsersLoadedEvent(this, event->error()));
if (_server->connectionRecord()->isReplicaSet()) // replica set
handleIfReplicaSetUnreachable(event);
else // single server
_bus->publish(new MongoDatabaseUsersLoadedEvent(this, event->error()));
genericEventErrorHandler(event, "Failed to refresh 'Users'.", _bus, this);
return;
}
_bus->publish(new MongoDatabaseUsersLoadedEvent(this, this, event->users()));
LOG_MSG("'Users' refreshed.", mongo::logger::LogSeverity::Info());
}
void MongoDatabase::handle(LoadFunctionsResponse *event)
{
if (event->isError()) {
_bus->publish(new MongoDatabaseFunctionsLoadedEvent(this, event->error()));
if (_server->connectionRecord()->isReplicaSet()) // replica set
handleIfReplicaSetUnreachable(event);
else // single server
_bus->publish(new MongoDatabaseFunctionsLoadedEvent(this, event->error()));
genericEventErrorHandler(event, "Failed to refresh 'Functions'.", _bus, this);
return;
}
_bus->publish(new MongoDatabaseFunctionsLoadedEvent(this, this, event->functions()));
LOG_MSG("'Functions' refreshed.", mongo::logger::LogSeverity::Info());
}
void MongoDatabase::clearCollections()
@@ -159,18 +194,91 @@ namespace Robomongo
_collections.push_back(collection);
}
void MongoDatabase::handle(CreateCollectionResponse *event) {
genericResponseHandler(event, "Failed to create collection.");
void MongoDatabase::handle(CreateCollectionResponse *event)
{
if (event->isError()) {
handleIfReplicaSetUnreachable(event);
genericEventErrorHandler(event, "Failed to create collection \'" + event->collection + "\'.", _bus, this);
}
else {
loadCollections();
LOG_MSG("Collection \'" + event->collection + "\' created.", mongo::logger::LogSeverity::Info());
}
}
void MongoDatabase::handle(DropCollectionResponse *event) {
genericResponseHandler(event, "Failed to drop collection.");
void MongoDatabase::handle(DropCollectionResponse *event)
{
if (event->isError()) {
handleIfReplicaSetUnreachable(event);
genericEventErrorHandler(event, "Failed to drop collection \'" + event->collection + "\'.", _bus, this);
}
else {
loadCollections();
LOG_MSG("Collection \'" + event->collection + "\' dropped", mongo::logger::LogSeverity::Info());
}
}
void MongoDatabase::genericResponseHandler(Event *event, const std::string &userFriendlyMessage) {
if (!event->isError())
void MongoDatabase::handle(DropFunctionResponse *event)
{
if (event->isError()) {
handleIfReplicaSetUnreachable(event);
genericEventErrorHandler(event, "Failed to remove function \'" + event->functionName + "\'.", _bus, this);
}
else {
loadFunctions();
LOG_MSG("Function \'" + event->functionName + "\' removed", mongo::logger::LogSeverity::Info());
}
}
void MongoDatabase::handle(DropUserResponse *event)
{
if (event->isError()) {
handleIfReplicaSetUnreachable(event);
genericEventErrorHandler(event, "Failed to drop user \'" + event->username + "\'.", _bus, this);
}
else {
loadUsers();
LOG_MSG("User \'" + event->username + "\' dropped", mongo::logger::LogSeverity::Info());
}
}
void MongoDatabase::handle(RenameCollectionResponse *event)
{
if (event->isError()) {
handleIfReplicaSetUnreachable(event);
genericEventErrorHandler(event, "Failed to rename collection.", _bus, this);
}
else {
loadCollections();
LOG_MSG("Collection \'" + event->oldCollection + "\' renamed to \'" +
event->newCollection +"\'." , mongo::logger::LogSeverity::Info());
}
}
void MongoDatabase::handle(DuplicateCollectionResponse *event)
{
if (event->isError()) {
handleIfReplicaSetUnreachable(event);
genericEventErrorHandler(event, "Failed to duplicate collection \'" + event->sourceCollection + "\'.",
_bus, this);
}
else {
loadCollections();
LOG_MSG("Collection \'" + event->sourceCollection + "\' duplicated as \'" +
event->duplicateCollection + "\'.", mongo::logger::LogSeverity::Info());
}
}
void MongoDatabase::handleIfReplicaSetUnreachable(Event *event)
{
if (!_server->connectionRecord()->isReplicaSet())
return;
_bus->publish(new OperationFailedEvent(this, event->error().errorMessage(), userFriendlyMessage));
if (EventError::SetPrimaryUnreachable == event->error().errorCode()) {
auto refreshEvent = ReplicaSetRefreshed(this, event->error(), event->error().replicaSetInfo());
_server->handle(&refreshEvent);
}
}
}
+7 -2
View File
@@ -63,7 +63,7 @@ namespace Robomongo
void copyCollection(MongoServer *server, const std::string &sourceDatabase, const std::string &collection);
void createUser(const MongoUser &user, bool overwrite);
void dropUser(const mongo::OID &id);
void dropUser(const mongo::OID &id, std::string const& userName);
void createFunction(const MongoFunction &fun);
void updateFunction(const std::string &name, const MongoFunction &fun);
@@ -83,14 +83,19 @@ namespace Robomongo
void handle(LoadCollectionNamesResponse *event);
void handle(LoadUsersResponse *event);
void handle(LoadFunctionsResponse *event);
void handle(CreateFunctionResponse *event);
void handle(CreateUserResponse *event);
void handle(CreateCollectionResponse *event);
void handle(DropCollectionResponse *event);
void handle(DropFunctionResponse *event);
void handle(DropUserResponse *event);
void handle(RenameCollectionResponse *event);
void handle(DuplicateCollectionResponse *event);
private:
void clearCollections();
void addCollection(MongoCollection *collection);
void genericResponseHandler(Event *event, const std::string &userFriendlyMessage);
void handleIfReplicaSetUnreachable(Event *event);
private:
MongoServer *_server;
+351 -110
View File
@@ -4,12 +4,17 @@
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/settings/SettingsManager.h"
#include "robomongo/core/settings/ReplicaSetSettings.h"
#include "robomongo/core/mongodb/MongoWorker.h"
#include "robomongo/core/mongodb/SshTunnelWorker.h"
#include "robomongo/core/AppRegistry.h"
#include "robomongo/core/domain/App.h"
#include "robomongo/core/EventBus.h"
#include "robomongo/core/events/MongoEventsInfo.h"
#include "robomongo/core/utils/Logger.h"
#include "robomongo/core/utils/QtUtils.h"
#include "robomongo/utils/common.h"
#include "robomongo/utils/string_operations.h"
namespace Robomongo {
R_REGISTER_EVENT(MongoServerLoadingDatabasesEvent)
@@ -17,45 +22,64 @@ namespace Robomongo {
MongoServer::MongoServer(int handle, ConnectionSettings *settings, ConnectionType connectionType) : QObject(),
_version(0.0f),
_connectionType(connectionType),
_client(NULL),
_worker(nullptr),
_isConnected(false),
_settings(settings),
_connSettings(settings),
_handle(handle),
_bus(AppRegistry::instance().bus()),
_app(AppRegistry::instance().app()) { }
_app(AppRegistry::instance().app()),
_replicaSetInfo(nullptr)
{}
bool MongoServer::isConnected() const {
return _isConnected;
}
ConnectionSettings *MongoServer::connectionRecord() const {
return _settings;
return _connSettings.get();
}
MongoServer::~MongoServer() {
clearDatabases();
if (_client != NULL) {
_client->stopAndDelete();
if (_worker) {
_worker->stopAndDelete();
}
// MongoWorker "_client" does not deleted here, because it is now owned by
// MongoWorker "_worker" is 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() {
_bus->send(_client, new EstablishConnectionRequest(this, _connectionType));
void MongoServer::tryConnect()
{
_bus->send(_worker, new EstablishConnectionRequest(this, _connectionType, _connSettings->uuid().toStdString()));
}
QStringList MongoServer::getDatabasesNames() const {
void MongoServer::tryRefresh()
{
_bus->send(_worker, new EstablishConnectionRequest(this, ConnectionRefresh, _connSettings->uuid().toStdString()));
}
void MongoServer::tryRefreshReplicaSetConnection()
{
_bus->send(_worker, new EstablishConnectionRequest(this, ConnectionRefresh, _connSettings->uuid().toStdString()));
}
void MongoServer::tryRefreshReplicaSetFolder(bool expanded, bool showLoading /*= true*/)
{
if (!_connSettings->isReplicaSet())
return;
if (showLoading)
_bus->publish(new ReplicaSetFolderLoading(this));
_bus->send(_worker, new RefreshReplicaSetFolderRequest(this, expanded));
}
QStringList MongoServer::getDatabasesNames() const
{
QStringList result;
for (QList<MongoDatabase *>::const_iterator it = _databases.begin(); it != _databases.end(); ++it) {
MongoDatabase *datab = *it;
@@ -64,11 +88,17 @@ namespace Robomongo {
return result;
}
void MongoServer::createDatabase(const std::string &dbName) {
_bus->send(_client, new CreateDatabaseRequest(this, dbName));
void MongoServer::addDatabase(MongoDatabase *database) {
_databases.append(database);
}
MongoDatabase *MongoServer::findDatabaseByName(const std::string &dbName) const {
void MongoServer::createDatabase(const std::string &dbName)
{
_bus->send(_worker, new CreateDatabaseRequest(this, dbName));
}
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) {
@@ -79,7 +109,7 @@ namespace Robomongo {
}
void MongoServer::dropDatabase(const std::string &dbName) {
_bus->send(_client, new DropDatabaseRequest(this, dbName));
_bus->send(_worker, new DropDatabaseRequest(this, dbName));
}
void MongoServer::insertDocuments(const std::vector<mongo::BSONObj> &objCont,
@@ -90,7 +120,7 @@ namespace Robomongo {
}
void MongoServer::insertDocument(const mongo::BSONObj &obj, const MongoNamespace &ns) {
_bus->send(_client, new InsertDocumentRequest(this, obj, ns));
_bus->send(_worker, new InsertDocumentRequest(this, obj, ns));
}
void MongoServer::saveDocuments(const std::vector<mongo::BSONObj> &objCont, const MongoNamespace &ns) {
@@ -100,33 +130,315 @@ namespace Robomongo {
}
void MongoServer::saveDocument(const mongo::BSONObj &obj, const MongoNamespace &ns) {
_bus->send(_client, new InsertDocumentRequest(this, obj, ns, true));
_bus->send(_worker, new InsertDocumentRequest(this, obj, ns, true));
}
void MongoServer::removeDocuments(mongo::Query query, const MongoNamespace &ns, bool justOne) {
_bus->send(_client, new RemoveDocumentRequest(this, query, ns, justOne));
void MongoServer::removeDocuments(mongo::Query query, const MongoNamespace &ns,
RemoveDocumentCount removeCount, int index)
{
_bus->send(_worker, new RemoveDocumentRequest(this, query, ns, removeCount, index));
}
void MongoServer::loadDatabases() {
void MongoServer::loadDatabases()
{
_bus->publish(new MongoServerLoadingDatabasesEvent(this));
_bus->send(_client, new LoadDatabaseNamesRequest(this));
if (_connSettings->isReplicaSet()) {
tryRefreshReplicaSetConnection();
}
else { // single server
_bus->send(_worker, new LoadDatabaseNamesRequest(this));
}
}
void MongoServer::clearDatabases() {
void MongoServer::clearDatabases()
{
qDeleteAll(_databases);
_databases.clear();
}
void MongoServer::addDatabase(MongoDatabase *database) {
_databases.append(database);
void MongoServer::handle(EstablishConnectionResponse *event)
{
_connectionType = event->connectionType;
// In any case, replica set info must be updated, there might be reachable secondary(ies).
// Also cached set name must be updated or cleared for failed connections.
updateReplicaSetSettings(event);
// --- Connection Failed
if (event->isError()) {
handleConnectionFailure(event);
return;
}
// --- Connections Successful
// Save various information after successful connection
const ConnectionInfo &info = event->info;
_version = info._version;
_storageEngineType = info._storageEngineType;
_isConnected = true;
// ConnectionRefresh is used just to update connection view (_version, _storageEngineType, _repPrimary etc..)
// So we return here after updating(refreshing) information related to connection view.
if (ConnectionRefresh == event->connectionType) {
if (_connSettings->isReplicaSet()) {
// If it is replica set connection, do not return yet.
LOG_MSG("Replica set refreshed. [" + _connSettings->connectionName() + ']',
mongo::logger::LogSeverity::Info());
}
else { // single server
LOG_MSG("Server refreshed.", mongo::logger::LogSeverity::Info());
return;
}
}
// Only for single servers
if (!_connSettings->isReplicaSet()) {
_bus->publish(new ConnectionEstablishedEvent(this, _connectionType, info));
// Do nothing if this is not a "primary" connection
if (ConnectionPrimary != _connectionType)
return;
}
if (ConnectionPrimary == _connectionType)
LOG_MSG("Establish connection successful. Connection: " + _connSettings->connectionName(),
mongo::logger::LogSeverity::Info());
clearDatabases();
for (auto const& dbname : info._databases) {
MongoDatabase *db = new MongoDatabase(this, dbname);
addDatabase(db); // todo: serverClones for replica sets should not do this
}
if (_connSettings->isReplicaSet()) {
_bus->publish(new ConnectionEstablishedEvent(this, event->connectionType, info));
// In order to do first connection much faster, time consuming refresh
// "repSetMonitor->startOrContinueRefresh(). refreshAll()" is being requested after
// successful connection.
if (ConnectionPrimary == event->connectionType)
_bus->send(_worker, new RefreshReplicaSetFolderRequest(this, false));
}
}
void MongoServer::handle(EstablishConnectionResponse *event) {
if (event->isError()) {
_isConnected = false;
void MongoServer::handle(RefreshReplicaSetFolderResponse *event)
{
handleReplicaSetRefreshEvents(event->isError(), event->error(), event->replicaSet, event->expanded);
}
std::stringstream ss("Unknown error");
auto eventErrorReason = event->errorReason;
void MongoServer::handle(LoadDatabaseNamesResponse *event)
{
if (event->isError()) {
_bus->publish(new DatabaseListLoadedEvent(this, event->error()));
return;
}
clearDatabases();
for (auto const& dbname : event->databaseNames) {
MongoDatabase *db = new MongoDatabase(this, dbname);
addDatabase(db);
}
_bus->publish(new DatabaseListLoadedEvent(this, _databases));
LOG_MSG("Database list refreshed. Connection: " + _connSettings->connectionName(),
mongo::logger::LogSeverity::Info());
}
void MongoServer::handle(InsertDocumentResponse *event)
{
if (event->isError()) {
if (_connSettings->isReplicaSet()) {
if (ConnectionPrimary == _connectionType) { // Insert document from explorer context menu
if (EventError::SetPrimaryUnreachable == event->error().errorCode()) {
auto refreshEvent = ReplicaSetRefreshed(this, event->error(), event->error().replicaSetInfo());
handle(&refreshEvent);
}
}
else { // Insert document from tab results window (Notifier, OutputWindow widget)
_bus->publish(new InsertDocumentResponse(this, event->error()));
}
}
genericEventErrorHandler(event, "Failed to insert document.", _bus, this);
}
else {
_bus->publish(new InsertDocumentResponse(this, event->error()));
LOG_MSG("Document inserted.", mongo::logger::LogSeverity::Info());
}
}
void MongoServer::handle(RemoveDocumentResponse *event)
{
if (event->removeCount == RemoveDocumentCount::MULTI && event->index > 0)
return;
std::string subStr;
switch (event->removeCount) {
case RemoveDocumentCount::ONE: subStr = "document."; break;
case RemoveDocumentCount::MULTI: subStr = "documents."; break;
case RemoveDocumentCount::ALL: subStr = "all documents."; break;
default: subStr = "(logic error)."; break;
}
if (event->isError()) {
if (_connSettings->isReplicaSet() &&
EventError::SetPrimaryUnreachable == event->error().errorCode()) {
auto refreshEvent = ReplicaSetRefreshed(this, event->error(), event->error().replicaSetInfo());
handle(&refreshEvent);
}
genericEventErrorHandler(event, "Failed to remove " + subStr, _bus, this);
}
else { // success
_bus->publish(new RemoveDocumentResponse(this, event->error(), event->removeCount, event->index));
LOG_MSG("Removed " + subStr, mongo::logger::LogSeverity::Info());
}
}
void MongoServer::runWorkerThread()
{
_worker = new MongoWorker(_connSettings->clone(),
AppRegistry::instance().settingsManager()->loadMongoRcJs(),
AppRegistry::instance().settingsManager()->batchSize(),
AppRegistry::instance().settingsManager()->mongoTimeoutSec(),
AppRegistry::instance().settingsManager()->shellTimeoutSec());
}
void MongoServer::handle(CreateDatabaseResponse *event)
{
if (event->isError()) {
if (_connSettings->isReplicaSet() &&
EventError::SetPrimaryUnreachable == event->error().errorCode()) {
auto refreshEvent = ReplicaSetRefreshed(this, event->error(), event->error().replicaSetInfo());
handle(&refreshEvent);
}
genericEventErrorHandler(event, "Failed to create database \'" + event->database + "\'.", _bus, this);
}
else {
loadDatabases();
LOG_MSG("Database \'" + event->database + "\' created.", mongo::logger::LogSeverity::Info());
}
}
void MongoServer::handle(DropDatabaseResponse *event)
{
if (event->isError()) {
if (_connSettings->isReplicaSet() &&
EventError::SetPrimaryUnreachable == event->error().errorCode()) {
auto refreshEvent = ReplicaSetRefreshed(this, event->error(), event->error().replicaSetInfo());
handle(&refreshEvent);
}
genericEventErrorHandler(event, "Failed to drop database \'" + event->database + "\'.", _bus, this);
}
else {
loadDatabases();
LOG_MSG("Database \'" + event->database + "\' dropped.", mongo::logger::LogSeverity::Info());
}
}
void MongoServer::handle(ReplicaSetRefreshed *event)
{
handleReplicaSetRefreshEvents(event->isError(), event->error(), event->replicaSet, false);
}
void MongoServer::handleReplicaSetRefreshEvents(bool isError, EventError eventError,
ReplicaSet const& replicaSet, bool expanded)
{
if (isError) { // Primary is unreachable
_replicaSetInfo.reset(new ReplicaSet(replicaSet));
LOG_MSG("Replica set folder refreshed with error. " + eventError.errorMessage() +
". Connection: " + _connSettings->connectionName(), mongo::logger::LogSeverity::Error());
_bus->publish(new ReplicaSetFolderRefreshed(this, eventError, true));
return;
}
// Primary is reachable
// Update replica set settings and mongo server _replicaSetInfo
_replicaSetInfo.reset(new ReplicaSet(replicaSet));
_connSettings->setServerHost(_replicaSetInfo->primary.host());
_connSettings->setServerPort(_replicaSetInfo->primary.port());
_connSettings->replicaSetSettings()->setCachedSetName(
_connSettings->replicaSetSettings()->setNameUserEntered().empty() ? _replicaSetInfo->setName : "");
LOG_MSG("Replica set folder refreshed. Connection: " + _connSettings->connectionName(),
mongo::logger::LogSeverity::Info());
_bus->publish(new ReplicaSetFolderRefreshed(this, expanded));
}
void MongoServer::updateReplicaSetSettings(EstablishConnectionResponse* event)
{
if (!_connSettings->isReplicaSet())
return;
_replicaSetInfo.reset(new ReplicaSet(event->replicaSet));
_connSettings->setServerHost(_replicaSetInfo->primary.host());
_connSettings->setServerPort(_replicaSetInfo->primary.port());
_connSettings->replicaSetSettings()->setCachedSetName(
_connSettings->replicaSetSettings()->setNameUserEntered().empty() ? _replicaSetInfo->setName : "");
if (_connSettings->replicaSetSettings()->setNameUserEntered().empty()) {
// Cache replica set name for 2 times faster first connection
if (ConnectionPrimary == _connectionType) {
ConnectionSettings* originalConnSettings = AppRegistry::instance().settingsManager()
->getConnectionSettingsByUuid(event->info._uuid);
if (originalConnSettings) {
auto setName = event->isError() ? "" : _replicaSetInfo->setName;
originalConnSettings->replicaSetSettings()->setCachedSetName(setName);
AppRegistry::instance().settingsManager()->save();
LOG_MSG("Replica set name cached as \"" + setName + "\".", mongo::logger::LogSeverity::Info());
}
else
LOG_MSG("Failed to cache the replica set name.", mongo::logger::LogSeverity::Warning());
}
}
else { // User entered set name is not empty, clear cached set name just in case
ConnectionSettings* originalConnSettings = AppRegistry::instance().settingsManager()
->getConnectionSettingsByUuid(event->info._uuid);
if (originalConnSettings) {
originalConnSettings->replicaSetSettings()->setCachedSetName("");
AppRegistry::instance().settingsManager()->save();
LOG_MSG("Replica set's cached set name cleared. Using user entered set name.",
mongo::logger::LogSeverity::Info());
}
}
}
void MongoServer::handleConnectionFailure(EstablishConnectionResponse* event)
{
_isConnected = false;
std::stringstream ss("Unknown error");
auto eventErrorReason = event->errorReason;
if (_connSettings->isReplicaSet())
{
ss.clear();
std::string server = "";
if (_connSettings->replicaSetSettings()->members().size() > 0)
server = "[" + _connSettings->replicaSetSettings()->members().front() + "]";
if (event->error().errorCode() == EventError::ErrorCode::ServerHasDifferentMembers) {
ss << "Cannot connect to replica set \"" << _connSettings->connectionName() << "\"" << server
<< ". \n\nA primary with different host name [" << event->replicaSet.primary <<
"] found in server side. "
"Please double check if same host names and ports are used as in server's replica set"
" configuration. \nIf same set name is used for different replica sets, this configuration"
" is supported only on different instances of Robomongo. "
" Please open a new Robomongo instance for each replica set which has the same set name."
"\n\nReason:\n" << event->error().errorMessage();
}
else {
ss << "Cannot connect to replica set \"" << _connSettings->connectionName() << "\"" << server
<< ". \nSet's primary is unreachable.\n\nReason:\n" << event->error().errorMessage();
}
_bus->publish(new ConnectionFailedEvent(this, _handle, event->connectionType, ss.str(),
ConnectionFailedEvent::MongoConnection));
_app->fireConnectionFailedEvent(_handle, event->connectionType, ss.str(),
ConnectionFailedEvent::MongoConnection);
LOG_MSG("Establish connection failed. " + event->error().errorMessage() + ". Connection: "
+ _connSettings->connectionName(), mongo::logger::LogSeverity::Error());
}
else // single server
{
if (EstablishConnectionResponse::ErrorReason::MongoSslConnection == eventErrorReason)
{
auto reason = ConnectionFailedEvent::SslConnection;
@@ -147,85 +459,14 @@ namespace Robomongo {
// When connection cannot be established, we should cleanup this instance of MongoServer if it wasn't
// shown in UI (i.e. it is not a Secondary connection that is used for shells tab)
if (_connectionType == ConnectionPrimary || _connectionType == ConnectionTest)
if (_connectionType == ConnectionPrimary || _connectionType == ConnectionTest)
{
LOG_MSG("Establish connection failed. " + event->error().errorMessage() +
". Connection: " + _connSettings->connectionName(),
mongo::logger::LogSeverity::Error());
_app->closeServer(this);
}
return;
}
// Save various information after successful connection
const ConnectionInfo &info = event->info();
_version = info._version;
_storageEngineType = info._storageEngineType;
_isConnected = true;
// Save Replica Set Status
_repPrimary = event->getRepPrimary(); // todo: what happens to this member if not replica set?
_repMembersHealths = event->getRepMembersHealths();
if (_settings->isReplicaSet()) {
_settings->setServerHost(_repPrimary.host());
_settings->setServerPort(_repPrimary.port());
}
_bus->publish(new ConnectionEstablishedEvent(this, _connectionType));
// Do nothing if this is not a "primary" connection
if (_connectionType != ConnectionPrimary)
return;
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) {
if (event->isError()) {
_bus->publish(new DatabaseListLoadedEvent(this, event->error()));
return;
}
clearDatabases();
for (std::vector<std::string>::iterator it = event->databaseNames.begin(); it != event->databaseNames.end(); ++it) {
const std::string &name = *it;
MongoDatabase *db = new MongoDatabase(this, name);
addDatabase(db);
}
_bus->publish(new DatabaseListLoadedEvent(this, _databases));
}
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(),
AppRegistry::instance().settingsManager()->mongoTimeoutSec(),
AppRegistry::instance().settingsManager()->shellTimeoutSec());
}
void MongoServer::handle(CreateDatabaseResponse *event) {
genericResponseHandler(event, "Failed to create database.");
}
void MongoServer::handle(DropDatabaseResponse *event) {
genericResponseHandler(event, "Failed to drop database.");
}
void MongoServer::genericResponseHandler(Event *event, const std::string &userFriendlyMessage) {
if (!event->isError())
return;
_bus->publish(new OperationFailedEvent(this, event->error().errorMessage(), userFriendlyMessage));
}
}
} // namespace Robomongo
+50 -19
View File
@@ -12,11 +12,12 @@ namespace Robomongo
class App;
// Messages
class EstablishConnectionResponse;
struct EstablishConnectionResponse;
struct RefreshReplicaSetFolderResponse;
class LoadDatabaseNamesResponse;
class InsertDocumentResponse;
class CreateDatabaseResponse;
class DropDatabaseResponse;
struct CreateDatabaseResponse;
struct DropDatabaseResponse;
/**
* @brief MongoServer represents active connection to MongoDB server.
@@ -40,22 +41,52 @@ namespace Robomongo
void runWorkerThread();
/**
* @brief Try to connect to MongoDB server.
* @brief Try to connect to MongoDB single server or replica set.
* @throws MongoException, if fails
*/
void tryConnect();
bool isConnected()const;
/**
* @brief Try to re-connect to MongoDB server in order to refresh connection view.
* Never shown in Explorer and can be used to refresh (via reconnecting) current connection view.
* (i.e. db version, storage engine etc...)
* @throws MongoException, if fails
*/
void tryRefresh();
/**
* @brief Try to re-connect to MongoDB replica set in order to refresh connection view.
* It is used to refresh (via reconnecting) current connection view.
* (i.e. db version, storage engine, current replica set primary, status of replica set etc...)
* It will update Explorer widgets depending on replica set status (online if primary reachable and
* offline otherwise)
*/
void tryRefreshReplicaSetConnection();
/**
* @brief Try to re-connect to MongoDB replica set in order to refresh connection view.
* It is used to refresh (via reconnecting) current connection view.
* (i.e. db version, storage engine, current replica set primary, status of replica set etc...)
* It will update only 'Replica Set' folder widgets depending on replica set status
* (online if primary reachable and offline otherwise)
*/
void tryRefreshReplicaSetFolder(bool expanded, bool showLoading = true);
bool isConnected() const;
void addDatabase(MongoDatabase *database);
void createDatabase(const std::string &dbName);
void dropDatabase(const std::string &dbName);
QStringList getDatabasesNames() const;
QList<MongoDatabase*> const& databases() const { return _databases; };
MongoDatabase *findDatabaseByName(const std::string &dbName) const;
void insertDocuments(const std::vector<mongo::BSONObj> &objCont, const MongoNamespace &ns);
void insertDocument(const mongo::BSONObj &obj, const MongoNamespace &ns);
void saveDocuments(const std::vector<mongo::BSONObj> &objCont, const MongoNamespace &ns);
void saveDocument(const mongo::BSONObj &obj, const MongoNamespace &ns);
void removeDocuments(mongo::Query query, const MongoNamespace &ns, bool justOne = true);
void removeDocuments(mongo::Query query, const MongoNamespace &ns, RemoveDocumentCount removeCount,
int index = 0);
float version() const{ return _version; }
const std::string& getStorageEngineType() const { return _storageEngineType; }
@@ -68,27 +99,30 @@ namespace Robomongo
* @brief Loads databases of this server asynchronously.
*/
void loadDatabases();
MongoWorker *const client() const { return _client; }
MongoWorker *const worker() const { return _worker; }
// --- Getters ---
const mongo::HostAndPort& getRepPrimary() const { return _repPrimary; }
const std::vector<bool>& getRepMembersHealths() const { return _repMembersHealths; }
ReplicaSet* replicaSetInfo() const { return _replicaSetInfo.get(); }
void handle(ReplicaSetRefreshed *event);
protected Q_SLOTS:
void handle(EstablishConnectionResponse *event);
void handle(RefreshReplicaSetFolderResponse *event);
void handle(LoadDatabaseNamesResponse *event);
void handle(InsertDocumentResponse *event);
void handle(RemoveDocumentResponse *event);
void handle(CreateDatabaseResponse *event);
void handle(DropDatabaseResponse *event);
private:
private:
void clearDatabases();
void addDatabase(MongoDatabase *database);
void genericResponseHandler(Event *event, const std::string &userFriendlyMessage);
void handleReplicaSetRefreshEvents(bool isError, EventError eventError, ReplicaSet const& replicaSet,
bool expanded);
void updateReplicaSetSettings(EstablishConnectionResponse* event);
void handleConnectionFailure(EstablishConnectionResponse* event);
MongoWorker *_client;
ConnectionSettings *_settings;
MongoWorker *_worker;
std::unique_ptr<ConnectionSettings> _connSettings;
EventBus *_bus;
App *_app;
@@ -99,10 +133,7 @@ namespace Robomongo
int _handle;
QList<MongoDatabase *> _databases;
// Replica Set Status
mongo::HostAndPort _repPrimary;
std::vector<bool> _repMembersHealths; // todo: vector of pairs of host and health
std::unique_ptr<ReplicaSet> _replicaSetInfo;
};
class MongoServerLoadingDatabasesEvent : public Event
+18 -9
View File
@@ -11,7 +11,7 @@
#include "robomongo/core/settings/SettingsManager.h"
namespace Robomongo
{
{
MongoShell::MongoShell(MongoServer *server, const ScriptInfo &scriptInfo) :
QObject(),
@@ -24,7 +24,7 @@ namespace Robomongo
{
AppRegistry::instance().bus()->publish(new ScriptExecutingEvent(this));
_scriptInfo.setScript(QtUtils::toQString(script));
AppRegistry::instance().bus()->send(_server->client(), new ExecuteScriptRequest(this, query(), dbName));
AppRegistry::instance().bus()->send(_server->worker(), new ExecuteScriptRequest(this, query(), dbName));
LOG_MSG(_scriptInfo.script(), mongo::logger::LogSeverity::Info());
}
@@ -37,19 +37,19 @@ namespace Robomongo
{
if (_scriptInfo.execute()) {
AppRegistry::instance().bus()->publish(new ScriptExecutingEvent(this));
AppRegistry::instance().bus()->send(_server->client(), new ExecuteScriptRequest(this, query(), dbName));
AppRegistry::instance().bus()->send(_server->worker(), new ExecuteScriptRequest(this, query(), dbName));
if (!_scriptInfo.script().isEmpty())
LOG_MSG(_scriptInfo.script(), mongo::logger::LogSeverity::Info());
} else {
AppRegistry::instance().bus()->publish(new ScriptExecutingEvent(this));
_scriptInfo.setScript("");
AppRegistry::instance().bus()->send(_server->client(), new ExecuteScriptRequest(this, query() , dbName));
AppRegistry::instance().bus()->send(_server->worker(), new ExecuteScriptRequest(this, query() , dbName));
}
}
void MongoShell::query(int resultIndex, const MongoQueryInfo &info)
{
AppRegistry::instance().bus()->send(_server->client(), new ExecuteQueryRequest(this, resultIndex, info));
AppRegistry::instance().bus()->send(_server->worker(), new ExecuteQueryRequest(this, resultIndex, info));
}
void MongoShell::autocomplete(const std::string &prefix)
@@ -57,12 +57,12 @@ namespace Robomongo
AutocompletionMode autocompletionMode = AppRegistry::instance().settingsManager()->autocompletionMode();
if (autocompletionMode == AutocompleteNone)
return;
AppRegistry::instance().bus()->send(_server->client(), new AutocompleteRequest(this, prefix, autocompletionMode));
AppRegistry::instance().bus()->send(_server->worker(), new AutocompleteRequest(this, prefix, autocompletionMode));
}
void MongoShell::stop()
{
// _server->client()->interrupt();
// _server->worker()->interrupt();
// mongo::Scope::setInterruptFlag(true);
}
@@ -94,8 +94,17 @@ namespace Robomongo
void MongoShell::handle(ExecuteScriptResponse *event)
{
if (event->isError()) {
AppRegistry::instance().bus()->publish(new ScriptExecutedEvent(this, event->error()));
return;
if (_server->connectionRecord()->isReplicaSet()) {
AppRegistry::instance().bus()->publish(
new ReplicaSetRefreshed(this, event->error(), event->error().replicaSetInfo()));
AppRegistry::instance().bus()->publish(new ScriptExecutedEvent(this, event->error()));
return;
}
else { // single server
AppRegistry::instance().bus()->publish(new ScriptExecutedEvent(this, event->error()));
return;
}
}
AppRegistry::instance().bus()->publish(new ScriptExecutedEvent(this, event->result, event->empty));
+10 -1
View File
@@ -29,15 +29,22 @@ namespace Robomongo
{
public:
MongoShellExecResult() { }
MongoShellExecResult(const std::vector<MongoShellResult> &results,
const std::string &currentServer, bool isCurrentServerValid,
const std::string &currentDatabase, bool isCurrentDatabaseValid);
MongoShellExecResult(bool error, std::string const& errorMsg = "") :
_error(error), _errorMessage(errorMsg) { }
std::vector<MongoShellResult> results() const { return _results; }
std::string currentServer() const { return _currentServer; }
std::string currentDatabase() const { return _currentDatabase; }
void setCurrentServer(std::string const& server) { _currentServer = server; }
std::string currentDatabase() const { return _currentDatabase; }
bool isCurrentServerValid() const { return _isCurrentServerValid; }
bool isCurrentDatabaseValid() const { return _isCurrentDatabaseValid; }
std::string errorMessage() const { return _errorMessage; }
bool error() const { return _error; }
private:
std::vector<MongoShellResult> _results;
@@ -45,5 +52,7 @@ namespace Robomongo
std::string _currentDatabase;
bool _isCurrentServerValid;
bool _isCurrentDatabaseValid;
std::string _errorMessage;
bool _error = false;
};
}
+98 -91
View File
@@ -25,10 +25,10 @@ namespace Robomongo
{
namespace detail
{
bool isSimpleType(Robomongo::BsonTreeItem *item)
bool isSimpleType(Robomongo::BsonTreeItem const *item)
{
return Robomongo::BsonUtils::isSimpleType(item->type())
|| Robomongo::BsonUtils::isUuidType(item->type(), item->binType());
return BsonUtils::isSimpleType(item->type()) ||
BsonUtils::isUuidType(item->type(), item->binType());
}
bool isObjectIdType(Robomongo::BsonTreeItem *item)
@@ -41,17 +41,17 @@ namespace Robomongo
return indexes.count() > 1;
}
bool isDocumentType(BsonTreeItem *item)
bool isDocumentType(BsonTreeItem const *item)
{
return Robomongo::BsonUtils::isDocument(item->type());
return BsonUtils::isDocument(item->type());
}
bool isArrayChild(BsonTreeItem *item)
bool isArrayChild(BsonTreeItem const *item)
{
return Robomongo::BsonUtils::isArray(dynamic_cast<BsonTreeItem *>(item->parent())->type());
return BsonUtils::isArray(dynamic_cast<BsonTreeItem*>(item->parent())->type());
}
bool isDocumentRoot(BsonTreeItem *item)
bool isDocumentRoot(BsonTreeItem const *item)
{
return ( item == item->superParent() );
}
@@ -102,8 +102,8 @@ namespace Robomongo
_queryInfo(queryInfo)
{
QWidget *wid = dynamic_cast<QWidget*>(_observer);
AppRegistry::instance().bus()->subscribe(this, InsertDocumentResponse::Type);
AppRegistry::instance().bus()->subscribe(this, RemoveDocumentResponse::Type);
AppRegistry::instance().bus()->subscribe(this, InsertDocumentResponse::Type, _shell->server());
AppRegistry::instance().bus()->subscribe(this, RemoveDocumentResponse::Type, _shell->server());
_deleteDocumentAction = new QAction("Delete Document...", wid);
VERIFY(connect(_deleteDocumentAction, SIGNAL(triggered()), SLOT(onDeleteDocument())));
@@ -158,19 +158,18 @@ namespace Robomongo
if (onItem && isEditable) menu->addAction(_editDocumentAction);
if (onItem) menu->addAction(_viewDocumentAction);
if (isEditable) menu->addAction(_insertDocumentAction);
if (onItem && (isSimple || isDocument)) menu->addSeparator();
if (onItem && isSimple) menu->addAction(_copyValueAction);
if (onItem && (isSimple || isDocument) && isNotArrayChild && !isRoot)
menu->addAction(_copyValueNameAction);
if (onItem && (isSimple || isDocument) && !isRoot)
menu->addAction(_copyValuePathAction);
if (onItem && isObjectId) menu->addAction(_copyTimestampAction);
if (onItem && isDocument) menu->addAction(_copyJsonAction);
if (onItem && isEditable) menu->addSeparator();
if (onItem && isEditable) menu->addAction(_deleteDocumentAction);
}
@@ -182,11 +181,12 @@ namespace Robomongo
if (isEditable) menu->addAction(_deleteDocumentsAction);
}
void Notifier::deleteDocuments(std::vector<BsonTreeItem*> items, bool force)
void Notifier::deleteDocuments(std::vector<BsonTreeItem*> const& items, bool force)
{
bool isNeededRefresh = false;
for (std::vector<BsonTreeItem*>::const_iterator it = items.begin(); it != items.end(); ++it) {
BsonTreeItem * documentItem = *it;
int index = 0;
for (auto const * const documentItem : items) {
if (!documentItem)
break;
@@ -194,7 +194,8 @@ namespace Robomongo
mongo::BSONElement id = obj.getField("_id");
if (id.eoo()) {
QMessageBox::warning(dynamic_cast<QWidget*>(_observer), "Cannot delete", "Selected document doesn't have _id field. \n"
QMessageBox::warning(dynamic_cast<QWidget*>(_observer), "Cannot delete",
"Selected document doesn't have _id field. \n"
"Maybe this is a system document that should be managed in a special way?");
break;
}
@@ -214,44 +215,97 @@ namespace Robomongo
}
isNeededRefresh = true;
_shell->server()->removeDocuments(query, _queryInfo._info._ns);
}
if (isNeededRefresh)
_shell->query(0, _queryInfo);
RemoveDocumentCount removeCount = items.size() == 1 ? RemoveDocumentCount::ONE :
RemoveDocumentCount::MULTI;
_shell->server()->removeDocuments(query, _queryInfo._info._ns, removeCount, index);
++index;
}
}
void Notifier::handle(InsertDocumentResponse *event)
{
if (event->isError()) {
QMessageBox::warning(NULL, "Database Error", QString::fromStdString(event->error().errorMessage()));
if (event->isError()) { // Error
if (_shell->server()->connectionRecord()->isReplicaSet()) {
// Insert document from tab results window (Notifier, OutputWindow widget)
if (EventError::SetPrimaryUnreachable == event->error().errorCode()) {
AppRegistry::instance().bus()->publish(
new ReplicaSetRefreshed(_shell, event->error(), event->error().replicaSetInfo()));
}
}
else // single server
QMessageBox::warning(NULL, "Database Error", QString::fromStdString(event->error().errorMessage()));
return;
}
// Success
_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.
if (event->isError()) {
if (!(event->removeCount == RemoveDocumentCount::MULTI && event->index > 0))
QMessageBox::warning(NULL, "Database Error", QString::fromStdString(event->error().errorMessage()));
}
else // Success
_shell->query(0, _queryInfo);
}
// QMessageBox::warning(NULL, "Database Error", QString::fromStdString(event->error().errorMessage()));
void Notifier::onCopyNameDocument()
{
QModelIndex const& selectedInd = _observer->selectedIndex();
if (!selectedInd.isValid())
return;
BsonTreeItem const *documentItem = QtUtils::item<BsonTreeItem*>(selectedInd);
if (!documentItem)
return;
if (!(detail::isSimpleType(documentItem) || detail::isDocumentType(documentItem) ||
!detail::isArrayChild(documentItem) || !detail::isDocumentRoot(documentItem)))
return;
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(QString::fromStdString(documentItem->fieldName()));
}
void Notifier::onCopyPathDocument()
{
QModelIndex const& selectedInd = _observer->selectedIndex();
if (!selectedInd.isValid())
return;
BsonTreeItem const *documentItem = QtUtils::item<BsonTreeItem*>(selectedInd);
if (!documentItem)
return;
if (!(detail::isSimpleType(documentItem) || detail::isDocumentType(documentItem) ||
!detail::isDocumentRoot(documentItem)))
return;
QStringList namesList;
BsonTreeItem const *documentItemHelper = documentItem;
while (!detail::isDocumentRoot(documentItemHelper)) {
if (!detail::isArrayChild(documentItemHelper)) {
namesList.push_front(QString::fromStdString(documentItemHelper->fieldName()));
}
documentItemHelper = dynamic_cast<BsonTreeItem*>(documentItemHelper->parent());
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(namesList.join("."));
}
void Notifier::handleDeleteCommand()
{
if (_observer->selectedIndexes().count() > 1) {
if (_observer->selectedIndexes().count() > 1)
onDeleteDocuments();
} else {
onDeleteDocument();
}
else
onDeleteDocument();
}
void Notifier::onDeleteDocuments()
@@ -263,13 +317,15 @@ namespace Robomongo
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) {
int const answer = QMessageBox::question(dynamic_cast<QWidget*>(_observer), "Delete",
QString("Do you want to delete %1 selected documents?").
arg(selectedIndexes.count()));
if (QMessageBox::Yes == answer) {
std::vector<BsonTreeItem*> items;
for (QModelIndexList::const_iterator it = selectedIndexes.begin(); it != selectedIndexes.end(); ++it) {
BsonTreeItem *item = QtUtils::item<BsonTreeItem*>(*it);
items.push_back(item);
}
for (auto index : selectedIndexes)
items.push_back(QtUtils::item<BsonTreeItem*>(index));
deleteDocuments(items, true);
}
}
@@ -343,7 +399,7 @@ namespace Robomongo
json, true, dynamic_cast<QWidget*>(_observer));
editor->setWindowTitle("View Document");
editor->show();
editor->exec();
}
void Notifier::onInsertDocument()
@@ -365,8 +421,6 @@ namespace Robomongo
for (DocumentTextEditor::ReturnType::const_iterator it = obj.begin(); it != obj.end(); ++it) {
_shell->server()->insertDocument(*it, _queryInfo._info._ns);
}
_shell->query(0, _queryInfo);
}
void Notifier::onCopyDocument()
@@ -386,53 +440,6 @@ namespace Robomongo
clipboard->setText(documentItem->value());
}
void Notifier::onCopyNameDocument()
{
QModelIndex selectedInd = _observer->selectedIndex();
if (!selectedInd.isValid())
return;
BsonTreeItem *documentItem = QtUtils::item<BsonTreeItem*>(selectedInd);
if (!documentItem)
return;
if ( !(detail::isSimpleType(documentItem) || detail::isDocumentType(documentItem) ||
!detail::isArrayChild(documentItem) || !detail::isDocumentRoot(documentItem)) )
return;
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(QString::fromStdString(documentItem->fieldName()));
}
void Notifier::onCopyPathDocument()
{
QModelIndex selectedInd = _observer->selectedIndex();
if (!selectedInd.isValid())
return;
BsonTreeItem *documentItem = QtUtils::item<BsonTreeItem*>(selectedInd);
if (!documentItem)
return;
if ( !(detail::isSimpleType(documentItem) || detail::isDocumentType(documentItem) ||
!detail::isDocumentRoot(documentItem)) )
return;
QStringList namesList;
BsonTreeItem *documentItemHelper = documentItem;
while ( !detail::isDocumentRoot(documentItemHelper) ) {
if ( !detail::isArrayChild(documentItemHelper) ) {
namesList.push_front( QString::fromStdString(documentItemHelper->fieldName()) );
}
documentItemHelper = dynamic_cast<BsonTreeItem *>(documentItemHelper->parent());
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(namesList.join("."));
}
void Notifier::onCopyTimestamp()
{
QModelIndex selectedInd = _observer->selectedIndex();
+8 -6
View File
@@ -14,14 +14,14 @@ namespace Robomongo
class MongoShell;
class BsonTreeItem;
class InsertDocumentResponse;
class RemoveDocumentResponse;
struct RemoveDocumentResponse;
namespace detail
{
bool isSimpleType(BsonTreeItem *item);
bool isSimpleType(BsonTreeItem const *item);
bool isObjectIdType(BsonTreeItem *item);
bool isMultiSelection(const QModelIndexList &indexes);
bool isDocumentType(BsonTreeItem *item);
bool isDocumentType(BsonTreeItem const *item);
QModelIndexList uniqueRows(QModelIndexList indexes, bool returnSuperParents = false);
}
@@ -45,7 +45,7 @@ namespace Robomongo
void initMenu(QMenu *const menu, BsonTreeItem *const item);
void initMultiSelectionMenu(QMenu *const menu);
void deleteDocuments(std::vector<BsonTreeItem*> items, bool force);
void deleteDocuments(std::vector<BsonTreeItem*> const& items, bool force);
void handleDeleteCommand();
public Q_SLOTS:
@@ -55,13 +55,15 @@ namespace Robomongo
void onViewDocument();
void onInsertDocument();
void onCopyDocument();
void onCopyNameDocument();
void onCopyPathDocument();
void onCopyTimestamp();
void onCopyJson();
void handle(InsertDocumentResponse *event);
void handle(RemoveDocumentResponse *event);
private Q_SLOTS:
void onCopyNameDocument();
void onCopyPathDocument();
private:
QAction *_deleteDocumentAction;
QAction *_deleteDocumentsAction;
+26 -18
View File
@@ -57,7 +57,7 @@ namespace Robomongo
{
ScriptEngine::ScriptEngine(ConnectionSettings *connection, int timeoutSec) :
_connection(connection),
_scope(NULL),
_scope(nullptr),
_engine(NULL),
_timeoutSec(timeoutSec),
_initialized(false),
@@ -65,26 +65,20 @@ namespace Robomongo
ScriptEngine::~ScriptEngine()
{
QMutexLocker lock(&_mutex);
delete _scope;
_scope = NULL;
// delete _engine;
// _engine = NULL;
}
void ScriptEngine::init(bool isLoadMongoRcJs)
void ScriptEngine::init(bool isLoadMongoRcJs, const std::string& serverAddr, const std::string& dbName)
{
QMutexLocker lock(&_mutex);
std::string connectDatabase = "test";
std::string connectDatabase = dbName.empty() ? "test" : dbName;
if (_connection->hasEnabledPrimaryCredential())
connectDatabase = _connection->primaryCredential()->databaseName();
std::stringstream ss;
ss << "db = connect('" << _connection->info().toString() << "/" << connectDatabase;
auto hostAndPort = serverAddr.empty() ? _connection->hostAndPort().toString() : serverAddr;
ss << "db = connect('" << hostAndPort << "/" << connectDatabase;
// v0.9
// ss << "db = connect('" << _connection->serverHost() << ":" << _connection->serverPort() << _connection->sslInfo() << _connection->sshInfo() << "/" << connectDatabase;
@@ -108,23 +102,23 @@ namespace Robomongo
mongo::globalScriptEngine->setScopeInitCallback(mongo::shell_utils::initScope);
mongo::globalScriptEngine->enableJIT(true);
mongo::Scope *scope = mongo::globalScriptEngine->newScope();
_scope = scope;
_scope.reset(mongo::globalScriptEngine->newScope());
_engine = mongo::globalScriptEngine;
// Load '.mongorc.js' from user's home directory
if (isLoadMongoRcJs) {
QString mongorcPath = QString("%1/.mongorc.js").arg(QDir::homePath());
if (QFile::exists(mongorcPath)) {
scope->execFile(QtUtils::toStdString(mongorcPath), false, false);
_scope->execFile(QtUtils::toStdString(mongorcPath), false, false);
}
}
// Load '.robomongorc.js'
QString robomongorcPath = QString("%1/.robomongorc.js").arg(QDir::homePath());
if (QFile::exists(robomongorcPath)) {
scope->execFile(QtUtils::toStdString(robomongorcPath), false, false);
_scope->execFile(QtUtils::toStdString(robomongorcPath), false, false);
}
_failedScope = false;
}
// Esprima ECMAScript parser: http://esprima.org/
@@ -161,8 +155,10 @@ namespace Robomongo
{
QMutexLocker lock(&_mutex);
if (!_scope)
return MongoShellExecResult();
if (!_scope) {
_failedScope = true;
return MongoShellExecResult(true, "Connection error. Uninitialized mongo scope.");
}
/*
* Replace all commands ('show dbs', 'use db' etc.) with call
@@ -202,10 +198,15 @@ namespace Robomongo
if (true /* ! wascmd */) {
try {
bool failed = false;
QElapsedTimer timer;
timer.start();
if ( _scope->exec( statement , "(shell)" , false , true , false, _timeoutSec * 1000) ) {
_scope->exec( "__robomongoLastRes = __lastres__; shellPrintHelper( __lastres__ );" , "(shell2)" , true , true , false, _timeoutSec * 1000);
_scope->exec( "__robomongoLastRes = __lastres__; shellPrintHelper( __lastres__ );",
"(shell2)" , true , true , false, _timeoutSec * 1000);
}
else { // failed to run script
failed = true;
}
qint64 elapsed = timer.elapsed();
@@ -213,6 +214,10 @@ namespace Robomongo
std::string logs = __logs.str();
std::string answer = logs.c_str();
std::string type = __type.c_str();
if (failed)
return MongoShellExecResult(true, answer);
std::vector<MongoDocumentPtr> docs = MongoDocument::fromBsonObj(__objects);
if (!answer.empty() || docs.size() > 0)
@@ -265,6 +270,9 @@ namespace Robomongo
void ScriptEngine::ping()
{
if (!_scope)
return;
QMutexLocker lock(&_mutex);
_scope->exec("if (db) { db.runCommand({ping:1}); }", "(ping)", false, false, false, 3000);
}
+5 -2
View File
@@ -18,7 +18,7 @@ namespace Robomongo
ScriptEngine(ConnectionSettings *connection, int timeoutSec);
~ScriptEngine();
void init(bool isLoadMongoJs);
void init(bool isLoadMongoJs, const std::string& serverAddr = "", const std::string& dbName = "");
MongoShellExecResult exec(const std::string &script, const std::string &dbName = std::string());
void interrupt();
@@ -29,6 +29,8 @@ namespace Robomongo
void invalidateDbCollectionsCache();
bool failedScope() const { return _failedScope; }
private:
ConnectionSettings *_connection;
@@ -42,7 +44,8 @@ namespace Robomongo
int _timeoutSec;
mongo::ScriptEngine *_engine;
mongo::Scope *_scope;
std::unique_ptr<mongo::Scope> _scope;
bool _failedScope = false;
QMutex _mutex;
bool _initialized;
};
@@ -5,6 +5,11 @@ namespace Robomongo
{
R_REGISTER_EVENT(EstablishConnectionRequest)
R_REGISTER_EVENT(EstablishConnectionResponse)
R_REGISTER_EVENT(ReplicaSetRefreshed)
R_REGISTER_EVENT(RefreshReplicaSetFolderRequest)
R_REGISTER_EVENT(RefreshReplicaSetFolderResponse)
R_REGISTER_EVENT(ReplicaSetFolderLoading)
R_REGISTER_EVENT(ReplicaSetFolderRefreshed)
R_REGISTER_EVENT(LoadDatabaseNamesRequest)
R_REGISTER_EVENT(LoadDatabaseNamesResponse)
R_REGISTER_EVENT(LoadCollectionNamesRequest)
+215 -126
View File
@@ -12,6 +12,7 @@
#include "robomongo/core/events/MongoEventsInfo.h"
#include "robomongo/core/Event.h"
#include "robomongo/core/Enums.h"
#include "robomongo/core/mongodb/ReplicaSet.h"
namespace Robomongo
{
@@ -26,54 +27,118 @@ namespace Robomongo
* @brief EstablishConnection
*/
class EstablishConnectionRequest : public Event
struct EstablishConnectionRequest : public Event
{
R_EVENT
EstablishConnectionRequest(QObject *sender, ConnectionType connectionType) :
EstablishConnectionRequest(QObject *sender, ConnectionType connectionType, std::string const& uuid) :
Event(sender),
connectionType(connectionType) {}
connectionType(connectionType),
uuid(uuid) {}
ConnectionType connectionType;
ConnectionType const connectionType;
std::string const uuid;
};
class EstablishConnectionResponse : public Event
struct EstablishConnectionResponse : public Event
{
R_EVENT
enum ErrorReason {
MongoConnection = 0,
MongoAuth = 1,
MongoSslConnection = 2
NoError = 0,
MongoConnection = 1,
MongoAuth = 2,
MongoSslConnection = 3
};
EstablishConnectionResponse(QObject *sender, const ConnectionInfo &info, ConnectionType connectionType,
const mongo::HostAndPort& repPrimary, const std::vector<bool>& repMembersHealths) :
const ReplicaSet& replicaSet) :
Event(sender),
_info(info),
info(info),
connectionType(connectionType),
_repPrimary(repPrimary),
_repMembersHealths(repMembersHealths)
replicaSet(replicaSet)
{}
EstablishConnectionResponse(QObject *sender, const EventError &error, ConnectionType connectionType, ErrorReason errorReason) :
EstablishConnectionResponse(QObject *sender, const EventError &error, ConnectionType connectionType,
ConnectionInfo const& info, const ReplicaSet& replicaSet, ErrorReason errorReason) :
Event(sender, error),
_info(),
info(info),
connectionType(connectionType),
errorReason(errorReason) {}
errorReason(errorReason),
replicaSet(replicaSet)
{}
// Getters
const ConnectionInfo &info() const { return _info; }
const mongo::HostAndPort& getRepPrimary() const { return _repPrimary; }
const std::vector<bool>& getRepMembersHealths() const { return _repMembersHealths; }
const ConnectionInfo _info;
ConnectionType connectionType;
ErrorReason errorReason;
mongo::HostAndPort _repPrimary;
std::vector<bool> _repMembersHealths; // todo: vector of host and health pair
ConnectionInfo const info;
ConnectionType const connectionType;
ErrorReason const errorReason = NoError;
ReplicaSet const replicaSet;
};
struct ReplicaSetRefreshed : public Event
{
R_EVENT
ReplicaSetRefreshed(QObject *sender) :
Event(sender) {}
ReplicaSetRefreshed(QObject *sender, const EventError &error, ReplicaSet const& replicaSet) :
Event(sender, error), replicaSet(replicaSet) {}
ReplicaSet const replicaSet;
};
struct RefreshReplicaSetFolderRequest : public Event
{
R_EVENT
RefreshReplicaSetFolderRequest(QObject *sender, bool expanded) :
Event(sender), expanded(expanded) {}
bool const expanded = false;
};
struct RefreshReplicaSetFolderResponse : public Event
{
R_EVENT
// Primary is reachable
RefreshReplicaSetFolderResponse(QObject *sender, ReplicaSet const& replicaSet, bool expanded) :
Event(sender), replicaSet(replicaSet), expanded(expanded) {}
// Primary is unreachable, secondary(ies) might be reachable
RefreshReplicaSetFolderResponse(QObject *sender, ReplicaSet const& replicaSet, bool expanded,
const EventError &error) :
Event(sender, error), replicaSet(replicaSet), expanded(expanded) {}
ReplicaSet const replicaSet;
bool const expanded = false;
};
struct ReplicaSetFolderLoading : public Event
{
R_EVENT
ReplicaSetFolderLoading(QObject *sender) :
Event(sender) {}
};
struct ReplicaSetFolderRefreshed : public Event
{
R_EVENT
ReplicaSetFolderRefreshed(QObject *sender, bool expanded) :
Event(sender), expanded(expanded) {}
ReplicaSetFolderRefreshed(QObject *sender, const EventError &error, bool expanded) :
Event(sender, error), expanded(expanded) {}
ReplicaSetFolderRefreshed(QObject *sender, const EventError &error, ReplicaSet const& replicaSet,
bool expanded) :
Event(sender, error), replicaSet(replicaSet), expanded(expanded) {}
ReplicaSet const replicaSet;
bool const expanded = false;
};
/**
* @brief LoadDatabaseNames
@@ -349,7 +414,7 @@ namespace Robomongo
InsertDocumentResponse(QObject *sender) :
Event(sender) {}
InsertDocumentResponse(QObject *sender, const EventError &error) :
InsertDocumentResponse(QObject *sender, EventError const& error) :
Event(sender, error) {}
};
@@ -357,37 +422,46 @@ namespace Robomongo
* @brief Remove Document
*/
enum class RemoveDocumentCount { ONE, MULTI, ALL };
class RemoveDocumentRequest : public Event
{
R_EVENT
public:
RemoveDocumentRequest(QObject *sender, mongo::Query query, const MongoNamespace &ns, bool justOne = true) :
RemoveDocumentRequest(QObject *sender, mongo::Query query, const MongoNamespace &ns,
RemoveDocumentCount removeCount, int index) :
Event(sender),
_query(query),
_ns(ns),
_justOne(justOne) {}
_removeCount(removeCount),
_index(index) {}
mongo::Query query() const { return _query; }
MongoNamespace ns() const { return _ns; }
bool justOne() const { return _justOne; }
RemoveDocumentCount removeCount() const { return _removeCount; }
int index() const { return _index; }
private:
mongo::Query _query;
const MongoNamespace _ns;
bool _justOne;
mongo::Query const _query;
MongoNamespace const _ns;
RemoveDocumentCount const _removeCount;
// if this is a multi remove, this is the index of current document deleted. 0 for first document.
int const _index;
};
class RemoveDocumentResponse : public Event
struct RemoveDocumentResponse : public Event
{
R_EVENT
public:
RemoveDocumentResponse(QObject *sender) :
Event(sender) {}
RemoveDocumentResponse(QObject *sender, RemoveDocumentCount removeCount, int index) :
Event(sender), removeCount(removeCount), index(index) {}
RemoveDocumentResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
RemoveDocumentResponse(QObject *sender, const EventError &error, RemoveDocumentCount removeCount, int index) :
Event(sender, error), removeCount(removeCount), index(index) {}
RemoveDocumentCount const removeCount;
int const index;
};
/**
@@ -406,52 +480,49 @@ namespace Robomongo
std::string database() const { return _database; }
private:
std::string _database;
std::string const _database;
};
class CreateDatabaseResponse : public Event
struct CreateDatabaseResponse : public Event
{
R_EVENT
public:
CreateDatabaseResponse(QObject *sender) :
Event(sender) {}
CreateDatabaseResponse(QObject *sender, const std::string &database) :
Event(sender), database(database) {}
CreateDatabaseResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
CreateDatabaseResponse(QObject *sender, const std::string &database, const EventError &error) :
Event(sender, error), database(database) {}
std::string const database;
};
/**
* @brief Drop Database
*/
class DropDatabaseRequest : public Event
struct DropDatabaseRequest : public Event
{
R_EVENT
public:
DropDatabaseRequest(QObject *sender, const std::string &database) :
Event(sender),
_database(database) {}
Event(sender), database(database) {}
std::string database() const { return _database; }
private:
std::string _database;
std::string const database;
};
class DropDatabaseResponse : public Event
struct DropDatabaseResponse : public Event
{
R_EVENT
public:
DropDatabaseResponse(QObject *sender) :
Event(sender) {}
DropDatabaseResponse(QObject *sender, const std::string &database) :
Event(sender), database(database) {}
DropDatabaseResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
DropDatabaseResponse(QObject *sender, const std::string &database, const EventError &error) :
Event(sender, error), database(database) {}
std::string const database;
};
/**
@@ -472,30 +543,31 @@ namespace Robomongo
Event(sender), _ns(ns), _extraOptions(extraOptions),
_size(size), _capped(capped), _maxDocNum(maxDocNum) {}
MongoNamespace getNs() const { return _ns; }
MongoNamespace ns() const { return _ns; }
long long getSize() const { return _size; }
bool getCapped() const { return _capped; }
int getMaxDocNum() const { return _maxDocNum; }
const mongo::BSONObj getExtraOptions() const { return _extraOptions; }
private:
const MongoNamespace _ns;
const long long _size;
const bool _capped;
const int _maxDocNum;
const mongo::BSONObj _extraOptions;
MongoNamespace const _ns;
long long const _size;
bool const _capped;
int const _maxDocNum;
mongo::BSONObj const _extraOptions;
};
class CreateCollectionResponse : public Event
struct CreateCollectionResponse : public Event
{
R_EVENT
public:
CreateCollectionResponse(QObject *sender) :
Event(sender) {}
CreateCollectionResponse(QObject *sender, std::string const& collection) :
Event(sender), collection(collection) {}
CreateCollectionResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
CreateCollectionResponse(QObject *sender, std::string const& collection, const EventError &error) :
Event(sender, error), collection(collection) {}
std::string const collection;
};
@@ -519,16 +591,18 @@ namespace Robomongo
MongoNamespace _ns;
};
class DropCollectionResponse : public Event
struct DropCollectionResponse : public Event
{
R_EVENT
public:
DropCollectionResponse(QObject *sender) :
Event(sender) {}
DropCollectionResponse(QObject *sender, std::string const& collection) :
Event(sender), collection(collection) {}
DropCollectionResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
DropCollectionResponse(QObject *sender, std::string const& collection, const EventError &error) :
Event(sender, error), collection(collection) {}
std::string const collection;
};
/**
@@ -549,20 +623,23 @@ namespace Robomongo
std::string newCollection() const { return _newCollection; }
private:
MongoNamespace _ns;
std::string _newCollection;
MongoNamespace const _ns;
std::string const _newCollection;
};
class RenameCollectionResponse : public Event
struct RenameCollectionResponse : public Event
{
R_EVENT
public:
RenameCollectionResponse(QObject *sender) :
Event(sender) {}
RenameCollectionResponse(QObject *sender, std::string const& oldCollection,
std::string const& newCollection) :
Event(sender), oldCollection(oldCollection), newCollection(newCollection) {}
RenameCollectionResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
std::string const oldCollection;
std::string const newCollection;
};
/**
@@ -583,20 +660,24 @@ namespace Robomongo
std::string newCollection() const { return _newCollection; }
private:
MongoNamespace _ns;
std::string _newCollection;
MongoNamespace const _ns;
std::string const _newCollection;
};
class DuplicateCollectionResponse : public Event
struct DuplicateCollectionResponse : public Event
{
R_EVENT
public:
DuplicateCollectionResponse(QObject *sender) :
Event(sender) {}
DuplicateCollectionResponse(QObject *sender, std::string const& sourceCollection,
std::string const& duplicateCollection) :
Event(sender), sourceCollection(sourceCollection), duplicateCollection(duplicateCollection) {}
DuplicateCollectionResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
DuplicateCollectionResponse(QObject *sender, std::string const& sourceCollection, const EventError &error) :
Event(sender, error), sourceCollection(sourceCollection) {}
std::string const sourceCollection;
std::string const duplicateCollection;
};
/**
@@ -661,16 +742,17 @@ namespace Robomongo
bool _overwrite;
};
class CreateUserResponse : public Event
struct CreateUserResponse : public Event
{
R_EVENT
public:
CreateUserResponse(QObject *sender) :
Event(sender) {}
CreateUserResponse(QObject *sender, std::string const& userName) :
Event(sender), userName(userName) {}
CreateUserResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
CreateUserResponse(QObject *sender, std::string const& userName, const EventError &error) :
Event(sender, error), userName(userName) {}
std::string const userName;
};
@@ -683,29 +765,31 @@ namespace Robomongo
R_EVENT
public:
DropUserRequest(QObject *sender, const std::string &database, const mongo::OID &id) :
Event(sender),
_database(database),
_id(id) {}
DropUserRequest(QObject *sender, const std::string &database, const mongo::OID &id,
std::string const& username) :
Event(sender), _database(database), _id(id), _username(username) {}
std::string database() const { return _database; }
mongo::OID id() const { return _id; }
std::string username() const { return _username; }
private:
std::string _database;
mongo::OID _id;
std::string const _username;
};
class DropUserResponse : public Event
struct DropUserResponse : public Event
{
R_EVENT
public:
DropUserResponse(QObject *sender) :
Event(sender) {}
DropUserResponse(QObject *sender, std::string const& username) :
Event(sender), username(username) {}
DropUserResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
DropUserResponse(QObject *sender, std::string const& username, const EventError &error) :
Event(sender, error), username(username) {}
std::string const username;
};
@@ -738,19 +822,21 @@ namespace Robomongo
bool _overwrite;
};
class CreateFunctionResponse : public Event
struct CreateFunctionResponse : public Event
{
R_EVENT
enum Operation { NewFunction, NameUpdate, CodeUpdate }; // todo
public:
CreateFunctionResponse(QObject *sender) :
Event(sender) {}
CreateFunctionResponse(QObject *sender, std::string const& functionName) :
Event(sender), functionName(functionName) {}
CreateFunctionResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
CreateFunctionResponse(QObject *sender, std::string const& functionName, const EventError &error) :
Event(sender, error), functionName(functionName) {}
std::string const functionName;
};
/**
* @brief Drop Function
*/
@@ -760,29 +846,30 @@ namespace Robomongo
R_EVENT
public:
DropFunctionRequest(QObject *sender, const std::string &database, const std::string &name) :
DropFunctionRequest(QObject *sender, const std::string &database, const std::string &funcName) :
Event(sender),
_database(database),
_name(name) {}
_functionName(funcName) {}
std::string database() const { return _database; }
std::string name() const { return _name; }
std::string functionName() const { return _functionName; }
private:
std::string _database;
std::string _name;
std::string _functionName;
};
class DropFunctionResponse : public Event
struct DropFunctionResponse : public Event
{
R_EVENT
public:
DropFunctionResponse(QObject *sender) :
Event(sender) {}
DropFunctionResponse(QObject *sender, const std::string &functionName) :
Event(sender), functionName(functionName) {}
DropFunctionResponse(QObject *sender, const EventError &error) :
Event(sender, error) {}
DropFunctionResponse(QObject *sender, const std::string &functionName, const EventError &error) :
Event(sender, error), functionName(functionName) {}
std::string const functionName;
};
/**
@@ -939,17 +1026,19 @@ namespace Robomongo
// class ScriptExecute
class ConnectionEstablishedEvent : public Event
struct ConnectionEstablishedEvent : public Event
{
R_EVENT
ConnectionEstablishedEvent(MongoServer *server, ConnectionType type) :
ConnectionEstablishedEvent(MongoServer *server, ConnectionType type, ConnectionInfo connInfo) :
Event((QObject *)server),
server(server),
connectionType(type) { }
connectionType(type),
connInfo(connInfo) { }
MongoServer *server;
ConnectionType connectionType;
ConnectionType const connectionType;
ConnectionInfo const connInfo;
};
class DatabaseListLoadedEvent : public Event
@@ -17,15 +17,19 @@ namespace Robomongo
_languageOverride(languageOverride),
_textWeights(textWeights) {}
ConnectionInfo::ConnectionInfo() :
ConnectionInfo::ConnectionInfo(std::string const& uuid) :
_address(),
_databases(),
_version(0.0f) {}
_version(0.0f),
_uuid(uuid)
{}
ConnectionInfo::ConnectionInfo(const std::string &address, const std::vector<std::string> &databases,
float version, const std::string& storageEngine) :
float version, const std::string& storageEngine, std::string const& uuid) :
_address(address),
_databases(databases),
_version(version),
_storageEngineType(storageEngine) {}
_storageEngineType(storageEngine),
_uuid(uuid)
{}
}
+4 -2
View File
@@ -34,11 +34,13 @@ namespace Robomongo
struct ConnectionInfo
{
ConnectionInfo();
ConnectionInfo(const std::string &address, const std::vector<std::string> &databases, float version, const std::string& storageEngine);
ConnectionInfo(std::string const& uuid);
ConnectionInfo(const std::string &address, const std::vector<std::string> &databases, float version,
const std::string& storageEngine, std::string const& uuid);
const std::string _address;
const std::vector<std::string> _databases;
const float _version;
const std::string _storageEngineType;
std::string const _uuid;
};
}
+102 -25
View File
@@ -108,9 +108,10 @@ namespace Robomongo
MongoNamespace ns(dbName, "system.users");
mongo::BSONObj obj = user.toBson();
if (!overwrite) {
if (!overwrite) { // create new user
_dbclient->insert(ns.toString(), obj);
} else {
}
else { // update existing user
mongo::BSONElement id = obj.getField("_id");
mongo::BSONObjBuilder builder;
builder.append(id);
@@ -119,6 +120,10 @@ namespace Robomongo
_dbclient->update(ns.toString(), query, obj, true, false);
}
std::string errorStr = _dbclient->getLastError();
if (!errorStr.empty())
throw mongo::DBException(errorStr, 0);
}
void MongoClient::dropUser(const std::string &dbName, const mongo::OID &id)
@@ -131,6 +136,10 @@ namespace Robomongo
mongo::Query query(bsonQuery);
_dbclient->remove(ns.toString(), query, true);
std::string errorStr = _dbclient->getLastError();
if (!errorStr.empty())
throw mongo::DBException(errorStr, 0);
}
std::vector<MongoFunction> MongoClient::getFunctions(const std::string &dbName)
@@ -293,36 +302,46 @@ namespace Robomongo
_dbclient->dropIndex(collection.ns().toString(), indexName);
}
void MongoClient::createFunction(const std::string &dbName, const MongoFunction &fun, const std::string &existingFunctionName /* = QString() */)
void MongoClient::createFunction(const std::string &dbName, const MongoFunction &fun,
const std::string &existingFunctionName /* = QString() */)
{
MongoNamespace ns(dbName, "system.js");
mongo::BSONObj obj = fun.toBson();
if (existingFunctionName.empty()) { // this is insert
if (existingFunctionName.empty()) { // create new function
_dbclient->insert(ns.toString(), obj);
std::string errorStr = _dbclient->getLastError();
if (!errorStr.empty())
throw mongo::DBException(errorStr, 0);
} else { // this is update
std::string name = fun.name();
if (existingFunctionName == name) {
if (existingFunctionName == name) { // update existing function code
mongo::BSONObjBuilder builder;
builder.append("_id", name);
mongo::BSONObj bsonQuery = builder.obj();
mongo::Query query(bsonQuery);
_dbclient->update(ns.toString(), query, obj, true, false);
} else {
std::string errorStr = _dbclient->getLastError();
if (!errorStr.empty())
throw mongo::DBException(errorStr, 0);
} else { // update function name (remove & insert)
_dbclient->insert(ns.toString(), obj);
std::string res = _dbclient->getLastError();
std::string errorStr = _dbclient->getLastError();
// if no errors
if (res.empty()) {
if (errorStr.empty()) {
mongo::BSONObjBuilder builder;
builder.append("_id", existingFunctionName);
mongo::BSONObj bsonQuery = builder.obj();
mongo::Query query(bsonQuery);
_dbclient->remove(ns.toString(), query, true);
}
else {
throw mongo::DBException(errorStr, 0);
}
}
}
}
@@ -337,6 +356,9 @@ namespace Robomongo
mongo::Query query(bsonQuery);
_dbclient->remove(ns.toString(), query, true);
std::string errorStr = _dbclient->getLastError();
if (!errorStr.empty())
throw mongo::DBException(errorStr, 0);
}
void MongoClient::createDatabase(const std::string &dbName)
@@ -351,7 +373,7 @@ namespace Robomongo
// If <dbName>.temp already exists, stop.
if (_dbclient->exists(ns.toString()))
return;
throw mongo::DBException(dbName + ".temp already exists.", 0);
// Building { _id : "temp" } document
mongo::BSONObjBuilder builder;
@@ -360,6 +382,9 @@ namespace Robomongo
// Insert this document
_dbclient->insert(ns.toString(), obj);
std::string errorStr = _dbclient->getLastError();
if (!errorStr.empty())
throw mongo::DBException(errorStr, 0);
// Drop temp collection
_dbclient->dropCollection(ns.toString());
@@ -367,10 +392,18 @@ namespace Robomongo
void MongoClient::dropDatabase(const std::string &dbName)
{
_dbclient->dropDatabase(dbName);
mongo::BSONObj result;
if (!_dbclient->dropDatabase(dbName, &result)) {
std::string errStr = result.getStringField("errmsg");
if (errStr.empty())
errStr = "Failed to get error message.";
throw mongo::DBException(errStr, 0);
}
}
void MongoClient::createCollection(const std::string& ns, long long size, bool capped, int max, const mongo::BSONObj& extraOptions, mongo::BSONObj* info)
void MongoClient::createCollection(const std::string& ns, long long size, bool capped, int max,
const mongo::BSONObj& extraOptions, mongo::BSONObj* info)
{
verify(!capped || size);
mongo::BSONObj o;
@@ -390,7 +423,19 @@ namespace Robomongo
}
b.appendElements(extraOptions);
_dbclient->runCommand(db.c_str(), b.done(), *info);
if (!_dbclient->exists(ns)) {
mongo::BSONObj result;
if (!_dbclient->runCommand(db.c_str(), b.done(), result)) {
std::string errStr = result.getStringField("errmsg");
if (errStr.empty())
errStr = "Failed to get error message.";
throw mongo::DBException(errStr, 0);
}
}
else {
throw mongo::DBException("Collection with same name already exists.", 0);
}
}
void MongoClient::renameCollection(const MongoNamespace &ns, const std::string &newCollectionName)
@@ -404,18 +449,38 @@ namespace Robomongo
command.append("to", to.toString());
mongo::BSONObj result;
_dbclient->runCommand("admin", command.obj(), result); // this command should be run against "admin" db
if (!_dbclient->runCommand("admin", command.obj(), result)) { // this command should be run against "admin" db
std::string errStr = result.getStringField("errmsg");
if (errStr.empty())
errStr = "Failed to get error message.";
throw mongo::DBException(errStr, 0);
}
}
void MongoClient::duplicateCollection(const MongoNamespace &ns, const std::string &newCollectionName)
{
MongoNamespace from(ns);
MongoNamespace to(ns.databaseName(), newCollectionName);
MongoNamespace sourceCollection(ns);
MongoNamespace newCollection(ns.databaseName(), newCollectionName);
if (!_dbclient->exists(to.toString()))
_dbclient->createCollection(to.toString());
if (!_dbclient->exists(newCollection.toString())) {
mongo::BSONObj result;
// todo: Issue #1258 : Duplicate Collection should support advanced collection options.
// _dbclient->createCollection() should be called with properties of source collection
// not with default parameters as below.
if (!_dbclient->createCollection(newCollection.toString(), 0, false, 0, &result)) {
std::string errStr = result.getStringField("errmsg");
if (errStr.empty())
errStr = "Failed to get error message.";
std::unique_ptr<mongo::DBClientCursor> cursor(_dbclient->query(from.toString(), mongo::Query()));
throw mongo::DBException(errStr, 0);
}
}
else {
throw mongo::DBException("Collection with same name already exists.", 0);
}
std::unique_ptr<mongo::DBClientCursor> cursor(_dbclient->query(sourceCollection.toString(), mongo::Query()));
// Cursor may be NULL, it means we have connectivity problem
if (!cursor)
@@ -423,11 +488,12 @@ namespace Robomongo
while (cursor->more()) {
mongo::BSONObj bsonObj = cursor->next();
_dbclient->insert(to.toString(), bsonObj);
_dbclient->insert(newCollection.toString(), bsonObj);
}
}
void MongoClient::copyCollectionToDiffServer(mongo::DBClientBase *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());
@@ -446,7 +512,19 @@ namespace Robomongo
void MongoClient::dropCollection(const MongoNamespace &ns)
{
_dbclient->dropCollection(ns.toString());
if (_dbclient->exists(ns.toString())) {
mongo::BSONObj result;
if (!_dbclient->dropCollection(ns.toString(), &result)) {
std::string errStr = result.getStringField("errmsg");
if (errStr.empty())
errStr = "Failed to get error message.";
throw mongo::DBException(errStr, 0);
}
}
else {
throw mongo::DBException("Collection does not exist.", 0);
}
}
void MongoClient::insertDocument(const mongo::BSONObj &obj, const MongoNamespace &ns)
@@ -524,11 +602,10 @@ namespace Robomongo
std::vector<MongoCollectionInfo> MongoClient::runCollStatsCommand(const std::vector<std::string> &namespaces)
{
std::vector<MongoCollectionInfo> infos;
for (std::vector<std::string>::const_iterator it = namespaces.begin(); it != namespaces.end(); ++it) {
MongoCollectionInfo info = runCollStatsCommand(*it);
if (info.ns().isValid()) {
for (auto const& ns : namespaces) {
MongoCollectionInfo info = runCollStatsCommand(ns);
if (info.ns().isValid())
infos.push_back(info);
}
}
return infos;
}
+493 -154
View File
@@ -1,5 +1,7 @@
#include "robomongo/core/mongodb/MongoWorker.h"
#include <algorithm>
#include <QThread>
#include "mongo/client/global_conn_pool.h"
@@ -19,17 +21,18 @@
#include "robomongo/core/settings/CredentialSettings.h"
#include "robomongo/core/settings/SettingsManager.h"
#include "robomongo/core/settings/SslSettings.h"
#include "robomongo/core/utils/BsonUtils.h"
#include "robomongo/core/utils/Logger.h"
#include "robomongo/core/utils/QtUtils.h"
#include "robomongo/utils/string_operations.h"
namespace Robomongo
{
std::string const PRIMARY_UNREACHABLE = "Replica set's primary is unreachable.";
MongoWorker::MongoWorker(ConnectionSettings *connection, bool isLoadMongoRcJs, int batchSize,
int mongoTimeoutSec, int shellTimeoutSec, QObject *parent) : QObject(parent),
_connSettings(connection),
_scriptEngine(NULL),
_dbclient(nullptr),
_dbclientRepSet(nullptr),
_scriptEngine(nullptr),
_isAdmin(true),
_isLoadMongoRcJs(isLoadMongoRcJs),
_batchSize(batchSize),
@@ -37,7 +40,10 @@ namespace Robomongo
_dbAutocompleteCacheTimerId(-1),
_mongoTimeoutSec(mongoTimeoutSec),
_shellTimeoutSec(shellTimeoutSec),
_isQuiting(0)
_isQuiting(0),
_dbclient(nullptr),
_dbclientRepSet(nullptr),
_connSettings(connection)
{
_thread = new QThread();
moveToThread(_thread);
@@ -53,8 +59,7 @@ namespace Robomongo
return;
}
if (_dbAutocompleteCacheTimerId == event->timerId() &&
_scriptEngine != NULL) {
if (_dbAutocompleteCacheTimerId == event->timerId() && !_scriptEngine) {
_scriptEngine->invalidateDbCollectionsCache();
return;
}
@@ -64,16 +69,11 @@ namespace Robomongo
{
try {
if (_dbclient) {
// Building { ping: 1 }
mongo::BSONObjBuilder command;
command.append("ping", 1);
mongo::BSONObj result;
std::string authBase = getAuthBase();
if (authBase.empty()) {
_dbclient->runCommand("admin", command.obj(), result);
} else {
_dbclient->runCommand(authBase, command.obj(), result);
}
pingDatabase(_dbclient.get());
}
if (_dbclientRepSet) {
pingDatabase(_dbclientRepSet.get());
}
if (_scriptEngine) {
@@ -81,21 +81,23 @@ namespace Robomongo
}
} catch(std::exception &ex) {
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
LOG_MSG("Failed to ping the server. MongoWorker::keepAlive() failed. " + std::string(ex.what()),
mongo::logger::LogSeverity::Error());
}
}
void MongoWorker::init()
{
try {
_scriptEngine = new ScriptEngine(_connSettings, _shellTimeoutSec);
_scriptEngine.reset(new ScriptEngine(_connSettings, _shellTimeoutSec));
_scriptEngine->init(_isLoadMongoRcJs);
_scriptEngine->use(_connSettings->defaultDatabase());
_scriptEngine->setBatchSize(_batchSize);
_timerId = startTimer(pingTimeMs);
_dbAutocompleteCacheTimerId = startTimer(30000);
} catch (const std::exception &ex) {
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
LOG_MSG("Failed to initialize MongoWorker. " + std::string(ex.what()),
mongo::logger::LogSeverity::Error());
}
}
@@ -119,7 +121,6 @@ namespace Robomongo
killTimer(_dbAutocompleteCacheTimerId);
delete _connSettings;
delete _scriptEngine;
// QThread "_thread" and MongoWorker itself will be deleted later
// (see MongoWorker() constructor)
@@ -134,32 +135,75 @@ namespace Robomongo
/**
* @brief Initiate connection to MongoDB
*/
void MongoWorker::handle(EstablishConnectionRequest *event)
bool MongoWorker::handle(EstablishConnectionRequest *event)
{
QMutexLocker lock(&_firstConnectionMutex);
std::unique_ptr<ReplicaSet> repSetInfo(new ReplicaSet);
auto errorCode = EventError::ErrorCode::Unknown;
try {
mongo::DBClientBase *conn = getConnection(true);
if (conn == NULL) {
// Protection by default value: Logically/ideally, this error should never be seen.
// --- Connection failed for single server & replica set (no member of the set is reachable)
if (!conn)
{
auto errorReason = std::string("Connection failure: Unknown error.");
if (_connSettings->sslSettings()->sslEnabled())
{
if (_connSettings->sslSettings()->sslEnabled()) {
// Note: Currently mongo-shell does not provide any interface to fetch actual error details
// for some SSL connection failures that's why we are unable to show exact error here.
errorReason = "SSL tunnel failure: Network is unreachable or SSL connection rejected by server.";
}
else
{
errorReason = "Network is unreachable.";
else { // Non-SSL connections
if (_connSettings->isReplicaSet()) {
errorReason = "No member of the set is reachable.";
std::vector<std::pair<std::string, bool>> membersAndHealths;
for (auto const& member : _connSettings->replicaSetSettings()->members()) {
membersAndHealths.push_back({ member, false });
}
repSetInfo.reset(new ReplicaSet("", mongo::HostAndPort(), membersAndHealths,
"No member of the set is reachable."));
}
else // single server
errorReason = "Network is unreachable.";
}
resetGlobalSSLparams();
reply(event->sender(), new EstablishConnectionResponse(this,
EventError(errorReason), event->connectionType,
EstablishConnectionResponse::MongoConnection));
return;
reply(event->sender(), new EstablishConnectionResponse(this, EventError(errorReason, errorCode),
event->connectionType, event->uuid, *repSetInfo.release(),
EstablishConnectionResponse::MongoConnection));
return false;
}
// --- Single server: Connection successful
// --- Replica set: Connection successful (primary reachable) or
// Connection failed (primary unreachable with at least one reachable member)
if (_connSettings->isReplicaSet()) {
bool refresh = (ConnectionType::ConnectionRefresh == event->connectionType);
ReplicaSet const& setInfo = getReplicaSetInfo(refresh);
// Check if same set name used with different members which is not supported
auto const& members = _connSettings->replicaSetSettings()->members();
if (std::find(members.cbegin(), members.cend(), setInfo.primary.toString()) == members.cend())
{ // primary not found between user entered members
std::string const errorStr = "Different members found under same replica set name \"" +
setInfo.setName + "\".";
repSetInfo.reset(new ReplicaSet(setInfo));
errorCode = EventError::ErrorCode::ServerHasDifferentMembers;
LOG_MSG(errorStr, mongo::logger::LogSeverity::Error());
throw std::runtime_error(errorStr);
}
if (setInfo.primary.empty()) { // No reachable primary
repSetInfo.reset(new ReplicaSet(setInfo)); // Pass possible reachable secondary(ies) info
LOG_MSG(setInfo.errorStr, mongo::logger::LogSeverity::Error());
throw std::runtime_error(setInfo.errorStr);
}
else { // Primary is reachable, save setInfo and continue
repSetInfo.reset(new ReplicaSet(setInfo));
}
}
if (_connSettings->hasEnabledPrimaryCredential()) {
@@ -191,44 +235,45 @@ namespace Robomongo
if (dbNames.size() == 0)
throw mongo::DBException("Failed to execute \"listdatabases\" command.", 0);
// todo: move to func. getRepSetStatus();
mongo::HostAndPort repPrimary;
std::vector<bool> repMembersHealths;
if (_connSettings->isReplicaSet())
{
// Refresh view of Replica Set Monitor to get live data
auto repSetMonitor = mongo::globalRSMonitorManager.getMonitor(_dbclientRepSet->getSetName());
repSetMonitor->startOrContinueRefresh().refreshAll();
// Update connection settings with primary(master) address and port
repPrimary = repSetMonitor->getMasterOrUassert();
_connSettings->setServerHost(repPrimary.host());
_connSettings->setServerPort(repPrimary.port());
// Save health of members
for (auto const& member : _connSettings->replicaSetSettings()->membersToHostAndPort())
{
repMembersHealths.push_back(repSetMonitor->isHostUp(member));
}
}
// Init MongoWorker
init();
if (!_connSettings->isReplicaSet())
init(); // Init MongoWorker for single server (for replica set connections early init is used)
resetGlobalSSLparams();
reply(event->sender(), new EstablishConnectionResponse(this, ConnectionInfo(_connSettings->getFullAddress(),
dbNames, client->getVersion(), client->getStorageEngineType()), event->connectionType, repPrimary, repMembersHealths));
} catch(const std::exception &ex) {
resetGlobalSSLparams();
auto connInfo = ConnectionInfo(_connSettings->getFullAddress(), dbNames, client->getVersion(),
client->getStorageEngineType(), event->uuid);
// todo: two ctors for rep.set and single server.
reply(event->sender(), new EstablishConnectionResponse(this, connInfo, event->connectionType,
*repSetInfo.release()));
return true;
}
catch(const std::exception &ex) {
resetGlobalSSLparams();
auto errorReason = _connSettings->sslSettings()->sslEnabled() ?
EstablishConnectionResponse::ErrorReason::MongoSslConnection :
EstablishConnectionResponse::ErrorReason::MongoAuth;
EstablishConnectionResponse::ErrorReason::MongoSslConnection :
EstablishConnectionResponse::ErrorReason::MongoAuth;
reply(event->sender(),
new EstablishConnectionResponse(this, EventError(ex.what()), event->connectionType, errorReason));
reply(event->sender(), new EstablishConnectionResponse(this, EventError(ex.what(), errorCode),
event->connectionType, ConnectionInfo(event->uuid), *repSetInfo.release(), errorReason));
}
return false;
}
void MongoWorker::handle(RefreshReplicaSetFolderRequest *event)
{
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
// Primary is unreachable, but there might be reachable secondary(ies)
if (replicaSetInfo.primary.empty()) {
reply(event->sender(), new RefreshReplicaSetFolderResponse(this, replicaSetInfo, event->expanded,
EventError(replicaSetInfo.errorStr)));
LOG_MSG(replicaSetInfo.errorStr, mongo::logger::LogSeverity::Error());
return;
}
else // Primary is reachable
reply(event->sender(), new RefreshReplicaSetFolderResponse(this, replicaSetInfo, event->expanded));
}
std::string MongoWorker::getAuthBase() const
@@ -306,8 +351,17 @@ namespace Robomongo
reply(event->sender(), new LoadCollectionNamesResponse(this, event->databaseName(), infos));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadCollectionNamesResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new LoadCollectionNamesResponse(this,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new LoadCollectionNamesResponse(this, EventError(ex.what())));
}
else // single server
reply(event->sender(), new LoadCollectionNamesResponse(this, EventError(ex.what())));
}
}
@@ -320,8 +374,17 @@ namespace Robomongo
reply(event->sender(), new LoadUsersResponse(this, event->databaseName(), users));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadUsersResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new LoadUsersResponse(this,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new LoadUsersResponse(this, EventError(ex.what())));
}
else // single server
reply(event->sender(), new LoadUsersResponse(this, EventError(ex.what())));
}
}
@@ -393,8 +456,17 @@ namespace Robomongo
reply(event->sender(), new LoadFunctionsResponse(this, event->databaseName(), funs));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new LoadFunctionsResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new LoadFunctionsResponse(this,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new LoadFunctionsResponse(this, EventError(ex.what())));
}
else // single server
reply(event->sender(), new LoadFunctionsResponse(this, EventError(ex.what())));
}
}
@@ -409,12 +481,22 @@ namespace Robomongo
client->insertDocument(event->obj(), event->ns());
client->done();
reply(event->sender(), new InsertDocumentResponse(this));
} catch(const mongo::DBException &ex) {
EventError error = EventError("Error when saving document: " + ex.toString());
reply(event->sender(), new InsertDocumentResponse(this, error));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
catch(const mongo::DBException &ex) {
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new InsertDocumentResponse(this,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new InsertDocumentResponse(this, EventError(ex.toString())));
}
else { // single server
reply(event->sender(), new InsertDocumentResponse(this,
EventError("Error when saving document: " + ex.toString())));
}
}
}
@@ -423,14 +505,25 @@ namespace Robomongo
try {
boost::scoped_ptr<MongoClient> client(getClient());
client->removeDocuments(event->ns(), event->query(), event->justOne());
client->removeDocuments(event->ns(), event->query(), (event->removeCount() == RemoveDocumentCount::ONE));
client->done();
reply(event->sender(), new RemoveDocumentResponse(this));
} catch(const mongo::DBException &ex) {
EventError error = EventError("Error when deleting document: " + ex.toString());
reply(event->sender(), new RemoveDocumentResponse(this, error));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
reply(event->sender(), new RemoveDocumentResponse(this, event->removeCount(), event->index()));
}
catch(const mongo::DBException &ex) {
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new RemoveDocumentResponse(this,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false), event->removeCount(), event->index()));
}
else // other errors
reply(event->sender(), new RemoveDocumentResponse(this, EventError(ex.what()),
event->removeCount(), event->index()));
}
else // single server
reply(event->sender(), new RemoveDocumentResponse(this,
EventError("Error when deleting document: " + ex.toString()), event->removeCount(), event->index()));
}
}
@@ -459,11 +552,49 @@ namespace Robomongo
return;
}
MongoShellExecResult result = _scriptEngine->exec(event->script, event->databaseName);
// Try to handle case where new shell (which was opened when server unreachable) was re-executed
if (_scriptEngine->failedScope()) {
try {
_scriptEngine->init(_isLoadMongoRcJs);
}
catch (std::exception const& ex) {
LOG_MSG(captilizeFirstChar(ex.what()) + ", cannot init mongo scope",
mongo::logger::LogSeverity::Error());
}
}
// todo: should we use dbName from event or _connSettings?
MongoShellExecResult result = _scriptEngine->exec(event->script, _connSettings->defaultDatabase());
// To fix the problem where 'result' comes with old primary address.
if (_connSettings->isReplicaSet())
result.setCurrentServer(_dbclientRepSet->getSuspectedPrimaryHostAndPort().toString());
if (result.error()) {
// If this is replica set, update script engine and try again
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new ExecuteScriptResponse(this,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
return;
}
else { // primary reachable
_scriptEngine->init(_isLoadMongoRcJs, replicaSetInfo.primary.toString(),
_connSettings->defaultDatabase());
result = _scriptEngine->exec(event->script, _connSettings->defaultDatabase());
}
}
else { // single server
reply(event->sender(), new ExecuteScriptResponse(this, EventError(result.errorMessage())));
return;
}
}
reply(event->sender(), new ExecuteScriptResponse(this, result, event->script.empty()));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new ExecuteScriptResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
}
catch(const std::exception &ex) {
reply(event->sender(), new ExecuteScriptResponse(this, EventError(ex.what(), EventError::Unknown, false)));
}
}
@@ -505,14 +636,23 @@ namespace Robomongo
boost::scoped_ptr<MongoClient> client(getClient());
client->createDatabase(event->database());
// Insert to list of created database
// Read docs for this hashset in the header
// Insert to list of created database. Read docs for this hashset in the header
_createdDbs.insert(event->database());
reply(event->sender(), new CreateDatabaseResponse(this));
reply(event->sender(), new CreateDatabaseResponse(this, event->database()));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new CreateDatabaseResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new CreateDatabaseResponse(this, event->database(),
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new CreateDatabaseResponse(this, event->database(),
EventError(ex.toString())));
}
else // single server
reply(event->sender(), new CreateDatabaseResponse(this, event->database(), EventError(ex.what())));
}
}
@@ -520,45 +660,76 @@ namespace Robomongo
{
try {
boost::scoped_ptr<MongoClient> client(getClient());
client->dropDatabase(event->database());
client->dropDatabase(event->database);
// Remove from the list of created database
// Read docs for this hashset in the header
_createdDbs.erase(event->database());
// Remove from the list of created database, Read docs for this hashset in the header
_createdDbs.erase(event->database);
reply(event->sender(), new DropDatabaseResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DropDatabaseResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
reply(event->sender(), new DropDatabaseResponse(this, event->database));
}
catch(const mongo::DBException &ex) {
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new DropDatabaseResponse(this, event->database,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new DropDatabaseResponse(this, event->database, EventError(ex.toString())));
}
else // single server
reply(event->sender(), new DropDatabaseResponse(this, event->database, EventError(ex.what())));
}
}
void MongoWorker::handle(CreateCollectionRequest *event)
{
std::string const& collection = event->ns().collectionName();
try {
boost::scoped_ptr<MongoClient> client(getClient());
client->createCollection(event->getNs().toString(), event->getSize(), event->getCapped(),
client->createCollection(event->ns().toString(), event->getSize(), event->getCapped(),
event->getMaxDocNum(), event->getExtraOptions());
client->done();
reply(event->sender(), new CreateCollectionResponse(this));
reply(event->sender(), new CreateCollectionResponse(this, collection));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new CreateCollectionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new CreateCollectionResponse(this, collection,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new CreateCollectionResponse(this, collection, EventError(ex.toString())));
}
else // single server
reply(event->sender(), new CreateCollectionResponse(this, collection, EventError(ex.what())));
}
}
void MongoWorker::handle(DropCollectionRequest *event)
{
std::string const& collection = event->ns().collectionName();
try {
boost::scoped_ptr<MongoClient> client(getClient());
client->dropCollection(event->ns());
client->done();
reply(event->sender(), new DropCollectionResponse(this));
reply(event->sender(), new DropCollectionResponse(this, collection));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DropCollectionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new DropCollectionResponse(this, collection,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new DropCollectionResponse(this, collection, EventError(ex.toString())));
}
else // single server
reply(event->sender(), new DropCollectionResponse(this, collection, EventError(ex.what())));
}
}
@@ -569,24 +740,46 @@ namespace Robomongo
client->renameCollection(event->ns(), event->newCollection());
client->done();
reply(event->sender(), new RenameCollectionResponse(this));
reply(event->sender(), new RenameCollectionResponse(this, event->ns().collectionName(),
event->newCollection()));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new RenameCollectionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new RenameCollectionResponse(this,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new RenameCollectionResponse(this, EventError(ex.toString())));
}
else // single server
reply(event->sender(), new RenameCollectionResponse(this, EventError(ex.what())));
}
}
void MongoWorker::handle(DuplicateCollectionRequest *event)
{
std::string const& sourceCollection = event->ns().collectionName();
try {
boost::scoped_ptr<MongoClient> client(getClient());
client->duplicateCollection(event->ns(), event->newCollection());
client->done();
reply(event->sender(), new DuplicateCollectionResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DuplicateCollectionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
reply(event->sender(), new DuplicateCollectionResponse(this, sourceCollection, event->newCollection()));
}
catch (const mongo::DBException &ex) {
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new DuplicateCollectionResponse(this, sourceCollection,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new DuplicateCollectionResponse(this, sourceCollection, EventError(ex.what())));
}
else // single server
reply(event->sender(), new DuplicateCollectionResponse(this, sourceCollection, EventError(ex.what())));
}
}
@@ -612,10 +805,21 @@ namespace Robomongo
client->createUser(event->database(), event->user(), event->overwrite());
client->done();
reply(event->sender(), new CreateUserResponse(this));
reply(event->sender(), new CreateUserResponse(this, event->user().name()));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new CreateUserResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new CreateUserResponse(this, event->user().name(),
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new CreateUserResponse(this, event->user().name(),
EventError(ex.what())));
}
else // single server
reply(event->sender(), new CreateUserResponse(this, event->user().name(),
EventError(ex.what())));
}
}
@@ -626,24 +830,44 @@ namespace Robomongo
client->dropUser(event->database(), event->id());
client->done();
reply(event->sender(), new DropUserResponse(this));
reply(event->sender(), new DropUserResponse(this, event->username()));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DropUserResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new DropUserResponse(this, event->username(),
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new DropUserResponse(this, event->username(), EventError(ex.what())));
}
else // single server
reply(event->sender(), new DropUserResponse(this, event->username(), EventError(ex.what())));
}
}
void MongoWorker::handle(CreateFunctionRequest *event)
{
std::string const& functionName = event->function().name();
try {
boost::scoped_ptr<MongoClient> client(getClient());
client->createFunction(event->database(), event->function(), event->existingFunctionName());
client->done();
reply(event->sender(), new CreateFunctionResponse(this));
reply(event->sender(), new CreateFunctionResponse(this, functionName));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new CreateFunctionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new CreateFunctionResponse(this, functionName,
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new CreateFunctionResponse(this, functionName, EventError(ex.what())));
}
else // single server
reply(event->sender(), new CreateFunctionResponse(this, functionName, EventError(ex.what())));
}
}
@@ -651,42 +875,52 @@ namespace Robomongo
{
try {
boost::scoped_ptr<MongoClient> client(getClient());
client->dropFunction(event->database(), event->name());
client->dropFunction(event->database(), event->functionName());
client->done();
reply(event->sender(), new DropFunctionResponse(this));
} catch(const mongo::DBException &ex) {
reply(event->sender(), new DropFunctionResponse(this, EventError(ex.what())));
LOG_MSG(ex.what(), mongo::logger::LogSeverity::Error());
reply(event->sender(), new DropFunctionResponse(this, event->functionName()));
}
catch (const mongo::DBException &ex) {
if (_connSettings->isReplicaSet()) {
ReplicaSet const& replicaSetInfo = getReplicaSetInfo(true);
if (replicaSetInfo.primary.empty()) { // primary not reachable
reply(event->sender(), new DropFunctionResponse(this, event->functionName(),
EventError(PRIMARY_UNREACHABLE, replicaSetInfo, false)));
}
else // other errors
reply(event->sender(), new DropFunctionResponse(this, event->functionName(), EventError(ex.what())));
}
else // single server
reply(event->sender(), new DropFunctionResponse(this, event->functionName(), EventError(ex.what())));
}
}
mongo::DBClientBase *MongoWorker::getConnection(bool mayReturnNull /* = false */)
{
// --- Configure SSL first --- Todo: move to function?
// As a precaution reset SSL global params for any kind of connection request (SSL or non-SSL)
resetGlobalSSLparams();
// Update global mongo SSL settings according to SSL enable/disabled status
if (_connSettings->sslSettings()->sslEnabled())
{
// Force SSL mode for outgoing connections
mongo::sslGlobalParams.sslMode.store(mongo::SSLParams::SSLMode_requireSSL);
updateGlobalSSLparams();
}
else
{
// Disable forced SSL mode for outgoing connections
mongo::sslGlobalParams.sslMode.store(mongo::SSLParams::SSLMode_allowSSL);
}
configureSSL();
// --- Perform connection ---
if (_connSettings->isReplicaSet()) { // connection to replica set
if (!_dbclientRepSet) {
_dbclientRepSet = std::unique_ptr<mongo::DBClientReplicaSet>(
// todo: where to get name of replica? (i.e. repset) - todo: this if causes crash after app close
new mongo::DBClientReplicaSet("repset",_connSettings->replicaSetSettings()->membersToHostAndPort(), _mongoTimeoutSec));
if (_connSettings->isReplicaSet()) // connection to replica set
{
if (!_dbclientRepSet)
{
init(); // Init mongoworker for early-use of _scriptEngine
// Step-1: Use user entered set name or retrieve set name from cache or from a reachable member
std::string setName = _connSettings->replicaSetSettings()->setNameUserEntered();
if (setName.empty()) {
setName = _connSettings->replicaSetSettings()->cachedSetName();
if (setName.empty()) // If there is no cached set name, get it from an on-line replica node
setName = connectAndGetReplicaSetName();
if (setName.empty()) // It is not possible to continue with empty set name
return nullptr;
}
// Step-2: Try connect to replica set with set name
auto const& membersHostsAndPorts = _connSettings->replicaSetSettings()->membersToHostAndPort();
_dbclientRepSet = DBClientReplicaSet(new mongo::DBClientReplicaSet(setName, membersHostsAndPorts,
_mongoTimeoutSec));
bool const connStatus = _dbclientRepSet->connect();
if (!connStatus) {
@@ -695,17 +929,15 @@ namespace Robomongo
}
return _dbclientRepSet.get();
}
else { // connection to single server
else { // connection to single server
if (!_dbclient) {
// Timeout for operations
// Connect timeout is fixed, but short, at 5 seconds (see headers for DBClientConnection)
_dbclient = std::unique_ptr<mongo::DBClientConnection>(new mongo::DBClientConnection(true, _mongoTimeoutSec));
_dbclient = DBClientConnection(new mongo::DBClientConnection(true, _mongoTimeoutSec));
mongo::Status status = _dbclient->connect(_connSettings->info());
if (!status.isOK() && mayReturnNull) {
mongo::Status status = _dbclient->connect(_connSettings->hostAndPort());
if (!status.isOK() && mayReturnNull)
return nullptr;
}
}
return _dbclient.get();
}
@@ -716,6 +948,22 @@ namespace Robomongo
return new MongoClient(getConnection());
}
void MongoWorker::configureSSL()
{
// As a precaution reset SSL global params for any kind of connection request (SSL or non-SSL)
resetGlobalSSLparams();
// Update global SSL mode and global mongo SSL settings
if (_connSettings->sslSettings()->sslEnabled()) {
// Force SSL mode for outgoing connections
mongo::sslGlobalParams.sslMode.store(mongo::SSLParams::SSLMode_requireSSL);
updateGlobalSSLparams();
}
else {
// Disable forced SSL mode for outgoing connections
mongo::sslGlobalParams.sslMode.store(mongo::SSLParams::SSLMode_allowSSL);
}
}
void MongoWorker::updateGlobalSSLparams() const
{
resetGlobalSSLparams();
@@ -747,6 +995,82 @@ namespace Robomongo
mongo::sslGlobalParams.sslAllowInvalidHostnames = false;
}
ReplicaSet MongoWorker::getReplicaSetInfo(bool refresh /*= true*/) const
{
std::string setName;
mongo::HostAndPort primary;
std::vector<std::pair<std::string, bool>> membersAndHealths;
// Refresh view of Replica Set Monitor to get live data
auto repSetMonitor = mongo::globalRSMonitorManager.getMonitor(_dbclientRepSet->getSetName());
// Handle long lasting (e.g. overnight) connections which causes replica set monitor to be null
if (!repSetMonitor) {
mongo::ReplicaSetMonitor::createIfNeeded(_dbclientRepSet->getSetName(),
_connSettings->replicaSetSettings()->membersToHostAndPortAsSet());
repSetMonitor = mongo::globalRSMonitorManager.getMonitor(_dbclientRepSet->getSetName());
if (!repSetMonitor) // if nullptr again even if the set is reachable, something went really wrong.
return ReplicaSet(setName, primary, membersAndHealths,
"Unexpected error. Unable to get replica set monitor for set name: " +
_dbclientRepSet->getSetName() + "\nPlease open a new connection.");
}
// refreshAll() takes long time, in order to do first establish connection request much faster
// refreshAll() will be by-passed first and it will/must be done after a successful connection.
if (refresh)
repSetMonitor->startOrContinueRefresh().refreshAll();
setName = repSetMonitor->getName();
auto const primaryOnly = mongo::ReadPreferenceSetting(mongo::ReadPreference::PrimaryOnly, mongo::TagSet());
auto primaryWithStatus = repSetMonitor->getHostOrRefresh(primaryOnly, mongo::Milliseconds(2000)); // todo
if (primaryWithStatus.isOK())
primary = primaryWithStatus.getValue();
QStringList servers;
// i.e. setNameAndMembers: "repset/localhost:27017,localhost:27018,localhost:27019"
auto setNameAndMembers = QString::fromStdString(repSetMonitor->getServerAddress());
QStringList result = setNameAndMembers.split("/");
if (result.size() > 1)
servers = result[1].split(",");
// Save address and health of replica members
for (QString const& server : servers) {
auto hostAndPort = mongo::HostAndPort(mongo::StringData(server.toStdString()));
membersAndHealths.push_back({ server.toStdString(), repSetMonitor->isHostUp(hostAndPort) });
}
return ReplicaSet(setName, primary, membersAndHealths, primaryWithStatus.getStatus().reason());
}
std::string MongoWorker::connectAndGetReplicaSetName() const
{
auto dbclientTemp = DBClientConnection(new mongo::DBClientConnection(true, 10));
std::string setName = "";
// Try connecting to the nodes one by one until getting replica set name.
for (auto const& node : _connSettings->replicaSetSettings()->membersToHostAndPort())
{
mongo::Status const status = dbclientTemp->connect(node);
if (status.isOK())
{
_scriptEngine->init(_isLoadMongoRcJs, node.toString());
MongoShellExecResult result = _scriptEngine->exec("rs.status()", "");
if (!result.results().empty())
{
auto resultDocs = result.results().front().documents();
if (!resultDocs.empty())
{
setName = resultDocs.front()->bsonObj().getStringField("set");
if (!setName.empty()) // We get the information, finish the loop
break;
}
}
}
}
return setName;
}
/**
* @brief Send event to this MongoWorker
*/
@@ -768,4 +1092,19 @@ namespace Robomongo
AppRegistry::instance().bus()->send(receiver, event);
}
void MongoWorker::pingDatabase(mongo::DBClientBase *dbclient) const
{
// Building { ping: 1 }
mongo::BSONObjBuilder command;
command.append("ping", 1);
mongo::BSONObj result;
std::string authBase = getAuthBase();
if (authBase.empty()) {
dbclient->runCommand("admin", command.obj(), result);
}
else {
dbclient->runCommand(authBase, command.obj(), result);
}
}
}
+47 -7
View File
@@ -4,7 +4,7 @@
#include <QMutex>
#include <unordered_set>
#include <mongo/client/dbclient_rs.h> // todo: move
#include <mongo/client/dbclient_rs.h>
#include "robomongo/core/events/MongoEvents.h"
@@ -24,16 +24,23 @@ namespace Robomongo
Q_OBJECT
public:
enum { pingTimeMs = 60 * 1000 };
typedef std::vector<std::string> DatabasesContainerType;
using DBClientReplicaSet = std::unique_ptr<mongo::DBClientReplicaSet>;
using DBClientConnection = std::unique_ptr<mongo::DBClientConnection>;
explicit MongoWorker(ConnectionSettings *connection, bool isLoadMongoRcJs, int batchSize,
int mongoTimeoutSec, int shellTimeoutSec, QObject *parent = NULL);
~MongoWorker();
enum { pingTimeMs = 60 * 1000 };
void interrupt();
void stopAndDelete();
protected Q_SLOTS: // handlers:
protected Q_SLOTS:
void init();
/**
* @brief Every minute we are issuing { ping : 1 } command to every used connection
* in order to avoid dropped connections.
@@ -43,7 +50,13 @@ namespace Robomongo
/**
* @brief Initiate connection to MongoDB
*/
void handle(EstablishConnectionRequest *event);
bool handle(EstablishConnectionRequest *event);
/**
* @brief Try to re-connect to MongoDB replica set in order to refresh connection view.
* For more details, see MongoServer::tryRefreshReplicaSetFolder()
*/
void handle(RefreshReplicaSetFolderRequest *event);
/**
* @brief Load list of all database names
@@ -130,14 +143,18 @@ namespace Robomongo
* @brief Send event to this MongoWorker
*/
void send(Event *event);
DatabasesContainerType getDatabaseNamesSafe();
std::string getAuthBase() const;
std::unique_ptr<mongo::DBClientConnection> _dbclient;
std::unique_ptr<mongo::DBClientReplicaSet> _dbclientRepSet;
mongo::DBClientBase *getConnection(bool mayReturnNull = false);
MongoClient *getClient();
/**
*@brief Reset and update global mongo SSL settings (mongo::sslGlobalParams)
*/
void configureSSL();
/**
*@brief Update global mongo SSL settings (mongo::sslGlobalParams) according to active connection
* request's SSL settings.
@@ -149,14 +166,34 @@ namespace Robomongo
*/
void resetGlobalSSLparams() const;
/**
*@brief Update Replica Set related parameters/settings.
* Warning: Using parameter 'refresh' as false might return incorrect cached values for status
* of secondary members.
* @param refresh : If true it will be deep scan of replica set status which
* might take quite some time. If false, it will be quick scan
* which might return incorrect cached values for status of secondary members.
* Use false only to achieve quick first connection, in all other cases true
* should be used.
*/
ReplicaSet getReplicaSetInfo(bool refresh = true) const;
std::string connectAndGetReplicaSetName() const;
/**
* @brief Send reply event to object 'obj'
*/
void reply(QObject *receiver, Event *event);
/**
* @brief Send hear beat messages to database in order to keep connection alive
*/
void pingDatabase(mongo::DBClientBase *dbclient) const;
QThread *_thread;
QMutex _firstConnectionMutex;
ScriptEngine *_scriptEngine;
std::unique_ptr<ScriptEngine> _scriptEngine;
bool _isAdmin;
const bool _isLoadMongoRcJs;
@@ -167,6 +204,9 @@ namespace Robomongo
int _shellTimeoutSec;
QAtomicInteger<int> _isQuiting;
std::unique_ptr<mongo::DBClientConnection> _dbclient;
std::unique_ptr<mongo::DBClientReplicaSet> _dbclientRepSet;
ConnectionSettings *_connSettings;
// Collection of created databases.
+12
View File
@@ -0,0 +1,12 @@
#include "robomongo/core/mongodb/ReplicaSet.h"
namespace Robomongo
{
ReplicaSet::ReplicaSet(std::string const& setName, const mongo::HostAndPort primary,
std::vector<std::pair<std::string, bool>> const& membersAndHealths,
std::string const& errorStr)
: setName(setName), primary(primary), membersAndHealths(membersAndHealths),
errorStr(errorStr)
{}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <mongo/util/net/hostandport.h>
namespace Robomongo
{
/**
* @brief Struct to represent live information of a replica set.
*/
struct ReplicaSet
{
ReplicaSet(std::string const& setName, const mongo::HostAndPort primary,
std::vector<std::pair<std::string, bool>> const& membersAndHealths, std::string const& errorStr = "");
ReplicaSet() {};
std::string const setName;
mongo::HostAndPort const primary;
std::vector<std::pair<std::string, bool>> const membersAndHealths; // pair: {HostNameAndPort, Health}
std::string const errorStr;
};
}
@@ -2,12 +2,16 @@
#include <stdio.h>
#include <QUuid>
#include "robomongo/core/settings/CredentialSettings.h"
#include "robomongo/core/settings/ReplicaSetSettings.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/settings/SslSettings.h"
#include "robomongo/core/utils/QtUtils.h"
#include <boost/algorithm/string.hpp>
namespace
{
const unsigned port = 27017;
@@ -19,21 +23,54 @@ namespace
namespace Robomongo
{
/**
* Creates ConnectionSettings with default values
*/
ConnectionSettings::ConnectionSettings() : QObject(),
ConnectionSettings::ConnectionSettings(bool isClone) : QObject(),
_connectionName(defaultNameConnection),
_host(defaultServerHost),
_port(port),
_imported(false),
_sshSettings(new SshSettings()),
_sslSettings(new SslSettings()),
_isReplicaSet(false),
_replicaSetSettings(new ReplicaSetSettings()),
_isReplicaSet(false)
_clone(isClone)
{ }
ConnectionSettings::ConnectionSettings(const mongo::MongoURI& uri, bool isClone)
: _connectionName(defaultNameConnection),
_host(defaultServerHost),
_port(port),
_imported(false),
_sshSettings(new SshSettings()),
_sslSettings(new SslSettings()),
_isReplicaSet((uri.type() == mongo::ConnectionString::ConnectionType::SET)),
_replicaSetSettings(new ReplicaSetSettings(uri)),
_clone(isClone)
{
if (!uri.getServers().empty()) {
_host = uri.getServers().front().host();
_port = uri.getServers().front().port();
}
auto str = std::string(uri.getOptions().getStringField("ssl"));
auto sslEnabled = ("true" == str);
if (sslEnabled) {
_sslSettings->enableSSL(true);
_sslSettings->setAllowInvalidCertificates(true);
}
auto credential = new CredentialSettings();
credential->setUserName(uri.getUser());
credential->setUserPassword(uri.getPassword());
credential->setDatabaseName(uri.getDatabase());
if (!credential->userName().empty() && !credential->userPassword().empty()) { // todo:
credential->setEnabled(true);
}
addCredential(credential);
}
void ConnectionSettings::fromVariant(const QVariantMap &map) {
setConnectionName(QtUtils::toStdString(map.value("connectionName").toString()));
setServerHost(QtUtils::toStdString(map.value("serverHost").toString().left(maxLength)));
@@ -60,6 +97,13 @@ namespace Robomongo
_replicaSetSettings->fromVariant(map.value("replicaSet").toMap());
}
// If UUID has never been created or is empty, create a new one. Otherwise load the existing.
if (!map.contains("uuid") || map.value("uuid").toString().isEmpty())
_uuid = QUuid::createUuid().toString();
else
_uuid = map.value("uuid").toString();
//#ifdef MONGO_SSL
// ,SSLInfo(map.value("sslEnabled").toBool(),QtUtils::toStdString(map.value("sslPemKeyFile").toString()))
//#endif
@@ -71,9 +115,6 @@ namespace Robomongo
ConnectionSettings::~ConnectionSettings()
{
clearCredentials();
delete _sshSettings;
delete _sslSettings;
delete _replicaSetSettings; // todo: unique_ptr
}
/**
@@ -81,9 +122,10 @@ namespace Robomongo
*/
ConnectionSettings *ConnectionSettings::clone() const
{
ConnectionSettings *record = new ConnectionSettings();
record->apply(this);
return record;
auto settings = new ConnectionSettings(true);
settings->setUuid(_uuid);
settings->apply(this);
return settings;
}
/**
@@ -104,9 +146,9 @@ namespace Robomongo
addCredential((*it)->clone());
}
_sshSettings = source->sshSettings()->clone();
_sslSettings = source->sslSettings()->clone();
_replicaSetSettings = source->_replicaSetSettings->clone(); // todo: leak candidate
_sshSettings.reset(source->sshSettings()->clone());
_sslSettings.reset(source->sslSettings()->clone());
_replicaSetSettings.reset(source->_replicaSetSettings->clone());
//#ifdef MONGO_SSL
// setSslInfo(source->sslInfo());
@@ -142,13 +184,14 @@ namespace Robomongo
map.insert("ssh", _sshSettings->toVariant());
map.insert("ssl", _sslSettings->toVariant());
map.insert("uuid", _uuid);
return map;
}
CredentialSettings *ConnectionSettings::findCredential(const std::string &databaseName) const
{
CredentialSettings *result = NULL;
CredentialSettings *result = nullptr;
for (QList<CredentialSettings *>::const_iterator it = _credentials.begin(); it != _credentials.end(); ++it) {
CredentialSettings *cred = *it;
if (cred->databaseName() == databaseName) {
@@ -172,7 +215,7 @@ namespace Robomongo
* @brief Checks whether this connection has primary credential
* which is also enabled.
*/
bool ConnectionSettings::hasEnabledPrimaryCredential()
bool ConnectionSettings::hasEnabledPrimaryCredential() const
{
if (_credentials.count() == 0)
return false;
@@ -183,7 +226,7 @@ namespace Robomongo
/**
* @brief Returns primary credential
*/
CredentialSettings *ConnectionSettings::primaryCredential()
CredentialSettings *ConnectionSettings::primaryCredential() const
{
if (_credentials.count() == 0) {
_credentials.append(new CredentialSettings());
@@ -203,9 +246,26 @@ namespace Robomongo
std::string ConnectionSettings::getFullAddress() const
{
return info().toString();
return hostAndPort().toString();
}
mongo::HostAndPort ConnectionSettings::hostAndPort() 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);
}
}
@@ -3,8 +3,10 @@
#include <QString>
#include <QVariant>
#include <QVariantMap>
#include <mongo/client/dbclientinterface.h>
#include <boost/algorithm/string.hpp>
#include <mongo/client/mongo_uri.h>
#include <mongo/util/net/hostandport.h>
namespace Robomongo
{
@@ -24,9 +26,14 @@ namespace Robomongo
/**
* @brief Creates ConnectionSettings with default values
*/
ConnectionSettings();
ConnectionSettings(bool isClone);
/**
* @brief Creates ConnectionSettings from mongo connection string URI
*/
ConnectionSettings(const mongo::MongoURI& uri, bool isClone);
explicit ConnectionSettings(QVariantMap map);
explicit ConnectionSettings(QVariantMap map, bool isClone);
/**
* @brief Cleanup used resources
@@ -64,7 +71,7 @@ namespace Robomongo
/**
* @brief Port of server
*/
unsigned short serverPort() const { return _port; }
int serverPort() const { return _port; }
void setServerPort(const int port) { _port = port; }
/**
@@ -93,12 +100,12 @@ namespace Robomongo
* @brief Checks whether this connection has primary credential
* which is also enabled.
*/
bool hasEnabledPrimaryCredential();
bool hasEnabledPrimaryCredential() const;
/**
* @brief Returns primary credential
*/
CredentialSettings *primaryCredential();
CredentialSettings *primaryCredential() const;
/**
* @brief Returns number of credentials
@@ -133,45 +140,46 @@ namespace Robomongo
return _connectionName;
}
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; }
SslSettings *sslSettings() const { return _sslSettings; }
ReplicaSetSettings *replicaSetSettings() const { return _replicaSetSettings; }
mongo::HostAndPort hostAndPort() const;
SshSettings *sshSettings() const { return _sshSettings.get(); }
SslSettings *sslSettings() const { return _sslSettings.get(); }
ReplicaSetSettings *replicaSetSettings() const { return _replicaSetSettings.get(); }
bool isReplicaSet() const { return _isReplicaSet; }
void setReplicaSet(bool flag) { _isReplicaSet = flag; }
QString uuid() const { return _uuid; }
void setUuid(QString const& uuid) { _uuid = uuid; }
private:
CredentialSettings *findCredential(const std::string &databaseName) const;
bool _isReplicaSet;
std::string _connectionName;
std::string _host;
int _port;
std::string _defaultDatabase;
QList<CredentialSettings *> _credentials;
SshSettings *_sshSettings;
SslSettings *_sslSettings;
ReplicaSetSettings *_replicaSetSettings;
mutable QList<CredentialSettings *> _credentials;
std::unique_ptr<SshSettings> _sshSettings;
std::unique_ptr<SslSettings> _sslSettings;
bool _isReplicaSet;
std::unique_ptr<ReplicaSetSettings> _replicaSetSettings;
// Was this connection imported from somewhere?
bool _imported;
// Flag to check if this is a clone(copy) or original ConnectionSettings
// Note: If this is not a clone connection settings, this object is original connection
// ConnectionSettings object which is loaded/saved into Robomongo config. file.
bool _clone = false;
// If this is a clone connection settings, unique ID will be used to identify from which
// original connection settings this object is cloned.
// -1 for invalid(uninitialized) unique ID which should not be seen in theory
int _uniqueId = -1;
// UUID string taken from QUuid.
// It is used to identify the unique ID of a connection settings object
QString _uuid;
};
}
@@ -6,9 +6,18 @@ namespace Robomongo
{
ReplicaSetSettings::ReplicaSetSettings()
: _members(), _readPreference(ReadPreference::PRIMARY)
: _cachedSetName(""), _members(), _readPreference(ReadPreference::PRIMARY)
{}
ReplicaSetSettings::ReplicaSetSettings(const mongo::MongoURI& uri)
: ReplicaSetSettings()
{
_cachedSetName = uri.getSetName();
for (auto const& server : uri.getServers()) {
_members.push_back(server.host() + ":" + std::to_string(server.port()));
}
}
ReplicaSetSettings *ReplicaSetSettings::clone() const
{
ReplicaSetSettings *cloned = new ReplicaSetSettings(*this);
@@ -18,6 +27,8 @@ namespace Robomongo
QVariant ReplicaSetSettings::toVariant() const
{
QVariantMap map;
map.insert("setNameUserEntered", QString::fromStdString(_setNameUserEntered));
map.insert("cachedSetName", QString::fromStdString(_cachedSetName));
int idx = 0;
for (std::string const& str : _members) {
map.insert(QString::number(idx), QtUtils::toQString(str));
@@ -29,9 +40,11 @@ namespace Robomongo
void ReplicaSetSettings::fromVariant(const QVariantMap &map)
{
setSetNameUserEntered(map.value("setNameUserEntered").toString().toStdString());
setCachedSetName(map.value("cachedSetName").toString().toStdString());
// Extract and set replica members
std::vector<std::string> vec;
auto& itr = map.begin();
auto itr = map.begin();
int idx = 0;
do
{
@@ -44,14 +57,28 @@ namespace Robomongo
// Extract and set read reference
setReadPreference(static_cast<ReadPreference>(map.value("readPreference").toInt()));
}
void ReplicaSetSettings::setMembers(const std::vector<std::string>& members)
{
_members.clear();
for (auto const& member : members)
_members.push_back(member);
}
const std::vector<mongo::HostAndPort> ReplicaSetSettings::membersToHostAndPort() const
void ReplicaSetSettings::setMembers(const std::vector<std::pair<std::string, bool>>& membersAndHealts)
{
_members.clear();
for (auto const& memberAndHealth : membersAndHealts)
_members.push_back(memberAndHealth.first);
}
std::vector<mongo::HostAndPort> ReplicaSetSettings::membersToHostAndPort() const
{
std::vector<mongo::HostAndPort> membersHostAndPort;
for (auto const& member : _members)
{
for (auto const& member : _members)
membersHostAndPort.push_back(mongo::HostAndPort(member));
}
return membersHostAndPort;
}
@@ -5,6 +5,7 @@
#include <QVariantMap>
#include <mongo/util/net/hostandport.h>
#include <mongo/client/mongo_uri.h>
namespace Robomongo
{
@@ -15,9 +16,12 @@ namespace Robomongo
{
PRIMARY = 0,
PRIMARY_PREFERRED = 1
// todo: Add all supported read preferences
};
ReplicaSetSettings();
ReplicaSetSettings(const mongo::MongoURI& uri);
/**
* Clones Replica Set settings.
@@ -30,22 +34,33 @@ namespace Robomongo
QVariant toVariant() const;
void fromVariant(const QVariantMap &map);
//// todo: remove
//void addMember(const std::string& str) { _members.push_back(str); }
// Getters
//
const std::vector<std::string>& members() const { return _members; }
const std::vector<mongo::HostAndPort> membersToHostAndPort() const;
std::string const& setNameUserEntered() { return _setNameUserEntered; }
std::string const& cachedSetName() { return _cachedSetName; }
std::vector<std::string> const& members() const { return _members; }
std::vector<mongo::HostAndPort> membersToHostAndPort() const;
std::set<mongo::HostAndPort> membersToHostAndPortAsSet() const
{
auto const& members = membersToHostAndPort();
return std::set<mongo::HostAndPort>(members.begin(), members.end());
}
ReadPreference readPreference() const { return _readPreference; }
// Setters
//
void setMembers(const std::vector<std::string>& members) { _members = members; }
void setSetNameUserEntered(const std::string& setName) { _setNameUserEntered = setName; }
void setCachedSetName(const std::string& setName) { _cachedSetName = setName; }
void setMembers(const std::vector<std::string>& members);
void setMembers(const std::vector<std::pair<std::string,bool>>& membersAndHealts);
void deleteAllMembers() { _members.clear(); }
void setReadPreference(ReadPreference readPreference) { _readPreference = readPreference; }
private:
std::string _setNameUserEntered;
std::string _cachedSetName;
std::vector<std::string> _members;
ReadPreference _readPreference;
ReadPreference _readPreference = ReadPreference::PRIMARY;
};
}
+333 -80
View File
@@ -3,6 +3,23 @@
#include <QDir>
#include <QFile>
#include <QVariantList>
#include <QUuid>
#include <QJsonArray>
#include <QXmlStreamReader>
// Todo:
// Warning:
// Currently, this include statement should be maintained manually.
// The Qt version must be updated if Qt is upgraded. Otherwise it might cause build error.
// The correct path will be: PROJECT_QT_VERSION/QtGui/private/qzipreader_p.h
// Details:
// Ideally, private class inside qzipreader_p.h should not be used since it is not
// a publicly/officially supported API by Qt. It is used here since we are doing a
// a very lightweight zip operation which is unzipping a zip archive with one, small file.
// The alternative is including QuaZip and Zlib third party libraries into CMAKE project and
// building, and using them which might be time consuming.
#include <5.7.0/QtGui/private/qzipreader_p.h>
#include <parser.h>
#include <serializer.h>
@@ -14,29 +31,68 @@
#include "robomongo/core/utils/QtUtils.h"
#include "robomongo/core/utils/StdUtils.h"
#include "robomongo/gui/AppStyle.h"
#include "robomongo/utils/common.h"
namespace
namespace Robomongo
{
/**
* @brief Version of schema
*/
const QString SchemaVersion = "2.0";
// 3T config files
auto const Studio3T_PropertiesDat = QString("%1/.3T/studio-3t/properties.dat").arg(QDir::homePath());
auto const DataMongodb_PropertiesDat = QString("%1/.3T/data-man-mongodb/properties.dat").arg(QDir::homePath());
auto const MongoChefPro_PropertiesDat = QString("%1/.3T/mongochef-pro/properties.dat").arg(QDir::homePath());
auto const MongoChefEnt_PropertiesDat = QString("%1/.3T/mongochef-enterprise/properties.dat").arg(QDir::homePath());
/**
* @brief Config file absolute path
* (usually: /home/user/.config/robomongo/robomongo.json)
*/
const QString _configPath = QString("%1/.config/robomongo/0.9/robomongo.json").arg(QDir::homePath());
const std::vector<std::pair<QString, QString>> S_3T_ZipFile_And_ConfigFile_List
{
{ Studio3T_PropertiesDat, "Studio3T.properties" },
{ DataMongodb_PropertiesDat, "3T.data-man-mongodb.properties" },
{ MongoChefPro_PropertiesDat, "3T.mongochef-pro.properties" },
{ MongoChefEnt_PropertiesDat, "3T.mongochef-enterprise.properties" }
};
// Extract zipFile and find the value of "anonymousID" field in propFile
QString extractAnonymousID(QString const& zipFile, QString const& propfile);
/**
* @brief Version of schema
*/
const QString SchemaVersion = "2.0";
/**
* @brief Config file containing directory path
* (usually: /home/user/.config/robomongo)
*/
const QString _configDir = QString("%1/.config/robomongo/0.9").arg(QDir::homePath());
}
namespace Robomongo
{
/**
* @brief Robomongo config. files
*/
const auto CONFIG_FILE_0_8_5 = QString("%1/.config/robomongo/robomongo.json").arg(QDir::homePath());
const auto CONFIG_FILE_0_9 = QString("%1/.config/robomongo/0.9/robomongo.json").arg(QDir::homePath());
const auto CONFIG_FILE_1_0_RC1 = QString("%1/.config/robomongo/1.0/robomongo.json").arg(QDir::homePath());
// Change of path due to re-branding to 3T
const auto CONFIG_FILE_1_0_0 = QString("%1/.3T/robomongo/1.0.0/robomongo.json").arg(QDir::homePath());
struct ConfigFileAndImportFunction
{
ConfigFileAndImportFunction(QString const& configFile, std::function<bool()> importFunction)
: file(configFile), import(importFunction) {}
QString file;
std::function<bool()> import;
};
// Warning: Config. file and import function of a new release must be placed as first element of
// vector initializer list below in order.
std::vector<ConfigFileAndImportFunction> const SettingsManager::_configFilesAndImportFunctions
{
ConfigFileAndImportFunction(CONFIG_FILE_1_0_RC1, SettingsManager::importConnectionsFrom_1_0_RC1_to_1_0_0),
ConfigFileAndImportFunction(CONFIG_FILE_0_9, SettingsManager::importConnectionsFrom_0_9_to_1_0_RC1),
ConfigFileAndImportFunction(CONFIG_FILE_0_8_5, SettingsManager::importConnectionsFrom_0_8_5_to_0_9)
};
std::vector<ConnectionSettings*> SettingsManager::_connections;
// Temporarily disabling Recent Connections feature
// std::vector<RecentConnection> SettingsManager::_recentConnections;
/**
* Creates SettingsManager for config file in default location
* ~/.config/robomongo/robomongo.json
@@ -47,18 +103,23 @@ namespace Robomongo
_timeZone(Utc),
_viewMode(Robomongo::Tree),
_autocompletionMode(AutocompleteAll),
_batchSize(50),
_loadMongoRcJs(false),
_minimizeToTray(false),
_lineNumbers(false),
_disableConnectionShortcuts(false),
_batchSize(50),
_textFontFamily(""),
_textFontPointSize(-1),
_lineNumbers(false),
_loadMongoRcJs(false),
_imported(false),
_mongoTimeoutSec(10),
_shellTimeoutSec(15)
_shellTimeoutSec(15),
_imported(false)
{
load();
LOG_MSG("SettingsManager initialized in " + _configPath, mongo::logger::LogSeverity::Info(), false);
if (!load()) { // if load fails (probably due to non-existing config. file or directory)
save(); // create empty settings file
load(); // try loading again for the purpose of import from previous Robomongo versions
}
LOG_MSG("SettingsManager initialized in " + ConfigFilePath, mongo::logger::LogSeverity::Info(), false);
}
SettingsManager::~SettingsManager()
@@ -72,10 +133,10 @@ namespace Robomongo
*/
bool SettingsManager::load()
{
if (!QFile::exists(_configPath))
if (!QFile::exists(ConfigFilePath))
return false;
QFile f(_configPath);
QFile f(ConfigFilePath);
if (!f.open(QIODevice::ReadOnly))
return false;
@@ -98,14 +159,14 @@ namespace Robomongo
{
QVariantMap map = convertToMap();
if (!QDir().mkpath(_configDir)) {
LOG_MSG("ERROR: Could not create settings path: " + _configDir, mongo::logger::LogSeverity::Error());
if (!QDir().mkpath(ConfigDir)) {
LOG_MSG("ERROR: Could not create settings path: " + ConfigDir, mongo::logger::LogSeverity::Error());
return false;
}
QFile f(_configPath);
QFile f(ConfigFilePath);
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
LOG_MSG("ERROR: Could not write settings to: " + _configPath, mongo::logger::LogSeverity::Error());
LOG_MSG("ERROR: Could not write settings to: " + ConfigFilePath, mongo::logger::LogSeverity::Error());
return false;
}
@@ -114,11 +175,21 @@ namespace Robomongo
s.setIndentMode(QJson::IndentFull);
s.serialize(map, &f, &ok);
LOG_MSG("Settings saved to: " + _configPath, mongo::logger::LogSeverity::Info());
LOG_MSG("Settings saved to: " + ConfigFilePath, mongo::logger::LogSeverity::Info());
return ok;
}
void SettingsManager::addCacheData(QString const& key, QVariant const& value)
{
_cacheData.insert(key, value);
}
QVariant SettingsManager::cacheData(QString const& key) const
{
return _cacheData.value(key);
}
/**
* Load settings from the map. Existings settings will be overwritten.
*/
@@ -132,7 +203,7 @@ namespace Robomongo
if (encoding > 3 || encoding < 0)
encoding = 0;
_uuidEncoding = (UUIDEncoding) encoding;
_uuidEncoding = (UUIDEncoding)encoding;
// 3. Load view mode
@@ -140,39 +211,41 @@ namespace Robomongo
int viewMode = map.value("viewMode").toInt();
if (viewMode > 2 || viewMode < 0)
viewMode = Custom; // Default View Mode
_viewMode = (ViewMode) viewMode;
} else {
_viewMode = (ViewMode)viewMode;
}
else {
_viewMode = Custom; // Default View Mode
}
_autoExpand = map.contains("autoExpand") ?
map.value("autoExpand").toBool() : true;
_autoExec = map.contains("autoExec") ?
map.value("autoExec").toBool() : true;
_lineNumbers = map.contains("lineNumbers") ?
map.value("lineNumbers").toBool() : false;
_imported = map.contains("imported") ?
map.value("imported").toBool() : false;
_autoExpand = map.contains("autoExpand") ? map.value("autoExpand").toBool() : true;
_autoExec = map.contains("autoExec") ? map.value("autoExec").toBool() : true;
_minimizeToTray = map.contains("minimizeToTray") ? map.value("minimizeToTray").toBool() : false;
_lineNumbers = map.contains("lineNumbers") ? map.value("lineNumbers").toBool() : false;
_imported = map.contains("imported") ? map.value("imported").toBool() : false;
// 4. Load TimeZone
int timeZone = map.value("timeZone").toInt();
if (timeZone > 1 || timeZone < 0)
timeZone = 0;
_timeZone = (SupportedTimes) timeZone;
_timeZone = (SupportedTimes)timeZone;
_loadMongoRcJs = map.value("loadMongoRcJs").toBool();
_disableConnectionShortcuts = map.value("disableConnectionShortcuts").toBool();
if (map.contains("acceptedEulaVersions"))
_acceptedEulaVersions = map.value("acceptedEulaVersions").toStringList().toSet();
// Load anonymousID
_anonymousID = getOrCreateAnonymousID(map);
// Load AutocompletionMode
if (map.contains("autocompletionMode")) {
int autocompletionMode = map.value("autocompletionMode").toInt();
if (autocompletionMode < 0 || autocompletionMode > 2)
autocompletionMode = AutocompleteAll; // Default Mode
_autocompletionMode = (AutocompletionMode) autocompletionMode;
} else {
_autocompletionMode = (AutocompletionMode)autocompletionMode;
}
else {
_autocompletionMode = AutocompleteAll; // Default Mode
}
@@ -180,6 +253,10 @@ namespace Robomongo
_batchSize = map.value("batchSize").toInt();
if (_batchSize == 0)
_batchSize = 50;
if (map.contains("checkForUpdates"))
_checkForUpdates = map.value("checkForUpdates").toBool();
_currentStyle = map.value("style").toString();
if (_currentStyle.isEmpty()) {
_currentStyle = AppStyle::StyleName;
@@ -200,33 +277,48 @@ namespace Robomongo
// 5. Load connections
_connections.clear();
QVariantList list = map.value("connections").toList();
for (QVariantList::iterator it = list.begin(); it != list.end(); ++it) {
ConnectionSettings *record = new ConnectionSettings();
record->fromVariant((*it).toMap());
addConnection(record);
QVariantList const& list = map.value("connections").toList();
for (auto const& conn : list) {
auto connSettings = new ConnectionSettings(false);
connSettings->fromVariant(conn.toMap());
addConnection(connSettings);
}
/* Temporarily disabling Recent Connections feature
// Load recent connections
_recentConnections.clear();
QVariantList const& rlist = map.value("recentConnections").toList();
for (auto const& rconn : rlist) {
_recentConnections.push_back(RecentConnection(rconn.toMap().value("uuid").toString(),
rconn.toMap().value("name").toString().toStdString()));
}
*/
_toolbars = map.value("toolbars").toMap();
ToolbarSettingsContainerType::const_iterator it = _toolbars.find("connect");
if (_toolbars.end() == it)
_toolbars["connect"] = true;
it = _toolbars.find("open_save");
if (_toolbars.end() == it)
_toolbars["open_save"] = true;
it = _toolbars.find("exec");
if (_toolbars.end() == it)
_toolbars["exec"] = true;
it = _toolbars.find("explorer");
if (_toolbars.end() == it)
_toolbars["explorer"] = true;
it = _toolbars.find("logs");
if (_toolbars.end() == it)
_toolbars["logs"] = false;
// Load connection settings from previous
// versions of Robomongo
importPreviousConnections();
_cacheData = map.value("cacheData").toMap();
// Load connection settings from previous versions of Robomongo
importConnections();
}
/**
@@ -258,40 +350,84 @@ namespace Robomongo
// 7. Save disableConnectionShortcuts
map.insert("disableConnectionShortcuts", _disableConnectionShortcuts);
// 8. Save acceptedEulaVersions array
QJsonArray arr;
for (auto const& str : _acceptedEulaVersions)
arr.push_back(str);
// 8. Save batchSize
map.insert("acceptedEulaVersions", arr.toVariantList());
// 9. Save batchSize
map.insert("batchSize", _batchSize);
map.insert("checkForUpdates", _checkForUpdates);
map.insert("mongoTimeoutSec", _mongoTimeoutSec);
map.insert("shellTimeoutSec", _shellTimeoutSec);
// 9. Save style
// 10. Save style
map.insert("style", _currentStyle);
// 10. Save font information
// 11. Save font information
map.insert("textFontFamily", _textFontFamily);
map.insert("textFontPointSize", _textFontPointSize);
// 11. Save connections
// 12. Save connections
QVariantList list;
for (ConnectionSettingsContainerType::const_iterator it = _connections.begin(); it != _connections.end(); ++it) {
QVariantMap rm = (*it)->toVariant().toMap();
list.append(rm);
}
for (auto const conn : _connections)
list.append(conn->toVariant().toMap());
map.insert("connections", list);
/* Temporarily disabling Recent Connections feature
// 13. Save recent connections
QVariantList recentConnsList;
QVariantMap recentConnMap;
for (auto const& rconn : _recentConnections) {
recentConnMap.insert("uuid", rconn.uuid);
recentConnMap.insert("name", QString::fromStdString(rconn.name));
recentConnsList.append(recentConnMap);
}
map.insert("recentConnections", recentConnsList);
*/
map.insert("autoExec", _autoExec);
map.insert("minimizeToTray", _minimizeToTray);
map.insert("toolbars", _toolbars);
map.insert("imported", _imported);
map.insert("anonymousID", _anonymousID);
map.insert("cacheData", _cacheData);
return map;
}
QString SettingsManager::getOrCreateAnonymousID(QVariantMap const& map) const
{
QString anonymousID = "";
// If anonymousID has never been created or is empty, create a new one. Otherwise load the existing.
if (map.contains("anonymousID")) {
QUuid id = map.value("anonymousID").toString();
if (!id.isNull())
anonymousID = id.toString();
}
for (auto const& zipFileAndConfigFile : S_3T_ZipFile_And_ConfigFile_List) {
if (!anonymousID.isEmpty())
break;
QUuid const& id = extractAnonymousID(zipFileAndConfigFile.first, zipFileAndConfigFile.second);
if (!id.isNull())
anonymousID = id.toString();
}
if (anonymousID.isEmpty())
anonymousID = QUuid::createUuid().toString();
return anonymousID;
}
/**
* Adds connection to the end of list
* Adds connection to the end of list and set it's uniqueID
*/
void SettingsManager::addConnection(ConnectionSettings *connection)
{
@@ -310,6 +446,50 @@ namespace Robomongo
}
}
/* Temporarily disabling Recent Connections feature
void SettingsManager::addRecentConnection(ConnectionSettings *connection)
{
_recentConnections.push_back(RecentConnection(connection->uuid(), connection->connectionName()));
}
void SettingsManager::deleteRecentConnection(ConnectionSettings *conn)
{
for (int i = 0; i < _recentConnections.size(); ++i) {
if (_recentConnections[i].uuid == conn->uuid())
_recentConnections.erase(_recentConnections.begin() + i);
}
}
void SettingsManager::setRecentConnections(std::vector<ConnectionSettings const*> const& recentConns)
{
_recentConnections.clear();
for(auto rconn : recentConns)
_recentConnections.push_back(RecentConnection(rconn->uuid(), rconn->connectionName()));
}
void SettingsManager::clearRecentConnections()
{
_recentConnections.clear();
}
*/
ConnectionSettings* SettingsManager::getConnectionSettingsByUuid(QString const& uuid) const
{
for (auto const connSettings : _connections){
if (connSettings->uuid() == uuid)
return connSettings;
}
LOG_MSG("Failed to find connection settings object by UUID.", mongo::logger::LogSeverity::Warning());
return nullptr;
}
ConnectionSettings* SettingsManager::getConnectionSettingsByUuid(std::string const& uuid) const
{
return getConnectionSettingsByUuid(QString::fromStdString(uuid));
}
void SettingsManager::setCurrentStyle(const QString& style)
{
_currentStyle = style;
@@ -334,32 +514,45 @@ namespace Robomongo
_toolbars[toolbarName] = visible;
}
void SettingsManager::importPreviousConnections() {
void SettingsManager::importConnections()
{
// Skip when settings already imported
if (_imported)
return;
// Find and import 'only' from the latest version
for (auto const& configFileAndImportFunction : _configFilesAndImportFunctions) {
if (QFile::exists(configFileAndImportFunction.file)) {
configFileAndImportFunction.import();
setImported(true); // Mark as imported
return;
}
}
}
bool SettingsManager::importConnectionsFrom_0_8_5_to_0_9()
{
// Load old configuration file (used till version 0.8.5)
const QString oldConfigPath = QString("%1/.config/robomongo/robomongo.json").arg(QDir::homePath());
if (!QFile::exists(oldConfigPath))
return;
if (!QFile::exists(CONFIG_FILE_0_8_5))
return false;
QFile f(oldConfigPath);
if (!f.open(QIODevice::ReadOnly))
return;
QFile oldConfigFile(CONFIG_FILE_0_8_5);
if (!oldConfigFile.open(QIODevice::ReadOnly))
return false;
bool ok;
QJson::Parser parser;
QVariantMap vmap = parser.parse(f.readAll(), &ok).toMap();
QVariantMap vmap = parser.parse(oldConfigFile.readAll(), &ok).toMap();
if (!ok)
return;
return false;
QVariantList vconns = vmap.value("connections").toList();
for (QVariantList::iterator itconn = vconns.begin(); itconn != vconns.end(); ++itconn) {
for (QVariantList::iterator itconn = vconns.begin(); itconn != vconns.end(); ++itconn)
{
QVariantMap vconn = (*itconn).toMap();
ConnectionSettings *conn = new ConnectionSettings();
auto conn = new ConnectionSettings(false);
conn->setImported(true);
conn->setConnectionName(QtUtils::toStdString(vconn.value("connectionName").toString()));
conn->setServerHost(QtUtils::toStdString(vconn.value("serverHost").toString().left(300)));
@@ -394,7 +587,7 @@ namespace Robomongo
for (QVariantList::const_iterator itcred = vcreds.begin(); itcred != vcreds.end(); ++itcred) {
QVariantMap vcred = (*itcred).toMap();
CredentialSettings *cred = new CredentialSettings();
auto cred = new CredentialSettings();
cred->setUserName(QtUtils::toStdString(vcred.value("userName").toString()));
cred->setUserPassword(QtUtils::toStdString(vcred.value("userPassword").toString()));
cred->setDatabaseName(QtUtils::toStdString(vcred.value("databaseName").toString()));
@@ -441,8 +634,46 @@ namespace Robomongo
addConnection(conn);
}
// Mark as imported
setImported(true);
return true;
}
bool SettingsManager::importConnectionsFrom_0_9_to_1_0_RC1()
{
return importConnectionsFromOldVersion(CONFIG_FILE_0_9);
}
bool SettingsManager::importConnectionsFrom_1_0_RC1_to_1_0_0()
{
return importConnectionsFromOldVersion(CONFIG_FILE_1_0_RC1);
}
bool SettingsManager::importConnectionsFromOldVersion(QString const& oldConfigFilePath)
{
if (!QFile::exists(oldConfigFilePath))
return false;
QFile oldConfigFile(oldConfigFilePath);
if (!oldConfigFile.open(QIODevice::ReadOnly))
return false;
bool ok;
QJson::Parser parser;
QVariantMap vmap = parser.parse(oldConfigFile.readAll(), &ok).toMap();
if (!ok)
return false;
QVariantList const& vconns = vmap.value("connections").toList();
for (auto const& vcon : vconns)
{
QVariantMap const& vconn = vcon.toMap();
auto connSettings = new ConnectionSettings(false);
connSettings->fromVariant(vconn);
connSettings->setImported(true);
addConnection(connSettings);
}
return true;
}
int SettingsManager::importedConnectionsCount() {
@@ -455,4 +686,26 @@ namespace Robomongo
return count;
}
QString extractAnonymousID(QString const& zipFile, QString const& propfile)
{
QZipReader zipReader(zipFile);
if (!zipReader.exists() || !zipReader.isReadable())
return QString("");
QXmlStreamReader reader(zipReader.fileData(propfile));
while (!reader.atEnd()) {
reader.readNext();
if (reader.text().toString() == "AnonymousID") {
reader.readNext();
reader.readNext();
reader.readNext();
reader.readNext();
return reader.text().toString();
}
}
return QString("");
}
}
+111 -7
View File
@@ -2,12 +2,46 @@
#include <QString>
#include <QVariantMap>
#include <QSet>
#include <QDir>
#include <vector>
#include "robomongo/core/Enums.h"
namespace Robomongo
{
class ConnectionSettings;
struct ConfigFileAndImportFunction;
// Current cache directory
QString const CacheDir = QString("%1/.3T/robomongo/%2/cache/").arg(QDir::homePath())
.arg(PROJECT_VERSION);
// Current config file
QString const ConfigFilePath = QString("%1/.3T/robomongo/%2/robomongo.json").arg(QDir::homePath())
.arg(PROJECT_VERSION);
// Current config file directory
QString const ConfigDir = QString("%1/.3T/robomongo/%2/").arg(QDir::homePath())
.arg(PROJECT_VERSION);
/* Temporarily disabling Recent Connections feature
struct RecentConnection
{
RecentConnection(QString const& uuid, std::string const& name)
: uuid(uuid), name(name) {}
// Not directly used but required by std::vector<T>::erase()
RecentConnection& operator=(const RecentConnection& other)
{
uuid = other.uuid;
name = other.name;
return *this;
}
QString uuid;
std::string name;
};
*/
/* ----------------------------- SettingsManager ------------------------------ */
/**
* @brief SettingsManager gives you access to all settings, that is used
* by Robomongo. It can load() and save() them. Config file usually
@@ -23,6 +57,7 @@ namespace Robomongo
public:
typedef std::vector<ConnectionSettings *> ConnectionSettingsContainerType;
typedef QMap<QString, QVariant> ToolbarSettingsContainerType;
/**
* @brief Creates SettingsManager for config file in default location
* (usually ~/.config/robomongo/robomongo.json)
@@ -50,13 +85,32 @@ namespace Robomongo
* @brief Adds connection to the end of list.
* Connection now will be owned by SettingsManager.
*/
void addConnection(ConnectionSettings *connection);
static void addConnection(ConnectionSettings *connection);
/**
* @brief Removes connection by index
*/
void removeConnection(ConnectionSettings *connection);
/**
* @brief Functions of Recent Connections feature
*/
/* Temporarily disabling Recent Connections feature
static void addRecentConnection(ConnectionSettings *connection);
static void deleteRecentConnection(ConnectionSettings *connection);
static void setRecentConnections(std::vector<ConnectionSettings const*> const& recentConns);
static void clearRecentConnections();
*/
/**
* @brief Finds and returns original (non-clone) connection settings which is
* loaded/saved from/into Robomongo config. file.
* @return If uniqueID is valid returns original connection settings,
* nullptr otherwise.
*/
ConnectionSettings* getConnectionSettingsByUuid(QString const& uuid) const;
ConnectionSettings* getConnectionSettingsByUuid(std::string const& uuid) const;
void reorderConnections(const ConnectionSettingsContainerType &connections);
void setToolbarSettings(QString toolbarName, bool visible);
@@ -65,6 +119,9 @@ namespace Robomongo
* @brief Returns list of connections
*/
ConnectionSettingsContainerType connections() const { return _connections; }
// Temporarily disabling Recent Connections feature
// std::vector<RecentConnection>& recentConnections() const { return _recentConnections; }
ToolbarSettingsContainerType toolbars() const { return _toolbars; }
@@ -86,6 +143,9 @@ namespace Robomongo
void setAutoExec(bool isAutoExec) { _autoExec = isAutoExec; }
bool autoExec() const { return _autoExec; }
void setMinimizeToTray(bool isMinimizingToTray) { _minimizeToTray = isMinimizingToTray; }
bool minimizeToTray() const { return _minimizeToTray; }
void setLineNumbers(bool showLineNumbers) { _lineNumbers = showLineNumbers; }
bool lineNumbers() const { return _lineNumbers; }
@@ -95,6 +155,12 @@ namespace Robomongo
void setDisableConnectionShortcuts(bool isDisable) { _disableConnectionShortcuts = isDisable; }
bool disableConnectionShortcuts() const { return _disableConnectionShortcuts; }
void addAcceptedEulaVersion(QString const& version) { _acceptedEulaVersions.insert(version); }
QSet<QString> const& acceptedEulaVersions() const { return _acceptedEulaVersions; }
void setCheckForUpdates(bool checkForUpdates) { _checkForUpdates = checkForUpdates; }
bool checkForUpdates() const { return _checkForUpdates; }
void setBatchSize(int batchSize) { _batchSize = batchSize; }
int batchSize() const { return _batchSize; }
@@ -110,15 +176,20 @@ namespace Robomongo
int mongoTimeoutSec() const { return _mongoTimeoutSec; }
int shellTimeoutSec() const { return _shellTimeoutSec; }
// True when settings from previous versions of
// Robomongo are imported
// True when settings from previous versions of Robomongo are imported
void setImported(bool imported) { _imported = imported; }
bool imported() const { return _imported; }
QString anonymousID() const { return _anonymousID; }
void addCacheData(QString const& key, QVariant const& value);
QVariant cacheData(QString const& key) const;
/**
* Returns number of imported connections
*/
int importedConnectionsCount();
private:
/**
@@ -131,10 +202,21 @@ namespace Robomongo
*/
QVariantMap convertToMap() const;
// Find existing anonymousID from Robomongo and 3T config files, if not found create
// a new anonymousID.
QString getOrCreateAnonymousID(QVariantMap const& map) const;
/**
* Load connection settings from previous versions of Robomongo
*/
void importPreviousConnections();
void importConnections();
// Imports connections from oldConfigFilePath into current config file
static bool importConnectionsFromOldVersion(QString const& oldConfigFilePath);
static bool importConnectionsFrom_0_8_5_to_0_9();
static bool importConnectionsFrom_0_9_to_1_0_RC1();
static bool importConnectionsFrom_1_0_RC1_to_1_0_0();
/**
* @brief Version of settings schema currently loaded
@@ -148,9 +230,12 @@ namespace Robomongo
bool _loadMongoRcJs;
bool _autoExpand;
bool _autoExec;
bool _minimizeToTray;
bool _lineNumbers;
bool _disableConnectionShortcuts;
QSet<QString> _acceptedEulaVersions;
int _batchSize;
bool _checkForUpdates = true;
QString _currentStyle;
QString _textFontFamily;
int _textFontPointSize;
@@ -158,14 +243,33 @@ namespace Robomongo
int _mongoTimeoutSec;
int _shellTimeoutSec;
// True when settings from previous versions of
// Robomongo are imported
// True when settings from previous versions of Robomongo are imported
bool _imported;
/**
* @brief This is an anonymous string taken from QUuid that is generated when Robomongo
* is first installed on a user's machine and then launched for the first time
* It stays the same throughout all upgrades.
*/
QString _anonymousID;
// Various cache data
QMap<QString, QVariant> _cacheData;
/**
* @brief List of connections
*/
ConnectionSettingsContainerType _connections;
static std::vector<ConnectionSettings*> _connections;
/* Temporarily disabling Recent Connections feature
// List of recent connections that are shown on Welcome Tab
static std::vector<RecentConnection> _recentConnections;
*/
ToolbarSettingsContainerType _toolbars;
// List of config. file absolute paths and related import functions.
// Must be updated with care and with every new version. Details on cpp file.
static const std::vector<ConfigFileAndImportFunction> _configFilesAndImportFunctions;
};
}
+9 -7
View File
@@ -80,6 +80,7 @@ namespace Robomongo
s << (isArray ? "[" : "{");
BSONObjIterator i(obj);
BSONElement e = i.next();
if ( !e.eoo() ) {
while ( 1 ) {
if ( pretty ) {
@@ -91,6 +92,7 @@ namespace Robomongo
else {
s << " ";
}
s << jsonString(e, format, true, pretty ? pretty + 1 : 0, uuidEncoding, timeFormat, isArray);
e = i.next();
@@ -109,7 +111,8 @@ namespace Robomongo
return s.str();
}
std::string jsonString(const BSONElement &elem, JsonStringFormat format, bool includeFieldNames, int pretty, UUIDEncoding uuidEncoding, SupportedTimes timeFormat, bool isArray)
std::string jsonString(const BSONElement &elem, JsonStringFormat format, bool includeFieldNames,
int pretty, UUIDEncoding uuidEncoding, SupportedTimes timeFormat, bool isArray)
{
using namespace std;
BSONType t = elem.type();
@@ -331,12 +334,11 @@ namespace Robomongo
break;
case bsonTimestamp:
if ( format == TenGen ) {
s << "Timestamp(" << ( elem.timestampValue() / 1000 ) << ", " << elem.timestampInc() << ")";
}
else {
s << "{ \"$timestamp\" : { \"t\" : " << ( elem.timestampValue() / 1000 ) << ", \"i\" : " << elem.timestampInc() << " } }";
}
if ( format == TenGen )
s << "Timestamp(" << elem.timestamp().getSecs() << ", " << elem.timestampInc() << ")";
else
s << "{ \"$timestamp\" : { \"t\" : " << elem.timestamp().getSecs() << ", \"i\" : "
<< elem.timestampInc() << " } }";
break;
case MinKey:
+14 -3
View File
@@ -2,13 +2,16 @@
#include <QDir>
#include <QMetaType>
#include "robomongo/core/utils/QtUtils.h"
namespace
{
std::string getLoggerPath()
{
static std::string path = Robomongo::QtUtils::toStdString(QString("%1/" PROJECT_NAME_LOWERCASE ".log").arg(QDir::tempPath()));
static std::string path =
Robomongo::QtUtils::toStdString(QString("%1/" PROJECT_NAME_LOWERCASE ".log").arg(QDir::tempPath()));
return path;
}
}
@@ -46,7 +49,15 @@ namespace Robomongo
{
// v0.9
// LOG(level) << "[" PROJECT_NAME_TITLE "] " << QtUtils::toStdString(mess) << std::endl;
if (notify)
emit printed(mess, level);
if (notify) {
// Make uniform log level strings e.g "Error: ", "Info: " etc...
auto logLevelStr = QString::fromStdString(level.toStringData().toString());
if (!logLevelStr.isEmpty()) {
logLevelStr = logLevelStr.toLower();
logLevelStr[0] = logLevelStr[0].toUpper();
logLevelStr += ": ";
}
emit printed(logLevelStr + mess, level);
}
}
}
+89 -6
View File
@@ -44,12 +44,30 @@ namespace Robomongo
return importedIcon;
}
const QIcon &GuiRegistry::serverPrimaryIcon() const
{
static const QIcon icon(":/robomongo/icons/server_primary_16x16.png");
return icon;
}
const QIcon &GuiRegistry::serverSecondaryIcon() const
{
static const QIcon icon(":/robomongo/icons/server_secondary_16x16.png");
return icon;
}
const QIcon &GuiRegistry::replicaSetIcon() const
{
static const QIcon replicaSetIc = QIcon(":/robomongo/icons/replica_set_16x16.png");
static const QIcon replicaSetIc(":/robomongo/icons/replica_set_16x16.png");
return replicaSetIc;
}
const QIcon &GuiRegistry::replicaSetOfflineIcon() const
{
static const QIcon icon(":/robomongo/icons/replica_set_offline_16x16.png");
return icon;
}
const QIcon &GuiRegistry::openIcon() const
{
static const QIcon openIc = qApp->style()->standardIcon(QStyle::SP_DialogOpenButton);
@@ -96,14 +114,26 @@ namespace Robomongo
const QIcon &GuiRegistry::maximizeIcon() const
{
static const QIcon maximizeIc = QIcon(":/robomongo/icons/maximize.gif");
static const QIcon maximizeIc(":/robomongo/icons/maximize.png");
return maximizeIc;
}
const QIcon &GuiRegistry::maximizeHighlightedIcon() const
const QIcon &GuiRegistry::minimizeIcon() const
{
static const QIcon maximizeHighlightedIc = QIcon(":/robomongo/icons/maximize_highlighted_16x16.png");
return maximizeHighlightedIc;
static const QIcon minimizeIc(":/robomongo/icons/minimize.png");
return minimizeIc;
}
const QIcon &GuiRegistry::undockIcon() const
{
static const QIcon undockIc(":/robomongo/icons/undock.png");
return undockIc;
}
const QIcon &GuiRegistry::dockIcon() const
{
static const QIcon dockIc(":/robomongo/icons/dock.png");
return dockIc;
}
const QIcon &GuiRegistry::textIcon() const
@@ -281,6 +311,18 @@ namespace Robomongo
return icon;
}
const QIcon &GuiRegistry::plusIcon() const
{
static const QIcon icon {":/robomongo/icons/plus_sign.png"};
return icon;
}
const QIcon &GuiRegistry::minusIcon() const
{
static const QIcon icon {":/robomongo/icons/minus_sign.png"};
return icon;
}
const QBrush &GuiRegistry::typeBrush() const
{
static const QBrush typeBrush = QBrush(QColor(150, 150, 150));
@@ -305,6 +347,12 @@ namespace Robomongo
static const QIcon mongodbIc = QIcon(":/robomongo/icons/mongodb_16x16.png");
return mongodbIc;
}
const QIcon &GuiRegistry::mongodbIconForMAC() const
{
static const QIcon mongodbIc = QIcon(":/robomongo/icons/mongodb_icon_for_MAC.png");
return mongodbIc;
}
const QIcon &GuiRegistry::connectIcon() const
{
@@ -324,13 +372,48 @@ namespace Robomongo
return stopIc;
}
const QIcon &GuiRegistry::exportIcon() const
{
static const QIcon exportIc = QIcon(":/robomongo/icons/export_64x64.png");
return exportIc;
}
const QIcon &GuiRegistry::importIcon() const
{
static const QIcon importIc = QIcon(":/robomongo/icons/import_64x64.png");
return importIc;
}
const QIcon &GuiRegistry::deleteIcon() const
{
static const QIcon icon(":/robomongo/icons/delete.png");
return icon;
}
const QIcon &GuiRegistry::deleteIconRed() const
{
static const QIcon icon(":/robomongo/icons/delete_red_color.png");
return icon;
}
const QIcon &GuiRegistry::deleteIconMouseHovered() const
{
static const QIcon icon(":/robomongo/icons/delete_mouse_hovered.png");
return icon;
}
const QIcon &GuiRegistry::mainWindowIcon() const
{
static const QIcon mainWindowIc = QIcon(":/robomongo/icons/logo-256x256.png");
// static const QIcon mainWindowIc = QIcon(":/robomongo/icons/main_window_icon.png");
return mainWindowIc;
}
const QIcon& GuiRegistry::welcomeTabIcon() const
{
static const QIcon icon(":/robomongo/icons/welcome_tab_icon.png");
return icon;
}
const QFont &GuiRegistry::font() const
{
QString family = AppRegistry::instance().settingsManager()->textFontFamily();
+15 -1
View File
@@ -30,7 +30,10 @@ namespace Robomongo
*/
const QIcon& serverIcon() const;
const QIcon& serverImportedIcon() const;
const QIcon& serverPrimaryIcon() const;
const QIcon& serverSecondaryIcon() const;
const QIcon& replicaSetIcon() const;
const QIcon& replicaSetOfflineIcon() const;
const QIcon& saveIcon() const;
const QIcon& openIcon() const;
const QIcon& databaseIcon() const;
@@ -39,7 +42,9 @@ namespace Robomongo
const QIcon& userIcon() const;
const QIcon& functionIcon() const;
const QIcon& maximizeIcon() const;
const QIcon& maximizeHighlightedIcon() const;
const QIcon& minimizeIcon() const;
const QIcon& undockIcon() const;
const QIcon& dockIcon() const;
const QIcon& textIcon() const;
const QIcon& textHighlightedIcon() const;
const QIcon& treeIcon() const;
@@ -55,9 +60,15 @@ namespace Robomongo
const QIcon& leftIcon() const;
const QIcon& rightIcon() const;
const QIcon& mongodbIcon() const;
const QIcon& mongodbIconForMAC() const;
const QIcon& connectIcon() const;
const QIcon& executeIcon() const;
const QIcon& stopIcon() const;
const QIcon& exportIcon() const;
const QIcon& importIcon() const;
const QIcon& deleteIcon() const;
const QIcon& deleteIconRed() const;
const QIcon& deleteIconMouseHovered() const;
const QIcon& mainWindowIcon() const;
const QIcon& bsonObjectIcon() const;
const QIcon& bsonArrayIcon() const;
@@ -76,6 +87,9 @@ namespace Robomongo
const QIcon& keyIcon() const;
const QIcon& showIcon() const;
const QIcon& hideIcon() const;
const QIcon& plusIcon() const;
const QIcon& minusIcon() const;
const QIcon& welcomeTabIcon() const;
const QBrush& typeBrush() const;
+412 -44
View File
@@ -17,6 +17,12 @@
#include <QStatusBar>
#include <QHBoxLayout>
#include <QSettings>
#include <QSystemTrayIcon>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QTextDocument>
#include <mongo/logger/log_severity.h>
#include "robomongo/core/settings/SettingsManager.h"
@@ -30,14 +36,20 @@
#include "robomongo/gui/widgets/LogWidget.h"
#include "robomongo/gui/widgets/explorer/ExplorerWidget.h"
#include "robomongo/gui/widgets/explorer/ExplorerCollectionTreeItem.h"
#include "robomongo/gui/widgets/explorer/ExplorerTreeWidget.h"
#include "robomongo/gui/widgets/workarea/WorkAreaTabWidget.h"
#include "robomongo/gui/widgets/workarea/QueryWidget.h"
#include "robomongo/gui/widgets/workarea/QueryWidget.h"
#include "robomongo/gui/widgets/workarea/WelcomeTab.h"
#include "robomongo/gui/dialogs/ConnectionsDialog.h"
#include "robomongo/gui/dialogs/AboutDialog.h"
#include "robomongo/gui/dialogs/PreferencesDialog.h"
#include "robomongo/gui/dialogs/ExportDialog.h"
#include "robomongo/gui/GuiRegistry.h"
#include "robomongo/gui/AppStyle.h"
namespace
{
void setToolBarIconSize(QToolBar *toolBar)
@@ -68,21 +80,32 @@ namespace
Robomongo::AppRegistry::instance().settingsManager()->save();
}
void saveMinimizeToTraySettings(bool isMinimizingToTray)
{
Robomongo::AppRegistry::instance().settingsManager()->setMinimizeToTray(isMinimizingToTray);
Robomongo::AppRegistry::instance().settingsManager()->save();
}
void saveLineNumbers(bool showLineNumbers)
{
Robomongo::AppRegistry::instance().settingsManager()->setLineNumbers(showLineNumbers);
Robomongo::AppRegistry::instance().settingsManager()->save();
}
}
}
/* End of anonymous namespace */
namespace Robomongo
{
/* -------------------------------- ConnectionMenu ---------------------------- */
class ConnectionMenu : public QMenu
{
public:
ConnectionMenu(QWidget *parent) : QMenu(parent) {}
protected:
virtual void keyPressEvent(QKeyEvent *event)
virtual void keyPressEvent(QKeyEvent *event) override
{
if (event->key() == Qt::Key_F12) {
hide();
@@ -93,12 +116,19 @@ namespace Robomongo
}
};
/* -------------------------------- MainWindow --------------------------------- */
MainWindow::MainWindow()
: BaseClass(),
_app(AppRegistry::instance().app()),
_workArea(NULL),
_connectionsMenu(NULL)
{
_logDock(nullptr), _workArea(nullptr), _explorer(nullptr), _app(AppRegistry::instance().app()),
_connectionsMenu(nullptr), _connectButton(nullptr), _viewMenu(nullptr), _toolbarsMenu(nullptr),
_connectAction(nullptr), _openAction(nullptr), _saveAction(nullptr), _saveAsAction(nullptr),
_executeAction(nullptr), _stopAction(nullptr), _orientationAction(nullptr), _execToolBar(nullptr),
//_exportAction(nullptr), _importAction(nullptr), // Temporarily disabling export/import feature
#if defined(Q_OS_WIN)
_trayIcon(nullptr),
#endif
_allowExit(false)
{
QColor background = palette().window().color();
QString controlKey = "Ctrl";
@@ -135,7 +165,7 @@ namespace Robomongo
// Exit action
QAction *exitAction = new QAction("&Exit", this);
exitAction->setShortcuts(QKeySequence::Quit);
VERIFY(connect(exitAction, SIGNAL(triggered()), this, SLOT(close())));
VERIFY(connect(exitAction, SIGNAL(triggered()), this, SLOT(exit())));
// Connect action
_connectAction = new QAction("&Connect...", this);
@@ -166,6 +196,26 @@ namespace Robomongo
QWidgetAction *connectButtonAction = new QWidgetAction(this);
connectButtonAction->setDefaultWidget(_connectButton);
// Tray icon
#if defined(Q_OS_WIN)
_trayIcon = new QSystemTrayIcon(GuiRegistry::instance().mainWindowIcon());
VERIFY(connect(_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason))));
auto trayMinimizeAction = new QAction("Minimize to Tray", _trayIcon);
VERIFY(connect(trayMinimizeAction, SIGNAL(triggered()), this, SLOT(toggleMinimize())));
auto trayExitAction = new QAction("Exit", _trayIcon);
VERIFY(connect(trayExitAction, SIGNAL(triggered()), this, SLOT(exit())));
auto contextMenu = new QMenu();
contextMenu->addAction(trayMinimizeAction);
contextMenu->addSeparator();
contextMenu->addAction(trayExitAction);
_trayIcon->setContextMenu(contextMenu);
#endif
// Orientation action
_orientationAction = new QAction("&Rotate", this);
_orientationAction->setShortcut(Qt::Key_F10);
@@ -177,7 +227,7 @@ namespace Robomongo
ViewMode viewMode = AppRegistry::instance().settingsManager()->viewMode();
// Text mode action
QAction *textModeAction = new QAction("&Text Mode", this);
auto textModeAction = new QAction("&Text Mode", this);
textModeAction->setShortcut(Qt::Key_F4);
textModeAction->setIcon(GuiRegistry::instance().textHighlightedIcon());
textModeAction->setToolTip("Show current tab in text mode, and make this mode default for all subsequent queries <b>(F4)</b>");
@@ -382,6 +432,20 @@ namespace Robomongo
VERIFY(connect(autoExec, SIGNAL(triggered()), this, SLOT(toggleAutoExec())));
optionsMenu->addAction(autoExec);
#if defined(Q_OS_WIN)
QAction *minimizeTray = new QAction("Close button should minimize to system tray");
minimizeTray->setCheckable(true);
minimizeTray->setChecked(AppRegistry::instance().settingsManager()->minimizeToTray());
VERIFY(connect(minimizeTray, SIGNAL(triggered()), this, SLOT(toggleMinimizeToTray())));
optionsMenu->addAction(minimizeTray);
#endif
auto *checkUpdates = new QAction(tr("Check for updates"), this);
checkUpdates->setCheckable(true);
checkUpdates->setChecked(AppRegistry::instance().settingsManager()->checkForUpdates());
VERIFY(connect(checkUpdates, SIGNAL(triggered()), this, SLOT(toggleCheckUpdates())));
optionsMenu->addAction(checkUpdates);
QAction *preferencesAction = new QAction("Preferences", this);
VERIFY(connect(preferencesAction, SIGNAL(triggered()), this, SLOT(openPreferences())));
preferencesAction->setVisible(false);
@@ -434,6 +498,12 @@ namespace Robomongo
duplicateAction->setVisible(true);
VERIFY(connect(duplicateAction, SIGNAL(triggered()), SLOT(duplicateTab())));
// Open welcome tab action
auto openWelcomeTabAction = new QAction("Open/Refresh Welcome Tab", this);
openWelcomeTabAction->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_W);
openWelcomeTabAction->setVisible(true);
VERIFY(connect(openWelcomeTabAction, SIGNAL(triggered()), SLOT(openWelcomeTab())));
// Window menu
QMenu *windowMenu = menuBar()->addMenu("Window");
//minimize
@@ -445,8 +515,10 @@ namespace Robomongo
windowMenu->addSeparator();
windowMenu->addAction(reloadAction);
windowMenu->addAction(duplicateAction);
windowMenu->addSeparator();
windowMenu->addAction(openWelcomeTabAction);
SettingsManager::ToolbarSettingsContainerType toolbarsSettings = AppRegistry::instance().settingsManager()->toolbars();
auto toolbarsSettings = AppRegistry::instance().settingsManager()->toolbars();
/*** About menu ***/
@@ -490,16 +562,70 @@ namespace Robomongo
setToolBarIconSize(_execToolBar);
addToolBar(_execToolBar);
_updateLabel = new QLabel;
_updateLabel->setWordWrap(true);
_updateLabel->setOpenExternalLinks(true);
_updateLabel->setTextFormat(Qt::TextFormat::RichText);
_updateLabel->setIndent(_updateLabel->fontMetrics().width("T"));
_closeButton = new QPushButton;
_closeButton->setIcon(QIcon(":/robomongo/icons/close_hover_16x16.png"));
_closeButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Minimum);
_closeButton->setMouseTracking(true);
_closeButton->setAttribute(Qt::WA_Hover);
_closeButton->installEventFilter(this);
VERIFY(connect(_closeButton, SIGNAL(clicked()), this, SLOT(on_closeButton_clicked())));
auto updateBarLay = new QHBoxLayout;
updateBarLay->addWidget(_updateLabel);
updateBarLay->addWidget(_closeButton, Qt::AlignRight);
updateBarLay->setSpacing(0);
updateBarLay->setMargin(0);
auto updateBarWid = new QWidget;
updateBarWid->setLayout(updateBarLay);
_updateBar = new QToolBar("Updates Toolbar");
_updateBar->addWidget(updateBarWid);
_updateBar->setStyleSheet("background-color: #b3e0ff; border: none;"); // blue
addToolBarBreak();
addToolBar(_updateBar);
_updateBar->setHidden(true);
_updateBar->setMovable(false);
_toolbarsMenu->addAction(_execToolBar->toggleViewAction());
VERIFY(connect(_execToolBar->toggleViewAction(), SIGNAL(triggered(bool)), this, SLOT(onExecToolbarVisibilityChanged(bool))));
/* --- Temporarily disabling export/import feature
// Export/Import Toolbar
auto expImpToolBar = new QToolBar(tr("Export/Import Toolbar"), this);
expImpToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
expImpToolBar->setMovable(false);
// Add export action
_exportAction = new QAction(this);
_exportAction->setData("Export");
_exportAction->setIcon(GuiRegistry::instance().exportIcon());
_exportAction->setDisabled(true);
VERIFY(connect(_exportAction, SIGNAL(triggered()), this, SLOT(openExportDialog())));
addToolBar(expImpToolBar);
expImpToolBar->addAction(_exportAction);
// Add import action
_importAction = new QAction(this);
_importAction->setData("Import");
_importAction->setIcon(GuiRegistry::instance().importIcon());
_importAction->setDisabled(true);
addToolBar(expImpToolBar);
expImpToolBar->addAction(_importAction);
*/
createTabs();
createStatusBar();
setWindowTitle(PROJECT_NAME_TITLE " " PROJECT_VERSION);
setWindowTitle("Robomongo 1.0 - Beta");
setWindowIcon(GuiRegistry::instance().mainWindowIcon());
QTimer::singleShot(0, this, SLOT(manageConnections()));
QTimer::singleShot(0, this, SLOT(manageConnections()));
updateMenus();
_updateMenusAtStart = false;
AppRegistry::instance().bus()->subscribe(this, ConnectionFailedEvent::Type);
AppRegistry::instance().bus()->subscribe(this, ScriptExecutedEvent::Type);
@@ -507,7 +633,21 @@ namespace Robomongo
AppRegistry::instance().bus()->subscribe(this, QueryWidgetUpdatedEvent::Type);
AppRegistry::instance().bus()->subscribe(this, OperationFailedEvent::Type);
restoreWindowsSettings();
restoreWindowSettings();
// Catch application windows focus changes
VERIFY(connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(on_focusChanged())));
_networkAccessManager = new QNetworkAccessManager;
VERIFY(connect(_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(on_networkReply(QNetworkReply*))));
// First check for updates 30 secs after program start
QTimer::singleShot(30000, this, SLOT(checkUpdates())); // 30000 for 30secs
// Check for updates every 1 hour
auto timer = new QTimer(this);
VERIFY(connect(timer, SIGNAL(timeout()), this, SLOT(checkUpdates())));
timer->start(3600000); // 1 hour = (3600000 msec = 60 * 60 * 1000 msec)
}
void MainWindow::createStylesMenu()
@@ -569,9 +709,15 @@ namespace Robomongo
AppRegistry::instance().settingsManager()->save();
}
void MainWindow::restoreWindowsSettings()
void MainWindow::exit()
{
QSettings settings("Paralect", "Robomongo");
_allowExit = true;
close();
}
void MainWindow::restoreWindowSettings()
{
QSettings settings("3T", "Robomongo");
// Restore settings if registery key exists, otherwise resize as app started for the first time.
if (settings.contains("MainWindow/geometry"))
{
@@ -581,8 +727,8 @@ namespace Robomongo
{
// Resize main window. We are trying to keep it "almost" maximized.
QRect screenGeometry = QApplication::desktop()->availableGeometry();
int horizontalMargin = (int)(screenGeometry.width() * 0.1); // todo: use C++ static cast instead of C-style cast
int verticalMargin = (int)(screenGeometry.height() * 0.1);
int horizontalMargin = static_cast<int>(screenGeometry.width() * 0.1);
int verticalMargin = static_cast<int>(screenGeometry.height() * 0.1);
int _width = screenGeometry.width() - horizontalMargin;
int _height = screenGeometry.height() - verticalMargin;
resize(QSize(_width, _height));
@@ -594,24 +740,41 @@ namespace Robomongo
}
}
void MainWindow::saveWindowsSettings() const
void MainWindow::saveWindowSettings() const
{
QSettings settings("Paralect", "Robomongo");
QSettings settings("3T", "Robomongo");
settings.setValue("MainWindow/geometry", saveGeometry());
}
void MainWindow::adjustUpdatesBarHeight()
{
QTextDocument doc;
doc.setHtml(_updateLabel->text());
auto strWidth = _updateLabel->fontMetrics().width(doc.toPlainText());
auto lineHeight = _updateLabel->fontMetrics().height();
auto widthForUpdateStr = width() - _closeButton->width();
#ifdef __APPLE__
_updateLabel->setFixedHeight((strWidth / widthForUpdateStr + 1) * lineHeight * 1.3);
#else
_updateLabel->setFixedHeight((strWidth / widthForUpdateStr + 1) * lineHeight);
#endif
}
void MainWindow::open()
{
QueryWidget *wid = _workArea->currentQueryWidget();
if (wid) {
wid->openFile();
}
else {
SettingsManager::ConnectionSettingsContainerType connections = AppRegistry::instance().settingsManager()->connections();
if (connections.size() == 1) {
else { // todo: currently this case not handled properly, since "Open" button is
// disabled when no tabs exist
auto connectionsCopy = AppRegistry::instance().settingsManager()->connections();
if (connectionsCopy.size() == 1) {
ScriptInfo inf = ScriptInfo(QString());
if (inf.loadFromFile()) {
_app->openShell(connections.at(0), inf);
// todo: for now do not open new shell
_app->openShell(nullptr, connectionsCopy.at(0), inf);
}
}
}
@@ -633,16 +796,6 @@ namespace Robomongo
}
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_F12) {
_connectButton->showMenu();
return;
}
BaseClass::keyPressEvent(event);
}
void MainWindow::updateConnectionsMenu()
{
_connectionsMenu->clear();
@@ -674,8 +827,17 @@ namespace Robomongo
_connectionsMenu->addAction(connectAction);
}
WelcomeTab* MainWindow::getWelcomeTab()
{
return _workArea->getWelcomeTab();
}
void MainWindow::manageConnections()
{
#if defined(Q_OS_WIN)
_trayIcon->hide(); // hide the tray icon so the main window can't be hidden behind the connections dialog
#endif
static bool checkForImported = true;
ConnectionsDialog dialog(AppRegistry::instance().settingsManager(), checkForImported, this);
int result = dialog.exec();
@@ -707,6 +869,10 @@ namespace Robomongo
}
}
#if defined(Q_OS_WIN)
_trayIcon->show(); // show the tray icon once the connections dialog is gone
#endif
// on linux focus is lost - we need to activate main window back
activateWindow();
}
@@ -772,6 +938,13 @@ namespace Robomongo
saveAutoExec(send->isChecked());
}
void MainWindow::toggleCheckUpdates()
{
auto action = qobject_cast<QAction*>(sender());
AppRegistry::instance().settingsManager()->setCheckForUpdates(action->isChecked());
AppRegistry::instance().settingsManager()->save();
}
void MainWindow::toggleLineNumbers()
{
QAction *send = qobject_cast<QAction*>(sender());
@@ -841,6 +1014,24 @@ namespace Robomongo
dlg.exec();
}
void MainWindow::openWelcomeTab()
{
_workArea->openWelcomeTab();
}
/* --- Temporarily disabling export/import feature
void MainWindow::openExportDialog()
{
auto selectedItem = dynamic_cast<ExplorerCollectionTreeItem*>(_explorer->getSelectedTreeItem());
auto dbName = QString::fromStdString(selectedItem->collection()->database()->name());
auto collName = QString::fromStdString(selectedItem->collection()->name());
auto dialog = new ExportDialog(dbName, collName, this);
dialog->show(); // show it mode-less so that user can perform multiple simultaneous exports
}
*/
void MainWindow::setDefaultUuidEncoding()
{
AppRegistry::instance().settingsManager()->setUuidEncoding(DefaultEncoding);
@@ -935,7 +1126,6 @@ namespace Robomongo
if (event->connectionType != ConnectionPrimary)
return;
// Very temporary solution to prevent multiple connection error messages
// from both SshTunnelWorker and MongoWorker
static int lastServerHandle = -1;
@@ -943,7 +1133,7 @@ namespace Robomongo
return;
lastServerHandle = event->serverHandle;
QMessageBox::information(this, "Error", QtUtils::toQString(event->message));
QMessageBox::critical(this, "Error", QtUtils::toQString(event->message));
}
void MainWindow::handle(ScriptExecutingEvent *)
@@ -964,13 +1154,78 @@ namespace Robomongo
ss << event->userFriendlyErrorMessage << std::endl << std::endl
<< "Error:" << std::endl << event->technicalErrorMessage;
QMessageBox::information(NULL, "Operation failed", QtUtils::toQString(ss.str()));
QMessageBox::critical(NULL, "Operation failed", QtUtils::toQString(ss.str()));
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_F12) {
_connectButton->showMenu();
return;
}
BaseClass::keyPressEvent(event);
}
void MainWindow::closeEvent(QCloseEvent *event)
{
saveWindowsSettings();
saveWindowSettings();
#if defined(Q_OS_WIN)
if (AppRegistry::instance().settingsManager()->minimizeToTray() && !_allowExit) {
event->ignore();
hide(); // hide the window because it can be reopened with the tray icon
}
else {
if (_trayIcon->isVisible())
_trayIcon->hide();
QMainWindow::closeEvent(event);
}
#else
QMainWindow::closeEvent(event);
#endif
}
void MainWindow::hideEvent(QHideEvent *event)
{
#if defined(Q_OS_WIN)
if (_trayIcon->contextMenu()->actions().size() > 0 && isHidden()) {
_trayIcon->contextMenu()->actions().at(0)->setText("Show Robomongo");
}
#endif
}
void MainWindow::showEvent(QShowEvent *event)
{
#if defined(Q_OS_WIN)
if (_trayIcon->contextMenu()->actions().size() > 0) {
_trayIcon->contextMenu()->actions().at(0)->setText("Minimize to Tray");
}
#endif
}
bool MainWindow::eventFilter(QObject *target, QEvent *event)
{
auto closeUpdatesBarButton = qobject_cast<QPushButton*>(target);
if (!closeUpdatesBarButton)
return false;
if (event->type() == QEvent::HoverEnter) {
closeUpdatesBarButton->setIcon(QIcon(":/robomongo/icons/close_hover_16x16_original.png"));
return true;
}
else if (event->type() == QEvent::HoverLeave) {
closeUpdatesBarButton->setIcon(QIcon(":/robomongo/icons/close_hover_16x16.png"));
return true;
}
return QWidget::eventFilter(target, event);
}
void MainWindow::resizeEvent(QResizeEvent* event)
{
QMainWindow::resizeEvent(event);
adjustUpdatesBarHeight();
}
void MainWindow::handle(QueryWidgetUpdatedEvent *event)
@@ -980,14 +1235,14 @@ namespace Robomongo
void MainWindow::createDatabaseExplorer()
{
ExplorerWidget *explorer = new ExplorerWidget(this);
AppRegistry::instance().bus()->subscribe(explorer, ConnectingEvent::Type);
AppRegistry::instance().bus()->subscribe(explorer, ConnectionFailedEvent::Type);
AppRegistry::instance().bus()->subscribe(explorer, ConnectionEstablishedEvent::Type);
_explorer = new ExplorerWidget(this);
AppRegistry::instance().bus()->subscribe(_explorer, ConnectingEvent::Type);
AppRegistry::instance().bus()->subscribe(_explorer, ConnectionFailedEvent::Type);
AppRegistry::instance().bus()->subscribe(_explorer, ConnectionEstablishedEvent::Type);
QDockWidget *explorerDock = new QDockWidget(tr("Database Explorer"));
explorerDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
explorerDock->setWidget(explorer);
explorerDock->setWidget(_explorer);
explorerDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
QWidget *titleWidget = new QWidget(this); // this lines simply remove
@@ -1014,7 +1269,7 @@ namespace Robomongo
_logDock = new QDockWidget(tr("Logs"));
_logDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea);
_logDock->setWidget(log);
_logDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
_logDock->setFeatures(QDockWidget::DockWidgetClosable);
_logDock->setVisible(false);
QAction *action = _logDock->toggleViewAction();
@@ -1030,8 +1285,20 @@ namespace Robomongo
void MainWindow::updateMenus()
{
int contTab = _workArea->count();
bool isEnable = _workArea && contTab > 0;
if (!_workArea)
return;
bool isEnable = false;
if (_updateMenusAtStart)
isEnable = false;
else {
if (getWelcomeTab() && getWelcomeTab()->isVisible())
isEnable = false;
else if (_workArea->count() == 0)
isEnable = false;
else
isEnable = true;
}
_execToolBar->setEnabled(isEnable);
_openAction->setEnabled(isEnable);
@@ -1044,6 +1311,7 @@ namespace Robomongo
_workArea = new WorkAreaTabWidget(this);
AppRegistry::instance().bus()->subscribe(_workArea, OpeningShellEvent::Type);
VERIFY(connect(_workArea, SIGNAL(currentChanged(int)), this, SLOT(updateMenus())));
VERIFY(connect(_workArea, SIGNAL(currentChanged(int)), this, SLOT(on_tabChange())));
QHBoxLayout *hlayout = new QHBoxLayout;
hlayout->setContentsMargins(0, 3, 0, 0);
@@ -1077,4 +1345,104 @@ namespace Robomongo
AppRegistry::instance().settingsManager()->setToolbarSettings("explorer", isVisible);
AppRegistry::instance().settingsManager()->save();
}
/* --- Temporarily disabling export/import feature
void MainWindow::onExplorerItemSelected(QTreeWidgetItem *selectedItem)
{
auto collectionItem = dynamic_cast<ExplorerCollectionTreeItem*>(selectedItem);
if (collectionItem) {
_exportAction->setEnabled(true);
}
else {
_exportAction->setDisabled(true);
}
}
*/
void MainWindow::on_tabChange()
{
auto activeTab = dynamic_cast<QueryWidget*>(_workArea->currentWidget());
if (activeTab) {
activeTab->bringDockToFront();
}
}
void MainWindow::toggleMinimize()
{
if (isHidden()) {
show();
}
else {
hide();
}
}
void MainWindow::trayActivated(QSystemTrayIcon::ActivationReason reason)
{
if (QSystemTrayIcon::DoubleClick == reason && isHidden()) {
show();
}
}
void MainWindow::toggleMinimizeToTray()
{
QAction *send = qobject_cast<QAction*>(sender());
saveMinimizeToTraySettings(send->isChecked());
}
void MainWindow::on_focusChanged()
{
// If focus is on floating output window, make it's parent (which is a QueryWidget tab) as active tab
auto const activeDock = dynamic_cast<QueryWidget::CustomDockWidget*>(qApp->activeWindow());
if (activeDock) {
_workArea->setCurrentWidget(activeDock->getParentQueryWidget());
}
}
void MainWindow::on_networkReply(QNetworkReply* reply)
{
QString str(QUrl::fromPercentEncoding(reply->readAll()));
if ("NO-UPDATES-ANNOUNCED.\n" == str || reply->error() != QNetworkReply::NoError || str.isEmpty()) {
_updateLabel->setText("");
_updateBar->setVisible(false);
return;
}
str.replace('+', ' ');
str.remove("Update,");
_updateLabel->setText(str);
_updateBar->setVisible(true);
adjustUpdatesBarHeight();
}
void MainWindow::on_closeButton_clicked()
{
_updateBar->setVisible(false);
}
void MainWindow::checkUpdates()
{
if (!Robomongo::AppRegistry::instance().settingsManager()->checkForUpdates())
return;
#ifdef _WIN32
QString const OS = "win";
#elif __APPLE__
QString const OS = "osx";
#elif __linux__
QString const OS = "linux";
#else
QString const OS = "unknown";
#endif
// softwareId=8: Robomongo product ID
QUrl url("http://updates.3tsoftwarelabs.com/check.php?os=" + OS + "&softwareId=8&softwareVersion=" +
QString(PROJECT_VERSION) + "&licenseInfo=FREE&setup=" +
AppRegistry::instance().settingsManager()->anonymousID() + "&notify=true#");
_networkAccessManager->get(QNetworkRequest(url));
}
}
+66 -6
View File
@@ -1,11 +1,17 @@
#pragma once
#include <QMainWindow>
#include <QSystemTrayIcon>
QT_BEGIN_NAMESPACE
class QLabel;
class QToolBar;
class QDockWidget;
class QToolButton;
class QPushButton;
class QTreeWidgetItem;
class QNetworkReply;
class QNetworkAccessManager;
QT_END_NAMESPACE
namespace Robomongo
@@ -19,6 +25,8 @@ namespace Robomongo
class WorkAreaTabWidget;
class ConnectionMenu;
class App;
class ExplorerWidget;
class WelcomeTab;
class MainWindow : public QMainWindow
{
@@ -27,7 +35,8 @@ namespace Robomongo
public:
typedef QMainWindow BaseClass;
MainWindow();
void keyPressEvent(QKeyEvent *event);
WelcomeTab* getWelcomeTab();
public Q_SLOTS:
void manageConnections();
@@ -51,6 +60,7 @@ namespace Robomongo
void save();
void saveAs();
void changeStyle(QAction *);
void exit();
void setDefaultUuidEncoding();
void setJavaUuidEncoding();
@@ -71,32 +81,62 @@ namespace Robomongo
void handle(OperationFailedEvent *event);
protected:
void closeEvent(QCloseEvent *event);
void keyPressEvent(QKeyEvent *event) override;
void closeEvent(QCloseEvent *event) override;
void hideEvent(QHideEvent *event) override;
void showEvent(QShowEvent *event) override;
bool eventFilter(QObject *target, QEvent *event) override;
void resizeEvent(QResizeEvent* event) override;
private Q_SLOTS:
void updateMenus();
void setUtcTimeZone();
void setLocalTimeZone();
void openPreferences();
void openWelcomeTab();
// Temporarily disabling export/import feature
//void openExportDialog();
void onConnectToolbarVisibilityChanged(bool isVisisble);
void onOpenSaveToolbarVisibilityChanged(bool isVisisble);
void onExecToolbarVisibilityChanged(bool isVisisble);
void onExplorerVisibilityChanged(bool isVisisble);
// Temporarily disabling export/import feature
//void onExplorerItemSelected(QTreeWidgetItem *selectedItem);
void on_tabChange();
void toggleMinimize();
void trayActivated(QSystemTrayIcon::ActivationReason reason);
void toggleMinimizeToTray();
// On application focus changes
void on_focusChanged();
void on_networkReply(QNetworkReply* reply);
void on_closeButton_clicked();
void checkUpdates();
void toggleCheckUpdates();
private:
void updateConnectionsMenu();
void createDatabaseExplorer();
void createTabs();
void createStylesMenu();
void createStatusBar();
void restoreWindowsSettings();
void saveWindowsSettings() const;
void restoreWindowSettings();
void saveWindowSettings() const;
void adjustUpdatesBarHeight();
QDockWidget *_logDock;
WorkAreaTabWidget *_workArea;
ExplorerWidget* _explorer;
App *_app;
ConnectionMenu *_connectionsMenu;
@@ -104,14 +144,34 @@ namespace Robomongo
QMenu *_viewMenu;
QMenu *_toolbarsMenu;
QAction *_connectAction;
// Open/Save tool bar
QAction *_openAction;
QAction *_saveAction;
QAction *_saveAsAction;
// Execution tool bar
QAction *_executeAction;
QAction *_stopAction;
QAction *_orientationAction;
QToolBar *_execToolBar;
QToolBar *_updateBar;
QLabel *_updateLabel;
QPushButton* _closeButton;
QNetworkAccessManager *_networkAccessManager;
// Temporarily disabling export/import feature
/*
// Export/import tool bar
QAction *_exportAction;
QAction *_importAction;
*/
#if defined(Q_OS_WIN)
QSystemTrayIcon *_trayIcon;
#endif
bool _allowExit;
bool _updateMenusAtStart = true;
};
}
+46 -16
View File
@@ -4,6 +4,9 @@
#include <QGridLayout>
#include <QLabel>
#include <QPushButton>
#include <QTabWidget>
#include <QTextBrowser>
#include <QFile>
#include "robomongo/gui/GuiRegistry.h"
#include "robomongo/core/utils/QtUtils.h"
@@ -15,11 +18,11 @@ namespace
"Shell-centric MongoDB management tool."
"<br/>"
"<br/>"
"Visit " PROJECT_NAME_TITLE " website: <a href=\"http://" PROJECT_DOMAIN "\">" PROJECT_DOMAIN "</a> <br/>"
"Visit " PROJECT_NAME_TITLE " website: <a href=\"https://" PROJECT_DOMAIN "\">" PROJECT_DOMAIN "</a> <br/>"
"<br/>"
"<a href=\"https://" PROJECT_GITHUB_FORK "\">Fork</a> project or <a href=\"https://" PROJECT_GITHUB_ISSUES "\">submit</a> issues/proposals on GitHub. <br/>"
"<a href=\"https://" PROJECT_GITHUB_ISSUES "\">Submit</a> issues/proposals on GitHub. <br/>"
"<br/>"
"Copyright 2013-2016 <a href=\"http:// " PROJECT_COMPANYNAME_DOMAIN "\">" PROJECT_COMPANYNAME "</a>. All rights reserved.<br/>"
"Copyright 2014-2017 <a href= " PROJECT_COMPANYNAME_DOMAIN " >" PROJECT_COMPANYNAME "</a>. All rights reserved.<br/>"
"<br/>"
"The program is provided AS IS with NO WARRANTY OF ANY KIND, "
"INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A "
@@ -37,30 +40,57 @@ namespace Robomongo
AboutDialog::AboutDialog(QWidget *parent)
: QDialog(parent)
{
setWindowIcon(GuiRegistry::instance().mainWindowIcon());
setWindowTitle("About " PROJECT_NAME_TITLE);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
QGridLayout *layout = new QGridLayout(this);
//// About tab
auto aboutTab = new QWidget;
aboutTab->setWindowIcon(GuiRegistry::instance().mainWindowIcon());
aboutTab->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
auto layout = new QGridLayout(this);
layout->setSizeConstraint(QLayout::SetFixedSize);
QLabel *copyRightLabel = new QLabel(description);
auto copyRightLabel = new QLabel(description);
copyRightLabel->setWordWrap(true);
copyRightLabel->setOpenExternalLinks(true);
copyRightLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
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())));
QIcon icon = GuiRegistry::instance().mainWindowIcon();
QPixmap iconPixmap = icon.pixmap(128, 128);
QLabel *logoLabel = new QLabel;
auto logoLabel = new QLabel;
logoLabel->setPixmap(iconPixmap);
layout->addWidget(logoLabel , 0, 0, 1, 1);
layout->addWidget(logoLabel, 0, 0, 1, 1);
layout->addWidget(copyRightLabel, 0, 1, 4, 4);
layout->addWidget(buttonBox, 4, 0, 1, 5);
aboutTab->setLayout(layout);
//// License Agreement tab
auto licenseTab = new QWidget;
auto textBrowser = new QTextBrowser;
textBrowser->setOpenExternalLinks(true);
textBrowser->setOpenLinks(true);
QFile file(":gnu_gpl3_license.html");
if (file.open(QFile::ReadOnly | QFile::Text))
textBrowser->setText(file.readAll());
auto licenseTabLay = new QVBoxLayout;
licenseTabLay->addWidget(textBrowser);
licenseTab->setLayout(licenseTabLay);
//// Button box
auto 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())));
//// Main layout
auto tabWidget = new QTabWidget;
tabWidget->addTab(aboutTab, "About");
tabWidget->addTab(licenseTab, "License Agreement");
auto mainLayout = new QVBoxLayout;
mainLayout->addWidget(tabWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
}
}
@@ -3,9 +3,25 @@
#include <QLabel>
#include <QGridLayout>
#include <QLineEdit>
/* --- Disabling unfinished export URI connection string feature
#include <QPushButton>
#include <QMessageBox>
#include <QClipboard>
#include <QApplication>
#include <QCheckBox>
#include <QToolTip>
*/
#include <mongo/client/mongo_uri.h>
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/utils/QtUtils.h"
/* --- Disabling unfinished export URI connection string feature
#include "robomongo/core/settings/CredentialSettings.h"
#include "robomongo/core/settings/ReplicaSetSettings.h"
#include "robomongo/core/settings/SslSettings.h"
#include "robomongo/gui/utils/GuiConstants.h"
*/
namespace Robomongo
{
@@ -16,23 +32,123 @@ namespace Robomongo
QLabel *defaultDatabaseDescriptionLabel = new QLabel(
"Database, that will be default (<code>db</code> shell variable will point to this database). "
"By default, default database will be the one you authenticate on, or <code>test</code> otherwise. "
"Leave this field empty, if you want default behaviour.");
"Leave this field empty, if you want default behavior.");
defaultDatabaseDescriptionLabel->setWordWrap(true);
defaultDatabaseDescriptionLabel->setContentsMargins(0, -2, 0, 20);
_defaultDatabaseName = new QLineEdit(QtUtils::toQString(_settings->defaultDatabase()));
QGridLayout *connectionLayout = new QGridLayout;
connectionLayout->setAlignment(Qt::AlignTop);
connectionLayout->addWidget(new QLabel("Default Database:"), 1, 0);
connectionLayout->addWidget(_defaultDatabaseName, 1, 1, 1, 1);
connectionLayout->addWidget(defaultDatabaseDescriptionLabel, 2, 1, 1, 1);
/* --- Disabling unfinished export URI connection string feature
_uriString = new QLineEdit;
_uriString->setReadOnly(true);
setLayout(connectionLayout);
_includePasswordCheckBox = new QCheckBox("Include passwords");
VERIFY(connect(_includePasswordCheckBox, SIGNAL(toggled(bool)),
this, SLOT(on_includePasswordsCheckBox_toggle(bool))));
auto generateButton = new QPushButton("Generate");
generateButton->setFixedWidth(70);
generateButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
VERIFY(connect(generateButton, SIGNAL(clicked()), this, SLOT(on_generateButton_clicked())));
_copyButton = new QPushButton("Copy");
_copyButton->setFixedWidth(50);
_copyButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
VERIFY(connect(_copyButton, SIGNAL(clicked()), this, SLOT(on_copyButton_clicked())));
auto hlay = new QHBoxLayout;
hlay->addWidget(_includePasswordCheckBox);
hlay->addWidget(generateButton, Qt::AlignLeft);
hlay->addWidget(_copyButton, Qt::AlignLeft);
*/
auto mainLayout = new QGridLayout;
mainLayout->setAlignment(Qt::AlignTop);
mainLayout->addWidget(new QLabel("Default Database:"), 1, 0);
mainLayout->addWidget(_defaultDatabaseName, 1, 1, 1, 2);
mainLayout->addWidget(defaultDatabaseDescriptionLabel, 2, 1, 1, 2);
/* --- Disabling unfinished export URI connection string feature
mainLayout->addWidget(new QLabel{ "URI Connection String:" }, 3, 0);
mainLayout->addWidget(_uriString, 3, 1);
mainLayout->addLayout(hlay, 4, 1);
*/
setLayout(mainLayout);
}
void ConnectionAdvancedTab::accept()
{
_settings->setDefaultDatabase(QtUtils::toStdString(_defaultDatabaseName->text()));
}
/* --- Disabling unfinished export URI connection string feature
void ConnectionAdvancedTab::on_generateButton_clicked()
{
if (_settings->isReplicaSet()) {
// todo:
// handle set name will be empty initially. It will be available(cached) after first
// successful connection.
std::string connStr = { "mongodb://" };
// todo: which credential to use if there are more than one?
if (_settings->hasEnabledPrimaryCredential() && !_settings->credentials().isEmpty()) {
connStr.append(_settings->primaryCredential()->userName() + ":");
connStr.append(_includePasswordCheckBox->isChecked() ?
_settings->primaryCredential()->userPassword() : "PASSWORD");
connStr.append("@");
}
for (auto const& member : _settings->replicaSetSettings()->membersToHostAndPort()) {
connStr.append(member.toString() + ",");
}
connStr.pop_back(); // delete last ","
connStr.append("/admin?");
connStr.append("replicaSet=" + _settings->replicaSetSettings()->setName());
if (_settings->sslSettings()->sslEnabled())
connStr.append("&ssl=true");
if (_settings->hasEnabledPrimaryCredential() && !_settings->credentials().isEmpty())
connStr.append("&authSource=" + _settings->primaryCredential()->databaseName());
// todo: validate connStr
_uriString->setText(QString::fromStdString(connStr));
_uriString->setCursorPosition(QTextCursor::Start);
}
else { // standalone server
mongo::HostAndPort server = _settings->hostAndPort();
mongo::ConnectionString connStr{ server };
auto str1 = connStr.toString();
auto uriWithStatus = mongo::StatusWith<mongo::MongoURI>(mongo::MongoURI::parse(connStr.toString()));
auto str2 = uriWithStatus.getValue().toString();
_uriString->setText(QString::fromStdString("mongodb://" + str1 + "/admin"));
_uriString->setCursorPosition(QTextCursor::Start);
}
}
void ConnectionAdvancedTab::on_copyButton_clicked()
{
if (_uriString->text().isEmpty())
return;
QClipboard *clipboard = qApp->clipboard();
clipboard->setText(_uriString->text());
auto posUnderGenerateButton = QPoint(_copyButton->pos().x()-100, _copyButton->y()+20);
QToolTip::showText(mapToGlobal(posUnderGenerateButton), "Copied into clipboard", nullptr, QRect(), 2000);
}
void ConnectionAdvancedTab::on_includePasswordsCheckBox_toggle(bool checked)
{
if (_uriString->text().isEmpty())
return;
on_generateButton_clicked();
}
*/
}
@@ -4,6 +4,8 @@
QT_BEGIN_NAMESPACE
class QLineEdit;
class QCheckBox;
class QPushButton;
QT_END_NAMESPACE
namespace Robomongo
@@ -16,10 +18,24 @@ namespace Robomongo
public:
ConnectionAdvancedTab(ConnectionSettings *settings);
QLineEdit *_defaultDatabaseName;
void accept();
/* --- Disabling unfinished export URI connection string feature
private Q_SLOTS :
void on_generateButton_clicked();
void on_copyButton_clicked();
void on_includePasswordsCheckBox_toggle(bool checked);
*/
private:
QLineEdit *_defaultDatabaseName;
/* --- Disabling unfinished export URI connection string feature
QLineEdit *_uriString;
QCheckBox *_includePasswordCheckBox;
QPushButton *_copyButton;
*/
ConnectionSettings *_settings;
};
}
@@ -54,7 +54,7 @@ namespace Robomongo
#endif
// Attempt to fix the issue for Windows High DPI button height is slightly taller than other widgets
#ifdef Q_OS_WIN
_echoModeButton->setMaximumHeight(HighDpiContants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_echoModeButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
#endif
VERIFY(connect(_echoModeButton, SIGNAL(clicked()), this, SLOT(toggleEchoMode())));
+188 -77
View File
@@ -1,4 +1,4 @@
#include "robomongo/gui/dialogs/ConnectionBasicTab.h"
#include "robomongo/gui/dialogs/ConnectionBasicTab.h"
#include <QLabel>
#include <QLineEdit>
@@ -8,26 +8,26 @@
#include <QPushButton>
#include <QFileDialog>
#include <QComboBox>
#include <QListWidget>
#include <QListWidgetItem>
#include <QShortCut>
#include <QKeySequence>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QMessageBox>
#include <QDialogButtonBox>
#include "robomongo/core/utils/QtUtils.h"
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/settings/ReplicaSetSettings.h"
#include "robomongo/gui/GuiRegistry.h"
namespace Robomongo
{
ConnectionBasicTab::ConnectionBasicTab(ConnectionSettings *settings) :
_settings(settings)
{
const ReplicaSetSettings* const replicaSettings = _settings->replicaSetSettings();
_typeLabel = new QLabel("Type:");
_connectionType = new QComboBox;
_connectionType->addItem(tr("Direct Connection"));
_connectionType->addItem(tr("Replica Set or Sharded Cluster"));
_connectionType->addItem(tr("Replica Set"));
_connectionType->setCurrentIndex(static_cast<int>(_settings->isReplicaSet()));
VERIFY(connect(_connectionType, SIGNAL(currentIndexChanged(int)), this, SLOT(on_ConnectionTypeChange(int))));
@@ -49,56 +49,66 @@ namespace Robomongo
_addInfoLabel->setContentsMargins(0, -2, 0, 20);
_membersLabel = new QLabel("Members:");
_members = new QListWidget;
if (_settings->isReplicaSet())
{
if (replicaSettings->members().empty())
{
auto item = new QListWidgetItem("localhost:27017");
item->setFlags(item->flags() | Qt::ItemIsEditable);
_members->addItem(item);
}
else
{
for (const std::string& str : replicaSettings->members())
{
auto item = new QListWidgetItem(QString::fromStdString(str));
_membersLabel->setFixedWidth(_membersLabel->sizeHint().width());
_members = new QTreeWidget;
_members->setHeaderHidden(true);
_members->setIndentation(0);
#ifdef _WIN32
auto lineHeight = _members->fontMetrics().height();
_members->setFixedHeight(lineHeight * 8);
#endif
VERIFY(connect(_members, SIGNAL(itemChanged(QTreeWidgetItem*, int)),
this, SLOT(on_replicaMemberItemEdit(QTreeWidgetItem*, int))));
if (_settings->isReplicaSet() && _settings->replicaSetSettings()->members().size() > 0) {
for (const std::string& str : _settings->replicaSetSettings()->members()) {
if (!str.empty()) {
auto item = new QTreeWidgetItem;
item->setText(0, QString::fromStdString(str));
item->setFlags(item->flags() | Qt::ItemIsEditable);
_members->addItem(item);
_members->addTopLevelItem(item);
}
}
// To fix strange MAC alignment issue
#ifndef _WIN32
auto lineHeight = _members->fontMetrics().height();
_members->setFixedHeight(lineHeight * 8);
#endif
}
else { // No members
auto item = new QTreeWidgetItem;
item->setText(0, "localhost:27017");
item->setFlags(item->flags() | Qt::ItemIsEditable);
_members->addTopLevelItem(item);
}
int const BUTTON_SIZE = 60;
_addButton = new QPushButton("Add");
_addButton->setFixedWidth(BUTTON_SIZE);
_removeButton = new QPushButton("Remove");
_removeButton->setFixedWidth(BUTTON_SIZE);
_discoverButton = new QPushButton("Discover");
_discoverButton->setFixedWidth(BUTTON_SIZE);
_addButton = new QPushButton;
_addButton->setIcon(GuiRegistry::instance().plusIcon());
_removeButton = new QPushButton;
_removeButton->setIcon(GuiRegistry::instance().minusIcon());
VERIFY(connect(_addButton, SIGNAL(clicked()), this, SLOT(on_addButton_clicked())));
VERIFY(connect(_removeButton, SIGNAL(clicked()), this, SLOT(on_removeButton_clicked())));
auto hLayout = new QHBoxLayout;
hLayout->addWidget(_addButton);
hLayout->addWidget(_removeButton);
hLayout->addWidget(_discoverButton);
hLayout->setAlignment(Qt::AlignRight);
_minusPlusButtonBox = new QDialogButtonBox(this);
_minusPlusButtonBox->setOrientation(Qt::Horizontal);
#ifdef _WIN32
_minusPlusButtonBox->addButton(_addButton, QDialogButtonBox::NoRole);
_minusPlusButtonBox->addButton(_removeButton, QDialogButtonBox::NoRole);
#else
_minusPlusButtonBox->addButton(_removeButton, QDialogButtonBox::NoRole);
_minusPlusButtonBox->addButton(_addButton, QDialogButtonBox::NoRole);
#endif
_readPrefLabel = new QLabel("Read Preference:");
_readPrefLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
_readPreference = new QComboBox;
_readPreference->addItems(QStringList({ "Primary", "Primary Preferred" })); //todo
if (_settings->isReplicaSet()) {
_readPreference->setCurrentIndex(static_cast<int>(replicaSettings->readPreference()));
}
_setNameLabel = new QLabel("Set Name:<br><i><font color=\"gray\">(Optional)</font></i>");
_setNameEdit = new QLineEdit(QString::fromStdString(_settings->replicaSetSettings()->setNameUserEntered()));
auto _optionalLabel = new QLabel("<i><font color=\"gray\">(Optional)</font></i>");
new QShortcut(QKeySequence(Qt::Key_Delete), this, SLOT(deleteItem()));
QGridLayout *connectionLayout = new QGridLayout;
auto connectionLayout = new QGridLayout;
connectionLayout->setVerticalSpacing(10);
connectionLayout->setAlignment(Qt::AlignTop);
connectionLayout->addWidget(_typeLabel, 1, 0);
connectionLayout->addWidget(_connectionType, 1, 1, 1, 3);
connectionLayout->addWidget(_connectionType, 1, 1, 1, 3);
connectionLayout->addWidget(new QLabel(""), 2, 0);
connectionLayout->addWidget(_nameLabel, 3, 0);
connectionLayout->addWidget(_connectionName, 3, 1, 1, 3);
@@ -109,42 +119,85 @@ namespace Robomongo
connectionLayout->addWidget(_serverPort, 5, 3);
connectionLayout->addWidget(_addInfoLabel, 6, 1, 1, 3);
connectionLayout->addWidget(_membersLabel, 7, 0, Qt::AlignTop);
connectionLayout->addWidget(_members, 7, 1, 1, 3);
connectionLayout->addLayout(hLayout, 8, 1, 1, 3, Qt::AlignRight);
connectionLayout->addWidget(_members, 7, 1, 1, 3);
connectionLayout->addWidget(_minusPlusButtonBox, 8, 3, Qt::AlignRight | Qt::AlignTop);
connectionLayout->addWidget(new QLabel(""), 9, 0);
connectionLayout->addWidget(_readPrefLabel, 10, 0);
connectionLayout->addWidget(_readPreference, 10, 1, 1, 3);
connectionLayout->addWidget(new QLabel(""), 11, 0);
connectionLayout->addWidget(_setNameLabel, 10, 0/*, Qt::AlignBottom*/);
connectionLayout->addWidget(_setNameEdit, 10, 1, 1, 3, Qt::AlignTop);
//connectionLayout->addWidget(_optionalLabel, 11, 1, 1, 3, Qt::AlignLeft | Qt::AlignTop);
connectionLayout->addWidget(new QLabel(""), 12, 0);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(connectionLayout);
setLayout(mainLayout);
_connectionName->setFocus();
on_ConnectionTypeChange(_connectionType->currentIndex());
}
void ConnectionBasicTab::accept()
bool ConnectionBasicTab::accept()
{
ReplicaSetSettings* const replicaSettings = _settings->replicaSetSettings();
_settings->setReplicaSet(static_cast<bool>(_connectionType->currentIndex()));
_settings->setConnectionName(QtUtils::toStdString(_connectionName->text()));
_settings->setServerHost(QtUtils::toStdString(_serverAddress->text()));
_settings->setServerPort(_serverPort->text().toInt());
if (_settings->isReplicaSet()) {
// save replica members
std::vector<std::string> members;
for (int i = 0; i < _members->count(); ++i)
{
QListWidgetItem const* item = _members->item(i);
members.push_back(item->text().toStdString());
}
replicaSettings->setMembers(members);
// save read preference option
replicaSettings->setReadPreference(
static_cast<ReplicaSetSettings::ReadPreference>(_readPreference->currentIndex()));
if (_settings->isReplicaSet() && _members->topLevelItemCount() == 0) {
QMessageBox::critical(this, "Error", "Replica set members cannot be empty. "
"Please enter at least one member.");
return false;
}
// Check and warn if there is duplicate member or
// if any of the replica set member items does not contain ":" character between hostname and port.
if (_settings->isReplicaSet() && _members->topLevelItemCount() > 1) {
QStringList members;
for (int i = 0; i < _members->topLevelItemCount(); ++i) {
QTreeWidgetItem const* item = _members->topLevelItem(i);
QStringList const hostAndPort = item->text(0).split(":");
if (hostAndPort.size() < 2) {
QMessageBox::critical(this, "Error", "Replica set member items must all contain ':' between"
" hostname and port.");
return false;
}
if (!item->text(0).isEmpty())
members.push_back(item->text(0));
}
if (members.size() > 1) {
if (members.removeDuplicates() > 0) {
QMessageBox::critical(this, "Error", "Please remove duplicate member, two replica"
" set members cannot have the same hostname and port.");
return false;
}
}
}
// Save to settings
if (_settings->isReplicaSet() && _members->topLevelItemCount() > 0) {
QStringList const hostAndPort = _members->topLevelItem(0)->text(0).split(":");
_settings->setServerHost(hostAndPort[0].toStdString());
_settings->setServerPort(hostAndPort[1].toInt());
}
else { // Single server
_settings->setServerHost(QtUtils::toStdString(_serverAddress->text()));
_settings->setServerPort(_serverPort->text().toInt());
}
if (_settings->isReplicaSet()) {
// Save replica members
std::vector<std::string> members;
for (int i = 0; i < _members->topLevelItemCount(); ++i)
{
QTreeWidgetItem const* item = _members->topLevelItem(i);
if (!item->text(0).isEmpty())
members.push_back(item->text(0).toStdString());
}
_settings->replicaSetSettings()->setMembers(members);
_settings->replicaSetSettings()->setSetNameUserEntered(_setNameEdit->text().toStdString());
// Clear cached set name
_settings->replicaSetSettings()->setCachedSetName("");
}
return true;
}
void ConnectionBasicTab::on_ConnectionTypeChange(int index)
@@ -154,12 +207,10 @@ namespace Robomongo
// Replica set
_membersLabel->setVisible(isReplica);
_members->setVisible(isReplica);
_addButton->setVisible(isReplica);
_removeButton->setVisible(isReplica);
_discoverButton->setVisible(isReplica);
_readPrefLabel->setVisible(isReplica);
_readPreference->setVisible(isReplica);
_minusPlusButtonBox->setVisible(isReplica);
_setNameLabel->setVisible(isReplica);
_setNameEdit->setVisible(isReplica);
// Direct Connection
_addressLabel->setVisible(!isReplica);
_serverAddress->setVisible(!isReplica);
@@ -175,8 +226,68 @@ namespace Robomongo
void ConnectionBasicTab::on_addButton_clicked()
{
auto item = new QListWidgetItem("localhost:27017");
auto item = new QTreeWidgetItem;
// Make member addition little smarter than expected
if (_members->topLevelItemCount() < 1) {
item->setText(0, "localhost:27017");
}
else { // Add the next member using last entered hostname and incremented port by one
QString const& lastMember = _members->topLevelItem(_members->topLevelItemCount()-1)->text(0);
QStringList const& hostAndPort = lastMember.split(':');
if (hostAndPort.size() == 2) {
auto const& hostName = hostAndPort[0];
auto const& port = hostAndPort[1].toInt();
item->setText(0, hostName + ':' + QString::number(port + 1));
}
else {
item->setText(0, "localhost:" + QString::number(_members->topLevelItemCount() + 27017));
}
}
item->setFlags(item->flags() | Qt::ItemIsEditable);
_members->addItem(item);
_members->addTopLevelItem(item);
}
void ConnectionBasicTab::on_removeButton_clicked()
{
if (_members->topLevelItemCount() <= 0)
return;
if (_members->currentItem())
delete _members->currentItem();
else
delete _members->topLevelItem(_members->topLevelItemCount() - 1);
}
void ConnectionBasicTab::on_replicaMemberItemEdit(QTreeWidgetItem* item, int column)
{
if (!item)
return;
auto str = item->text(0);
// Remove white spaces
str = str.simplified();
str.remove(" ");
// Remove item from tree widget if it has empty text
if (str.isEmpty()) {
delete item;
return;
}
// Force port as integer
QStringList const& hostAndPort = str.split(':');
if (hostAndPort.size() >= 2) {
auto const& hostName = hostAndPort[0];
auto portStr = hostAndPort[1];
portStr.remove(QRegExp("[^\\d]"));
str = hostName + ':' + QString::number(portStr.toInt());
}
else
str += ":27017";
item->setText(0, str);
}
}
+11 -3
View File
@@ -7,7 +7,9 @@ class QLabel;
class QCheckBox;
class QPushButton;
class QComboBox;
class QListWidget;
class QTreeWidget;
class QTreeWidgetItem;
class QDialogButtonBox;
QT_END_NAMESPACE
namespace Robomongo
@@ -20,12 +22,14 @@ namespace Robomongo
public:
ConnectionBasicTab(ConnectionSettings *settings);
void accept();
bool accept();
private Q_SLOTS:
void on_ConnectionTypeChange(int index);
void deleteItem();
void on_addButton_clicked();
void on_removeButton_clicked();
void on_replicaMemberItemEdit(QTreeWidgetItem* item, int column);
private:
QLabel *_typeLabel;
@@ -39,12 +43,16 @@ namespace Robomongo
QLineEdit *_serverPort;
QLabel *_addInfoLabel;
QLabel *_membersLabel;
QListWidget *_members;
QTreeWidget *_members;
QPushButton *_addButton;
QPushButton *_removeButton;
QDialogButtonBox *_minusPlusButtonBox;
QLabel *_setNameLabel;
QLineEdit *_setNameEdit;
QPushButton *_discoverButton;
QLabel *_readPrefLabel;
QComboBox *_readPreference;
ConnectionSettings *const _settings;
};
}
@@ -11,6 +11,7 @@
#include "robomongo/core/settings/CredentialSettings.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/settings/SslSettings.h"
#include "robomongo/core/settings/ReplicaSetSettings.h"
#include "robomongo/gui/GuiRegistry.h"
#include "robomongo/core/AppRegistry.h"
#include "robomongo/core/domain/App.h"
@@ -23,13 +24,11 @@ namespace Robomongo
{
ConnectionDiagnosticDialog::ConnectionDiagnosticDialog(ConnectionSettings *connection, QWidget *parent) :
QDialog(parent),
_connection(NULL),
_connSettings(connection->clone()),
_server(NULL),
_serverHandle(0),
_continueExec(true)
{
_connection = connection->clone();
AppRegistry::instance().bus()->subscribe(this, ConnectionEstablishedEvent::Type);
AppRegistry::instance().bus()->subscribe(this, ConnectionFailedEvent::Type);
@@ -97,7 +96,7 @@ namespace Robomongo
_viewErrorLink->hide();
if (!AppRegistry::instance().app()->openServer(_connection, ConnectionTest)) {
if (!AppRegistry::instance().app()->openServer(_connSettings, ConnectionTest)) {
_continueExec = false;
return;
}
@@ -119,7 +118,7 @@ namespace Robomongo
void ConnectionDiagnosticDialog::sshStatus(State state)
{
if (!_connection->sshSettings()->enabled()) {
if (!_connSettings->sshSettings()->enabled()) {
_sshIconLabel->setVisible(false);
_sshLabel->setVisible(false);
return;
@@ -128,18 +127,18 @@ namespace Robomongo
if (state == InitialState) {
_sshIconLabel->setMovie(_loadingMovie);
_sshLabel->setText(QString("Connecting to SSH server at <b>%1:%2</b>...")
.arg(QtUtils::toQString(_connection->sshSettings()->host()))
.arg(_connection->sshSettings()->port()));
.arg(QtUtils::toQString(_connSettings->sshSettings()->host()))
.arg(_connSettings->sshSettings()->port()));
} else if (state == CompletedState) {
_sshIconLabel->setPixmap(_yesPixmap);
_sshLabel->setText(QString("Connected to SSH server at <b>%1:%2</b>")
.arg(QtUtils::toQString(_connection->sshSettings()->host()))
.arg(_connection->sshSettings()->port()));
.arg(QtUtils::toQString(_connSettings->sshSettings()->host()))
.arg(_connSettings->sshSettings()->port()));
} else if (state == FailedState) {
_sshIconLabel->setPixmap(_noPixmap);
_sshLabel->setText(QString("Unable to connect to SSH server at <b>%1:%2</b>")
.arg(QtUtils::toQString(_connection->sshSettings()->host()))
.arg(_connection->sshSettings()->port()));
.arg(QtUtils::toQString(_connSettings->sshSettings()->host()))
.arg(_connSettings->sshSettings()->port()));
}
}
@@ -147,34 +146,45 @@ namespace Robomongo
{
// Add info about tunneling if SSH or SSL is used
QString tunnelNote(""); // No tunnel info when neither SSH nor SSL enabled
if (_connection->sshSettings()->enabled())
{
if (_connSettings->sshSettings()->enabled())
tunnelNote = " via SSH tunnel";
}
else if (_connection->sslSettings()->sslEnabled())
{
else if (_connSettings->sslSettings()->sslEnabled())
tunnelNote = " via SSL tunnel";
}
else
tunnelNote = "";
auto replicaSetStr = QString::fromStdString(_connSettings->connectionName()) + " [Replica Set]";
replicaSetStr = (_connSettings->replicaSetSettings()->members().size() > 0)
? replicaSetStr + '(' + QString::fromStdString(
_connSettings->replicaSetSettings()->members()[0]) + ')'
: replicaSetStr + "";
QString const& serverAddress = _connSettings->isReplicaSet()
? replicaSetStr
: QString::fromStdString(_connSettings->getFullAddress());
// Set main info text at dialog
if (state == InitialState) {
_connectionIconLabel->setMovie(_loadingMovie);
_connectionLabel->setText(QString("Connecting to <b>%1</b>%2...").arg(QtUtils::toQString(_connection->getFullAddress()), tunnelNote));
_connectionLabel->setText(QString("Connecting to <b>%1</b>%2...").arg(serverAddress, tunnelNote));
} else if (state == CompletedState) {
_connectionIconLabel->setPixmap(_yesPixmap);
_connectionLabel->setText(QString("Connected to <b>%1</b>%2").arg(QtUtils::toQString(_connection->getFullAddress()), tunnelNote));
_connectionLabel->setText(QString("Connected to <b>%1</b>%2").arg(serverAddress, tunnelNote));
} else if (state == FailedState) {
_connectionIconLabel->setPixmap(_noPixmap);
_connectionLabel->setText(QString("Failed to connect to <b>%1</b>%2").arg(QtUtils::toQString(_connection->getFullAddress()), tunnelNote));
_connectionLabel->setText(QString("Failed to connect to <b>%1</b>%2").arg(serverAddress, tunnelNote));
} else if (state == NotPerformedState) {
_connectionIconLabel->setPixmap(_questionPixmap);
_connectionLabel->setText(QString("No chance to try connection to <b>%1</b>%2").arg(QtUtils::toQString(_connection->getFullAddress()), tunnelNote));
_connectionLabel->setText(QString("No chance to try connection to <b>%1</b>%2").
arg(serverAddress, tunnelNote));
}
}
void ConnectionDiagnosticDialog::authStatus(State state)
{
if (!_connection->hasEnabledPrimaryCredential()) {
if (!_connSettings->hasEnabledPrimaryCredential()) {
_authIconLabel->setVisible(false);
_authLabel->setVisible(false);
return;
@@ -183,18 +193,18 @@ namespace Robomongo
if (state == InitialState) {
_authIconLabel->setMovie(_loadingMovie);
_authLabel->setText(QString("Authorizing on <b>%1</b> database as <b>%2</b>...")
.arg(QtUtils::toQString(_connection->primaryCredential()->databaseName()))
.arg(QtUtils::toQString(_connection->primaryCredential()->userName())));
.arg(QtUtils::toQString(_connSettings->primaryCredential()->databaseName()))
.arg(QtUtils::toQString(_connSettings->primaryCredential()->userName())));
} else if (state == CompletedState) {
_authIconLabel->setPixmap(_yesPixmap);
_authLabel->setText(QString("Authorized on <b>%1</b> database as <b>%2</b>")
.arg(QtUtils::toQString(_connection->primaryCredential()->databaseName()))
.arg(QtUtils::toQString(_connection->primaryCredential()->userName())));
.arg(QtUtils::toQString(_connSettings->primaryCredential()->databaseName()))
.arg(QtUtils::toQString(_connSettings->primaryCredential()->userName())));
} else if (state == FailedState) {
_authIconLabel->setPixmap(_noPixmap);
_authLabel->setText(QString("Authorization failed on <b>%1</b> database as <b>%2</b>")
.arg(QtUtils::toQString(_connection->primaryCredential()->databaseName()))
.arg(QtUtils::toQString(_connection->primaryCredential()->userName())));
.arg(QtUtils::toQString(_connSettings->primaryCredential()->databaseName()))
.arg(QtUtils::toQString(_connSettings->primaryCredential()->userName())));
} else if (state == NotPerformedState) {
_authIconLabel->setPixmap(_questionPixmap);
_authLabel->setText(QString("No chance to authorize"));
@@ -203,7 +213,7 @@ namespace Robomongo
void ConnectionDiagnosticDialog::listStatus(State state)
{
if (_connection->hasEnabledPrimaryCredential()) {
if (_connSettings->hasEnabledPrimaryCredential()) {
_listIconLabel->setVisible(false);
_listLabel->setVisible(false);
return;
@@ -8,7 +8,7 @@ class QMovie;
namespace Robomongo
{
class ConnectionEstablishedEvent;
struct ConnectionEstablishedEvent;
class ConnectionFailedEvent;
class ConnectionSettings;
class MongoServer;
@@ -29,7 +29,8 @@ namespace Robomongo
private:
enum State {
enum State
{
InitialState,
CompletedState,
FailedState,
@@ -41,7 +42,7 @@ namespace Robomongo
void authStatus(State state);
void listStatus(State state);
ConnectionSettings *_connection;
ConnectionSettings *_connSettings;
QIcon _yesIcon;
QIcon _noIcon;
QIcon _questionIcon;
@@ -91,11 +91,11 @@ namespace Robomongo
"Please use one of them.");
return false;
}
_basicTab->accept();
_authTab->accept();
_advancedTab->accept();
if (!_sshTab->accept() || !_sslTab->accept())
if (!_basicTab->accept() || !_sshTab->accept() || !_sslTab->accept())
{
return false;
}
+95 -66
View File
@@ -11,15 +11,22 @@
#include <QKeyEvent>
#include <QApplication>
#include <QSettings>
#include <QUuid>
#include "robomongo/core/AppRegistry.h"
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/settings/ReplicaSetSettings.h"
#include "robomongo/core/settings/SettingsManager.h"
#include "robomongo/core/settings/SslSettings.h"
#include "robomongo/core/settings/SshSettings.h"
#include "robomongo/core/settings/CredentialSettings.h"
#include "robomongo/core/settings/SettingsManager.h"
#include "robomongo/core/utils/QtUtils.h"
#include "robomongo/gui/GuiRegistry.h"
#include "robomongo/gui/dialogs/ConnectionDialog.h"
#include "robomongo/gui/MainWindow.h"
#include "robomongo/gui/widgets/workarea/WelcomeTab.h"
namespace Robomongo
{
@@ -45,36 +52,49 @@ namespace Robomongo
*/
void setConnection(ConnectionSettings *connection)
{
if (connection->isReplicaSet()) {
setText(0, QtUtils::toQString(connection->connectionName()) + " [Replica Set]");
auto const repSetSize = connection->replicaSetSettings()->members().size();
setText(1, QString::number(repSetSize) + " Servers (" + QtUtils::toQString(connection->getFullAddress()) + ")");
}
else {
setText(0, QtUtils::toQString(connection->connectionName()));
setText(1, QtUtils::toQString(connection->getFullAddress()));
}
if (connection->hasEnabledPrimaryCredential()) {
QString authString = QString("%1 / %2").arg(QtUtils::toQString(connection->primaryCredential()->databaseName())).arg(QtUtils::toQString(connection->primaryCredential()->userName()));
setText(2, authString);
setIcon(2, GuiRegistry::instance().keyIcon());
} else {
setIcon(2, QIcon());
setText(2, "");
}
_connection = connection;
if (connection->isReplicaSet()) {
if (_connection->isReplicaSet()) {
setIcon(0, GuiRegistry::instance().replicaSetIcon());
setText(0, QtUtils::toQString(_connection->connectionName()));
auto const repSetSize = _connection->replicaSetSettings()->members().size();
auto addrText = QString::number(repSetSize) + ((repSetSize > 1) ? " nodes" : " node");
if (!_connection->replicaSetSettings()->members().empty()) {
addrText += QString::fromStdString(" (" + _connection->replicaSetSettings()->members().front() + ")");
}
setText(1, addrText);
}
else {
setIcon(0, GuiRegistry::instance().serverIcon());
setText(0, QtUtils::toQString(_connection->connectionName()));
setText(1, QtUtils::toQString(_connection->getFullAddress()));
}
if (connection->imported()) {
if (_connection->imported()) {
setIcon(0, GuiRegistry::instance().serverImportedIcon());
}
// Header "Attributes" (column[2])
setText(2, _connection->isReplicaSet() ? "Replica Set" : "");
if (_connection->sslSettings()->sslEnabled())
setText(2, text(2) + (text(2).isEmpty() ? "SSL" : ", SSL"));
if (_connection->sshSettings()->enabled())
setText(2, text(2) + (text(2).isEmpty() ? "SSH" : ", SSH"));
// Header "Auth. Database/User" (column[3])
if (_connection->hasEnabledPrimaryCredential()) {
auto authString = QString("%1 / %2").arg(QtUtils::toQString(_connection->primaryCredential()->databaseName()))
.arg(QtUtils::toQString(_connection->primaryCredential()->userName()));
setText(3, authString + " ");
setIcon(3, GuiRegistry::instance().keyIcon());
}
else {
setIcon(3, QIcon());
setText(3, "");
}
}
private:
@@ -113,12 +133,13 @@ namespace Robomongo
_listWidget->setIndentation(5);
QStringList colums;
colums << "Name" << "Address" << "Auth. Database / User";
colums << "Name" << "Address" << "Attributes" << "Auth. Database / User";
_listWidget->setHeaderLabels(colums);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0))
_listWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
_listWidget->header()->setSectionResizeMode(1, QHeaderView::Stretch);
_listWidget->header()->setSectionResizeMode(2, QHeaderView::Stretch);
_listWidget->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
_listWidget->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
#endif
//_listWidget->setViewMode(QListView::ListMode);
_listWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
@@ -183,19 +204,19 @@ namespace Robomongo
mainLayout->addLayout(firstColumnLayout, 1);
// Populate list with connections
SettingsManager::ConnectionSettingsContainerType connections = _settingsManager->connections();
for (SettingsManager::ConnectionSettingsContainerType::const_iterator it = connections.begin(); it != connections.end(); ++it) {
ConnectionSettings *connectionModel = *it;
std::vector<ConnectionSettings*> connectionSettings = _settingsManager->connections();
for (auto const& connSetting : connectionSettings) {
ConnectionSettings *connectionModel { connSetting };
add(connectionModel);
}
// Highlight first item
// Highlight last item
if (_listWidget->topLevelItemCount() > 0)
_listWidget->setCurrentItem(_listWidget->topLevelItem(0));
_listWidget->setCurrentItem(_listWidget->topLevelItem(_listWidget->topLevelItemCount()-1));
_listWidget->setFocus();
restoreWindowsSettings();
restoreWindowSettings();
}
/**
@@ -203,27 +224,27 @@ namespace Robomongo
*/
void ConnectionsDialog::accept()
{
ConnectionListWidgetItem *currentItem = (ConnectionListWidgetItem *) _listWidget->currentItem();
auto currentItem = dynamic_cast<ConnectionListWidgetItem*>(_listWidget->currentItem());
// Do nothing if no item selected
if (!currentItem)
return;
_selectedConnection = currentItem->connection();
saveWindowsSettings();
saveWindowSettings();
QDialog::accept();
}
void ConnectionsDialog::reject()
{
saveWindowsSettings();
saveWindowSettings();
QDialog::reject();
}
void ConnectionsDialog::closeEvent(QCloseEvent *event)
{
saveWindowsSettings();
saveWindowSettings();
QWidget::closeEvent(event);
}
@@ -243,19 +264,19 @@ namespace Robomongo
* @brief Initiate 'add' action, usually when user clicked on Add button
*/
void ConnectionsDialog::add()
{
ConnectionSettings *newModel = new ConnectionSettings();
ConnectionDialog editDialog(newModel);
{
auto newConnSettings = std::unique_ptr<ConnectionSettings>(new ConnectionSettings(false));
ConnectionDialog editDialog(newConnSettings.get());
// Do nothing if not accepted
if (editDialog.exec() != QDialog::Accepted) {
delete newModel;
return;
}
_settingsManager->addConnection(newModel);
add(newConnSettings.get());
_settingsManager->addConnection(newConnSettings.release());
_listWidget->setFocus();
add(newModel);
}
/**
@@ -263,14 +284,13 @@ namespace Robomongo
*/
void ConnectionsDialog::edit()
{
ConnectionListWidgetItem *currentItem =
(ConnectionListWidgetItem *) _listWidget->currentItem();
auto currentItem = dynamic_cast<ConnectionListWidgetItem*>(_listWidget->currentItem());
// Do nothing if no item selected
if (currentItem == 0)
if (!currentItem)
return;
ConnectionSettings *connection = currentItem->connection();
auto connection = currentItem->connection();
boost::scoped_ptr<ConnectionSettings> clonedConnection(connection->clone());
ConnectionDialog editDialog(clonedConnection.get());
@@ -302,42 +322,53 @@ namespace Robomongo
*/
void ConnectionsDialog::remove()
{
ConnectionListWidgetItem *currentItem =
(ConnectionListWidgetItem *)_listWidget->currentItem();
auto currentItem = dynamic_cast<ConnectionListWidgetItem*>(_listWidget->currentItem());
// Do nothing if no item selected
if (!currentItem)
return;
ConnectionSettings *connectionModel = currentItem->connection();
ConnectionSettings *connSettings = currentItem->connection();
// Ask user
int answer = QMessageBox::question(this,
int const answer = QMessageBox::question(this,
"Connections",
QString("Really delete \"%1\" connection?").arg(QtUtils::toQString(connectionModel->getReadableName())),
QString("Really delete \"%1\" connection?").arg(QtUtils::toQString(connSettings->getReadableName())),
QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton);
if (answer != QMessageBox::Yes)
return;
_settingsManager->removeConnection(connectionModel);
/* Temporarily disabling Recent Connections feature
_settingsManager->deleteRecentConnection(connSettings);
// Remove from WelcomeTab
for (auto widget : QApplication::topLevelWidgets()) {
if (auto mainWin = dynamic_cast<MainWindow*>(widget))
mainWin->getWelcomeTab()->removeRecentConnectionItem(connSettings);
}
*/
_settingsManager->removeConnection(connSettings);
delete currentItem;
}
void ConnectionsDialog::clone()
{
ConnectionListWidgetItem *currentItem =
(ConnectionListWidgetItem *) _listWidget->currentItem();
auto currentItem = dynamic_cast<ConnectionListWidgetItem*>(_listWidget->currentItem());
// Do nothing if no item selected
if (currentItem == 0)
if (!currentItem)
return;
// Clone connection
ConnectionSettings *connection = currentItem->connection()->clone();
std::string newConnectionName = "Copy of "+connection->connectionName();
// This is a special clone which will actually be a new connection and must have unique UUID
connection->setUuid(QUuid::createUuid().toString());
std::string newConnectionName = "Copy of " + connection->connectionName();
connection->setConnectionName(newConnectionName);
connection->replicaSetSettings()->setCachedSetName("");
ConnectionDialog editDialog(connection);
@@ -357,15 +388,13 @@ namespace Robomongo
*/
void ConnectionsDialog::listWidget_layoutChanged()
{
int count = _listWidget->topLevelItemCount();
// Make childrens toplevel again. This is a bad, but quickiest item reordering
// implementation.
for (int i = 0; i < count; i++)
for (int i = 0; i < _listWidget->topLevelItemCount(); i++)
{
ConnectionListWidgetItem * item = (ConnectionListWidgetItem *) _listWidget->topLevelItem(i);
auto item = (ConnectionListWidgetItem *) _listWidget->topLevelItem(i);
if (item->childCount() > 0) {
ConnectionListWidgetItem *childItem = (ConnectionListWidgetItem *) item->child(0);
auto childItem = (ConnectionListWidgetItem *) item->child(0);
item->removeChild(childItem);
_listWidget->insertTopLevelItem(++i, childItem);
_listWidget->setCurrentItem(childItem);
@@ -373,11 +402,10 @@ namespace Robomongo
}
}
count = _listWidget->topLevelItemCount();
SettingsManager::ConnectionSettingsContainerType items;
for (int i = 0; i < count; i++)
for (int i = 0; i < _listWidget->topLevelItemCount(); i++)
{
ConnectionListWidgetItem * item = (ConnectionListWidgetItem *) _listWidget->topLevelItem(i);
auto item = (ConnectionListWidgetItem *) _listWidget->topLevelItem(i);
items.push_back(item->connection());
}
@@ -389,8 +417,9 @@ namespace Robomongo
*/
void ConnectionsDialog::add(ConnectionSettings *connection)
{
ConnectionListWidgetItem *item = new ConnectionListWidgetItem(connection);
auto item = new ConnectionListWidgetItem(connection);
_listWidget->addTopLevelItem(item);
_listWidget->setCurrentItem(item);
_connectionItems.push_back(item);
}
@@ -416,15 +445,15 @@ namespace Robomongo
QDialog::keyPressEvent(event);
}
void ConnectionsDialog::restoreWindowsSettings()
void ConnectionsDialog::restoreWindowSettings()
{
QSettings settings("Paralect", "Robomongo");
QSettings settings("3T", "Robomongo");
resize(settings.value("ConnectionsDialog/size").toSize());
}
void ConnectionsDialog::saveWindowsSettings() const
void ConnectionsDialog::saveWindowSettings() const
{
QSettings settings("Paralect", "Robomongo");
QSettings settings("3T", "Robomongo");
settings.setValue("ConnectionsDialog/size", size());
}
+12 -12
View File
@@ -35,27 +35,27 @@ namespace Robomongo
/**
* @brief This function is called when user clicks on "Connect" button.
*/
virtual void accept();
void accept() override;
/**
* @brief Called when "Cancel" button clicked.
*/
virtual void reject();
void reject() override;
/**
* @brief Add connection to the list widget
*/
void add(ConnectionSettings *connection);
protected:
/**
* @brief Reimplementing closeEvent in order to do some pre-close actions.
*/
void closeEvent(QCloseEvent *event);
void closeEvent(QCloseEvent *event) override;
private Q_SLOTS:
void linkActivated(const QString &link);
/**
* @brief Add connection to the list widget
*/
void add(ConnectionSettings *connection);
/**
* @brief Initiate 'add' action, usually when user clicked on Add button
*/
@@ -84,19 +84,19 @@ namespace Robomongo
*/
void listWidget_layoutChanged();
void keyPressEvent(QKeyEvent* event);
void keyPressEvent(QKeyEvent* event) override;
private:
/**
* @brief Restore windows settings from system registry
* @brief Restore window settings from system registry
*/
void restoreWindowsSettings();
void restoreWindowSettings();
/**
* @brief Save windows settings into system registry
*/
void saveWindowsSettings() const;
void saveWindowSettings() const;
/**
* @brief ConnectionSettings, that was selected after pressing on
@@ -243,7 +243,8 @@ namespace Robomongo
if (state) // resize, dialog is expanding
{
setMinimumSize(560, 440);
QSettings settings("Paralect", "Robomongo");
_validateJsonButton->setHidden(_advancedOptions->currentIndex() == OPTIONS_TAB);
QSettings settings("3T", "Robomongo");
if (settings.contains("CreateCollectionDialog/size"))
{
restoreWindowSettings();
@@ -257,7 +258,8 @@ namespace Robomongo
else // resize, dialog is shrinking
{
saveWindowSettings(); // save expanded geometry first
setMinimumSize(300, 150);
_validateJsonButton->hide();
setFixedSize(300, 150);
adjustSize();
}
}
@@ -403,13 +405,13 @@ namespace Robomongo
void CreateCollectionDialog::saveWindowSettings() const
{
QSettings settings("Paralect", "Robomongo");
QSettings settings("3T", "Robomongo");
settings.setValue("CreateCollectionDialog/size", size());
}
void CreateCollectionDialog::restoreWindowSettings()
{
QSettings settings("Paralect", "Robomongo");
QSettings settings("3T", "Robomongo");
resize(settings.value("CreateCollectionDialog/size").toSize());
}
@@ -84,18 +84,18 @@ namespace Robomongo
/**
* @brief Validate all input fields and make extraOptions object if all valid when create button clicked.
*/
virtual void accept();
void accept() override;
/**
* @brief Called when "Cancel" button clicked.
*/
virtual void reject();
void reject() override;
protected:
/**
* @brief Reimplementing closeEvent in order to do implement functioanlites before close this dialog.
* @brief Reimplementing closeEvent in order to do implement functionalities before close this dialog.
*/
void closeEvent(QCloseEvent *event);
void closeEvent(QCloseEvent *event) override;
private Q_SLOTS:
/**
@@ -0,0 +1,183 @@
#include "robomongo/gui/dialogs/CreateConnectionDialog.h"
#include <QPushButton>
#include <QCloseEvent>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QApplication>
#include <QSplitter>
#include <QDialogButtonBox>
#include <QLineEdit>
#include <QPixmap>
#include <QLabel>
#include "robomongo/gui/GuiRegistry.h"
#include "robomongo/gui/dialogs/ConnectionsDialog.h"
#include "robomongo/gui/dialogs/ConnectionDiagnosticDialog.h"
#include "robomongo/core/settings/ConnectionSettings.h"
#include "robomongo/core/utils/QtUtils.h"
#include <mongo/client/connection_string.h>
//#include <mongo/base/status_with.h>
namespace Robomongo
{
/**
* @brief Constructs dialog with specified connection
*/
CreateConnectionDialog::CreateConnectionDialog(ConnectionsDialog* parent)
: QDialog(), _connectionsDialog(parent), _connection(nullptr), _fromURI(true), // todo: _fromURI:false
_mongoUriWithStatus(nullptr)
{
setWindowTitle("Create New Connection");
setWindowIcon(GuiRegistry::instance().serverIcon());
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); // Remove help button (?)
setMinimumWidth(450);
//setAttribute(Qt::WA_DeleteOnClose);
// Splitter-Left
auto label01 = new QLabel("Replica Set");
label01->setStyleSheet("font-weight: bold");
auto wid1 = new QLabel;
auto repSetImage = QPixmap("C:\\Users\\gsimsek\\Google Drive\\Cases - Drive\\replica_sets\\icons\\repset.png");
wid1->setPixmap(repSetImage);
_uriLineEdit = new QLineEdit("mongodb://localhost:27017,localhost:27018,localhost:27019/admin?replicaSet=repset");
auto const& createConnStr= QString("b) <a style='color: %1' href='create'>Create connection manually</a>").arg("#106CD6");
auto createConnLabel = new QLabel(createConnStr);
VERIFY(connect(createConnLabel, SIGNAL(linkActivated(QString)), this, SLOT(on_createConnLinkActivated())));
auto splitterL = new QSplitter;
splitterL->setOrientation(Qt::Vertical);
splitterL->addWidget(label01);
splitterL->addWidget(wid1);
//splitterL->addWidget(new QLabel(""));
//splitterL->addWidget(nameWid);
splitterL->addWidget(new QLabel(""));
splitterL->addWidget(new QLabel("a) I have a URI connection string:"));
splitterL->addWidget(_uriLineEdit);
splitterL->addWidget(new QLabel(""));
splitterL->addWidget(createConnLabel);
splitterL->addWidget(new QLabel(""));
// Splitter-Right
auto label2 = new QLabel("Stand Alone Server");
label2->setStyleSheet("font-weight: bold");
auto wid2 = new QLabel;
auto standAlone = QPixmap("C:\\Users\\gsimsek\\Google Drive\\Cases - Drive\\replica_sets\\icons\\stand_alone.png");
wid2->setPixmap(standAlone);
auto splitterR = new QSplitter;
splitterR->setOrientation(Qt::Vertical);
splitterR->addWidget(label2);
splitterR->addWidget(wid2);
// Splitter layout
auto splitterLay = new QHBoxLayout;
splitterLay->addWidget(splitterL);
//splitterLay->addWidget(splitterR);
// Button Box
QPushButton *testButton = new QPushButton("&Test");
testButton->setIcon(qApp->style()->standardIcon(QStyle::SP_MessageBoxInformation));
VERIFY(connect(testButton, SIGNAL(clicked()), this, SLOT(testConnection())));
QHBoxLayout *bottomLayout = new QHBoxLayout;
bottomLayout->addWidget(testButton, 1, Qt::AlignLeft);
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
buttonBox->setOrientation(Qt::Horizontal);
buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
buttonBox->button(QDialogButtonBox::Save)->setText("Next");
VERIFY(connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())));
VERIFY(connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())));
bottomLayout->addWidget(buttonBox);
// main layout
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(splitterLay);
mainLayout->addLayout(bottomLayout);
setLayout(mainLayout);
//_basicTab->setFocus();
adjustSize();
// Set minimum width - adjustment after adding SSLTab
setMinimumWidth(550);
}
/**
* @brief Accept() is called when user agree with entered data.
*/
void CreateConnectionDialog::accept()
{
//if (validateAndApply()) {
// QDialog::accept();
//}
std::unique_ptr<mongo::StatusWith<mongo::MongoURI>> uriWithStatus = getUriWithStatus();
if (!uriWithStatus || !uriWithStatus->isOK()) {
return;
}
_mongoUriWithStatus.reset(uriWithStatus.release());
QDialog::accept();
}
bool CreateConnectionDialog::validateAndApply()
{
return true;
}
std::unique_ptr<mongo::StatusWith<mongo::MongoURI>> CreateConnectionDialog::getUriWithStatus()
{
const std::string& url = _uriLineEdit->text().toStdString();
std::unique_ptr<mongo::StatusWith<mongo::MongoURI>> uriWithStatus = nullptr;
try {
uriWithStatus.reset(new mongo::StatusWith<mongo::MongoURI>(mongo::MongoURI::parse(url)));
if (!uriWithStatus->isOK()) {
QMessageBox::critical(this, "Error", "Failed to parse connection string URI.\n"
"Reason: " + QString::fromStdString(uriWithStatus->getStatus().reason()));
return uriWithStatus;
}
}
catch (const std::exception& exception) {
QMessageBox::critical(this, "Error", "Failed to parse connection string URI.\n"
"Reason: " + QString::fromStdString(exception.what()));
return nullptr;
}
return uriWithStatus;
}
/**
* @brief Test current connection
*/
void CreateConnectionDialog::testConnection()
{
//if (!validateAndApply())
// return;
auto uriWithStatus = getUriWithStatus();
if (!uriWithStatus || !uriWithStatus->isOK()) {
return;
}
auto uri = uriWithStatus->getValue();
ConnectionSettings connection(uri, true); // todo: refactor
ConnectionDiagnosticDialog diag(&connection, this);
if (!diag.continueExec())
return;
diag.exec();
}
void CreateConnectionDialog::on_createConnLinkActivated()
{
_fromURI = false;
accept();
}
}
@@ -0,0 +1,85 @@
#pragma once
#include <QDialog>
QT_BEGIN_NAMESPACE
class QLineEdit;
QT_END_NAMESPACE
#include "robomongo/core/Core.h"
#include <mongo/client/mongo_uri.h>
namespace Robomongo
{
class ConnectionsDialog;
class ConnectionSettings;
/**
* @brief This class is not finished, it is still under development.
* It was planned to be used as very first dialog right after 'create' new
* connection is clicked on connections dialog in order to provide user
* some create more friendly create connection options with pictures
* e.g. create connection from URI string, create replica set or create single server.
*/
class CreateConnectionDialog : public QDialog
{
Q_OBJECT
public:
using MongoURIwithStatus = mongo::StatusWith<mongo::MongoURI>;
/**
* @brief Constructs dialog with specified connection
*/
CreateConnectionDialog(ConnectionsDialog *parent);
ConnectionSettings *const connection() const { return _connection; }
// todo:
// mongo::MongoURI getMongoUri() const { return _mongoUri; }
bool fromURI() const { return _fromURI; }
std::unique_ptr<mongo::StatusWith<mongo::MongoURI>> getMongoUriWithStatus()
{
return std::move(_mongoUriWithStatus);
}
public Q_SLOTS:
/**
* @brief Accept() is called when user agree with entered data.
*/
virtual void accept();
private Q_SLOTS:
/**
* @brief Test current connection
*/
void testConnection();
void on_createConnLinkActivated();
private:
bool validateAndApply();
// todo: may return nullptr
/* example usage:
auto uriWithStatus = getUriWithStatus();
if (!uriWithStatus || !uriWithStatus->isOK()) {
return;
}
auto uri = uriWithStatus->getValue();
*/
std::unique_ptr<MongoURIwithStatus> getUriWithStatus();
QLineEdit* _uriLineEdit;
/**
* @brief Edited connection
*/
ConnectionSettings* _connection;
ConnectionsDialog* _connectionsDialog;
std::unique_ptr<mongo::StatusWith<mongo::MongoURI>> _mongoUriWithStatus;
bool _fromURI;
};
}
@@ -6,7 +6,9 @@
#include <QMessageBox>
#include <QDialogButtonBox>
#include <QDesktopWidget>
#include <QSettings>
#include <Qsci/qscilexerjavascript.h>
#include <mongo/client/dbclientinterface.h>
#include "robomongo/gui/editors/JSLexer.h"
@@ -34,7 +36,15 @@ namespace Robomongo
QSize size(screenGeometry.width() - horizontalMargin,
screenGeometry.height() - verticalMargin);
resize(size);
QSettings settings("3T", "Robomongo");
if (settings.contains("DocumentTextEditor/size"))
{
restoreWindowSettings();
}
else
{
resize(size);
}
setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint);
@@ -105,6 +115,8 @@ namespace Robomongo
if (!validate())
return;
saveWindowSettings();
QDialog::accept();
}
@@ -127,6 +139,8 @@ namespace Robomongo
return;
}
saveWindowSettings();
QDialog::reject();
}
@@ -186,6 +200,12 @@ namespace Robomongo
validate(false);
}
void DocumentTextEditor::closeEvent(QCloseEvent *event)
{
saveWindowSettings();
QWidget::closeEvent(event);
}
/*
** Configure QsciScintilla query widget
*/
@@ -204,4 +224,17 @@ namespace Robomongo
_queryText->sciScintilla()->setStyleSheet("QFrame { background-color: rgb(73, 76, 78); border: 1px solid #c7c5c4; border-radius: 4px; margin: 0px; padding: 0px;}");
}
void DocumentTextEditor::saveWindowSettings() const
{
QSettings settings("3T", "Robomongo");
settings.setValue("DocumentTextEditor/size", size());
}
void DocumentTextEditor::restoreWindowSettings()
{
QSettings settings("3T", "Robomongo");
resize(settings.value("DocumentTextEditor/size").toSize());
}
}
+19 -2
View File
@@ -28,16 +28,33 @@ namespace Robomongo
void setCursorPosition(int line, int column);
public Q_SLOTS:
virtual void accept();
virtual void reject();
void accept() override;
void reject() override;
bool validate(bool silentOnSuccess = true);
private Q_SLOTS:
void onQueryTextChanged();
void onValidateButtonClicked();
protected:
/**
* @brief Reimplementing closeEvent in order to do some pre-close actions.
*/
void closeEvent(QCloseEvent *event) override;
private:
void _configureQueryText();
/**
* @brief Restore window settings from system registry
*/
void restoreWindowSettings();
/**
* @brief Save window settings into system registry
*/
void saveWindowSettings() const;
const CollectionInfo _info;
FindFrame *_queryText;
bool _readonly;
+243
View File
@@ -0,0 +1,243 @@
#include "robomongo/gui/dialogs/EulaDialog.h"
#include <QApplication>
#include <QHBoxLayout>
#include <QPushButton>
#include <QMessageBox>
#include <QDialogButtonBox>
#include <QFile>
#include <QSettings>
#include <QLabel>
#include <QTextBrowser>
#include <QLineEdit>
#include <QRadioButton>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrlQuery>
#include <QJsonObject>
#include <QJsonDocument>
#include <QDesktopWidget>
#include "robomongo/core/utils/QtUtils.h"
namespace Robomongo
{
EulaDialog::EulaDialog(QWidget *parent)
: QWizard(parent)
{
setWindowTitle("EULA");
//// First page
auto firstPage = new QWizardPage;
auto agreeButton = new QRadioButton("I agree");
VERIFY(connect(agreeButton, SIGNAL(clicked()),
this, SLOT(on_agreeButton_clicked())));
auto notAgreeButton = new QRadioButton("I don't agree");
notAgreeButton->setChecked(true);
VERIFY(connect(notAgreeButton, SIGNAL(clicked()),
this, SLOT(on_notAgreeButton_clicked())));
auto radioButtonsLay = new QHBoxLayout;
radioButtonsLay->setAlignment(Qt::AlignHCenter);
radioButtonsLay->setSpacing(30);
radioButtonsLay->addWidget(agreeButton);
radioButtonsLay->addWidget(notAgreeButton);
auto textBrowser = new QTextBrowser;
textBrowser->setOpenExternalLinks(true);
textBrowser->setOpenLinks(true);
QFile file(":gnu_gpl3_license.html");
if (file.open(QFile::ReadOnly | QFile::Text))
textBrowser->setText(file.readAll());
auto hline = new QFrame();
hline->setFrameShape(QFrame::HLine);
hline->setFrameShadow(QFrame::Sunken);
auto mainLayout1 = new QVBoxLayout();
mainLayout1->addWidget(new QLabel("<h3>End-User License Agreement</h3>"));
mainLayout1->addWidget(new QLabel(""));
mainLayout1->addWidget(textBrowser);
mainLayout1->addWidget(new QLabel(""));
mainLayout1->addLayout(radioButtonsLay, Qt::AlignCenter);
mainLayout1->addWidget(new QLabel(""));
mainLayout1->addWidget(hline);
firstPage->setLayout(mainLayout1);
//// Second page
auto secondPage = new QWizardPage;
auto nameLabel = new QLabel("<b>First Name:</b>");
_nameEdit = new QLineEdit;
auto lastNameLabel = new QLabel("<b>Last Name:</b>");
_lastNameEdit = new QLineEdit;
auto emailLabel = new QLabel("<b>Email:</b>");
_emailEdit = new QLineEdit;
auto buttomLabel = new QLabel("By submitting this form I agree to 3T Software Labs "
"<a href='https://studio3t.com/privacy-policy'>Privacy Policy</a>.");
buttomLabel->setOpenExternalLinks(true);
auto bodyLabel = new QLabel("\nShare your email address with us and we'll keep you "
"up-to-date with updates from us and new features as they come out.");
bodyLabel->setWordWrap(true);
auto mainLayout2 = new QGridLayout();
mainLayout2->addWidget(new QLabel, 0, 0, 1, 2);
mainLayout2->addWidget(new QLabel("<h3>Thank you for choosing Robomongo!</h3>"), 1, 0, 1, 2);
mainLayout2->addWidget(bodyLabel, 2, 0 , 1, 2);
mainLayout2->addWidget(new QLabel, 3, 0, 1, 2);
mainLayout2->addWidget(nameLabel, 4, 0);
mainLayout2->addWidget(_nameEdit, 4, 1);
mainLayout2->addWidget(lastNameLabel, 5, 0);
mainLayout2->addWidget(_lastNameEdit, 5, 1);
mainLayout2->addWidget(emailLabel, 6, 0);
mainLayout2->addWidget(_emailEdit, 6, 1);
mainLayout2->addWidget(new QLabel, 7, 0, 1, 2);
mainLayout2->addWidget(buttomLabel, 8, 0, 1, 2);
secondPage->setLayout(mainLayout2);
addPage(firstPage);
#if defined(_WIN32) || defined(__APPLE__)
addPage(secondPage);
#endif
//// Buttons
setButtonText(QWizard::CustomButton1, tr("Back"));
setButtonText(QWizard::CustomButton2, tr("Next"));
setButtonText(QWizard::CustomButton3, tr("Finish"));
VERIFY(connect(button(QWizard::CustomButton1), SIGNAL(clicked()), this, SLOT(on_back_clicked())));
VERIFY(connect(button(QWizard::CustomButton2), SIGNAL(clicked()), this, SLOT(on_next_clicked())));
VERIFY(connect(button(QWizard::CustomButton3), SIGNAL(clicked()), this, SLOT(on_finish_clicked())));
setButtonLayout(QList<WizardButton>{ QWizard::Stretch, QWizard::CustomButton1, QWizard::CustomButton2,
QWizard::CancelButton, QWizard::CustomButton3});
#if defined(_WIN32) || defined(__APPLE__)
button(QWizard::CustomButton1)->setDisabled(true);
button(QWizard::CustomButton2)->setDisabled(true);
button(QWizard::CustomButton3)->setDisabled(true);
#else // linux
button(QWizard::CustomButton1)->setHidden(true);
button(QWizard::CustomButton2)->setHidden(true);
button(QWizard::CustomButton3)->setDisabled(true);
#endif
setWizardStyle(QWizard::ModernStyle);
QSettings const settings("3T", "Robomongo");
if (settings.contains("EulaDialog/size")) {
restoreWindowSettings();
}
else {
auto const desktop = QApplication::desktop();
auto const& mainScreenSize = desktop->availableGeometry(desktop->primaryScreen()).size();
resize(mainScreenSize.width()*0.5, mainScreenSize.height()*0.6);
}
}
void EulaDialog::accept()
{
saveWindowSettings();
// Build post data and send
postUserData();
QDialog::accept();
}
void EulaDialog::reject()
{
saveWindowSettings();
QDialog::reject();
}
void EulaDialog::closeEvent(QCloseEvent *event)
{
saveWindowSettings();
QWidget::closeEvent(event);
}
void EulaDialog::on_agreeButton_clicked()
{
#if defined(_WIN32) || defined(__APPLE__)
button(QWizard::CustomButton2)->setEnabled(true);
#else
button(QWizard::CustomButton3)->setEnabled(true);
#endif
}
void EulaDialog::on_notAgreeButton_clicked()
{
#if defined(_WIN32) || defined(__APPLE__)
button(QWizard::CustomButton2)->setEnabled(false);
#else
button(QWizard::CustomButton3)->setEnabled(false);
#endif
}
void EulaDialog::on_next_clicked()
{
next();
button(QWizard::CustomButton1)->setEnabled(true);
button(QWizard::CustomButton2)->setEnabled(false);
button(QWizard::CustomButton3)->setEnabled(true);
}
void EulaDialog::on_back_clicked()
{
back();
button(QWizard::CustomButton1)->setEnabled(false);
button(QWizard::CustomButton2)->setEnabled(true);
button(QWizard::CustomButton3)->setEnabled(false);
}
void EulaDialog::on_finish_clicked()
{
accept();
}
void EulaDialog::postUserData() const
{
if (_emailEdit->text().isEmpty())
return;
// Build post data and send
QJsonObject jsonStr {
{ "email", _emailEdit->text() },
{ "firstName", _nameEdit->text() },
{ "lastName", _lastNameEdit->text() }
};
QJsonDocument jsonDoc(jsonStr);
QUrlQuery postData(jsonDoc.toJson());
postData = QUrlQuery("rd=" + postData.toString(QUrl::FullyEncoded).toUtf8());
QNetworkRequest request(QUrl("https://rm-form.3t.io/"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
auto networkManager = new QNetworkAccessManager;
_reply = networkManager->post(request, postData.toString(QUrl::FullyEncoded).toUtf8());
}
void EulaDialog::saveWindowSettings() const
{
QSettings settings("3T", "Robomongo");
settings.setValue("EulaDialog/size", size());
}
void EulaDialog::restoreWindowSettings()
{
QSettings settings("3T", "Robomongo");
resize(settings.value("EulaDialog/size").toSize());
}
}
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include <QWizard>
QT_BEGIN_NAMESPACE
class QLabel;
class QLineEdit;
class QCheckBox;
class QPushButton;
class QDialogButtonBox;
class QComboBox;
class QNetworkReply;
QT_END_NAMESPACE
namespace Robomongo
{
class EulaDialog : public QWizard
{
Q_OBJECT
public:
static const QSize minimumSize;
explicit EulaDialog(QWidget *parent = nullptr);
public Q_SLOTS:
void accept() override;
void reject() override;
protected:
void closeEvent(QCloseEvent *event) override;
private Q_SLOTS:
void on_agreeButton_clicked();
void on_notAgreeButton_clicked();
void on_next_clicked();
void on_back_clicked();
void on_finish_clicked();
private:
// Send name, last name and email data of user to home server
void postUserData() const;
/**
* @brief Restore window settings from system registry
*/
void restoreWindowSettings();
/**
* @brief Save window settings into system registry
*/
void saveWindowSettings() const;
QLineEdit* _nameEdit;
QLineEdit* _lastNameEdit;
QLineEdit* _emailEdit;
QByteArray _postData;
mutable QNetworkReply* _reply;
};
}
+488
View File
@@ -0,0 +1,488 @@
#include "robomongo/gui/dialogs/ExportDialog.h"
#include <QPushButton>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QTextEdit>
#include <QLabel>
#include <QDialogButtonBox>
#include <QTreeWidget>
#include <QComboBox>
#include <QGroupBox>
#include <QApplication>
#include <QProcess>
#include <QDir>
#include <QFileDialog>
#include <QDateTime>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QFileInfo>
#include <QSizePolicy>
#include <QProgressDialog>
#include "robomongo/core/utils/QtUtils.h"
#include "robomongo/core/domain/App.h"
#include "robomongo/core/AppRegistry.h"
#include "robomongo/core/domain/MongoCollection.h"
#include "robomongo/core/domain/MongoDatabase.h"
#include "robomongo/gui/widgets/explorer/ExplorerServerTreeItem.h"
#include "robomongo/gui/widgets/workarea/IndicatorLabel.h"
#include "robomongo/gui/widgets/explorer/ExplorerWidget.h"
#include "robomongo/gui/widgets/explorer/ExplorerTreeWidget.h"
#include "robomongo/gui/widgets/explorer/ExplorerServerTreeItem.h"
#include "robomongo/gui/widgets/explorer/ExplorerCollectionTreeItem.h"
#include "robomongo/gui/widgets/explorer/ExplorerDatabaseCategoryTreeItem.h"
#include "robomongo/gui/widgets/explorer/ExplorerDatabaseTreeItem.h"
#include "robomongo/gui/widgets/explorer/ExplorerReplicaSetTreeItem.h"
#include "robomongo/gui/utils/GuiConstants.h"
#include "robomongo/gui/GuiRegistry.h"
namespace Robomongo
{
namespace
{
const QString defaultDir = "D:\\exports\\"; // Default location
auto const AUTO_MODE_SIZE = QSize(500, 450);
auto const MANUAL_MODE_SIZE = QSize(500, 400);
auto const SHOW_DETAILS = "<a href='error' style='color: #777777;'>Show details</a>";
auto const HIDE_DETAILS = "<a href='error' style='color: #777777;'>Hide details</a>";
// This structure represents the arguments as in "mongoexport.exe --help"
// See http://docs.mongodb.org/manual/reference/program/mongoexport/ for more information
struct MongoExportArgs
{
static QString db(const QString& dbName) { return ("--db" + dbName); }
static QString collection(const QString& collection) { return ("--collection" + collection); }
// i.e. absFilePath: "/exports/coll1.json"
static QString out(const QString& absFilePath) { return ("--out " + absFilePath); }
};
}
ExportDialog::ExportDialog(QString const& dbName, QString const& collName, QWidget *parent) :
QDialog(parent), _mode(AUTO), _mongoExportArgs(), _activeProcess(nullptr)
{
setWindowTitle("Export Collection");
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); // Remove help button (?)
setMinimumSize(AUTO_MODE_SIZE);
//setFixedHeight(AUTO_MODE_SIZE.height());
_activeProcess = new QProcess(this);
//_activeProcess->setProcessChannelMode(QProcess::MergedChannels);
VERIFY(connect(_activeProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(on_exportFinished(int, QProcess::ExitStatus))));
VERIFY(connect(_activeProcess, SIGNAL(errorOccurred(QProcess::ProcessError)),
this, SLOT(on_processErrorOccurred(QProcess::ProcessError))));
// todo: move to a global location
// Enable copyable text for QMessageBox
qApp->setStyleSheet("QMessageBox { messagebox-text-interaction-flags: 5; }");
const QString serverName = "localhost:20017"; // todo: remove
Indicator *serverIndicator = new Indicator(GuiRegistry::instance().serverIcon(), serverName);
// Horizontal line
QFrame *horline = new QFrame();
horline->setFrameShape(QFrame::HLine);
horline->setFrameShadow(QFrame::Sunken);
// Widgets related to Input
auto dbNameLabel = new QLabel("Database Name:");
auto dbNameLineEdit = new QLineEdit;
auto dbNameLay = new QHBoxLayout;
dbNameLay->addWidget(dbNameLabel);
dbNameLay->addWidget(dbNameLineEdit);
auto collNameLabel = new QLabel("Collection Name:");
auto collNameLineEdit = new QLineEdit;
auto collNameLay = new QHBoxLayout;
collNameLay->addWidget(collNameLabel);
collNameLay->addWidget(collNameLineEdit);
auto selectedCollLay = new QGridLayout;
selectedCollLay->setAlignment(Qt::AlignTop);
selectedCollLay->setColumnStretch(2, 1);
auto serverIcon = new QLabel("<html><img src=':/robomongo/icons/server_16x16.png'></html>");
auto dbIcon = new QLabel("<html><img src=':/robomongo/icons/database_16x16.png'></html>");
auto collIcon = new QLabel("<html><img src=':/robomongo/icons/collection_16x16.png'></html>");
selectedCollLay->addWidget(serverIcon, 1, 0);
selectedCollLay->addWidget(new QLabel("Server: "), 1, 1);
selectedCollLay->addWidget(new QLabel("local_(2)"), 1, 2);
selectedCollLay->addWidget(dbIcon, 2, 0);
selectedCollLay->addWidget(new QLabel("Database: "), 2, 1);
selectedCollLay->addWidget(new QLabel(dbName), 2, 2);
selectedCollLay->addWidget(collIcon, 3, 0);
selectedCollLay->addWidget(new QLabel("Collection: "), 3, 1);
selectedCollLay->addWidget(new QLabel(collName), 3, 2);
// Widgets related to Output
_formatComboBox = new QComboBox;
_formatComboBox->addItem("JSON");
_formatComboBox->addItem("CSV");
VERIFY(connect(_formatComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(on_formatComboBox_change(int))));
_fieldsLabel = new QLabel("Fields:");
_fields = new QLineEdit; // todo: use textedit
// Initially hidden
_fieldsLabel->setHidden(true);
_fields->setHidden(true);
_query = new QLineEdit("{}"); // todo: JSON frame can be used
_outputFileName = new QLineEdit;
_outputDir = new QLineEdit;
_browseButton = new QPushButton("...");
_browseButton->setMaximumWidth(50);
VERIFY(connect(_browseButton, SIGNAL(clicked()), this, SLOT(on_browseButton_clicked())));
// Export summary widgets
_exportOutput = new QTextEdit;
QFontMetrics font(_exportOutput->font());
_exportOutput->setFixedHeight((4+1.5) * (font.lineSpacing())); // 4-line text edit
_exportOutput->setReadOnly(true);
_mongoExportOutput = new QTextEdit;
_mongoExportOutput->setFixedHeight(4 * (font.lineSpacing() + 8)); // 4-line text edit
_mongoExportOutput->setReadOnly(true);
_mongoExportOutput->setHidden(true);
// Attempt to fix issue for Windows High DPI button height is slightly taller than other widgets
#ifdef Q_OS_WIN
_browseButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
#endif
auto outputsInnerLay = new QGridLayout;
outputsInnerLay->addWidget(new QLabel("Format:"), 0, 0);
outputsInnerLay->addWidget(_formatComboBox, 0, 1, 1, 2);
outputsInnerLay->addWidget(_fieldsLabel, 1, 0, Qt::AlignTop);
outputsInnerLay->addWidget(_fields, 1, 1, 1, 2);
outputsInnerLay->addWidget(new QLabel("Query:"), 2, 0);
outputsInnerLay->addWidget(_query, 2, 1, 1, 2);
outputsInnerLay->addWidget(new QLabel("File Name:"), 3, 0);
outputsInnerLay->addWidget(_outputFileName, 3, 1, 1, 2);
outputsInnerLay->addWidget(new QLabel("Directory:"), 4, 0);
outputsInnerLay->addWidget(_outputDir, 4, 1);
outputsInnerLay->addWidget(_browseButton, 4, 2);
//outputsInnerLay->addWidget(new QLabel("Result:"), 5, 0, Qt::AlignTop);
//outputsInnerLay->addWidget(_exportOutput, 6, 0, 1, 3, Qt::AlignTop);
auto manualLayout = new QGridLayout;
auto cmdLabel = new QLabel("Command:");
cmdLabel->setFixedHeight(cmdLabel->sizeHint().height());
_manualExportCmd = new QTextEdit;
QFontMetrics font1(_manualExportCmd->font());
_manualExportCmd->setFixedHeight(2 * (font1.lineSpacing()+8)); // 2-line text edit
manualLayout->addWidget(cmdLabel, 0, 0, Qt::AlignTop);
manualLayout->addWidget(_manualExportCmd, 1, 0, Qt::AlignTop);
// Button box and Manual Mode button
_modeButton = new QPushButton("Manual Mode");
VERIFY(connect(_modeButton, SIGNAL(clicked()), this, SLOT(on_modeButton_clicked())));
_buttonBox = new QDialogButtonBox(this);
_buttonBox->setOrientation(Qt::Horizontal);
_buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
_buttonBox->button(QDialogButtonBox::Save)->setText("E&xport");
_buttonBox->button(QDialogButtonBox::Save)->setMaximumWidth(70);
_buttonBox->button(QDialogButtonBox::Cancel)->setMaximumWidth(70);
VERIFY(connect(_buttonBox, SIGNAL(accepted()), this, SLOT(accept())));
VERIFY(connect(_buttonBox, SIGNAL(rejected()), this, SLOT(reject())));
// Sub layouts
auto serverIndicatorlayout = new QHBoxLayout();
if (!serverName.isEmpty()) {
serverIndicatorlayout->addWidget(serverIndicator, 0, Qt::AlignLeft);
}
// Input layout
_inputsGroupBox = new QGroupBox("Selected Collection");
_inputsGroupBox->setLayout(selectedCollLay);
_inputsGroupBox->setStyleSheet("QGroupBox::title { left: 0px }");
_inputsGroupBox->setFixedHeight(_inputsGroupBox->sizeHint().height());
// Outputs
_autoOutputsGroup = new QGroupBox("Output Properties");
_autoOutputsGroup->setLayout(outputsInnerLay);
_autoOutputsGroup->setStyleSheet("QGroupBox::title { left: 0px }");
_autoOutputsGroup->setFixedHeight(_autoOutputsGroup->sizeHint().height());
// Manual Groupbox
_manualGroupBox = new QGroupBox("Manual Export");
_manualGroupBox->setLayout(manualLayout);
_manualGroupBox->setStyleSheet("QGroupBox::title { left: 0px }");
_manualGroupBox->setHidden(true);
// Export Summary
auto exportSummaryGroup = new QGroupBox("Export Summary");
exportSummaryGroup->setStyleSheet("QGroupBox::title { left: 0px }");
_viewOutputLink = new QLabel(SHOW_DETAILS);
VERIFY(connect(_viewOutputLink, SIGNAL(linkActivated(QString)), this, SLOT(on_viewOutputLink(QString))));
_viewOutputLink->setFixedHeight(_viewOutputLink->sizeHint().height());
auto tempLayout = new QVBoxLayout();
//tempLayout->setSizeConstraint(QLayout::SetFixedSize);
tempLayout->addWidget(_exportOutput, Qt::AlignTop);
tempLayout->addWidget(_viewOutputLink, Qt::AlignLeft | Qt::AlignTop);
//tempLayout->addWidget(_mongoExportOutput);
exportSummaryGroup->setLayout(tempLayout);
exportSummaryGroup->setFixedHeight(exportSummaryGroup->sizeHint().height());
// Buttonbox layout
auto hButtonBoxlayout = new QHBoxLayout();
hButtonBoxlayout->addStretch(1);
hButtonBoxlayout->addWidget(_buttonBox);
hButtonBoxlayout->addWidget(_modeButton);
// Main Layout
auto layout = new QVBoxLayout();
layout->addWidget(_inputsGroupBox, Qt::AlignTop);
//layout->addWidget(horline, Qt::AlignTop);
layout->addWidget(_autoOutputsGroup, Qt::AlignTop);
layout->addWidget(_manualGroupBox);
layout->addWidget(exportSummaryGroup, Qt::AlignTop);
layout->addLayout(hButtonBoxlayout);
setLayout(layout);
// todo: move to a function
// Help user filling inputs automatically
auto date = QDateTime::currentDateTime().toString("dd.MM.yyyy");
auto time = QDateTime::currentDateTime().toString("hh.mm.ss");
auto timeStamp = date + "_" + time;
auto format = _formatComboBox->currentIndex() == 0 ? "json" : "csv";
_outputFileName->setText(dbName + "." + collName + "_" + timeStamp + "." + format);
_outputDir->setText(defaultDir);
_manualExportCmd->setText("mongoexport --db " + dbName + " --collection " + collName +
" --out " + _outputDir->text() + _outputFileName->text());
// todo: setExportArgs()
// First set db and coll
_mongoExportArgs = " --db " + dbName + " --collection " + collName;
_outputFileName->setFocus();
}
void ExportDialog::setOkButtonText(const QString &text)
{
_buttonBox->button(QDialogButtonBox::Save)->setText(text);
}
// todo: remove
void ExportDialog::setInputLabelText(const QString &text)
{
//_inputLabel->setText(text);
}
void ExportDialog::accept()
{
QString mongoExport = "D:\\mongo_export\\bin\\mongoexport.exe";
bool disable = false;
enableDisableWidgets(disable);
if (AUTO == _mode)
{
_exportOutput->clear();
_exportOutput->setText("Exporting...");
// If CSV append output format and fields
if (_formatComboBox->currentIndex() == 1) {
if (_fields->text().isEmpty()) {
QMessageBox::critical(this, "Error", "\"Fields\" option is required in CSV mode.");
return;
}
_mongoExportArgs.append(" --type=csv");
_mongoExportArgs.append(" --fields " + _fields->text().replace(" ", ""));
}
if (!_query->text().isEmpty() && _query->text() != "{}") {
_mongoExportArgs.append(" --query " + _query->text());
}
// Append file path and name
auto absFilePath = _outputDir->text() + _outputFileName->text();
_mongoExportArgs.append(" --out " + absFilePath);
// Start mongoexport non-blocking
_activeProcess->start(mongoExport + _mongoExportArgs);
}
else if (MANUAL == _mode)
{
_exportOutput->clear();
_exportOutput->setText("Exporting...");
// todo: check if _activeProcess->state() is QProcess::NotRunning
// Start mongoexport non-blocking
_activeProcess->start("D:\\mongo_export\\bin\\" + _manualExportCmd->toPlainText());
}
}
void ExportDialog::ui_itemExpanded(QTreeWidgetItem *item)
{
auto categoryItem = dynamic_cast<ExplorerDatabaseCategoryTreeItem *>(item);
if (categoryItem) {
categoryItem->expand();
return;
}
ExplorerServerTreeItem *serverItem = dynamic_cast<ExplorerServerTreeItem *>(item);
if (serverItem) {
serverItem->expand();
return;
}
auto dirItem = dynamic_cast<ExplorerCollectionDirIndexesTreeItem *>(item);
if (dirItem) {
dirItem->expand();
}
}
void ExportDialog::ui_itemDoubleClicked(QTreeWidgetItem *item, int column)
{
// todo
}
void ExportDialog::on_browseButton_clicked()
{
// Select output directory
QString origDir = QFileDialog::getExistingDirectory(this, tr("Select Directory"), defaultDir,
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
auto dir = QDir::toNativeSeparators(origDir);
QApplication::activeModalWidget()->raise();
QApplication::activeModalWidget()->activateWindow();
if (dir.isNull())
return;
_outputDir->setText(dir + "\\");
}
void ExportDialog::on_formatComboBox_change(int index)
{
bool const isCsv = static_cast<bool>(index);
_fieldsLabel->setVisible(isCsv);
_fields->setVisible(isCsv);
// todo: divide ui_itemClicked()
//ui_itemClicked(_treeWidget->currentItem());
}
void ExportDialog::on_modeButton_clicked()
{
_exportOutput->clear();
_mode = (AUTO == _mode ? MANUAL : AUTO);
_modeButton->setText(AUTO == _mode ? "Manual Mode" : "Auto Mode");
_autoOutputsGroup->setVisible(AUTO == _mode);
_manualGroupBox->setVisible(MANUAL == _mode);
setMinimumSize(AUTO == _mode ? AUTO_MODE_SIZE : MANUAL_MODE_SIZE);
_inputsGroupBox->setTitle(AUTO == _mode ? "Selected Collection" : "Selected Server");
adjustSize();
}
void ExportDialog::on_exportFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
bool enable = true;
enableDisableWidgets(enable);
// Extract absolute file path
QString absFilePath;
if (AUTO == _mode)
{
absFilePath = _outputDir->text() + _outputFileName->text();
}
else if (MANUAL == _mode)
{
// extract absolute file path string
QStringList strlist1 = _manualExportCmd->toPlainText().split("--out");
if (strlist1.size() > 1) {
QString str1 = strlist1[1];
QStringList strlist2 = str1.split("--");
if (strlist2.size() > 1) {
absFilePath = strlist2[0];
}
else {
absFilePath = str1;
}
}
}
absFilePath.replace(" ", ""); // todo: handle paths with white spaces
// todo: also check process exit code
// Check exported file exists and mongoexport output does not contain error
QFileInfo const file(absFilePath);
_mongoExportOutputStr = _activeProcess->readAllStandardError(); // Extract mongoexport command output
if (file.exists() && file.isFile() && _mongoExportOutputStr.contains("exported")) {
QStringList splitA = _mongoExportOutputStr.split("exported");
QStringList splitB = splitA[1].split("records");
_exportResult = "Export Successful: \n"
"Exported file: " + absFilePath + "\n"
"Number of records exported:" + splitB[0];
_exportOutput->setText(_exportResult);
}
else {
_exportOutput->setText("Export Failed.\n");
_exportOutput->append("Output:\n" + _mongoExportOutputStr);
}
_exportOutput->moveCursor(QTextCursor::Start);
}
void ExportDialog::on_processErrorOccurred(QProcess::ProcessError error)
{
bool enable = true;
enableDisableWidgets(enable);
if (QProcess::FailedToStart == error) {
_exportOutput->setText("Error: \"mongoexport\" process failed to start. Either the "
"invoked program is missing, or you may have insufficient permissions to invoke the program.");
}
else if (QProcess::Crashed == error) {
_exportOutput->setText("Error: \"mongoexport\" process crashed some time after starting"
" successfully..");
}
else {
_exportOutput->setText("Error: \"mongoexport\" process failed. Error code: "
+ QString::number(error));
}
_exportOutput->moveCursor(QTextCursor::Start);
}
void ExportDialog::on_viewOutputLink(QString)
{
QMessageBox::information(this, "Details", _mongoExportOutputStr);
}
Indicator *ExportDialog::createDatabaseIndicator(const QString &database)
{
return new Indicator(GuiRegistry::instance().databaseIcon(), database);
}
Indicator *ExportDialog::createCollectionIndicator(const QString &collection)
{
return new Indicator(GuiRegistry::instance().collectionIcon(), collection);
}
void ExportDialog::enableDisableWidgets(bool enable) const
{
// Auto mode widgets
//_treeWidget->setEnabled(enable);
_formatComboBox->setEnabled(enable);
_fieldsLabel->setEnabled(enable);
_fields->setEnabled(enable);
_query->setEnabled(enable);
_outputFileName->setEnabled(enable);
_outputDir->setEnabled(enable);
_browseButton->setEnabled(enable);
_buttonBox->button(QDialogButtonBox::Save)->setEnabled(enable);
_modeButton->setEnabled(enable);
// Manual mode widgets
//_treeWidget->setEnabled(enable);
_manualExportCmd->setEnabled(enable);
_buttonBox->button(QDialogButtonBox::Save)->setEnabled(enable);
_modeButton->setEnabled(enable);
}
}
+106
View File
@@ -0,0 +1,106 @@
#pragma once
#include <QDialog>
#include <QProcess>
QT_BEGIN_NAMESPACE
class QLabel;
class QDialogButtonBox;
class QLineEdit;
class QTreeWidgetItem;
class QTreeWidget;
class QComboBox;
class QPushButton;
class QGroupBox;
class QTextEdit;
class QProcess;
QT_END_NAMESPACE
namespace Robomongo
{
class Indicator;
/**
* @brief This class is not finished, it is still under development.
* Currently export is disabled in the GUI. If enabled in GUI, export
* will work with current code. However, the code is unfinished;
* it needs to finished, reviewed and tested.
* Currently, there is support for simultaneous export which can be done
* on multiple export dialogs without blocking each other or main window.
* So user can still work while multiple export dialogs are doing export.
* This achieved by the assumption that there will be usable export executable for
* Robomongo to use. Currently, this location is hard coded as
* "D:\\mongo_export\\bin\\mongoexport.exe". One of the next actions will be
* to design the location of mongoexport which should be cross-platform,
* generic and not hard coded.
*/
class ExportDialog : public QDialog
{
Q_OBJECT
enum MODE
{
AUTO = 0,
MANUAL = 1
};
public:
explicit ExportDialog(QString const& dbName, QString const& collName, QWidget *parent = 0);
void setOkButtonText(const QString &text);
void setInputLabelText(const QString &text);
void setInputText(const QString &text);
enum { maxLenghtName = 60 };
public Q_SLOTS:
virtual void accept();
private Q_SLOTS:
void ui_itemExpanded(QTreeWidgetItem *item);
void ui_itemDoubleClicked(QTreeWidgetItem *item, int column);
void on_browseButton_clicked();
void on_formatComboBox_change(int index);
void on_modeButton_clicked();
void on_exportFinished(int exitCode, QProcess::ExitStatus exitStatus);
void on_processErrorOccurred(QProcess::ProcessError);
void on_viewOutputLink(QString);
private:
// todo: remove if unused
Indicator *createDatabaseIndicator(const QString &database);
Indicator *createCollectionIndicator(const QString &collection);
// Enable/Disable widgets during/after export operation
// @param enable: true to enable, false to disable widgets
void enableDisableWidgets(bool enable) const;
// Auto Mode
QGroupBox* _inputsGroupBox;
QComboBox* _formatComboBox;
QLabel* _fieldsLabel;
QLineEdit* _fields;
QLineEdit* _query;
QLineEdit* _outputFileName;
QLineEdit* _outputDir;
QPushButton* _browseButton;
QGroupBox* _autoOutputsGroup;
QTextEdit* _exportOutput;
QLabel* _viewOutputLink;
//
QTextEdit* _mongoExportOutput;
// Manual Mode
QTextEdit* _manualExportCmd;
QPushButton* _modeButton;
QGroupBox* _manualGroupBox;
// Common
QDialogButtonBox* _buttonBox;
MODE _mode;
QString _dbName;
QString _collName;
QString _mongoExportArgs;
QString _exportResult;
QString _mongoExportOutputStr;
QProcess* _activeProcess; // pointer to running/finished process
};
}
+3 -3
View File
@@ -152,9 +152,9 @@ namespace Robomongo
#endif
// Attempt to fix the issue for Windows High DPI button height is slightly taller than other widgets
#ifdef Q_OS_WIN
_passwordEchoModeButton->setMaximumHeight(HighDpiContants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_passphraseEchoModeButton->setMaximumHeight(HighDpiContants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_selectPrivateFileButton->setMaximumHeight(HighDpiContants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_passwordEchoModeButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_passphraseEchoModeButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_selectPrivateFileButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
#endif
}
+4 -4
View File
@@ -189,10 +189,10 @@ namespace Robomongo
// Attempt to fix issue for Windows High DPI button height is slightly taller than other widgets
#ifdef Q_OS_WIN
_caFileBrowseButton->setMaximumHeight(HighDpiContants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_pemFileBrowseButton->setMaximumHeight(HighDpiContants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_pemPassShowButton->setMaximumHeight(HighDpiContants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_crlFileBrowseButton->setMaximumHeight(HighDpiContants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_caFileBrowseButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_pemFileBrowseButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_pemPassShowButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
_crlFileBrowseButton->setMaximumHeight(HighDpiConstants::WIN_HIGH_DPI_BUTTON_HEIGHT);
#endif
}
+17
View File
@@ -20,18 +20,27 @@
<file>scripts/esprima.js</file>
<file>scripts/uuidhelpers.js</file>
<file>icons/maximize.gif</file>
<file>icons/undock.png</file>
<file>icons/dock.png</file>
<file>icons/maximize.png</file>
<file>icons/minimize.png</file>
<file>icons/text_16x16.png</file>
<file>icons/tree_16x16.png</file>
<file>icons/rotate_16x16.png</file>
<file>icons/close_16x16.png</file>
<file>icons/close_hover_16x16.png</file>
<file>icons/close_2_16x16.png</file>
<file>icons/close_hover_16x16_original.png</file>
<file>icons/text2_16x16.png</file>
<file>icons/mongodb_16x16.png</file>
<file>icons/mongodb_icon_for_MAC.png</file>
<file>icons/connect_24x24.png</file>
<file>icons/server_16x16.png</file>
<file>icons/server_primary_16x16.png</file>
<file>icons/server_secondary_16x16.png</file>
<file>icons/server_imported_16x16.png</file>
<file>icons/replica_set_16x16.png</file>
<file>icons/replica_set_offline_16x16.png</file>
<file>icons/execute_24x24.png</file>
<file>icons/main_window_icon_old.png</file>
<file>icons/logo-256x256.png</file>
@@ -62,5 +71,13 @@
<file>icons/index_16x16.png</file>
<file>icons/hide_64x64.png</file>
<file>icons/show_64x64.png</file>
<file>icons/export_64x64.png</file>
<file>icons/import_64x64.png</file>
<file>icons/plus_sign.png</file>
<file>icons/minus_sign.png</file>
<file>icons/delete.png</file>
<file>icons/delete_red_color.png</file>
<file>icons/delete_mouse_hovered.png</file>
<file>icons/welcome_tab_icon.png</file>
</qresource>
</RCC>
Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 446 B

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