{"action":"created","comment":{"url":"https://api.github.com/repos/mopidy/mopidy/pulls/comments/34137304","id":34137304,"diff_hunk":"@@ -0,0 +1,140 @@\n+from __future__ import unicode_literals\n+\n+import logging\n+import operator\n+import os\n+import sys\n+import urllib2\n+\n+from mopidy import backend, exceptions, models\n+from mopidy.audio import scan, utils\n+from mopidy.internal import path\n+\n+logger = logging.getLogger(__name__)\n+\n+\n+class FilesLibraryProvider(backend.LibraryProvider):\n+    \"\"\"Library for browsing local files.\"\"\"\n+    # TODO: get_images that can pull from metadata and/or .folder.png etc?\n+    # TODO: handle playlists?\n+\n+    @property\n+    def root_directory(self):\n+        if not self._media_dirs:\n+            return None\n+        elif len(self._media_dirs) == 1:\n+            local_path = self._media_dirs[0]['path']\n+            uri = path.path_to_uri(local_path)\n+        else:\n+            uri = 'file:root'\n+        return models.Ref.directory(name='Files', uri=uri)\n+\n+    def __init__(self, backend, config):\n+        super(FilesLibraryProvider, self).__init__(backend)\n+        self._media_dirs = list(self._get_media_dirs(config))\n+        self._follow_symlinks = config['files']['follow_symlinks']\n+        self._show_dotfiles = config['files']['show_dotfiles']\n+        self._scanner = scan.Scanner(\n+            timeout=config['files']['metadata_timeout'])\n+\n+    def browse(self, uri):\n+        logger.debug('Browsing files at: %s', uri)\n+        result = []\n+        local_path = path.uri_to_path(uri)\n+        if local_path == 'root':\n+            return list(self._get_media_dirs_refs())\n+        for dir_entry in os.listdir(local_path):\n+            child_path = os.path.join(local_path, dir_entry)\n+            uri = path.path_to_uri(child_path)\n+            printable_path = child_path.decode(sys.getfilesystemencoding(),\n+                                               'ignore')\n+\n+            if os.path.islink(child_path) and not self._follow_symlinks:\n+                logger.debug('Ignoring symlink: %s', printable_path)\n+                continue\n+\n+            if not self._is_in_basedir(os.path.realpath(child_path)):\n+                logger.debug('Ignoring symlink to outside base dir: %s',\n+                             printable_path)\n+                continue\n+\n+            if not self._show_dotfiles and dir_entry.startswith(b'.'):\n+                continue\n+\n+            dir_entry = dir_entry.decode(sys.getfilesystemencoding(),\n+                                         'replace')\n+            if os.path.isdir(child_path):\n+                result.append(models.Ref.directory(name=dir_entry, uri=uri))\n+            elif os.path.isfile(child_path):\n+                if self._is_audiofile(uri):\n+                    result.append(models.Ref.track(name=dir_entry, uri=uri))\n+                else:\n+                    logger.debug('Ignoring non-audiofile: %s', printable_path)\n+\n+        result.sort(key=operator.attrgetter('name'))\n+        return result\n+\n+    def lookup(self, uri):\n+        logger.debug('looking up uri = %s', uri)\n+        local_path = path.uri_to_path(uri)\n+        if not self._is_in_basedir(local_path):\n+            logger.warn('Ignoring URI outside base dir: %s', local_path)\n+            return []\n+        try:\n+            result = self._scanner.scan(uri)\n+            track = utils.convert_tags_to_track(result.tags).copy(\n+                uri=uri, length=result.duration)\n+        except exceptions.ScannerError as e:\n+            logger.warning('Problem looking up %s: %s', uri, e)","path":"mopidy/files/library.py","position":88,"original_position":88,"commit_id":"3b1a16dcce127dfad80604e2aee6db7f6bca3c42","original_commit_id":"3b1a16dcce127dfad80604e2aee6db7f6bca3c42","user":{"login":"jodal","id":43726,"avatar_url":"https://avatars.githubusercontent.com/u/43726?v=3","gravatar_id":"","url":"https://api.github.com/users/jodal","html_url":"https://github.com/jodal","followers_url":"https://api.github.com/users/jodal/followers","following_url":"https://api.github.com/users/jodal/following{/other_user}","gists_url":"https://api.github.com/users/jodal/gists{/gist_id}","starred_url":"https://api.github.com/users/jodal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jodal/subscriptions","organizations_url":"https://api.github.com/users/jodal/orgs","repos_url":"https://api.github.com/users/jodal/repos","events_url":"https://api.github.com/users/jodal/events{/privacy}","received_events_url":"https://api.github.com/users/jodal/received_events","type":"User","site_admin":false},"body":"Scratch that. This is files on your own system, not Internet streams, so I guess warning level is OK. Change the text, though.","created_at":"2015-07-08T11:19:09Z","updated_at":"2015-07-08T11:19:09Z","html_url":"https://github.com/mopidy/mopidy/pull/1207#discussion_r34137304","pull_request_url":"https://api.github.com/repos/mopidy/mopidy/pulls/1207","_links":{"self":{"href":"https://api.github.com/repos/mopidy/mopidy/pulls/comments/34137304"},"html":{"href":"https://github.com/mopidy/mopidy/pull/1207#discussion_r34137304"},"pull_request":{"href":"https://api.github.com/repos/mopidy/mopidy/pulls/1207"}}},"pull_request":{"url":"https://api.github.com/repos/mopidy/mopidy/pulls/1207","id":38897963,"html_url":"https://github.com/mopidy/mopidy/pull/1207","diff_url":"https://github.com/mopidy/mopidy/pull/1207.diff","patch_url":"https://github.com/mopidy/mopidy/pull/1207.patch","issue_url":"https://api.github.com/repos/mopidy/mopidy/issues/1207","number":1207,"state":"open","locked":false,"title":"file-browser: initial commit","user":{"login":"rawdlite","id":1696183,"avatar_url":"https://avatars.githubusercontent.com/u/1696183?v=3","gravatar_id":"","url":"https://api.github.com/users/rawdlite","html_url":"https://github.com/rawdlite","followers_url":"https://api.github.com/users/rawdlite/followers","following_url":"https://api.github.com/users/rawdlite/following{/other_user}","gists_url":"https://api.github.com/users/rawdlite/gists{/gist_id}","starred_url":"https://api.github.com/users/rawdlite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rawdlite/subscriptions","organizations_url":"https://api.github.com/users/rawdlite/orgs","repos_url":"https://api.github.com/users/rawdlite/repos","events_url":"https://api.github.com/users/rawdlite/events{/privacy}","received_events_url":"https://api.github.com/users/rawdlite/received_events","type":"User","site_admin":false},"body":"This time based on branch develop.\r\n","created_at":"2015-06-30T16:33:00Z","updated_at":"2015-07-08T11:19:09Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7b4cd48c61290847e4291870d8a8084788e8fc6e","assignee":{"login":"jodal","id":43726,"avatar_url":"https://avatars.githubusercontent.com/u/43726?v=3","gravatar_id":"","url":"https://api.github.com/users/jodal","html_url":"https://github.com/jodal","followers_url":"https://api.github.com/users/jodal/followers","following_url":"https://api.github.com/users/jodal/following{/other_user}","gists_url":"https://api.github.com/users/jodal/gists{/gist_id}","starred_url":"https://api.github.com/users/jodal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jodal/subscriptions","organizations_url":"https://api.github.com/users/jodal/orgs","repos_url":"https://api.github.com/users/jodal/repos","events_url":"https://api.github.com/users/jodal/events{/privacy}","received_events_url":"https://api.github.com/users/jodal/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/mopidy/mopidy/milestones/40","html_url":"https://github.com/mopidy/mopidy/milestones/v1.1%20-%20Robust%20core","labels_url":"https://api.github.com/repos/mopidy/mopidy/milestones/40/labels","id":965996,"number":40,"title":"v1.1 - Robust core","description":"\r\n\r\n<!---\r\n@huboard:{\"order\":31.75}\r\n-->\r\n","creator":{"login":"jodal","id":43726,"avatar_url":"https://avatars.githubusercontent.com/u/43726?v=3","gravatar_id":"","url":"https://api.github.com/users/jodal","html_url":"https://github.com/jodal","followers_url":"https://api.github.com/users/jodal/followers","following_url":"https://api.github.com/users/jodal/following{/other_user}","gists_url":"https://api.github.com/users/jodal/gists{/gist_id}","starred_url":"https://api.github.com/users/jodal/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jodal/subscriptions","organizations_url":"https://api.github.com/users/jodal/orgs","repos_url":"https://api.github.com/users/jodal/repos","events_url":"https://api.github.com/users/jodal/events{/privacy}","received_events_url":"https://api.github.com/users/jodal/received_events","type":"User","site_admin":false},"open_issues":8,"closed_issues":36,"state":"open","created_at":"2015-02-04T19:26:36Z","updated_at":"2015-07-08T10:55:45Z","due_on":"2015-12-31T23:00:00Z","closed_at":null},"commits_url":"https://api.github.com/repos/mopidy/mopidy/pulls/1207/commits","review_comments_url":"https://api.github.com/repos/mopidy/mopidy/pulls/1207/comments","review_comment_url":"https://api.github.com/repos/mopidy/mopidy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mopidy/mopidy/issues/1207/comments","statuses_url":"https://api.github.com/repos/mopidy/mopidy/statuses/3b1a16dcce127dfad80604e2aee6db7f6bca3c42","head":{"label":"rawdlite:feature/file-browsing","ref":"feature/file-browsing","sha":"3b1a16dcce127dfad80604e2aee6db7f6bca3c42","user":{"login":"rawdlite","id":1696183,"avatar_url":"https://avatars.githubusercontent.com/u/1696183?v=3","gravatar_id":"","url":"https://api.github.com/users/rawdlite","html_url":"https://github.com/rawdlite","followers_url":"https://api.github.com/users/rawdlite/followers","following_url":"https://api.github.com/users/rawdlite/following{/other_user}","gists_url":"https://api.github.com/users/rawdlite/gists{/gist_id}","starred_url":"https://api.github.com/users/rawdlite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rawdlite/subscriptions","organizations_url":"https://api.github.com/users/rawdlite/orgs","repos_url":"https://api.github.com/users/rawdlite/repos","events_url":"https://api.github.com/users/rawdlite/events{/privacy}","received_events_url":"https://api.github.com/users/rawdlite/received_events","type":"User","site_admin":false},"repo":{"id":27315026,"name":"mopidy","full_name":"rawdlite/mopidy","owner":{"login":"rawdlite","id":1696183,"avatar_url":"https://avatars.githubusercontent.com/u/1696183?v=3","gravatar_id":"","url":"https://api.github.com/users/rawdlite","html_url":"https://github.com/rawdlite","followers_url":"https://api.github.com/users/rawdlite/followers","following_url":"https://api.github.com/users/rawdlite/following{/other_user}","gists_url":"https://api.github.com/users/rawdlite/gists{/gist_id}","starred_url":"https://api.github.com/users/rawdlite/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rawdlite/subscriptions","organizations_url":"https://api.github.com/users/rawdlite/orgs","repos_url":"https://api.github.com/users/rawdlite/repos","events_url":"https://api.github.com/users/rawdlite/events{/privacy}","received_events_url":"https://api.github.com/users/rawdlite/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/rawdlite/mopidy","description":"Mopidy is an extensible music server that plays music from local disk, Spotify, SoundCloud, Google Play Music, and more. You edit the playlist from any phone, tablet, or computer using a range of MPD and web clients.","fork":true,"url":"https://api.github.com/repos/rawdlite/mopidy","forks_url":"https://api.github.com/repos/rawdlite/mopidy/forks","keys_url":"https://api.github.com/repos/rawdlite/mopidy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rawdlite/mopidy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rawdlite/mopidy/teams","hooks_url":"https://api.github.com/repos/rawdlite/mopidy/hooks","issue_events_url":"https://api.github.com/repos/rawdlite/mopidy/issues/events{/number}","events_url":"https://api.github.com/repos/rawdlite/mopidy/events","assignees_url":"https://api.github.com/repos/rawdlite/mopidy/assignees{/user}","branches_url":"https://api.github.com/repos/rawdlite/mopidy/branches{/branch}","tags_url":"https://api.github.com/repos/rawdlite/mopidy/tags","blobs_url":"https://api.github.com/repos/rawdlite/mopidy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rawdlite/mopidy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rawdlite/mopidy/git/refs{/sha}","trees_url":"https://api.github.com/repos/rawdlite/mopidy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rawdlite/mopidy/statuses/{sha}","languages_url":"https://api.github.com/repos/rawdlite/mopidy/languages","stargazers_url":"https://api.github.com/repos/rawdlite/mopidy/stargazers","contributors_url":"https://api.github.com/repos/rawdlite/mopidy/contributors","subscribers_url":"https://api.github.com/repos/rawdlite/mopidy/subscribers","subscription_url":"https://api.github.com/repos/rawdlite/mopidy/subscription","commits_url":"https://api.github.com/repos/rawdlite/mopidy/commits{/sha}","git_commits_url":"https://api.github.com/repos/rawdlite/mopidy/git/commits{/sha}","comments_url":"https://api.github.com/repos/rawdlite/mopidy/comments{/number}","issue_comment_url":"https://api.github.com/repos/rawdlite/mopidy/issues/comments{/number}","contents_url":"https://api.github.com/repos/rawdlite/mopidy/contents/{+path}","compare_url":"https://api.github.com/repos/rawdlite/mopidy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rawdlite/mopidy/merges","archive_url":"https://api.github.com/repos/rawdlite/mopidy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rawdlite/mopidy/downloads","issues_url":"https://api.github.com/repos/rawdlite/mopidy/issues{/number}","pulls_url":"https://api.github.com/repos/rawdlite/mopidy/pulls{/number}","milestones_url":"https://api.github.com/repos/rawdlite/mopidy/milestones{/number}","notifications_url":"https://api.github.com/repos/rawdlite/mopidy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rawdlite/mopidy/labels{/name}","releases_url":"https://api.github.com/repos/rawdlite/mopidy/releases{/id}","created_at":"2014-11-29T21:05:42Z","updated_at":"2014-11-29T21:05:43Z","pushed_at":"2015-07-08T06:25:32Z","git_url":"git://github.com/rawdlite/mopidy.git","ssh_url":"git@github.com:rawdlite/mopidy.git","clone_url":"https://github.com/rawdlite/mopidy.git","svn_url":"https://github.com/rawdlite/mopidy","homepage":"www.mopidy.com","size":18705,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"develop"}},"base":{"label":"mopidy:develop","ref":"develop","sha":"3b1b0dd2e9052cadb7bd1a29e724498f9fba7bca","user":{"login":"mopidy","id":451998,"avatar_url":"https://avatars.githubusercontent.com/u/451998?v=3","gravatar_id":"","url":"https://api.github.com/users/mopidy","html_url":"https://github.com/mopidy","followers_url":"https://api.github.com/users/mopidy/followers","following_url":"https://api.github.com/users/mopidy/following{/other_user}","gists_url":"https://api.github.com/users/mopidy/gists{/gist_id}","starred_url":"https://api.github.com/users/mopidy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mopidy/subscriptions","organizations_url":"https://api.github.com/users/mopidy/orgs","repos_url":"https://api.github.com/users/mopidy/repos","events_url":"https://api.github.com/users/mopidy/events{/privacy}","received_events_url":"https://api.github.com/users/mopidy/received_events","type":"Organization","site_admin":false},"repo":{"id":447036,"name":"mopidy","full_name":"mopidy/mopidy","owner":{"login":"mopidy","id":451998,"avatar_url":"https://avatars.githubusercontent.com/u/451998?v=3","gravatar_id":"","url":"https://api.github.com/users/mopidy","html_url":"https://github.com/mopidy","followers_url":"https://api.github.com/users/mopidy/followers","following_url":"https://api.github.com/users/mopidy/following{/other_user}","gists_url":"https://api.github.com/users/mopidy/gists{/gist_id}","starred_url":"https://api.github.com/users/mopidy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mopidy/subscriptions","organizations_url":"https://api.github.com/users/mopidy/orgs","repos_url":"https://api.github.com/users/mopidy/repos","events_url":"https://api.github.com/users/mopidy/events{/privacy}","received_events_url":"https://api.github.com/users/mopidy/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mopidy/mopidy","description":"Mopidy is an extensible music server that plays music from local disk, Spotify, SoundCloud, Google Play Music, and more. You edit the playlist from any phone, tablet, or computer using a range of MPD and web clients.","fork":false,"url":"https://api.github.com/repos/mopidy/mopidy","forks_url":"https://api.github.com/repos/mopidy/mopidy/forks","keys_url":"https://api.github.com/repos/mopidy/mopidy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mopidy/mopidy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mopidy/mopidy/teams","hooks_url":"https://api.github.com/repos/mopidy/mopidy/hooks","issue_events_url":"https://api.github.com/repos/mopidy/mopidy/issues/events{/number}","events_url":"https://api.github.com/repos/mopidy/mopidy/events","assignees_url":"https://api.github.com/repos/mopidy/mopidy/assignees{/user}","branches_url":"https://api.github.com/repos/mopidy/mopidy/branches{/branch}","tags_url":"https://api.github.com/repos/mopidy/mopidy/tags","blobs_url":"https://api.github.com/repos/mopidy/mopidy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mopidy/mopidy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mopidy/mopidy/git/refs{/sha}","trees_url":"https://api.github.com/repos/mopidy/mopidy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mopidy/mopidy/statuses/{sha}","languages_url":"https://api.github.com/repos/mopidy/mopidy/languages","stargazers_url":"https://api.github.com/repos/mopidy/mopidy/stargazers","contributors_url":"https://api.github.com/repos/mopidy/mopidy/contributors","subscribers_url":"https://api.github.com/repos/mopidy/mopidy/subscribers","subscription_url":"https://api.github.com/repos/mopidy/mopidy/subscription","commits_url":"https://api.github.com/repos/mopidy/mopidy/commits{/sha}","git_commits_url":"https://api.github.com/repos/mopidy/mopidy/git/commits{/sha}","comments_url":"https://api.github.com/repos/mopidy/mopidy/comments{/number}","issue_comment_url":"https://api.github.com/repos/mopidy/mopidy/issues/comments{/number}","contents_url":"https://api.github.com/repos/mopidy/mopidy/contents/{+path}","compare_url":"https://api.github.com/repos/mopidy/mopidy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mopidy/mopidy/merges","archive_url":"https://api.github.com/repos/mopidy/mopidy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mopidy/mopidy/downloads","issues_url":"https://api.github.com/repos/mopidy/mopidy/issues{/number}","pulls_url":"https://api.github.com/repos/mopidy/mopidy/pulls{/number}","milestones_url":"https://api.github.com/repos/mopidy/mopidy/milestones{/number}","notifications_url":"https://api.github.com/repos/mopidy/mopidy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mopidy/mopidy/labels{/name}","releases_url":"https://api.github.com/repos/mopidy/mopidy/releases{/id}","created_at":"2009-12-23T14:25:36Z","updated_at":"2015-07-07T19:57:14Z","pushed_at":"2015-07-08T11:05:35Z","git_url":"git://github.com/mopidy/mopidy.git","ssh_url":"git@github.com:mopidy/mopidy.git","clone_url":"https://github.com/mopidy/mopidy.git","svn_url":"https://github.com/mopidy/mopidy","homepage":"www.mopidy.com","size":26969,"stargazers_count":2063,"watchers_count":2063,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":242,"mirror_url":null,"open_issues_count":151,"forks":242,"open_issues":151,"watchers":2063,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/mopidy/mopidy/pulls/1207"},"html":{"href":"https://github.com/mopidy/mopidy/pull/1207"},"issue":{"href":"https://api.github.com/repos/mopidy/mopidy/issues/1207"},"comments":{"href":"https://api.github.com/repos/mopidy/mopidy/issues/1207/comments"},"review_comments":{"href":"https://api.github.com/repos/mopidy/mopidy/pulls/1207/comments"},"review_comment":{"href":"https://api.github.com/repos/mopidy/mopidy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mopidy/mopidy/pulls/1207/commits"},"statuses":{"href":"https://api.github.com/repos/mopidy/mopidy/statuses/3b1a16dcce127dfad80604e2aee6db7f6bca3c42"}}}}