-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Adds folder sensor #12208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds folder sensor #12208
Changes from 14 commits
ffd634f
f9707ce
adbbabc
16c078b
5ee5e0d
929b532
2c8bfc0
54739fb
5c8a792
c4cef26
07462e7
51bc020
6cb0695
e3615f8
117be0d
8c0c0a2
ff5872c
8fd2a71
6676eff
1974a7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| """ | ||
| Sensor for monitoring the contents of a folder. | ||
|
|
||
| For more details about this platform, refer to the documentation at | ||
| https://home-assistant.io/components/sensor.folder/ | ||
| """ | ||
| from datetime import datetime as dt | ||
| from datetime import timedelta | ||
| import glob | ||
| import logging | ||
| import os | ||
|
|
||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.helpers.entity import Entity | ||
| from homeassistant.helpers.template import DATE_STR_FORMAT | ||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.components.sensor import PLATFORM_SCHEMA | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| CONF_FOLDER_PATHS = 'folder' | ||
| CONF_FILTER = 'filter' | ||
| DEFAULT_FILTER = '*' | ||
|
|
||
| SCAN_INTERVAL = timedelta(seconds=10) | ||
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
| vol.Required(CONF_FOLDER_PATHS): cv.isdir, | ||
| vol.Optional(CONF_FILTER, default=DEFAULT_FILTER): cv.string, | ||
| }) | ||
|
|
||
|
|
||
| def get_sorted_files_list(folder_path, filter_term): | ||
| """Return the sorted list of files, applying filter.""" | ||
| query = folder_path + filter_term | ||
| files_list = glob.glob(query) | ||
| sorted_files_list = sorted(files_list, key=os.path.getmtime) | ||
| return sorted_files_list | ||
|
|
||
|
|
||
| def get_last_updated(recent_modified_file): | ||
| """Return the time a file was last modified.""" | ||
| modified_time = os.path.getmtime(recent_modified_file) | ||
| modified_time_datetime = dt.fromtimestamp(modified_time) | ||
| return modified_time_datetime.strftime(DATE_STR_FORMAT) | ||
|
|
||
|
|
||
| def setup_platform(hass, config, add_devices, discovery_info=None): | ||
| """Set up the folder sensor.""" | ||
| path = config.get(CONF_FOLDER_PATHS) | ||
|
|
||
| if not hass.config.is_allowed_path(path): | ||
| _LOGGER.error("folder %s is not valid or allowed", path) | ||
| else: | ||
| folder = Folder(path, config.get(CONF_FILTER)) | ||
| add_devices([folder], True) | ||
|
|
||
|
|
||
| class Folder(Entity): | ||
| """Representation of a folder.""" | ||
|
|
||
| ICON = 'mdi:folder' | ||
|
|
||
| def __init__(self, folder_path, filter_term): | ||
| """Initialize the data object.""" | ||
| folder_path = os.path.join(folder_path, '') # If no trailing / add it | ||
| self._folder_path = folder_path # Need to check its a valid path | ||
| self._filter_term = filter_term | ||
| self._sorted_files_list = [] | ||
| self._number_of_files = None | ||
| self._recent_modified_file = None | ||
| self._last_updated = None | ||
| self._name = os.path.split(os.path.split(folder_path)[0])[1] | ||
|
|
||
| def update(self): | ||
| """Update the sensor.""" | ||
| self._sorted_files_list = get_sorted_files_list( | ||
| self._folder_path, self._filter_term) | ||
|
|
||
| self._recent_modified_file = self._sorted_files_list[-1] | ||
|
|
||
| self._last_updated = get_last_updated( | ||
| self._recent_modified_file) | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the name of the sensor.""" | ||
| return self._name | ||
|
|
||
| @property | ||
| def state(self): | ||
| """Return the state of the sensor.""" | ||
| return self._last_updated | ||
|
|
||
| @property | ||
| def icon(self): | ||
| """Icon to use in the frontend, if any.""" | ||
| return self.ICON | ||
|
|
||
| @property | ||
| def device_state_attributes(self): | ||
| """Return other details about the sensor state.""" | ||
| attr = { | ||
| 'folder': self._folder_path, | ||
| 'filter': self._filter_term, | ||
| 'modified_file': os.path.split(self._recent_modified_file)[1], | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's drop this attribute. If people want this info, we should get a watchdog component to monitor folders/files and fire events. If people want to automate on this, it will not work if 2 files get added between 2 updates. Then they want to have that fixed etc. I think that we should have people do the right thing right away by not including this, so they will look for the watchdog component instead.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK |
||
| 'number_of_files': len(self._sorted_files_list), | ||
| 'files': [os.path.split(f)[1] for f in self._sorted_files_list] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should not do this. This is way too much data for in the state machine.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
| } | ||
| return attr | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| """The tests for the folder sensor.""" | ||
| import unittest | ||
| import os | ||
|
|
||
| from homeassistant.components.sensor.folder import CONF_FOLDER_PATHS | ||
| from homeassistant.setup import setup_component | ||
| from tests.common import get_test_home_assistant | ||
|
|
||
|
|
||
| CWD = os.path.join(os.path.dirname(__file__)) | ||
| TEST_FOLDER = 'test_folder' | ||
| TEST_DIR = os.path.join(CWD, TEST_FOLDER) | ||
| TEST_TXT = 'mock_test_folder.txt' | ||
| TEST_FILE = os.path.join(TEST_DIR, TEST_TXT) | ||
|
|
||
|
|
||
| def create_file(path): | ||
| """Create a test file.""" | ||
| with open(path, 'w') as test_file: | ||
| test_file.write("test") | ||
|
|
||
|
|
||
| class TestFolderSensor(unittest.TestCase): | ||
| """Test the filesize sensor.""" | ||
|
|
||
| def setup_method(self, method): | ||
| """Set up things to be run when tests are started.""" | ||
| self.hass = get_test_home_assistant() | ||
| if not os.path.isdir(TEST_DIR): | ||
| os.mkdir(TEST_DIR) | ||
| self.hass.config.whitelist_external_dirs = set((TEST_DIR)) | ||
|
|
||
| def teardown_method(self, method): | ||
| """Stop everything that was started.""" | ||
| if os.path.isfile(TEST_FILE): | ||
| os.remove(TEST_FILE) | ||
| os.rmdir(TEST_DIR) | ||
| self.hass.stop() | ||
|
|
||
| def test_invalid_path(self): | ||
| """Test that an invalid path is caught.""" | ||
| config = { | ||
| 'sensor': { | ||
| 'platform': 'folder', | ||
| CONF_FOLDER_PATHS: 'invalid_path'} | ||
| } | ||
| self.assertTrue( | ||
| setup_component(self.hass, 'sensor', config)) | ||
| assert len(self.hass.states.entity_ids()) == 0 | ||
|
|
||
| def test_valid_path(self): | ||
| """Test for a valid path.""" | ||
| create_file(TEST_FILE) | ||
| config = { | ||
| 'sensor': { | ||
| 'platform': 'folder', | ||
| CONF_FOLDER_PATHS: TEST_DIR} | ||
| } | ||
| self.assertTrue( | ||
| setup_component(self.hass, 'sensor', config)) | ||
| assert len(self.hass.states.entity_ids()) == 1 | ||
| state = self.hass.states.get('sensor.test_folder') | ||
| assert state.attributes.get('number_of_files') == 1 | ||
| assert state.attributes.get('files') == [TEST_TXT] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better if we don't keep this in memory but instead only set the instance variables that we want to derive from it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK