[
  {
    "path": ".gitignore",
    "content": "venv\n*.pyc"
  },
  {
    "path": "README.md",
    "content": "# redditfs\n\nEverything is better in an 80x25 green-and-black terminal -- even Reddit! `redditfs` maps subreddits to a FUSE filesystem, so you can use your favorite shell (or GUI file browser, or SSH, or FTP) to browse Reddit.\n\n## Example\n\n    $ ls -l /r/programming\n    total 0\n    dr-xr-xr-x@ 3 root  wheel  0 Jan 16 10:30 ANN:_pandas_0130_released\n    dr-xr-xr-x@ 3 root  wheel  0 Jan 18 13:08 An_evaluation_of_simple_Python_performance_tweaks\n    dr-xr-xr-x@ 3 root  wheel  0 Jan 17 08:36 Anyone_have_experience_installing_Folium_for_Py_33?\n    dr-xr-xr-x@ 3 root  wheel  0 Jan 18 09:48 Are_there_any_python_made_games_on_Steam?\n    dr-xr-xr-x@ 3 root  wheel  0 Jan 16 20:06 Beginner:_Getting_Beyond_Syntax\n    dr-xr-xr-x@ 3 root  wheel  0 Jan 18 00:53 Best_Questions_to_ask_when_hiring_a_Python_dev?\n    ...\n    $ ls -l /r/programming/An_evaluation_of_simple_Python_performance_tweaks\n    total 16\n    -r--r--r--@ 1 root  wheel  97 Jan 18 13:08 permalink\n    -r--r--r--@ 1 root  wheel   0 Jan 18 13:08 selftext\n    -r--r--r--@ 1 root  wheel  72 Jan 18 13:08 url\n    $ cat /r/programming/Best_Questions_to_ask_when_hiring_a_Python_dev?/selftext\n    I'm a long time C/C++/C# dev who is now diving into python head on, and using it\n     on a project here in Seattle.  Part of this is I need to grow my team and hire \n    ...\n    $ lynx $(cat /r/programming/An_evaluation_of_simple_Python_performance_tweaks/url)\n    \n## Howto\n\nYou'll need Python >= 2.7 or >= 3.4, and FUSE. You can install FUSE via the package managers on most Linux distros. On OSX, you can get FUSE support via [OSXFUSE](http://osxfuse.github.io/).\n\n    $ git clone https://github.com/ianpreston/redditfs.git\n    $ cd redditfs && virtualenv env && source env/bin/activate\n    $ pip install -r reqs.txt\n    $ mkdir /r\n    $ python redditfs.py /r\n    \n## License\n\nAvailable under the MIT License.\n"
  },
  {
    "path": "fsfile.py",
    "content": "import stat\nimport enum\n\n\nDirectoryType = enum.Enum('DirectoryType', 'root normal subreddit')\n\n\nclass FSFile(object):\n    BASE_MODE = stat.S_IFREG\n\n    def __init__(self, filename, mode, content, ctime):\n        self.filename = filename\n        self._mode    = FSFile.BASE_MODE | mode\n        self._content = content.encode('ascii', errors='ignore')\n        self._size    = len(self._content)\n        self._time    = ctime\n\n    def getattr(self):\n        return {\n            'st_size': self._size,\n            'st_nlink': 1,\n            'st_ctime': self._time,\n            'st_mtime': self._time,\n            'st_atime': self._time,\n            'st_mode': self._mode,\n        }\n\n    def read(self, size, offset):\n        return self._content[offset:offset+size]\n\n    def dir(self):\n        return False\n\n\nclass FSDirectory(object):\n    BASE_MODE = stat.S_IFDIR\n\n    def __init__(self, filename, dirtype, mode, ctime):\n        self.filename  = filename\n        self.dirtype   = dirtype\n        self._mode     = FSDirectory.BASE_MODE | mode\n        self._time     = ctime\n        self._children = {}\n\n    def add_child(self, child):\n        self._children[child.filename] = child\n\n    def get_child(self, path):\n        return self._children.get(path)\n\n    def remove_child(self, path):\n        del self._children[path]\n\n    def getattr(self):\n        return {\n            'st_size': 0,\n            'st_nlink': len(self._children),\n            'st_ctime': self._time,\n            'st_mtime': self._time,\n            'st_atime': self._time,\n            'st_mode': self._mode,\n        }\n\n    def readdir(self):\n        return ['.', '..'] + list(self._children.keys())\n\n    def dir(self):\n        return True\n"
  },
  {
    "path": "redditfs.py",
    "content": "import fuse\nimport errno\nimport stat\nimport time\nimport sys\nimport requests\nimport os\nimport os.path\nfrom fsfile import *\n\ntry:\n    import urlparse\nexcept ImportError:\n    import urllib.parse as urlparse\n\n\nCACHE_TIMEOUT = 60 * 60\n\n\nclass RedditFS(fuse.Operations):\n    PERMS = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH\n    DIR_PERMS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH\n\n    def __init__(self):\n        self.fd = 0\n        self.fs = FSDirectory(\n            '/',\n            DirectoryType.root,\n            RedditFS.PERMS | RedditFS.DIR_PERMS,\n            time.time(),\n        )\n\n    @property\n    def dirlist(self):\n        if not self._dirlist:\n            self._dirlist = self._populate_dirlist()\n        return self._dirlist\n\n    def open(self, path, flags):\n        self.fd += 1\n        return self.fd\n\n    def getattr(self, path, fh=None):\n        f = self.traverse(path)\n\n        if f is None:\n            raise fuse.FuseOSError(errno.ENOENT)\n\n        return f.getattr()\n\n    def read(self, path, size, offset, fh):\n        f = self.traverse(path)\n        if f is None:\n            raise fuse.FuseOSError(errno.ENOENT)\n        if f.dir():\n            raise fuse.FuseOSError(errno.EISDIR)\n\n        return f.read(size, offset)\n\n    def readdir(self, path, fh):\n        f = self.traverse(path)\n        if f is None:\n            raise fuse.FuseOSError(errno.ENOENT)\n        if not f.dir():\n            raise fuse.FuseOSError(errno.ENOTDIR)\n\n        return f.readdir()\n\n    def traverse(self, path):\n        path = self._split_path(path)\n        node = self.fs\n        return self._traverse(path, node)\n\n    def _traverse(self, path, node):\n        if len(path) == 0:\n            return node\n\n        fn = path.pop(0)\n        next_node = node.get_child(fn)\n\n        if node.dirtype == DirectoryType.root:\n            self._lazy_load_subreddit(next_node, fn)\n\n        return self._traverse(path, next_node)\n\n    def _split_path(self, path):\n        # TODO Move to a util module ?\n        head, tail = os.path.split(path)\n        if tail == '':\n            return []\n        if head == '' or head == os.sep:\n            return [tail]\n        return self._split_path(head) + [tail]\n\n    def _lazy_load_subreddit(self, node, filename):\n        # If the directory does not exist, attempt to load it\n        if node is None:\n            return self._populate_subreddit(filename)\n\n        # If the directory exists but was created more than CACHE_TIMEOUT\n        # seconds ago, re-populate the directory\n        if node.getattr().get('st_ctime') < (time.time() - CACHE_TIMEOUT):\n            self.fs.remove_child(node.filename)\n            return self._populate_subreddit(node.filename)\n\n        # The directory exists and is fresh, return it\n        return node\n\n    def _populate_subreddit(self, subreddit):\n        r = requests.get(\n            'http://api.reddit.com/r/{}/hot'.format(subreddit),\n            headers={\n                'User-Agent': 'redditfs /u/evilyomiel'\n            },\n            allow_redirects=False,\n        )\n        if r.status_code in [404, 302]:\n            return\n        r.raise_for_status()\n\n        links = [link['data'] for link in r.json()['data']['children']]\n\n        root_file = FSDirectory(\n            filename=subreddit,\n            dirtype=DirectoryType.subreddit,\n            mode=RedditFS.PERMS | RedditFS.PERMS,\n            ctime=time.time(),\n        )\n\n        for zelda in links:\n            self._add_reddit_link_to_fs(root_file, zelda)\n        self.fs.add_child(root_file)\n\n        return root_file\n\n    def _add_reddit_link_to_fs(self, fs, zelda):\n        title    = zelda['title']\n        filename = self._sanitize_path(title)\n\n        permalink = urlparse.urljoin(\n            'http://www.reddit.com/',\n            zelda['permalink']\n        )\n        url = zelda['url']\n        selftext = zelda['selftext']\n\n        root_file = FSDirectory(\n            filename=filename,\n            dirtype=DirectoryType.normal,\n            mode=RedditFS.PERMS | RedditFS.DIR_PERMS,\n            ctime=zelda['created_utc'],\n        )\n\n        permalink_file = FSFile(\n            filename='permalink',\n            mode=RedditFS.PERMS,\n            content=permalink,\n            ctime=zelda['created_utc'],\n        )\n\n        url_file = FSFile(\n            filename='url',\n            mode=RedditFS.PERMS,\n            content=url,\n            ctime=zelda['created_utc'],\n        )\n\n        selftext_file = FSFile(\n            filename='selftext',\n            mode=RedditFS.PERMS,\n            content=selftext,\n            ctime=zelda['created_utc'],\n        )\n\n        for f in (permalink_file, url_file, selftext_file):\n            root_file.add_child(f)\n        fs.add_child(root_file)\n\n    def _sanitize_path(self, path):\n        replace = (\n            ('/', ''),\n            (' ', '_'),\n            (\"'\", ''),\n            ('\"', ''),\n        )\n        for r in replace:\n            path = path.replace(*r)\n        return path.lower()\n\n\ndef main():\n    fuse.FUSE(RedditFS(), sys.argv[1], foreground=True, nothreads=True)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "reqs.txt",
    "content": "enum34==1.0\nfusepy==2.0.2\nrequests==2.2.0\n"
  }
]