Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions motioneye/controls/diskctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ def _list_mounts():
fstype = parts[2]
opts = parts[3]

if not os.access(mount_point, os.W_OK):
continue

if target in seen_targets:
continue # probably a bind mount

Expand All @@ -60,6 +57,8 @@ def _list_mounts():
'mount_point': mount_point,
'fstype': fstype,
'opts': opts,
# Used by the UI to mark unwritable mounts.
'writable': os.access(mount_point, os.W_OK),
}
)

Expand Down
20 changes: 18 additions & 2 deletions motioneye/static/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,13 @@ function initUI() {

return true;
}, '');
makeCustomValidator($('#storageDeviceSelect'), function (value) {
if ($('#storageDeviceSelect option:selected').prop('disabled')) {
return i18n.gettext('The configured storage device is not writable. Recordings cannot be saved until write access is restored.');
}

return true;
}, true);
makeCustomValidator($('#emailFromEntry'), function (value) {
if (value && !value.match(emailValidRegExp)) {
return i18n.gettext("enigu validan retpoŝtadreson");
Expand Down Expand Up @@ -2174,10 +2181,19 @@ function dict2CameraUi(dict) {
label += '/part' + partition.part_no;
}
label += ' (' + partition.target + ')';
if (partition.writable === false) {
label += ' [' + i18n.gettext('No write access') + ']';
}
Comment thread
Marijn0 marked this conversation as resolved.

storageDeviceOptions[option] = true;
// Show unwritable mounts, but prevent selecting them.
$('#storageDeviceSelect').append(
$('<option></option>')
.val(option)
.text(label)
.prop('disabled', partition.writable === false)
);

$('#storageDeviceSelect').append('<option value="' + option + '">' + label + '</option>');
storageDeviceOptions[option] = true;
});
});
$('#storageDeviceSelect').append('<option value="custom-path">'+i18n.gettext("Propra dosierindiko")+'</option>');
Expand Down
6 changes: 3 additions & 3 deletions motioneye/static/js/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ function makeProgressBar($div) {

/* validators */

function makeCustomValidator($input, isValidFunc) {
function makeCustomValidator($input, isValidFunc, ignoreVisibility) {
$input.each(function () {
var element = this;

Expand All @@ -353,8 +353,8 @@ function makeCustomValidator($input, isValidFunc) {
function validate() {
var strVal = element.value || '';

/* An invisible element is considered always valid */
var valid = !isVisible(element) || isValidFunc(strVal);
/* An invisible element is considered always valid, unless ignoreVisibility is set */
var valid = (!ignoreVisibility && !isVisible(element)) || isValidFunc(strVal);

/* Handle validators that return error messages or true */
var isValidResult = (valid === true);
Expand Down
57 changes: 57 additions & 0 deletions tests/test_diskctl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright (c) 2013 Calin Crisan
# This file is part of motionEye.
#
# motionEye is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import unittest
from unittest.mock import mock_open, patch

from motioneye.controls import diskctl


class ListMountsTest(unittest.TestCase):
_PROC_MOUNTS = (
'/dev/sda1 /media/usb ext4 rw,relatime 0 0\n'
'/dev/sdb1 /media/usb2 vfat rw,relatime 0 0\n'
'/dev/sdb1 /media/bind ext4 rw,relatime 0 0\n' # duplicate target (bind)
)

def test_includes_mounts_without_write_access(self):
# a drive the motion user cannot write to used to be filtered out on a
# missing os.W_OK, so it never appeared as a storage device (#3024)
with patch(
'motioneye.controls.diskctl.open', mock_open(read_data=self._PROC_MOUNTS)
), patch('motioneye.controls.diskctl.os.access', return_value=False):
mounts = diskctl._list_mounts()

targets = [m['target'] for m in mounts]
self.assertIn('/dev/sda1', targets)
self.assertIn('/dev/sdb1', targets)
# ... but each is flagged as not writable so the UI can warn (#3024)
self.assertTrue(all(m['writable'] is False for m in mounts))

def test_deduplicates_bind_mounts(self):
with patch(
'motioneye.controls.diskctl.open', mock_open(read_data=self._PROC_MOUNTS)
), patch('motioneye.controls.diskctl.os.access', return_value=True):
mounts = diskctl._list_mounts()

# the second /dev/sdb1 entry (a bind mount) is collapsed
self.assertEqual([m['target'] for m in mounts].count('/dev/sdb1'), 1)
self.assertEqual(len(mounts), 2)
self.assertTrue(all(m['writable'] is True for m in mounts))


if __name__ == '__main__':
unittest.main()
Loading