From fed81f70593aa9dcee37b57334644e44e3183570 Mon Sep 17 00:00:00 2001 From: Calin Crisan Date: Sun, 28 Sep 2014 18:46:44 +0300 Subject: [PATCH] added a delete media file button --- src/handlers.py | 97 ++++++++++++++++++++++++++++++++++----------- src/mediafiles.py | 15 +++++++ src/remote.py | 36 +++++++++++++++++ src/server.py | 4 +- static/css/main.css | 28 +++++++++++-- static/js/main.js | 59 ++++++++++++++++++++++++--- 6 files changed, 205 insertions(+), 34 deletions(-) diff --git a/src/handlers.py b/src/handlers.py index 87b0e0a..d398c74 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -655,6 +655,19 @@ class PictureHandler(BaseHandler): else: raise HTTPError(400, 'unknown operation') + @asynchronous + 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(): + raise HTTPError(404, 'no such camera') + + if op == 'delete': + self.delete(camera_id, filename) + + else: + raise HTTPError(400, 'unknown operation') + @BaseHandler.auth(prompt=False) def current(self, camera_id): self.set_header('Content-Type', 'image/jpeg') @@ -695,9 +708,6 @@ class PictureHandler(BaseHandler): def list(self, camera_id): logging.debug('listing pictures for camera %(id)s' % {'id': camera_id}) - if camera_id not in config.get_camera_ids(): - raise HTTPError(404, 'no such camera') - camera_config = config.get_camera(camera_id) if utils.local_camera(camera_config): def on_media_list(media_list): @@ -736,9 +746,6 @@ class PictureHandler(BaseHandler): logging.debug('downloading picture %(filename)s of camera %(id)s' % { 'filename': filename, 'id': camera_id}) - if camera_id not in config.get_camera_ids(): - raise HTTPError(404, 'no such camera') - camera_config = config.get_camera(camera_id) if utils.local_camera(camera_config): content = mediafiles.get_media_content(camera_config, filename, 'picture') @@ -768,9 +775,6 @@ class PictureHandler(BaseHandler): logging.debug('previewing picture %(filename)s of camera %(id)s' % { 'filename': filename, 'id': camera_id}) - if camera_id not in config.get_camera_ids(): - raise HTTPError(404, 'no such camera') - camera_config = config.get_camera(camera_id) if utils.local_camera(camera_config): content = mediafiles.get_media_preview(camera_config, filename, 'picture', @@ -802,10 +806,31 @@ class PictureHandler(BaseHandler): height=self.get_argument('height', None)) @BaseHandler.auth() - def zipped(self, camera_id, group): - if camera_id not in config.get_camera_ids(): - raise HTTPError(404, 'no such camera') + def delete(self, camera_id, filename): + logging.debug('deleting picture %(filename)s of camera %(id)s' % { + 'filename': filename, 'id': camera_id}) + + camera_config = config.get_camera(camera_id) + if utils.local_camera(camera_config): + try: + mediafiles.del_media_content(camera_config, filename, '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 from %(url)s: %(msg)s.' % { + 'url': remote.make_camera_url(camera_config)}, 'msg': error}) + + self.finish_json() + remote.del_media_content(camera_config, on_response, filename=filename, media_type='picture') + + @BaseHandler.auth() + def zipped(self, camera_id, group): key = self.get_argument('key', None) if key: logging.debug('serving zip file for group %(group)s of camera %(id)s with key %(key)s' % { @@ -861,9 +886,6 @@ class PictureHandler(BaseHandler): @BaseHandler.auth() def timelapse(self, camera_id, group): - if camera_id not in config.get_camera_ids(): - raise HTTPError(404, 'no such camera') - key = self.get_argument('key', None) if key: logging.debug('serving timelapse movie for group %(group)s of camera %(id)s with key %(key)s' % { @@ -948,13 +970,23 @@ class MovieHandler(BaseHandler): else: raise HTTPError(400, 'unknown operation') + @asynchronous + def post(self, camera_id, op, filename=None): + if camera_id is not None: + camera_id = int(camera_id) + if camera_id not in config.get_camera_ids(): + raise HTTPError(404, 'no such camera') + + if op == 'delete': + self.delete(camera_id, filename) + + else: + raise HTTPError(400, 'unknown operation') + @BaseHandler.auth() def list(self, camera_id): logging.debug('listing movies for camera %(id)s' % {'id': camera_id}) - if camera_id not in config.get_camera_ids(): - raise HTTPError(404, 'no such camera') - camera_config = config.get_camera(camera_id) if utils.local_camera(camera_config): def on_media_list(media_list): @@ -984,9 +1016,6 @@ class MovieHandler(BaseHandler): logging.debug('downloading movie %(filename)s of camera %(id)s' % { 'filename': filename, 'id': camera_id}) - if camera_id not in config.get_camera_ids(): - raise HTTPError(404, 'no such camera') - camera_config = config.get_camera(camera_id) if utils.local_camera(camera_config): content = mediafiles.get_media_content(camera_config, filename, 'movie') @@ -1016,9 +1045,6 @@ class MovieHandler(BaseHandler): logging.debug('previewing movie %(filename)s of camera %(id)s' % { 'filename': filename, 'id': camera_id}) - if camera_id not in config.get_camera_ids(): - raise HTTPError(404, 'no such camera') - camera_config = config.get_camera(camera_id) if utils.local_camera(camera_config): content = mediafiles.get_media_preview(camera_config, filename, 'movie', @@ -1049,6 +1075,29 @@ class MovieHandler(BaseHandler): width=self.get_argument('width', None), height=self.get_argument('height', None)) + def delete(self, camera_id, filename): + logging.debug('deleting movie %(filename)s of camera %(id)s' % { + 'filename': filename, 'id': camera_id}) + + camera_config = config.get_camera(camera_id) + if utils.local_camera(camera_config): + try: + mediafiles.del_media_content(camera_config, filename, '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 from %(url)s: %(msg)s.' % { + 'url': remote.make_camera_url(camera_config)}, 'msg': error}) + + self.finish_json() + + remote.del_media_content(camera_config, on_response, filename=filename, media_type='movie') + class UpdateHandler(BaseHandler): @BaseHandler.auth(admin=True) diff --git a/src/mediafiles.py b/src/mediafiles.py index 7d798b1..dc7e155 100644 --- a/src/mediafiles.py +++ b/src/mediafiles.py @@ -574,6 +574,21 @@ def get_media_preview(camera_config, path, media_type, width, height): return sio.getvalue() +def del_media_content(camera_config, path, media_type): + target_dir = camera_config.get('target_dir') + + full_path = os.path.join(target_dir, path) + + try: + os.remove(full_path) + + except Exception as e: + logging.error('failed to read file %(path)s: %(msg)s' % { + 'path': full_path, 'msg': unicode(e)}) + + raise + + def get_current_picture(camera_config, width, height): import mjpgclient diff --git a/src/remote.py b/src/remote.py index 32abab1..fb0812f 100644 --- a/src/remote.py +++ b/src/remote.py @@ -517,3 +517,39 @@ def get_media_preview(local_config, callback, filename, media_type, width, heigh http_client = AsyncHTTPClient() http_client.fetch(request, on_response) + + +def del_media_content(local_config, callback, filename, 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 file %(filename)s of remote camera %(id)s on %(url)s' % { + 'filename': filename, + 'id': camera_id, + 'url': make_camera_url(local_config)}) + + uri += '/%(media_type)s/%(id)s/delete/%(filename)s' % { + 'media_type': media_type, + 'id': camera_id, + 'filename': filename} + + request = _make_request(host, port, username, password, uri, method='POST', timeout=settings.REMOTE_REQUEST_TIMEOUT) + + def on_response(response): + if response.error: + logging.error('failed to delete file %(filename)s of remote camera %(id)s on %(url)s: %(msg)s' % { + 'filename': filename, + '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) diff --git a/src/server.py b/src/server.py index f42758c..5a00a36 100644 --- a/src/server.py +++ b/src/server.py @@ -45,10 +45,10 @@ application = Application( (r'^/config/(?P\d+)/(?Pget|set|rem|set_preview)/?$', handlers.ConfigHandler), (r'^/config/(?Padd|list|list_devices)/?$', handlers.ConfigHandler), (r'^/picture/(?P\d+)/(?Pcurrent|list|frame)/?$', handlers.PictureHandler), - (r'^/picture/(?P\d+)/(?Pdownload|preview)/(?P.+?)/?$', handlers.PictureHandler), + (r'^/picture/(?P\d+)/(?Pdownload|preview|delete)/(?P.+?)/?$', handlers.PictureHandler), (r'^/picture/(?P\d+)/(?Pzipped|timelapse)/(?P.+?)/?$', handlers.PictureHandler), (r'^/movie/(?P\d+)/(?Plist)/?$', handlers.MovieHandler), - (r'^/movie/(?P\d+)/(?Pdownload|preview)/(?P.+?)/?$', handlers.MovieHandler), + (r'^/movie/(?P\d+)/(?Pdownload|preview|delete)/(?P.+?)/?$', handlers.MovieHandler), (r'^/update/?$', handlers.UpdateHandler), (r'^/power/(?Pshutdown)/?$', handlers.PowerHandler), (r'^/version/?$', handlers.VersionHandler), diff --git a/static/css/main.css b/static/css/main.css index ca4897d..27b59a2 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -580,19 +580,27 @@ div.media-list-entry-details span.details-moment-short { display: none; } -div.media-list-download-button { +div.media-list-download-button, +div.media-list-delete-button { float: right; + clear: right; height: 1.5em; + width: 5em; line-height: 1.5em; text-align: center; - margin: 1.2em 0.5em; + margin: 0px 0.5em; padding: 0px 0.5em; color: white; - background-color: #317CAD; border-radius: 3px; transition: all 0.1s linear; } +div.media-list-download-button { + margin-top: 0.4em; + margin-bottom: 0.1em; + background: #317CAD; +} + div.media-list-download-button:HOVER { background-color: #3498db; } @@ -601,6 +609,20 @@ div.media-list-download-button:ACTIVE { background-color: #317CAD; } +div.media-list-delete-button { + margin-top: 0.1em; + margin-bottom: 0.4em; + background: #c0392b; +} + +div.media-list-delete-button:HOVER { + background-color: #D43F2F; +} + +div.media-list-delete-button:ACTIVE { + background-color: #B03427; +} + div.media-dialog-buttons { margin: 0.5em 0px 0px 0px; text-align: center; diff --git a/static/js/main.js b/static/js/main.js index 4b3fd15..da413b9 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1312,6 +1312,21 @@ function doDownloadZipped(cameraId, groupKey) { }); } +function doDeleteFile(uri, callback) { + var url = window.location.href; + var parts = url.split('/'); + url = parts.slice(0, 3).join('/') + uri; + + runConfirmDialog('Really delete this file?', function () { + ajax('POST', url, null, function () { + if (callback) { + callback(); + } + }); + }, {stack: true}); +} + + /* fetch & push */ @@ -1502,12 +1517,32 @@ function getCameraIdsByInstance() { /* dialogs */ -function runAlertDialog(message, onOk) { - runModalDialog({title: message, buttons: 'ok', onOk: onOk}); +function runAlertDialog(message, onOk, options) { + var params = { + title: message, + buttons: 'ok', + onOk: onOk + }; + + if (options) { + Object.update(params, options); + } + + runModalDialog(params); } -function runConfirmDialog(message, onYes) { - runModalDialog({title: message, buttons: 'yesno', onYes: onYes}); +function runConfirmDialog(message, onYes, options) { + var params = { + title: message, + buttons: 'yesno', + onYes: onYes + }; + + if (options) { + Object.update(params, options); + } + + runModalDialog(params); } function runPictureDialog(entries, pos, mediaType) { @@ -2074,6 +2109,12 @@ function runMediaDialog(cameraId, mediaType) { var downloadButton = $('
Download
'); entryDiv.append(downloadButton); + var deleteButton = $('
Delete
'); + + if (user === 'admin') { + entryDiv.append(deleteButton); + } + var nameDiv = $('
' + entry.name + '
'); entryDiv.append(nameDiv); @@ -2081,10 +2122,18 @@ function runMediaDialog(cameraId, mediaType) { entryDiv.append(detailsDiv); downloadButton.click(function () { - downloadFile('/picture/' + cameraId + '/download' + entry.path); + 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); -- 2.39.5