1
1
Pārlūkot izejas kodu

Resolve "improve update performance"

Eric Tröbs 4 gadi atpakaļ
vecāks
revīzija
a439a2a81a

+ 13 - 3
pycs/frontend/WebServer.py

@@ -52,7 +52,10 @@ class WebServer:
         # define events
         # define events
         @self.__sio.event
         @self.__sio.event
         def connect(id, msg):
         def connect(id, msg):
-            self.__sio.emit('app_status', self.__status, to=id)
+            self.__sio.emit('app_status', {
+                'keys': [],
+                'value': self.__status
+            }, to=id)
 
 
         @self.__flask.route('/settings', methods=['POST'])
         @self.__flask.route('/settings', methods=['POST'])
         def edit_settings():
         def edit_settings():
@@ -281,6 +284,13 @@ class WebServer:
 
 
         eventlet.wsgi.server(eventlet.listen((host, port)), self.__app)
         eventlet.wsgi.server(eventlet.listen((host, port)), self.__app)
 
 
-    def __update_application_status(self, status):
+    def __update_application_status(self, status, keys):
+        value = status
+        for key in keys[:-1]:
+            value = value[key]
+
         self.__status = status
         self.__status = status
-        self.__sio.emit('app_status', status)
+        self.__sio.emit('app_status', {
+            'keys': keys[:-1],
+            'value': value
+        })

+ 19 - 11
pycs/observable/Observable.py

@@ -1,33 +1,41 @@
 class Observable:
 class Observable:
     @staticmethod
     @staticmethod
-    def create(source, parent=None):
+    def create(source, parent=None, key=None):
         from . import ObservableDict
         from . import ObservableDict
         from . import ObservableList
         from . import ObservableList
 
 
-        if isinstance(source, ObservableDict):
-            return source
-        if isinstance(source, ObservableList):
+        if isinstance(source, ObservableDict) or isinstance(source, ObservableList):
+            source.parent = parent
+            source.key = key
             return source
             return source
         if isinstance(source, dict):
         if isinstance(source, dict):
-            return ObservableDict(source, parent)
+            return ObservableDict(source, parent, key)
         if isinstance(source, list):
         if isinstance(source, list):
-            return ObservableList(source, parent)
+            return ObservableList(source, parent, key)
         else:
         else:
             return source
             return source
 
 
-    def __init__(self, parent):
+    def __init__(self, parent, key):
         self.parent = parent
         self.parent = parent
+        self.key = key
         self.subscriptions = []
         self.subscriptions = []
 
 
     def subscribe(self, handler, immediate=False):
     def subscribe(self, handler, immediate=False):
         self.subscriptions.append(handler)
         self.subscriptions.append(handler)
 
 
         if immediate:
         if immediate:
-            handler(self)
+            handler(self, [])
+
+    def notify(self, keys=None):
+        if keys is None:
+            keys = []
 
 
-    def notify(self):
         for s in self.subscriptions:
         for s in self.subscriptions:
-            s(self)
+            s(self, keys)
 
 
         if self.parent is not None:
         if self.parent is not None:
-            self.parent.notify()
+            if self.key is None:
+                raise ValueError
+
+            keys.insert(0, self.key)
+            self.parent.notify(keys)

+ 6 - 6
pycs/observable/ObservableDict.py

@@ -2,20 +2,20 @@ from . import Observable
 
 
 
 
 class ObservableDict(dict, Observable):
 class ObservableDict(dict, Observable):
-    def __init__(self, obj: dict, parent: Observable = None):
+    def __init__(self, obj: dict, parent: Observable = None, key: str = None):
         dict.__init__(self)
         dict.__init__(self)
-        Observable.__init__(self, parent)
+        Observable.__init__(self, parent, key)
 
 
         for key in obj.keys():
         for key in obj.keys():
-            dict.__setitem__(self, key, Observable.create(obj[key], self))
+            dict.__setitem__(self, key, Observable.create(obj[key], self, key))
 
 
     def __setitem__(self, key, value):
     def __setitem__(self, key, value):
