]> www.vanbest.org Git - motioneye-debian/commitdiff
media browser: it is now possible to remove an entire group of media
authorCalin Crisan <ccrisan@gmail.com>
Sat, 27 Dec 2014 14:13:12 +0000 (16:13 +0200)
committerCalin Crisan <ccrisan@gmail.com>
Sat, 27 Dec 2014 14:13:12 +0000 (16:13 +0200)
files

src/handlers.py
src/mediafiles.py
src/remote.py
src/server.py
static/css/main.css
static/js/main.js

index b9380481f4a618780ae43040cd1e2dfc60b73345..3396bfefa93c47c7ca485484ed7564fd5ffb09b5 100644 (file)
@@ -683,6 +683,9 @@ class PictureHandler(BaseHandler):
         if op == 'delete':
             self.delete(camera_id, filename)
 
+        elif op == 'delete_all':
+            self.delete_all(camera_id, group)
+        
         else:
             raise HTTPError(400, 'unknown operation')
     
@@ -843,7 +846,7 @@ class PictureHandler(BaseHandler):
                     width=self.get_argument('width', None),
                     height=self.get_argument('height', None))
     
-    @BaseHandler.auth()
+    @BaseHandler.auth(admin=True)
     def delete(self, camera_id, filename):
         logging.debug('deleting picture %(filename)s of camera %(id)s' % {
                 'filename': filename, 'id': camera_id})
@@ -980,6 +983,30 @@ class PictureHandler(BaseHandler):
     
                 remote.get_timelapse_movie(camera_config, framerate, interval, callback=on_response, group=group)
 
+    @BaseHandler.auth(admin=True)
+    def delete_all(self, camera_id, group):
+        logging.debug('deleting picture group %(group)s of camera %(id)s' % {
+                'group': group, 'id': camera_id})
+
+        camera_config = config.get_camera(camera_id)
+        if utils.local_camera(camera_config):
+            try:
+                mediafiles.del_media_group(camera_config, group, 'picture')
+                self.finish_json()
+                
+            except Exception as e:
+                self.finish_json({'error': unicode(e)})
+
+        else: # remote camera
+            def on_response(response=None, error=None):
+                if error:
+                    return self.finish_json({'error': 'Failed to delete picture group from %(url)s: %(msg)s.' % {
+                            'url': remote.make_camera_url(camera_config), 'msg': error}})
+
+                self.finish_json()
+
+            remote.del_media_group(camera_config, on_response, group=group, media_type='picture')
+
     def try_finish(self, content):
         try:
             self.finish(content)
@@ -1009,7 +1036,7 @@ class MovieHandler(BaseHandler):
             raise HTTPError(400, 'unknown operation')
     
     @asynchronous
-    def post(self, camera_id, op, filename=None):
+    def post(self, camera_id, op, filename=None, group=None):
         if camera_id is not None:
             camera_id = int(camera_id)
             if camera_id not in config.get_camera_ids():
@@ -1018,6 +1045,9 @@ class MovieHandler(BaseHandler):
         if op == 'delete':
             self.delete(camera_id, filename)
         
+        elif op == 'delete_all':
+            self.delete_all(camera_id, group)
+        
         else:
             raise HTTPError(400, 'unknown operation')
     
@@ -1113,6 +1143,7 @@ class MovieHandler(BaseHandler):
                     width=self.get_argument('width', None),
                     height=self.get_argument('height', None))
 
+    @BaseHandler.auth(admin=True)
     def delete(self, camera_id, filename):
         logging.debug('deleting movie %(filename)s of camera %(id)s' % {
                 'filename': filename, 'id': camera_id})
@@ -1136,6 +1167,30 @@ class MovieHandler(BaseHandler):
 
             remote.del_media_content(camera_config, on_response, filename=filename, media_type='movie')
 
+    @BaseHandler.auth(admin=True)
+    def delete_all(self, camera_id, group):
+        logging.debug('deleting movie group %(group)s of camera %(id)s' % {
+                'group': group, 'id': camera_id})
+
+        camera_config = config.get_camera(camera_id)
+        if utils.local_camera(camera_config):
+            try:
+                mediafiles.del_media_group(camera_config, group, 'movie')
+                self.finish_json()
+                
+            except Exception as e:
+                self.finish_json({'error': unicode(e)})
+
+        else: # remote camera
+            def on_response(response=None, error=None):
+                if error:
+                    return self.finish_json({'error': 'Failed to delete movie group from %(url)s: %(msg)s.' % {
+                            'url': remote.make_camera_url(camera_config), 'msg': error}})
+
+                self.finish_json()
+
+            remote.del_media_group(camera_config, on_response, group=group, media_type='movie')
+
 
 class UpdateHandler(BaseHandler):
     @BaseHandler.auth(admin=True)
index dc7e155ecb1a710ad0904bc90c4e497bcc590a15..b03ed36853a4aa0710fa6a91e20a6d920be25cf4 100644 (file)
@@ -583,12 +583,34 @@ def del_media_content(camera_config, path, media_type):
         os.remove(full_path)
     
     except Exception as e:
