Skip to content

Commit 30765c1

Browse files
authored
Redesign RideEventHandler and file system monitoring feature (#2232)
* Added RideFSWatcherHandler for new RideEventHandler implementation * Removed old RideEventHandler solution. * Windows canot detect root folder rename, fix with workaround * Using EVT_ACTIVATE_APP instead of EVT_ACTIVATE * Add protection when sub folder is removed * Optimize event handle logic * Update FS event name * Check project data file status with higher priority * Add additional check for TestDataDirectoryController obj * Add additional check for TestDataDirectoryController obj * Adjust for MSW platform * Adjust for MSW platform * Fix typos
1 parent 715eb3f commit 30765c1

File tree

9 files changed

+167
-110
lines changed

9 files changed

+167
-110
lines changed

src/robotide/application/application.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@
3535
from ..application.updatenotifier import UpdateNotifierController, UpdateDialog
3636
from ..ui.treeplugin import TreePlugin
3737
from ..ui.fileexplorerplugin import FileExplorerPlugin
38-
from .. import utils
38+
from ..utils import RideFSWatcherHandler, run_python_command
3939

4040

4141
class RIDE(wx.App):
4242

4343
def __init__(self, path=None, updatecheck=True):
44-
self._initial_path = path
4544
self._updatecheck = updatecheck
45+
self.workspace_path = path
4646
context.APP = self
4747
wx.App.__init__(self, redirect=False)
4848

@@ -86,6 +86,7 @@ def OnInit(self):
8686
UpdateNotifierController(self.settings).notify_update_if_needed(UpdateDialog)
8787
wx.CallLater(200, ReleaseNotes(self).bring_to_front)
8888
wx.CallLater(200, self.fileexplorerplugin._update_tree)
89+
self.Bind(wx.EVT_ACTIVATE_APP, self.OnAppActivate)
8990
return True
9091

9192
def _publish_system_info(self):
@@ -111,13 +112,13 @@ def _get_editor(self):
111112
return maybe_editor
112113

113114
def _load_data(self):
114-
path = self._initial_path or self._get_latest_path()
115-
if path:
115+
self.workspace_path = self.workspace_path or self._get_latest_path()
116+
if self.workspace_path:
116117
observer = LoadProgressObserver(self.frame)
117-
self._controller.load_data(path, observer)
118+
self._controller.load_data(self.workspace_path, observer)
118119

119120
def _find_robot_installation(self):
120-
output = utils.run_python_command(
121+
output = run_python_command(
121122
['import robot; print(robot.__file__ + \", \" + robot.__version__)'])
122123
robot_found = b"ModuleNotFoundError" not in output and output
123124
if robot_found:
@@ -178,3 +179,18 @@ def active_event_loop(self):
178179
wx.EventLoop.SetActive(loop)
179180
yield
180181
del loop
182+
183+
def OnEventLoopEnter(self, loop):
184+
if loop and wx.EventLoopBase.IsMain(loop):
185+
RideFSWatcherHandler.create_fs_watcher(self.workspace_path)
186+
187+
def OnAppActivate(self, event):
188+
if RideFSWatcherHandler.is_watcher_created():
189+
if event.GetActive():
190+
if self._controller.is_project_changed_from_disk() or \
191+
RideFSWatcherHandler.is_workspace_dirty():
192+
self.frame.show_confirm_reload_dlg(event)
193+
RideFSWatcherHandler.stop_listening()
194+
else:
195+
RideFSWatcherHandler.start_listening(self.workspace_path)
196+
event.Skip()

src/robotide/controller/project.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
from .basecontroller import WithNamespace, _BaseController
2525
from .dataloader import DataLoader
26-
from .filecontrollers import DataController, ResourceFileControllerFactory
26+
from .filecontrollers import DataController, ResourceFileControllerFactory, TestDataDirectoryController
2727
from .robotdata import NewTestCaseFile, NewTestDataDirectory
2828
from robotide.spec.librarydatabase import DATABASE_FILE
2929
from robotide.spec.librarymanager import LibraryManager
@@ -295,6 +295,17 @@ def resource_import_modified(self, path, directory):
295295
if resource:
296296
return self._create_resource_controller(resource)
297297

298+
def is_project_changed_from_disk(self):
299+
for data_file in self.datafiles:
300+
if isinstance(data_file, TestDataDirectoryController):
301+
if not os.path.exists(data_file.directory):
302+
return True
303+
else:
304+
if data_file.has_been_modified_on_disk() or \
305+
data_file.has_been_removed_from_disk():
306+
return True
307+
return False
308+
298309

299310
class Serializer(object):
300311

src/robotide/editor/kweditor.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
RideSettingsChanged, PUBLISHER, RideBeforeSaving)
3131
from robotide.usages.UsageRunner import Usages, VariableUsages
3232
from robotide.ui.progress import RenameProgressObserver
33-
from robotide import robotapi, utils
34-
from robotide.utils import RideEventHandler, variablematcher
33+
from robotide import robotapi
34+
from robotide.utils import variablematcher
3535
from robotide.widgets import Dialog, PopupMenu, PopupMenuItems
3636

3737
from .gridbase import GridEditor
@@ -40,10 +40,7 @@
4040
ListVariableDialog
4141
from .contentassist import ExpandingContentAssistTextCtrl
4242
from .gridcolorizer import Colorizer
43-
from robotide.lib.robot.utils.compat import with_metaclass
4443

45-
# Metaclass fix from http://code.activestate.com/recipes/204197-solving-the-metaclass-conflict/
46-
from robotide.utils.noconflict import classmaker
4744

4845
_DEFAULT_FONT_SIZE = 11
4946

@@ -62,7 +59,7 @@ def decorated_function(self, *args):
6259
return decorated_function
6360

6461

65-
class KeywordEditor(with_metaclass(classmaker(), GridEditor, RideEventHandler)):
62+
class KeywordEditor(GridEditor):
6663
_no_cell = (-1, -1)
6764
_popup_menu_shown = False
6865
dirty = property(lambda self: self._controller.dirty)

src/robotide/editor/listeditor.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,10 @@
1414
# limitations under the License.
1515

1616
import wx
17-
from robotide.lib.robot.utils.compat import with_metaclass
1817
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
1918
from robotide.controller.ctrlcommands import MoveUp, MoveDown, DeleteItem
20-
from robotide.utils import RideEventHandler
2119
from robotide.widgets import PopupMenu, PopupMenuItems, ButtonWithHandler, Font
2220
from robotide.context import ctrl_or_cmd, bind_keys_to_evt_menu, IS_WINDOWS
23-
# Metaclass fix from http://code.activestate.com/recipes/204197-solving-the-metaclass-conflict/
24-
from robotide.utils.noconflict import classmaker
2521

2622

2723
class ListEditorBase(wx.Panel):
@@ -143,7 +139,7 @@ def has_error(self, controller):
143139
return False
144140

145141

146-
class ListEditor(with_metaclass(classmaker(), ListEditorBase, RideEventHandler)):
142+
class ListEditor(ListEditorBase):
147143
pass
148144

149145

src/robotide/editor/settingeditors.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from robotide.publish import PUBLISHER
2828
from robotide import utils
2929
from robotide.utils.highlightmatcher import highlight_matcher
30-
from robotide.lib.robot.utils.compat import with_metaclass
3130
from .formatters import ListToStringFormatter
3231
from .gridcolorizer import ColorizationSettings
3332
from .editordialogs import EditorDialog, DocumentationDialog, MetadataDialog,\
@@ -37,11 +36,8 @@
3736
from .popupwindow import HtmlPopupWindow
3837
from .tags import TagsDisplay
3938

40-
# Metaclass fix from http://code.activestate.com/recipes/204197-solving-the-metaclass-conflict/
41-
from robotide.utils.noconflict import classmaker
4239

43-
44-
class SettingEditor(with_metaclass(classmaker(), wx.Panel, utils.RideEventHandler)):
40+
class SettingEditor(wx.Panel):
4541

4642
def __init__(self, parent, controller, plugin, tree):
4743
wx.Panel.__init__(self, parent)

src/robotide/ui/mainframe.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717
import wx
1818
import wx.lib.agw.aui as aui
1919
from wx import Icon
20-
from robotide.lib.robot.utils.compat import with_metaclass
2120
from robotide.action import ActionInfoCollection, ActionFactory, SeparatorInfo
2221
from robotide.context import ABOUT_RIDE, SHORTCUT_KEYS
2322
from robotide.controller.ctrlcommands import SaveFile, SaveAll
2423
from robotide.publish import RideSaveAll, RideClosing, RideSaved, PUBLISHER,\
2524
RideInputValidationError, RideTreeSelection, RideModificationPrevented, RideBeforeSaving
2625
from robotide.ui.tagdialogs import ViewAllTagsDialog
2726
from robotide.ui.filedialogs import RobotFilePathDialog
28-
from robotide.utils import RideEventHandler
27+
from robotide.utils import RideFSWatcherHandler
2928
from robotide.widgets import Dialog, ImageProvider, HtmlWindow
3029
from robotide.preferences import PreferenceEditor
3130

@@ -71,9 +70,6 @@
7170
ID_CustomizeToolbar = wx.ID_HIGHEST + 1
7271
ID_SampleItem = ID_CustomizeToolbar + 1
7372

74-
# Metaclass fix from http://code.activestate.com/recipes/
75-
# 204197-solving-the-metaclass-conflict/
76-
from robotide.utils.noconflict import classmaker
7773

7874
### DEBUG some testing
7975
# -- SizeReportCtrl --
@@ -139,7 +135,7 @@ def OnSize(self, event):
139135
self.Refresh()
140136

141137

142-
class RideFrame(with_metaclass(classmaker(), wx.Frame, RideEventHandler)):
138+
class RideFrame(wx.Frame):
143139

144140
def __init__(self, application, controller):
145141
size = application.settings.get('mainframe size', (1100, 700))
@@ -452,6 +448,9 @@ def check_unsaved_modifications(self):
452448

453449
def open_suite(self, path):
454450
self._controller.update_default_dir(path)
451+
# self._controller.default_dir will only save dir path
452+
# need to save path to self._application.workspace_path too
453+
self._application.workspace_path = path
455454
try:
456455
err = self._controller.load_datafile(path, LoadProgressObserver(self))
457456
finally:
@@ -593,6 +592,37 @@ def CreateSizeReportCtrl(self, width=80, height=80):
593592
ctrl = SizeReportCtrl(self, -1, wx.DefaultPosition, wx.Size(width, height), self._mgr)
594593
return ctrl
595594

595+
def show_confirm_reload_dlg(self, event):
596+
msg = ['Workspace modifications detected on the file system.',
597+
'Do you want to reload the workspace?',
598+
'Answering <No> will overwrite the changes on disk.']
599+
if self._controller.is_dirty():
600+
msg.insert(2, 'Answering <Yes> will discard unsaved changes.')
601+
ret = wx.MessageBox('\n'.join(msg), 'Files Changed On Disk',
602+
style=wx.YES_NO | wx.ICON_WARNING)
603+
confirmed = ret == wx.YES
604+
if confirmed:
605+
# workspace_path should update after open directory/suite
606+
# There're two scenarios:
607+
# 1. path is a directory
608+
# 2. path is a suite file
609+
new_path = RideFSWatcherHandler.get_workspace_new_path()
610+
if new_path and os.path.exists(new_path):
611+
wx.CallAfter(self.open_suite, new_path)
612+
else:
613+
# in case workspace is totally removed
614+
# ask user to open new directory
615+
# TODO add some notification msg to users
616+
wx.CallAfter(self.OnOpenDirectory, event)
617+
else:
618+
for _ in self._controller.datafiles:
619+
if _.has_been_modified_on_disk() or _.has_been_removed_from_disk():
620+
if not os.path.exists(_.directory):
621+
# sub folder is removed, create new one before saving
622+
os.makedirs(_.directory)
623+
_.mark_dirty()
624+
self.save_all()
625+
596626

597627
# Code moved from actiontriggers
598628
class ToolBar(aui.AuiToolBar):

src/robotide/ui/treeplugin.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@
2525

2626
TREETEXTCOLOUR = Colour(0xA9, 0xA9, 0xA9)
2727

28-
from robotide.lib.robot.utils.compat import with_metaclass
2928
from robotide.controller.ui.treecontroller import TreeController, \
3029
TestSelectionController
3130
from robotide.context import IS_WINDOWS
32-
from robotide.action.actioninfo import ActionInfo
3331
from robotide.controller.filecontrollers import ResourceFileController, TestDataDirectoryController, \
3432
TestCaseFileController
3533
from robotide.publish.messages import RideTestRunning, RideTestPaused, \
@@ -51,8 +49,7 @@
5149
from robotide import utils
5250
from .treenodehandlers import ResourceRootHandler, action_handler_class, ResourceFileHandler
5351
from .images import TreeImageList
54-
# Metaclass fix from http://code.activestate.com/recipes/204197-solving-the-metaclass-conflict/
55-
from robotide.utils.noconflict import classmaker
52+
5653

5754
_TREE_ARGS = {'style': wx.TR_DEFAULT_STYLE}
5855
_TREE_ARGS['agwStyle'] = customtreectrl.TR_DEFAULT_STYLE | customtreectrl.TR_HIDE_ROOT | \
@@ -160,9 +157,7 @@ def _update_tree(self, event=None):
160157
self._tree._refresh_view()
161158

162159

163-
class Tree(with_metaclass(classmaker(), treemixin.DragAndDrop,
164-
customtreectrl.CustomTreeCtrl,
165-
utils.RideEventHandler)):
160+
class Tree(treemixin.DragAndDrop, customtreectrl.CustomTreeCtrl):
166161
_RESOURCES_NODE_LABEL = 'External Resources'
167162

168163
def __init__(self, parent, action_registerer, settings=None):

src/robotide/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
unic, asserts, unescape, html_escape, attribute_escape, robottime,\
2626
get_timestamp, Matcher, is_list_like, is_dict_like, system_decode,\
2727
ArgumentParser, get_error_details, is_unicode, is_string, py2to3
28-
from .eventhandler import RideEventHandler
28+
from .eventhandler import RideFSWatcherHandler
2929
from .printing import Printing
3030

3131

0 commit comments

Comments
 (0)