-        dict.__setitem__(self, key, Observable.create(value, self))
-        Observable.notify(self)
+        dict.__setitem__(self, key, Observable.create(value, self, key))
+        Observable.notify(self, [key])
 
 
     def __delitem__(self, key):
     def __delitem__(self, key):
         super().__delitem__(key)
         super().__delitem__(key)
-        Observable.notify(self)
+        Observable.notify(self, [key])
 
 
     def copy(self):
     def copy(self):
         result = {}
         result = {}

+ 9 - 8
pycs/observable/ObservableList.py

@@ -2,29 +2,30 @@ from . import Observable
 
 
 
 
 class ObservableList(list, Observable):
 class ObservableList(list, Observable):
-    def __init__(self, lst: list, parent: Observable = None):
+    def __init__(self, lst: list, parent: Observable = None, key: int = None):
         list.__init__(self)
         list.__init__(self)
-        Observable.__init__(self, parent)
+        Observable.__init__(self, parent, key)
 
 
         for element in lst:
         for element in lst:
-            list.append(self, Observable.create(element, self))
+            list.append(self, Observable.create(element, self, key))
 
 
     def __getitem__(self, value):
     def __getitem__(self, value):
         return super().__getitem__(int(value))
         return super().__getitem__(int(value))
 
 
     def __setitem__(self, key, value):
     def __setitem__(self, key, value):
-        super().__setitem__(key, Observable.create(value, self))
-        Observable.notify(self)
+        super().__setitem__(key, Observable.create(value, self, key))
+        Observable.notify(self, [key])
 
 
     def __delitem__(self, key):
     def __delitem__(self, key):
         super().__delitem__(key)
         super().__delitem__(key)
-        Observable.notify(self)
+        Observable.notify(self, [key])
 
 
     def append(self, value):
     def append(self, value):
-        obs = Observable.create(value, self)
+        key = len(self)
+        obs = Observable.create(value, self, key)
 
 
         super().append(obs)
         super().append(obs)
-        Observable.notify(self)
+        Observable.notify(self, [key])
 
 
         return obs
         return obs
 
 

+ 5 - 4
pycs/projects/MediaFile.py

@@ -7,14 +7,15 @@ from pycs.observable import ObservableDict
 
 
 
 
 class MediaFile(ObservableDict):
 class MediaFile(ObservableDict):
-    def __init__(self, obj, parent):
+    def __init__(self, obj, project_id):
         if 'predictionResults' not in obj.keys():
         if 'predictionResults' not in obj.keys():
             obj['predictionResults'] = {}
             obj['predictionResults'] = {}
 
 
-        super().__init__(obj, parent)
+        self.project_id = project_id
+        super().__init__(obj)
 
 
     def __get_file(self, identifier):
     def __get_file(self, identifier):
-        file_directory = path.join(getcwd(), 'projects', self.parent['id'], 'data')
+        file_directory = path.join(getcwd(), 'projects', self.project_id, 'data')
         file_name = identifier + self['extension']
         file_name = identifier + self['extension']
 
 
         return file_directory, file_name
         return file_directory, file_name
@@ -58,7 +59,7 @@ class MediaFile(ObservableDict):
 
 
     def resize(self, maximum_width):
     def resize(self, maximum_width):
         # check if resized file already exists
         # check if resized file already exists
-        resized = MediaFile(self, self.parent)
+        resized = MediaFile(self, self.project_id)
         resized['id'] = self['id'] + '-' + maximum_width
         resized['id'] = self['id'] + '-' + maximum_width
 
 
         target_directory, target_name = self.__get_file(resized['id'])
         target_directory, target_name = self.__get_file(resized['id'])

+ 6 - 6
pycs/projects/Project.py

@@ -14,10 +14,6 @@ class Project(ObservableDict):
             if key not in obj.keys():
             if key not in obj.keys():
                 obj[key] = {}
                 obj[key] = {}
 
 