-        logging.error('failed to read file %(path)s: %(msg)s' % {
+        logging.error('failed to remove file %(path)s: %(msg)s' % {
                 'path': full_path, 'msg': unicode(e)})
         
         raise
 
 
+def del_media_group(camera_config, group, media_type):
+    if media_type == 'picture':
+        exts = _PICTURE_EXTS
+        
+    elif media_type == 'movie':
+        exts = _MOVIE_EXTS
+        
+    target_dir = camera_config.get('target_dir')
+    full_path = os.path.join(target_dir, group)
+
+    mf = _list_media_files(target_dir, exts=exts, prefix=group)
+    for (path, st) in mf:  # @UnusedVariable
+        try:
+            os.remove(path)
+    
+        except Exception as e:
+            logging.error('failed to remove file %(path)s: %(msg)s' % {
+                    'path': full_path, 'msg': unicode(e)})
+
+            raise
+
+
 def get_current_picture(camera_config, width, height):
     import mjpgclient
 
index 92d5b319e2d1688dbe72b4948d3b59800440aa12..77cf5f2d67fdaa10b554e78dca2ba331a784c798 100644 (file)
@@ -562,3 +562,39 @@ def del_media_content(local_config, callback, filename, media_type):
 
     http_client = AsyncHTTPClient()
     http_client.fetch(request, on_response)
+
+
+def del_media_group(local_config, callback, group, media_type):
+    host = local_config.get('@host', local_config.get('host'))
+    port = local_config.get('@port', local_config.get('port'))
+    username = local_config.get('@username', local_config.get('username'))
+    password = local_config.get('@password', local_config.get('password'))
+    uri = local_config.get('@uri', local_config.get('uri')) or ''
+    camera_id = local_config.get('@remote_camera_id', local_config.get('remote_camera_id'))
+    
+    logging.debug('deleting group %(group)s of remote camera %(id)s on %(url)s' % {
+            'group': group,
+            'id': camera_id,
+            'url': make_camera_url(local_config)})
+    
+    uri += '/%(media_type)s/%(id)s/delete/%(group)s' % {
+            'media_type': media_type,
+            'id': camera_id,
+            'group': group}
+
+    request = _make_request(host, port, username, password, uri, method='POST', data='{}', timeout=settings.REMOTE_REQUEST_TIMEOUT)
+
+    def on_response(response):
+        if response.error:
+            logging.error('failed to delete group %(group)s of remote camera %(id)s on %(url)s: %(msg)s' % {
+                    'group': group,
+                    'id': camera_id,
+                    'url': make_camera_url(local_config),
+                    'msg': unicode(response.error)})
+            
+            return callback(error=unicode(response.error))
+        
+        return callback()
+
+    http_client = AsyncHTTPClient()
+    http_client.fetch(request, on_response)
index 3098ac5485a7a3adde692a7a1617e48bb0d1d5c8..50414192c2d7fc0ef96b987591654e0e26c24857 100644 (file)
@@ -46,9 +46,10 @@ application = Application(
         (r'^/config/(?P<op>add|list|list_devices)/?$', handlers.ConfigHandler),
         (r'^/picture/(?P<camera_id>\d+)/(?P<op>current|list|frame)/?$', handlers.PictureHandler),
         (r'^/picture/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.PictureHandler),
-        (r'^/picture/(?P<camera_id>\d+)/(?P<op>zipped|timelapse)/(?P<group>.+?)/?$', handlers.PictureHandler),
+        (r'^/picture/(?P<camera_id>\d+)/(?P<op>zipped|timelapse|delete_all)/(?P<group>.+?)/?$', handlers.PictureHandler),
         (r'^/movie/(?P<camera_id>\d+)/(?P<op>list)/?$', handlers.MovieHandler),
         (r'^/movie/(?P<camera_id>\d+)/(?P<op>download|preview|delete)/(?P<filename>.+?)/?$', handlers.MovieHandler),
+        (r'^/movie/(?P<camera_id>\d+)/(?P<op>delete_all)/(?P<group>.+?)/?$', handlers.MovieHandler),
         (r'^/update/?$', handlers.UpdateHandler),
         (r'^/power/(?P<op>shutdown)/?$', handlers.PowerHandler),
         (r'^/version/?$', handlers.VersionHandler),
index f20544ca2a42c4837c07fc246407b819b44711be..fa14bb75e1372ae9a119f39db7b94a9be079513a 100644 (file)
@@ -500,6 +500,8 @@ div.media-dialog-groups.small-screen {
 
 div.media-dialog-group-button {
     height: 1.5em;
+    width: 9.5em;
+    box-sizing: border-box;
     line-height: 1.5em;
     text-align: center;
     margin: 0em 0.2em 0.2em 0.2em;
@@ -721,6 +723,20 @@ table.timelapse-dialog select {
     width: 10em;
 }
 
+div.media-dialog-delete-all-button {
+    margin-top: 0.1em;
+    margin-bottom: 0.4em;
+    background: #c0392b;
+}
+
+div.media-dialog-delete-all-button:HOVER {
+    background-color: #D43F2F;
+}
+
+div.media-dialog-delete-all-button:ACTIVE {
+    background-color: #B03427;
+}
+
 
     /* camera frames */
 
index 8ad2974d311886d4d14e1fd1b490b3adf615c508..1e5b258deab26f51df1eca7bdbf41e88684bf546 100644 (file)
@@ -1550,7 +1550,11 @@ function doDeleteFile(uri, callback) {
     url = parts.slice(0, 3).join('/') + uri;
     
     runConfirmDialog('Really delete this file?', function () {
+        showModalDialog('<div class="modal-progress"></div>', null, null, true);
         ajax('POST', url, null, function (data) {
+            hideModalDialog(); /* progress */
+            hideModalDialog(); /* confirm */
+            
             if (data == null || data.error) {
                 showErrorMessage(data && data.error);
                 return;
@@ -1560,9 +1564,31 @@ function doDeleteFile(uri, callback) {
                 callback();
             }
         });
+        
+        return false;
     }, {stack: true});
 }
 
+function doDeleteAllFiles(mediaType, cameraId, groupKey, callback) {
+    runConfirmDialog('Really delete all ' + mediaType + 's in ' + groupKey + '?', function () {
+        showModalDialog('<div class="modal-progress"></div>', null, null, true);
+        ajax('POST', '/' + mediaType + '/' + cameraId + '/delete_all/' + groupKey + '/', null, function (data) {
+            hideModalDialog(); /* progress */
+            hideModalDialog(); /* confirm */
+            
+            if (data == null || data.error) {
+                showErrorMessage(data && data.error);
+                return;
+            }
+
+            if (callback) {
+                callback();
+            }
+        });
+        
+        return false;
+    }, {stack: true});
+}
 
 
     /* fetch & push */
@@ -2259,28 +2285,199 @@ function runMediaDialog(cameraId, mediaType) {
     
     dialogDiv.append(groupsDiv);
     dialogDiv.append(mediaListDiv);
+    dialogDiv.append(buttonsDiv);
+    
+    /* add a temporary div to compute 3em in px */
+    var tempDiv = $('<div style="width: 3em; height: 3em;"></div>');
+    $('div.modal-container').append(tempDiv);
+    var height = tempDiv.height();
+    tempDiv.remove();
+
+    function showGroup(key) {
+        groupKey = key;
+        
+        if (mediaListDiv.find('img.media-list-progress').length) {
+            return; /* already in progress of loading */
+        }
+        
+        /* (re)set the current state of the group buttons */
+        groupsDiv.find('div.media-dialog-group-button').each(function () {
+            var $this = $(this);
+            if (this.key == key) {
+                $this.addClass('current');
+            }
+            else {
+                $this.removeClass('current');
+            }
+        });
+        
+        var mediaListByName = {};
+        var entries = groups[key];
+        
+        /* cleanup the media list */
+        mediaListDiv.children('div.media-list-entry').detach();
+        mediaListDiv.html('');
+        
+        function addEntries() {
+            /* add the entries to the media list */
+            entries.forEach(function (entry) {
+                var entryDiv = entry.div;
+                var detailsDiv = null;
+                
+                if (!entryDiv) {
+                    entryDiv = $('<div class="media-list-entry"></div>');
+                    
+                    var previewImg = $('<img class="media-list-preview" src="' + staticUrl + 'img/modal-progress.gif"/>');
+                    entryDiv.append(previewImg);
+                    previewImg[0]._src = addAuthParams('GET', '/' + mediaType + '/' + cameraId + '/preview' + entry.path + '?height=' + height);
+                    
+                    var downloadButton = $('<div class="media-list-download-button button">Download</div>');
+                    entryDiv.append(downloadButton);
+                    
+                    var deleteButton = $('<div class="media-list-delete-button button">Delete</div>');
+                    
+                    if (username === adminUsername) {
+                        entryDiv.append(deleteButton);
+                    }
+
+                    var nameDiv = $('<div class="media-list-entry-name">' + entry.name + '</div>');
+                    entryDiv.append(nameDiv);
+                    
+                    detailsDiv = $('<div class="media-list-entry-details"></div>');
+                    entryDiv.append(detailsDiv);
+                    
+                    downloadButton.click(function () {
+                        downloadFile('/' + mediaType + '/' + cameraId + '/download' + entry.path);
+                        return false;
+                    });
+                    
+                    deleteButton.click(function () {
+                        doDeleteFile('/' + mediaType + '/' + cameraId + '/delete' + entry.path, function () {
+                            entryDiv.remove();
+                        });
+                        
+                        return false;
+                    });
+
+                    entryDiv.click(function () {
+                        var pos = entries.indexOf(entry);
+                        runPictureDialog(entries, pos, mediaType);
+                    });
+                    
+                    entry.div = entryDiv;
+                }
+                else {
+                    detailsDiv = entry.div.find('div.media-list-entry-details');
+                }                    
+                
+                var momentSpan = $('<span class="details-moment">' + entry.momentStr + ', </span>');
+                var momentShortSpan = $('<span class="details-moment-short">' + entry.momentStrShort + '</span>');
+                var sizeSpan = $('<span class="details-size">' + entry.sizeStr + '</span>');
+                detailsDiv.empty();
+                detailsDiv.append(momentSpan);
+                detailsDiv.append(momentShortSpan);
+                detailsDiv.append(sizeSpan);
+                mediaListDiv.append(entryDiv);
+            });
+
+            /* trigger a scroll event */
+            mediaListDiv.scroll();
+        }
+        
+        /* if details are already fetched, simply add the entries and return */
+        if (entries[0].timestamp) {
+            return addEntries();
+        }
+        
+        var previewImg = $('<img class="media-list-progress" src="' + staticUrl + 'img/modal-progress.gif"/>');
+        mediaListDiv.append(previewImg);
+        
+        var url = '/' + mediaType + '/' + cameraId + '/list/?prefix=' + (key || 'ungrouped');
+        ajax('GET', url, null, function (data) {
+            previewImg.remove();
+            
+            if (data == null || data.error) {
+                hideModalDialog();
+                showErrorMessage(data && data.error);
+                return;
+            }
+            
+            /* index the media list by name */
+            data.mediaList.forEach(function (media) {
+                var path = media.path;
+                var parts = path.split('/');
+                var name = parts[parts.length - 1];
+                
+                mediaListByName[name] = media;
+            });
+            
+            /* assign details to entries */
+                entries.forEach(function (entry) {
+                    var media = mediaListByName[entry.name];
+                    if (media) {
+                        entry.momentStr = media.momentStr;
+                        entry.momentStrShort = media.momentStrShort;
+                        entry.sizeStr = media.sizeStr;
+                        entry.timestamp = media.timestamp;
+                    }
+                });
+                /* sort the entries by timestamp */
+            entries.sortKey(function (e) {return e.timestamp || e.name;}, true);
+            
+            addEntries();
+        });
+    }
     
     if (mediaType == 'picture') {
-        dialogDiv.append(buttonsDiv);
         
-        var zippedButton = $('<div class="media-dialog-button">Zipped Pictures</div>');
+        var zippedButton = $('<div class="media-dialog-button">Zipped</div>');
         buttonsDiv.append(zippedButton);
         
         zippedButton.click(function () {
-            if (groupKey) {
+            if (groupKey != null) {
                 doDownloadZipped(cameraId, groupKey);
             }
         });
         
-        var timelapseButton = $('<div class="media-dialog-button">Timelapse Movie</div>');
+        var timelapseButton = $('<div class="media-dialog-button">Timelapse</div>');
         buttonsDiv.append(timelapseButton);
         
         timelapseButton.click(function () {
-            if (groupKey) {
+            if (groupKey != null) {
                 runTimelapseDialog(cameraId, groupKey, groups[groupKey]);
             }
         });
     }
+
+    var deleteAllButton = $('<div class="media-dialog-button media-dialog-delete-all-button">Delete</div>');
+    buttonsDiv.append(deleteAllButton);
+    
+    deleteAllButton.click(function () {
+        if (groupKey != null) {
+            doDeleteAllFiles(mediaType, cameraId, groupKey, function () {
+                /* delete th group button */
+                groupsDiv.find('div.media-dialog-group-button').each(function () {
+                    var $this = $(this);
+                    if (this.key == groupKey) {
+                        $this.remove();
+                    }
+                });
+                
+                /* delete the group itself */
+                delete groups[groupKey];
+                
+                /* show the first existing group, if any */
+                var keys = Object.keys(groups);
+                if (keys.length) {
+                    showGroup(keys[0]);
+                }
+                else {
+                    hideModalDialog();
+                }
+            });
+        }
+    });
     
     function updateDialogSize() {
         var windowWidth = $(window).width();
@@ -2361,148 +2558,6 @@ function runMediaDialog(cameraId, mediaType) {
         keys.sort();
         keys.reverse();
         
-        /* add a temporary div to compute 3em in px */
-        var tempDiv = $('<div style="width: 3em; height: 3em;"></div>');
-        $('div.modal-container').append(tempDiv);
-        var height = tempDiv.height();
-        tempDiv.remove();
-
-        function showGroup(key) {
-            groupKey = key;
-            
-            if (mediaListDiv.find('img.media-list-progress').length) {
-                return; /* already in progress of loading */
-            }
-            
-            /* (re)set the current state of the group buttons */
-            groupsDiv.find('div.media-dialog-group-button').each(function () {
-                var $this = $(this);
-                if (this.key == key) {
-                    $this.addClass('current');
-                }
-                else {
-                    $this.removeClass('current');
-                }
-            });
-            
-            var mediaListByName = {};
-            var entries = groups[key];
-            
-            /* cleanup the media list */
-            mediaListDiv.children('div.media-list-entry').detach();
-            mediaListDiv.html('');
-            
-            function addEntries() {
-                /* add the entries to the media list */
-                entries.forEach(function (entry) {
-                    var entryDiv = entry.div;
-                    var detailsDiv = null;
-                    
-                    if (!entryDiv) {
-                        entryDiv = $('<div class="media-list-entry"></div>');
-                        
-                        var previewImg = $('<img class="media-list-preview" src="' + staticUrl + 'img/modal-progress.gif"/>');
-                        entryDiv.append(previewImg);
-                        previewImg[0]._src = addAuthParams('GET', '/' + mediaType + '/' + cameraId + '/preview' + entry.path + '?height=' + height);
-                        
-                        var downloadButton = $('<div class="media-list-download-button button">Download</div>');
-                        entryDiv.append(downloadButton);
-                        
-                        var deleteButton = $('<div class="media-list-delete-button button">Delete</div>');
-                        
-                        if (username === adminUsername) {
-                            entryDiv.append(deleteButton);
-                        }
-
-                        var nameDiv = $('<div class="media-list-entry-name">' + entry.name + '</div>');
-                        entryDiv.append(nameDiv);
-                        
-                        detailsDiv = $('<div class="media-list-entry-details"></div>');
-                        entryDiv.append(detailsDiv);
-                        
-                        downloadButton.click(function () {
-                            downloadFile('/' + mediaType + '/' + cameraId + '/download' + entry.path);
-                            return false;
-                        });
-                        
-                        deleteButton.click(function () {
-                            doDeleteFile('/' + mediaType + '/' + cameraId + '/delete' + entry.path, function () {
-                                entryDiv.remove();
-                            });
-                            
-                            return false;
-                        });
-
-                        entryDiv.click(function () {
-                            var pos = entries.indexOf(entry);
-                            runPictureDialog(entries, pos, mediaType);
-                        });
-                        
-                        entry.div = entryDiv;
-                    }
-                    else {
-                        detailsDiv = entry.div.find('div.media-list-entry-details');
-                    }                    
-                    
-                    var momentSpan = $('<span class="details-moment">' + entry.momentStr + ', </span>');
-                    var momentShortSpan = $('<span class="details-moment-short">' + entry.momentStrShort + '</span>');
-                    var sizeSpan = $('<span class="details-size">' + entry.sizeStr + '</span>');
-                    detailsDiv.empty();
-                    detailsDiv.append(momentSpan);
-                    detailsDiv.append(momentShortSpan);
-                    detailsDiv.append(sizeSpan);
-                    mediaListDiv.append(entryDiv);
-                });
-
-                /* trigger a scroll event */
-                mediaListDiv.scroll();
-            }
-            
-            /* if details are already fetched, simply add the entries and return */
-            if (entries[0].timestamp) {
-                return addEntries();
-            }
-            
-            var previewImg = $('<img class="media-list-progress" src="' + staticUrl + 'img/modal-progress.gif"/>');
-            mediaListDiv.append(previewImg);
-            
-            var url = '/' + mediaType + '/' + cameraId + '/list/?prefix=' + (key || 'ungrouped');
-            ajax('GET', url, null, function (data) {
-                previewImg.remove();
-                
-                if (data == null || data.error) {
-                    hideModalDialog();
-                    showErrorMessage(data && data.error);
-                    return;
-                }
-                
-                /* index the media list by name */
-                data.mediaList.forEach(function (media) {
-                    var path = media.path;
-                    var parts = path.split('/');
-                    var name = parts[parts.length - 1];
-                    
-                    mediaListByName[name] = media;
-                });
-                
-                /* assign details to entries */
-                entries.forEach(function (entry) {
-                    var media = mediaListByName[entry.name];
-                    if (media) {
-                        entry.momentStr = media.momentStr;
-                        entry.momentStrShort = media.momentStrShort;
-                        entry.sizeStr = media.sizeStr;
-                        entry.timestamp = media.timestamp;
-                    }
-                });
-                /* sort the entries by timestamp */
-                entries.sortKey(function (e) {return e.timestamp || e.name;}, true);
-                
-                addEntries();
-            });
-        }
-        
         if (keys.length) {
             keys.forEach(function (key) {
                 var groupButton = $('<div class="media-dialog-group-button"></div>');