-        # save data as MediaFile objects
-        for key in obj['data'].keys():
-            obj['data'][key] = MediaFile(obj['data'][key], self)
-
         # load model data
         # load model data
         folder = path.join('projects', obj['id'], 'model')
         folder = path.join('projects', obj['id'], 'model')
         with open(path.join(folder, 'distribution.json'), 'r') as file:
         with open(path.join(folder, 'distribution.json'), 'r') as file:
@@ -26,11 +22,15 @@ class Project(ObservableDict):
 
 
             obj['model'] = model
             obj['model'] = model
 
 
+        # save data as MediaFile objects
+        for key in obj['data'].keys():
+            obj['data'][key] = MediaFile(obj['data'][key], obj['id'])
+
         # initialize super
         # initialize super
         super().__init__(obj, parent)
         super().__init__(obj, parent)
 
 
         # subscribe to changes to write to disk afterwards
         # subscribe to changes to write to disk afterwards
-        self.subscribe(lambda d: self.parent.write_project(self['id']))
+        self.subscribe(lambda d, k: self.parent.write_project(self['id']))
 
 
     def update_properties(self, update):
     def update_properties(self, update):
         set_recursive(update, self)
         set_recursive(update, self)
@@ -39,7 +39,7 @@ class Project(ObservableDict):
         return path.join('projects', self['id'], 'data'), str(uuid1())
         return path.join('projects', self['id'], 'data'), str(uuid1())
 
 
     def add_media_file(self, file):
     def add_media_file(self, file):
-        file = MediaFile(file, self)
+        file = MediaFile(file, self['id'])
         self['data'][file['id']] = file
         self['data'][file['id']] = file
 
 
     def remove_media_file(self, file_id):
     def remove_media_file(self, file_id):

+ 4 - 4
pycs/projects/ProjectManager.py

@@ -36,11 +36,11 @@ class ProjectManager(ObservableDict):
                 self[project['id']] = project
                 self[project['id']] = project
 
 
     def write_project(self, uuid):
     def write_project(self, uuid):
-        with open(path.join('projects', uuid, 'project.json'), 'w') as file:
-            copy = self[uuid].copy()
-            del copy['jobs']
-            del copy['model']
+        copy = self[uuid].copy()
+        del copy['jobs']
+        del copy['model']
 
 
+        with open(path.join('projects', uuid, 'project.json'), 'w') as file:
             dump(copy, file, indent=4)
             dump(copy, file, indent=4)
 
 
     def create_project(self, name, description, model):
     def create_project(self, name, description, model):

+ 3 - 3
test/test_observable.py

@@ -81,7 +81,7 @@ class TestObservable(unittest.TestCase):
 
 
         # root
         # root
         counter1 = Wrapper()
         counter1 = Wrapper()
-        obs['dir1'].subscribe(lambda x: counter1.inc())
+        obs['dir1'].subscribe(lambda v, k: counter1.inc())
 
 
         obs['dir1']['file11'] = 12
         obs['dir1']['file11'] = 12
         self.assertEqual(1, counter1.value)
         self.assertEqual(1, counter1.value)
@@ -91,7 +91,7 @@ class TestObservable(unittest.TestCase):
 
 
         # dict
         # dict
         counter2 = Wrapper()
         counter2 = Wrapper()
-        obs['dir1'].subscribe(lambda x: counter2.inc())
+        obs['dir1'].subscribe(lambda v, k: counter2.inc())
 
 
         obs['dir1']['file11'] = 12
         obs['dir1']['file11'] = 12
         self.assertEqual(1, counter2.value)
         self.assertEqual(1, counter2.value)
@@ -101,7 +101,7 @@ class TestObservable(unittest.TestCase):
 
 
         # list
         # list
         counter3 = Wrapper()
         counter3 = Wrapper()
-        obs['dir1']['file12'].subscribe(lambda x: counter3.inc())
+        obs['dir1']['file12'].subscribe(lambda v, k: counter3.inc())
 
 
         obs['dir1']['file12'][0] = 'three'
         obs['dir1']['file12'][0] = 'three'
         self.assertEqual(1, counter3.value)
         self.assertEqual(1, counter3.value)

+ 27 - 15
webui/src/App.vue

@@ -76,7 +76,7 @@ export default {
     TopNavigationBar,
     TopNavigationBar,
     ProjectOpenWindow
     ProjectOpenWindow
   },
   },
-  data: function() {
+  data: function () {
     return {
     return {
       connected: false,
       connected: false,
       socket: {
       socket: {
@@ -84,7 +84,7 @@ export default {
         // initialize socket.io connection
         // initialize socket.io connection
         io: io(window.location.protocol + '//' + window.location.hostname + ':5000'),
         io: io(window.location.protocol + '//' + window.location.hostname + ':5000'),
         // http methods
         // http methods
-        post: function(name, value) {
+        post: function (name, value) {
           if (!name.startsWith('http'))
           if (!name.startsWith('http'))
             name = this.baseurl + name;
             name = this.baseurl + name;
 
 
@@ -93,7 +93,7 @@ export default {
             body: JSON.stringify(value)
             body: JSON.stringify(value)
           });
           });
         },
         },
-        upload: function(name, file) {
+        upload: function (name, file) {
           const form = new FormData();
           const form = new FormData();
           form.append('file', file);
           form.append('file', file);
 
 
@@ -102,7 +102,7 @@ export default {
             body: form
             body: form
           });
           });
         },
         },
-        media: function(projectId, fileId) {
+        media: function (projectId, fileId) {
           return this.baseurl + '/projects/' + projectId + '/data/' + fileId;
           return this.baseurl + '/projects/' + projectId + '/data/' + fileId;
         }
         }
       },
       },
@@ -116,13 +116,13 @@ export default {
     }
     }
   },
   },
   computed: {
   computed: {
-    projects: function() {
+    projects: function () {
       if (this.status == null)
       if (this.status == null)
         return [];
         return [];
 
 
       return Object.keys(this.status.projects).map(key => this.status.projects[key]);
       return Object.keys(this.status.projects).map(key => this.status.projects[key]);
     },
     },
-    currentProject: function() {
+    currentProject: function () {
       for (let i = 0; i < this.projects.length; i++)
       for (let i = 0; i < this.projects.length; i++)
         if (this.projects[i].id === this.window.project)
         if (this.projects[i].id === this.window.project)
           return this.projects[i];
           return this.projects[i];
@@ -131,25 +131,37 @@ export default {
     }
     }
   },
   },
   methods: {
   methods: {
-    resize: function() {
+    resize: function () {
       this.window.wide = (document.body.offsetWidth > 1024);
       this.window.wide = (document.body.offsetWidth > 1024);
     },
     },
-    show: function(value) {
+    show: function (value) {
       this.window.content = value;
       this.window.content = value;
     },
     },
-    openProject: function(project) {
+    openProject: function (project) {
       this.window.project = project.id;
       this.window.project = project.id;
       this.show('settings');
       this.show('settings');
     },
     },
-    closeProject: function() {
+    closeProject: function () {
       this.window.project = null;
       this.window.project = null;
       this.show('projects');
       this.show('projects');
     }
     }
   },
   },
-  created: function() {
-    this.socket.io.on('app_status', status => {
-      this.status = status;
-      this.connected = true;
+  created: function () {
+    this.socket.io.on('app_status', data => {
+      if (data.keys.length === 0) {
+        this.status = data.value;
+        this.connected = true;
+        return;
+      }
+
+      const last = data.keys.pop();
+      let target = this.status;
+
+      for (let key of data.keys) {
+        target = target[key];
+      }
+
+      target[last] = data.value;
     });
     });
 
 
     setInterval(() => this.connected = this.socket.io.connected, 2000);
     setInterval(() => this.connected = this.socket.io.connected, 2000);
@@ -157,7 +169,7 @@ export default {
     window.addEventListener('resize', this.resize);
     window.addEventListener('resize', this.resize);
     this.resize();
     this.resize();
   },
   },
-  destroyed: function() {
+  destroyed: function () {
     window.removeEventListener('resize', this.resize);
     window.removeEventListener('resize', this.resize);
   }
   }
 }
 }