#!/usr/bin/env python
#
# Connector for elFinder File Manager
# author Troex Nevelin <troex@fury.scancode.ru>

import hashlib
import mimetypes
import os
import os.path
import re
import shutil
import time
from datetime import datetime

import threading
threading._DummyThread._Thread__stop = lambda x: 42


class connector():
    """Connector for elFinder"""

    _options = {
        'root': '',
        'URL': '',
        'rootAlias': 'Home',
        'dotFiles': False,
        'dirSize': True,
        'fileMode': 0644,
        'dirMode': 0755,
        'imgLib': 'auto',
        'tmbDir': '.tmb',
        'tmbAtOnce': 5,
        'tmbSize': 48,
        'fileURL': True,
        'uploadMaxSize': 256,
        'uploadWriteChunk': 8192,
        'uploadAllow': [],
        'uploadDeny': [],
        'uploadOrder': ['deny', 'allow'],
        # 'aclObj': None, # TODO
        # 'aclRole': 'user', # TODO
        'defaults': {
        'read': True,
        'write': True,
        'rm': True
        },
        'perms': {},
        'archiveMimes': {},
        'archivers': {
        'create': {},
        'extract': {}
        },
        'disabled': [],
        'debug': False
    }

    _commands = {
        'open': '__open',
        'reload': '__reload',
        'mkdir': '__mkdir',
        'mkfile': '__mkfile',
        'rename': '__rename',
        'upload': '__upload',
        'paste': '__paste',
        'rm': '__rm',
        'duplicate': '__duplicate',
        'read': '__read',
        'edit': '__edit',
        'extract': '__extract',
        'archive': '__archive',
        'resize': '__resize',
        'tmb': '__thumbnails',
        'ping': '__ping'
    }

    _mimeType = {
        # text
        'txt': 'text/plain',
        'conf': 'text/plain',
        'ini': 'text/plain',
        'php': 'text/x-php',
        'html': 'text/html',
        'htm': 'text/html',
        'js': 'text/javascript',
        'css': 'text/css',
        'rtf': 'text/rtf',
        'rtfd': 'text/rtfd',
        'py': 'text/x-python',
        'java': 'text/x-java-source',
        'rb': 'text/x-ruby',
        'sh': 'text/x-shellscript',
        'pl': 'text/x-perl',
        'sql': 'text/x-sql',
        # apps
        'doc': 'application/msword',
        'ogg': 'application/ogg',
        '7z': 'application/x-7z-compressed',
        # video
        'ogm': 'appllication/ogm',
        'mkv': 'video/x-matroska'
    }

    _time = 0
    _request = {}
    _response = {}
    _errorData = {}
    _form = {}
    _im = None
    _sp = None
    _today = 0
    _yesterday = 0

    # public variables
    httpAllowedParameters = (
        'cmd', 'target', 'targets[]', 'current', 'tree', 'name',
        'content', 'src', 'dst', 'cut', 'init', 'type', 'width', 'height',
        'upload[]')
    # return variables
    httpStatusCode = 0
    httpHeader = {}
    httpResponse = None

    def __init__(self, opts):
        for opt in opts:
            self._options[opt] = opts.get(opt)

        self._response['debug'] = {}
        self._options['URL'] = self.__checkUtf8(self._options['URL'])
        self._options['URL'] = self._options['URL'].rstrip('/')
        self._options['root'] = self.__checkUtf8(self._options['root'])
        self._options['root'] = self._options['root'].rstrip(os.sep)
        self.__debug('URL', self._options['URL'])
        self.__debug('root', self._options['root'])

        for cmd in self._options['disabled']:
            if cmd in self._commands:
                del self._commands[cmd]

        if self._options['tmbDir']:
            self._options['tmbDir'] = os.path.join(
                self._options['root'], self._options['tmbDir'])
            if not os.path.exists(self._options['tmbDir']):
                self._options['tmbDir'] = False

    def __reset(self):
        """Flush per request variables"""
        self.httpStatusCode = 0
        self.httpHeader = {}
        self.httpResponse = None
        self._request = {}
        self._response = {}
        self._errorData = {}
        self._form = {}

        self._time = time.time()
        t = datetime.fromtimestamp(self._time)
        self._today = time.mktime(datetime(t.year, t.month, t.day).timetuple())
        self._yesterday = self._today - 86400

        self._response['debug'] = {}

    def run(self, httpRequest=[]):
        """main function"""
        self.__reset()
        rootOk = True
        if not os.path.exists(self._options['root'])\
                or self._options['root'] == '':
            rootOk = False
            self._response['error'] = 'Invalid backend configuration'
        elif not self.__isAllowed(self._options['root'], 'read'):
            rootOk = False
            self._response['error'] = 'Access denied'

        for field in self.httpAllowedParameters:
            if field in httpRequest:
                self._request[field] = httpRequest[field]

        if rootOk is True:
            if 'cmd' in self._request:
                if self._request['cmd'] in self._commands:
                    cmd = self._commands[self._request['cmd']]
                    func = getattr(
                        self, '_' + self.__class__.__name__ + cmd, None)
                    if callable(func):
                        try:
                            func()
                        except Exception, e:
                            self._response['error'] = 'Command Failed'
                            self.__debug('exception', str(e))
                else:
                    self._response['error'] = 'Unknown command'
            else:
                self.__open()

            if 'init' in self._request:
                self.__checkArchivers()
                self._response['disabled'] = self._options['disabled']
                if not self._options['fileURL']:
                    url = ''
                else:
                    url = self._options['URL']
                self._response['params'] = {
                    'dotFiles': self._options['dotFiles'],
                    'uplMaxSize': str(self._options['uploadMaxSize']) + 'M',
                    'archives': self._options['archivers']['create'].keys(),
                    'extract': self._options['archivers']['extract'].keys(),
                    'url': url
                }

        if self._errorData:
            self._response['errorData'] = self._errorData

        if self._options['debug']:
            self.__debug('time', (time.time() - self._time))
        else:
            if 'debug' in self._response:
                del self._response['debug']

        if self.httpStatusCode < 100:
            self.httpStatusCode = 200

        if not 'Content-type' in self.httpHeader:
            if ('cmd' in self._request and self._request['cmd'] == 'upload')\
                    or self._options['debug']:
                self.httpHeader['Content-type'] = 'text/html'
            else:
                self.httpHeader['Content-type'] = 'application/json'

        self.httpResponse = self._response

        return self.httpStatusCode, self.httpHeader, self.httpResponse

    def __open(self):
        """Open file or directory"""
        # try to open file
        if 'current' in self._request:
            curDir = self.__findDir(self._request['current'], None)
            curFile = self.__find(self._request['target'], curDir)

            if not curDir or not curFile or os.path.isdir(curFile):
                self.httpStatusCode = 404
                self.httpHeader['Content-type'] = 'text/html'
                self.httpResponse = 'File not found'
                return
            if not self.__isAllowed(curDir, 'read')\
                    or not self.__isAllowed(curFile, 'read'):
                self.httpStatusCode = 403
                self.httpHeader['Content-type'] = 'text/html'
                self.httpResponse = 'Access denied'
                return

            if os.path.islink(curFile):
                curFile = self.__readlink(curFile)
                if not curFile or os.path.isdir(curFile):
                    self.httpStatusCode = 404
                    self.httpHeader['Content-type'] = 'text/html'
                    self.httpResponse = 'File not found'
                    return
                if (
                        not self.__isAllowed(os.path.dirname(curFile), 'read')
                        or not self.__isAllowed(curFile, 'read')
                ):
                    self.httpStatusCode = 403
                    self.httpHeader['Content-type'] = 'text/html'
                    self.httpResponse = 'Access denied'
                    return

            mime = self.__mimetype(curFile)
            parts = mime.split('/', 2)
            if parts[0] == 'image':
                disp = 'image'
            elif parts[0] == 'text':
                disp = 'inline'
            else:
                disp = 'attachments'

            self.httpStatusCode = 200
            self.httpHeader['Content-type'] = mime
            self.httpHeader[
                'Content-Disposition'] = disp + '; filename='\
                + os.path.basename(curFile)
            self.httpHeader['Content-Location'] = curFile.replace(
                self._options['root'], '')
            self.httpHeader['Content-Transfer-Encoding'] = 'binary'
            self.httpHeader['Content-Length'] = str(os.lstat(curFile).st_size)
            self.httpHeader['Connection'] = 'close'
            self._response['file'] = open(curFile, 'r')
            return
        # try dir
        else:
            path = self._options['root']

            if 'target' in self._request and self._request['target']:
                target = self.__findDir(self._request['target'], None)
                if not target:
                    self._response['error'] = 'Invalid parameters'
                elif not self.__isAllowed(target, 'read'):
                    self._response['error'] = 'Access denied'
                else:
                    path = target

            self.__content(path, 'tree' in self._request)
        pass

    def __rename(self):
        """Rename file or dir"""
        current = name = target = None
        curDir = curName = newName = None
        if 'name' in self._request and 'current' in self._request\
                and 'target' in self._request:
            name = self._request['name']
            current = self._request['current']
            target = self._request['target']
            curDir = self.__findDir(current, None)
            curName = self.__find(target, curDir)
            newName = os.path.join(curDir, name)

        if not curDir or not curName:
            self._response['error'] = 'File not found'
        elif not self.__isAllowed(curDir, 'write')\
                and self.__isAllowed(curName, 'rm'):
            self._response['error'] = 'Access denied'
        elif not self.__checkName(name):
            self._response['error'] = 'Invalid name'
        elif os.path.exists(newName):
            self._response[
                'error'] = 'File or folder with the same name already exists'
        else:
            self.__rmTmb(curName)
            try:
                os.rename(curName, newName)
                self._response['select'] = [self.__hash(newName)]
                self.__content(curDir, os.path.isdir(newName))
            except:
                self._response['error'] = 'Unable to rename file'

    def __mkdir(self):
        """Create new directory"""
        current = None
        path = None
        newDir = None
        if 'name' in self._request and 'current' in self._request:
            name = self._request['name']
            current = self._request['current']
            path = self.__findDir(current, None)
            newDir = os.path.join(path, name)

        if not path:
            self._response['error'] = 'Invalid parameters'
        elif not self.__isAllowed(path, 'write'):
            self._response['error'] = 'Access denied'
        elif not self.__checkName(name):
            self._response['error'] = 'Invalid name'
        elif os.path.exists(newDir):
            self._response[
                'error'] = 'File or folder with the same name already exists'
        else:
            try:
                os.mkdir(newDir, int(self._options['dirMode']))
                self._response['select'] = [self.__hash(newDir)]
                self.__content(path, True)
            except:
                self._response['error'] = 'Unable to create folder'

    def __mkfile(self):
        """Create new file"""
        name = current = None
        curDir = newFile = None
        if 'name' in self._request and 'current' in self._request:
            name = self._request['name']
            current = self._request['current']
            curDir = self.__findDir(current, None)
            newFile = os.path.join(curDir, name)

        if not curDir or not name:
            self._response['error'] = 'Invalid parameters'
        elif not self.__isAllowed(curDir, 'write'):
            self._response['error'] = 'Access denied'
        elif not self.__checkName(name):
            self._response['error'] = 'Invalid name'
        elif os.path.exists(newFile):
            self._response[
                'error'] = 'File or folder with the same name already exists'
        else:
            try:
                open(newFile, 'w').close()
                self._response['select'] = [self.__hash(newFile)]
                self.__content(curDir, False)
            except:
                self._response['error'] = 'Unable to create file'

    def __rm(self):
        """Delete files and directories"""
        current = rmList = None
        curDir = rmFile = None
        if 'current' in self._request and 'targets[]' in self._request:
            current = self._request['current']
            rmList = self._request['targets[]']
            curDir = self.__findDir(current, None)

        if not rmList or not curDir:
            self._response['error'] = 'Invalid parameters'
            return False

        if not isinstance(rmList, list):
            rmList = [rmList]

        for rm in rmList:
            rmFile = self.__find(rm, curDir)
            if not rmFile:
                continue
            self.__remove(rmFile)
        # TODO if errorData not empty return error
        self.__content(curDir, True)

    def __upload(self):
        """Upload files"""
        try:  # Windows needs stdio set for binary mode.
            import msvcrt
            msvcrt.setmode(0, os.O_BINARY)  # stdin  = 0
            msvcrt.setmode(1, os.O_BINARY)  # stdout = 1
        except ImportError:
            pass

        if 'current' in self._request:
            curDir = self.__findDir(self._request['current'], None)
            if not curDir:
                self._response['error'] = 'Invalid parameters'
                return
            if not self.__isAllowed(curDir, 'write'):
                self._response['error'] = 'Access denied'
                return
            if not 'upload[]' in self._request:
                self._response['error'] = 'No file to upload'
                return

            upFiles = self._request['upload[]']
            # invalid format
            # must be dict('filename1': 'filedescriptor1', 'filename2':
            # 'filedescriptor2', ...)
            if not isinstance(upFiles, dict):
                self._response['error'] = 'Invalid parameters'
                return

            self._response['select'] = []
            total = 0
            upSize = 0
            maxSize = self._options['uploadMaxSize'] * 1024 * 1024
            for name, data in upFiles.iteritems():
                if name:
                    total += 1
                    name = os.path.basename(name)
                    if not self.__checkName(name):
                        self.__errorData(name, 'Invalid name')
                    else:
                        name = os.path.join(curDir, name)
                        try:
                            f = open(name, 'wb', self._options[
                                     'uploadWriteChunk'])
                            for chunk in self.__fbuffer(data):
                                f.write(chunk)
                            f.close()
                            upSize += os.lstat(name).st_size
                            if self.__isUploadAllow(name):
                                os.chmod(name, self._options['fileMode'])
                                self._response['select'].append(
                                    self.__hash(name, True))
                            else:
                                self.__errorData(name, 'Not allowed file type')
                                try:
                                    os.unlink(name)
                                except:
                                    pass
                        except:
                            self.__errorData(
                                name, 'Unable to save uploaded file')
                        if upSize > maxSize:
                            try:
                                os.unlink(name)
                                self.__errorData(
                                    name, 'File exceeds the maximum ' +
                                    'allowed filesize')
                            except:
                                pass
                                # TODO ?
                                self.__errorData(
                                    name, 'File was only partially uploaded')
                            break

            if self._errorData:
                if len(self._errorData) == total:
                    self._response['error'] = 'Unable to upload files'
                else:
                    self._response['error'] = 'Some files was not uploaded'

            self.__content(curDir, False)
            return

    def __paste(self):
        """Copy or cut files/directories"""
        if 'current' in self._request and 'src' in self._request\
                and 'dst' in self._request:
            curDir = self.__findDir(self._request['current'], None)
            src = self.__findDir(self._request['src'], None)
            dst = self.__findDir(self._request['dst'], None)
            if not curDir or not src or not dst\
                    or not 'targets[]' in self._request:
                self._response['error'] = 'Invalid parameters'
                return
            files = self._request['targets[]']
            if not isinstance(files, list):
                files = [files]

            cut = False
            if 'cut' in self._request:
                if self._request['cut'] == '1':
                    cut = True

            if not self.__isAllowed(src, 'read')\
                    or not self.__isAllowed(dst, 'write'):
                self._response['error'] = 'Access denied'
                return

            for fhash in files:
                f = self.__find(fhash, src)
                if not f:
                    self._response['error'] = 'File not found'
                    return
                newDst = os.path.join(dst, os.path.basename(f))
                if dst.find(f) == 0:
                    self._response['error'] = 'Unable to copy into itself'
                    return

                if cut:
                    if not self.__isAllowed(f, 'rm'):
                        self._response['error'] = 'Move failed'
                        self._errorData(f, 'Access denied')
                        self.__content(curDir, True)
                        return
                    # TODO thumbs
                    if os.path.exists(newDst):
                        self._response['error'] = 'Unable to move files'
                        self._errorData(
                            f, 'File or folder with the same name ' +
                            'already exists')
                        self.__content(curDir, True)
                        return
                    try:
                        os.rename(f, newDst)
                        self.__rmTmb(f)
                        continue
                    except:
                        self._response['error'] = 'Unable to move files'
                        self._errorData(f, 'Unable to move')
                        self.__content(curDir, True)
                        return
                else:
                    if not self.__copy(f, newDst):
                        self._response['error'] = 'Unable to copy files'
                        self.__content(curDir, True)
                        return
                    continue
            self.__content(curDir, True)
        else:
            self._response['error'] = 'Invalid parameters'

        return

    def __duplicate(self):
        """Create copy of files/directories"""
        if 'current' in self._request and 'target' in self._request:
            curDir = self.__findDir(self._request['current'], None)
            target = self.__find(self._request['target'], curDir)
            if not curDir or not target:
                self._response['error'] = 'Invalid parameters'
                return
            if not self.__isAllowed(target, 'read')\
                    or not self.__isAllowed(curDir, 'write'):
                self._response['error'] = 'Access denied'
            newName = self.__uniqueName(target)
            if not self.__copy(target, newName):
                self._response['error'] = 'Unable to create file copy'
                return

        self.__content(curDir, True)
        return

    def __resize(self):
        """Scale image size"""
        if not (
                'current' in self._request and 'target' in self._request
                and 'width' in self._request and 'height' in self._request
        ):
            self._response['error'] = 'Invalid parameters'
            return

        width = int(self._request['width'])
        height = int(self._request['height'])
        curDir = self.__findDir(self._request['current'], None)
        curFile = self.__find(self._request['target'], curDir)

        if width < 1 or height < 1 or not curDir or not curFile:
            self._response['error'] = 'Invalid parameters'
            return
        if not self.__isAllowed(curFile, 'write'):
            self._response['error'] = 'Access denied'
            return
        if not self.__mimetype(curFile).find('image') == 0:
            self._response['error'] = 'File is not an image'
            return

        self.__debug('resize ' + curFile, str(width) + ':' + str(height))
        self.__initImgLib()
        try:
            im = self._im.open(curFile)
            imResized = im.resize((width, height), self._im.ANTIALIAS)
            imResized.save(curFile)
        except Exception, e:
            self.__debug('resizeFailed_' + path, str(e))
            self._response['error'] = 'Unable to resize image'
            return

        self._response['select'] = [self.__hash(curFile)]
        self.__content(curDir, False)
        return

    def __thumbnails(self):
        """Create previews for images"""
        if 'current' in self._request:
            curDir = self.__findDir(self._request['current'], None)
            if not curDir or curDir == self._options['tmbDir']:
                return False
        else:
            return False

        self.__initImgLib()
        if self.__canCreateTmb():
            if self._options['tmbAtOnce'] > 0:
                tmbMax = self._options['tmbAtOnce']
            else:
                tmbMax = 5
            self._response['current'] = self.__hash(curDir)
            self._response['images'] = {}
            i = 0
            for f in os.listdir(curDir):
                path = os.path.join(curDir, f)
                fhash = self.__hash(path)
                if self.__canCreateTmb(path)\
                        and self.__isAllowed(path, 'read'):
                    tmb = os.path.join(self._options['tmbDir'], fhash + '.png')
                    if not os.path.exists(tmb):
                        if self.__tmb(path, tmb):
                            self._response['images'].update({
                                fhash: self.__path2url(tmb)
                            })
                            i += 1
                if i >= tmbMax:
                    self._response['tmb'] = True
                    break
        else:
            return False

        return

    def __content(self, path, tree):
        """CWD + CDC + maybe(TREE)"""
        self.__cwd(path)
        self.__cdc(path)

        if tree:
            self._response['tree'] = self.__tree(self._options['root'])

    def __cwd(self, path):
        """Current Working Directory"""
        name = os.path.basename(path)
        if path == self._options['root']:
            name = self._options['rootAlias']
            root = True
        else:
            root = False

        if self._options['rootAlias']:
            basename = self._options['rootAlias']
        else:
            basename = os.path.basename(self._options['root'])

        rel = basename + path[len(self._options['root']):]

        self._response['cwd'] = {
            'hash': self.__hash(path),
            'name': self.__checkUtf8(name),
            'mime': 'directory',
            'rel': self.__checkUtf8(rel),
            'size': 0,
            'date': datetime.fromtimestamp(os.stat(path).st_mtime).
            strftime("%d %b %Y %H:%M"),
            'read': True,
            'write': self.__isAllowed(path, 'write'),
            'rm': not root and self.__isAllowed(path, 'rm')
        }

    def __cdc(self, path):
        """Current Directory Content"""
        files = []
        dirs = []

        for f in sorted(os.listdir(path)):
            if not self.__isAccepted(f):
                continue
            pf = os.path.join(path, f)
            info = {}
            info = self.__info(pf)
            info['hash'] = self.__hash(pf)
            if info['mime'] == 'directory':
                dirs.append(info)
            else:
                files.append(info)

        dirs.extend(files)
        self._response['cdc'] = dirs

    def __info(self, path):
        mime = ''
        filetype = 'file'
        if os.path.isfile(path):
            filetype = 'file'
        if os.path.isdir(path):
            filetype = 'dir'
        if os.path.islink(path):
            filetype = 'link'

        stat = os.lstat(path)
        statDate = datetime.fromtimestamp(stat.st_mtime)

        fdate = ''
        if stat.st_mtime >= self._today:
            fdate = 'Today ' + statDate.strftime("%H:%M")
        elif stat.st_mtime >= self._yesterday and stat.st_mtime < self._today:
            fdate = 'Yesterday ' + statDate.strftime("%H:%M")
        else:
            fdate = statDate.strftime("%d %b %Y %H:%M")

        info = {
            'name': self.__checkUtf8(os.path.basename(path)),
            'hash': self.__hash(path),
            'mime': 'directory' if filetype == 'dir'
            else self.__mimetype(path),
            'date': fdate,
            'size': self.__dirSize(path) if filetype == 'dir'
            else stat.st_size,
            'read': self.__isAllowed(path, 'read'),
            'write': self.__isAllowed(path, 'write'),
            'rm': self.__isAllowed(path, 'rm')
        }

        if filetype == 'link':
            lpath = self.__readlink(path)
            if not lpath:
                info['mime'] = 'symlink-broken'
                return info

            if os.path.isdir(lpath):
                info['mime'] = 'directory'
            else:
                info['parent'] = self.__hash(os.path.dirname(lpath))
                info['mime'] = self.__mimetype(lpath)

            if self._options['rootAlias']:
                basename = self._options['rootAlias']
            else:
                basename = os.path.basename(self._options['root'])

            info['link'] = self.__hash(lpath)
            info['linkTo'] = basename + lpath[len(self._options['root']):]
            info['read'] = info['read'] and self.__isAllowed(lpath, 'read')
            info['write'] = info['write'] and self.__isAllowed(lpath, 'write')
            info['rm'] = self.__isAllowed(lpath, 'rm')
        else:
            lpath = False

        if not info['mime'] == 'directory':
            if self._options['fileURL'] and info['read'] is True:
                if lpath:
                    info['url'] = self.__path2url(lpath)
                else:
                    info['url'] = self.__path2url(path)
            if info['mime'][0:5] == 'image':
                if self.__canCreateTmb():
                    dim = self.__getImgSize(path)
                    if dim:
                        info['dim'] = dim
                        info['resize'] = True

                    # if we are in tmb dir, files are thumbs itself
                    if os.path.dirname(path) == self._options['tmbDir']:
                        info['tmb'] = self.__path2url(path)
                        return info

                    tmb = os.path.join(self._options[
                                       'tmbDir'], info['hash'] + '.png')

                    if os.path.exists(tmb):
                        tmbUrl = self.__path2url(tmb)
                        info['tmb'] = tmbUrl
                    else:
                        self._response['tmb'] = True

        return info

    def __tree(self, path):
        """Return directory tree starting from path"""

        if not os.path.isdir(path):
            return ''
        if os.path.islink(path):
            return ''

        if path == self._options['root'] and self._options['rootAlias']:
            name = self._options['rootAlias']
        else:
            name = os.path.basename(path)
        tree = {
            'hash': self.__hash(path),
            'name': self.__checkUtf8(name),
            'read': self.__isAllowed(path, 'read'),
            'write': self.__isAllowed(path, 'write'),
            'dirs': []
        }

        if self.__isAllowed(path, 'read'):
            for d in sorted(os.listdir(path)):
                pd = os.path.join(path, d)
                if os.path.isdir(pd) and not os.path.islink(pd)\
                        and self.__isAccepted(d):
                    tree['dirs'].append(self.__tree(pd))

        return tree

    def __uniqueName(self, path, copy=' copy'):
        """Generate unique name for file copied file"""
        curDir = os.path.dirname(path)
        curName = os.path.basename(path)
        lastDot = curName.rfind('.')
        ext = newName = ''

        if not os.path.isdir(path)\
                and re.search(r'\..{3}\.(gz|bz|bz2)$', curName):
            pos = -7
            if curName[-1:] == '2':
                pos -= 1
            ext = curName[pos:]
            oldName = curName[0:pos]
            newName = oldName + copy
        elif os.path.isdir(path) or lastDot <= 0:
            oldName = curName
            newName = oldName + copy
            pass
        else:
            ext = curName[lastDot:]
            oldName = curName[0:lastDot]
            newName = oldName + copy

        pos = 0

        if oldName[-len(copy):] == copy:
            newName = oldName
        elif re.search(r'' + copy + '\s\d+$', oldName):
            pos = oldName.rfind(copy) + len(copy)
            newName = oldName[0:pos]
        else:
            newPath = os.path.join(curDir, newName + ext)
            if not os.path.exists(newPath):
                return newPath

        # if we are here then copy already exists or making copy of copy
        # we will make new indexed copy *black magic*
        idx = 1
        if pos > 0:
            idx = int(oldName[pos:])
        while True:
            idx += 1
            newNameExt = newName + ' ' + str(idx) + ext
            newPath = os.path.join(curDir, newNameExt)
            if not os.path.exists(newPath):
                return newPath
            # if idx >= 1000: break # possible loop

        return

    def __remove(self, target):
        """Internal remove procedure"""
        if not self.__isAllowed(target, 'rm'):
            self.__errorData(target, 'Access denied')

        if not os.path.isdir(target):
            try:
                os.unlink(target)
                return True
            except:
                self.__errorData(target, 'Remove failed')
                return False
        else:
            for i in os.listdir(target):
                if self.__isAccepted(i):
                    self.__remove(os.path.join(target, i))

            try:
                os.rmdir(target)
                return True
            except:
                self.__errorData(target, 'Remove failed')
                return False
        pass

    def __copy(self, src, dst):
        """Internal copy procedure"""
        dstDir = os.path.dirname(dst)
        if not self.__isAllowed(src, 'read'):
            self.__errorData(src, 'Access denied')
            return False
        if not self.__isAllowed(dstDir, 'write'):
            self.__errorData(dstDir, 'Access denied')
            return False
        if os.path.exists(dst):
            self.__errorData(
                dst, 'File or folder with the same name already exists')
            return False

        if not os.path.isdir(src):
            try:
                shutil.copyfile(src, dst)
                shutil.copymode(src, dst)
                return True
            except:
                self.__errorData(src, 'Unable to copy files')
                return False
        else:
            try:
                os.mkdir(dst)
                shutil.copymode(src, dst)
            except:
                self.__errorData(src, 'Unable to copy files')
                return False

            for i in os.listdir(src):
                newSrc = os.path.join(src, i)
                newDst = os.path.join(dst, i)
                if not self.__copy(newSrc, newDst):
                    self.__errorData(newSrc, 'Unable to copy files')
                    return False

        return True

    def __checkName(self, name):
        """Check for valid file/dir name"""
        pattern = r'[\/\\\:\<\>]'
        if re.search(pattern, name):
            return False
        return True

    def __findDir(self, fhash, path):
        """Find directory by hash"""
        fhash = str(fhash)
        if not path:
            path = self._options['root']
            if fhash == self.__hash(path):
                return path

        if not os.path.isdir(path):
            return None

        for d in os.listdir(path):
            pd = os.path.join(path, d)
            if os.path.isdir(pd) and not os.path.islink(pd):
                if fhash == self.__hash(pd):
                    return pd
                else:
                    ret = self.__findDir(fhash, pd)
                    if ret:
                        return ret

        return None

    def __find(self, fhash, parent):
        """Find file/dir by hash"""
        fhash = str(fhash)
        if os.path.isdir(parent):
            for i in os.listdir(parent):
                path = os.path.join(parent, i)
                if fhash == self.__hash(path):
                    return path
        return None

    def __read(self):
        if 'current' in self._request and 'target' in self._request:
            curDir = self.__findDir(self._request['current'], None)
            curFile = self.__find(self._request['target'], curDir)
            if curDir and curFile:
                if self.__isAllowed(curFile, 'read'):
                    self._response['content'] = open(curFile, 'r').read()
                else:
                    self._response['error'] = 'Access denied'
                return

        self._response['error'] = 'Invalid parameters'
        return

    def __edit(self):
        """Save content in file"""
        error = ''
        if 'current' in self._request and 'target' in self._request\
                and 'content' in self._request:
            curDir = self.__findDir(self._request['current'], None)
            curFile = self.__find(self._request['target'], curDir)
            error = curFile
            if curFile and curDir:
                if self.__isAllowed(curFile, 'write'):
                    try:
                        f = open(curFile, 'w+')
                        f.write(self._request['content'])
                        f.close()
                        self._response['target'] = self.__info(curFile)
                    except:
                        self._response['error'] = 'Unable to write to file'
                else:
                    self._response['error'] = 'Access denied'
            return

        self._response['error'] = 'Invalid parameters'
        return

    def __archive(self):
        """Compress files/directories to archive"""
        self.__checkArchivers()

        if (
                not self._options['archivers']['create']
                or not 'type' in self._request
                or not 'current' in self._request
                or not 'targets[]' in self._request):
            self._response['error'] = 'Invalid parameters'
            return

        curDir = self.__findDir(self._request['current'], None)
        archiveType = self._request['type']
        if (
                not archiveType in self._options['archivers']['create']
                or not archiveType in self._options['archiveMimes']
                or not curDir
                or not self.__isAllowed(curDir, 'write')):
            self._response['error'] = 'Unable to create archive'
            return

        files = self._request['targets[]']
        if not isinstance(files, list):
            files = [files]

        realFiles = []
        for fhash in files:
            curFile = self.__find(fhash, curDir)
            if not curFile:
                self._response['error'] = 'File not found'
                return
            realFiles.append(os.path.basename(curFile))

        arc = self._options['archivers']['create'][archiveType]
        if len(realFiles) > 1:
            archiveName = 'Archive'
        else:
            archiveName = realFiles[0]
        archiveName += '.' + arc['ext']
        archiveName = self.__uniqueName(archiveName, '')
        archivePath = os.path.join(curDir, archiveName)

        cmd = [arc['cmd']]
        for a in arc['argc'].split():
            cmd.append(a)
        cmd.append(archiveName)
        for f in realFiles:
            cmd.append(f)

        curCwd = os.getcwd()
        os.chdir(curDir)
        self.__runSubProcess(cmd)
        os.chdir(curCwd)

        if os.path.exists(archivePath):
            self.__content(curDir, False)
            self._response['select'] = [self.__hash(archivePath)]
        else:
            self._response['error'] = 'Unable to create archive'

        return

    def __extract(self):
        """Uncompress archive"""
        if not 'current' in self._request or not 'target' in self._request:
            self._response['error'] = 'Invalid parameters'
            return

        curDir = self.__findDir(self._request['current'], None)
        curFile = self.__find(self._request['target'], curDir)
        mime = self.__mimetype(curFile)
        self.__checkArchivers()

        if (
                not mime in self._options['archivers']['extract']
                or not curDir
                or not curFile
                or not self.__isAllowed(curDir, 'write')):
            self._response['error'] = 'Invalid parameters'
            return

        arc = self._options['archivers']['extract'][mime]

        cmd = [arc['cmd']]
        for a in arc['argc'].split():
            cmd.append(a)
        cmd.append(os.path.basename(curFile))

        curCwd = os.getcwd()
        os.chdir(curDir)
        ret = self.__runSubProcess(cmd)
        os.chdir(curCwd)

        if ret:
            self.__content(curDir, True)
        else:
            self._response['error'] = 'Unable to extract files from archive'

        return

    def __ping(self):
        """Workaround for Safari"""
        self.httpStatusCode = 200
        self.httpHeader['Connection'] = 'close'
        return

    def __mimetype(self, path):
        """Detect mimetype of file"""
        mime = mimetypes.guess_type(path)[0] or 'unknown'
        ext = path[path.rfind('.') + 1:]

        if mime == 'unknown' and ('.' + ext) in mimetypes.types_map:
            mime = mimetypes.types_map['.' + ext]

        if mime == 'text/plain' and ext == 'pl':
            mime = self._mimeType[ext]

        if mime == 'application/vnd.ms-office' and ext == 'doc':
            mime = self._mimeType[ext]

        if mime == 'unknown':
            if os.path.basename(path) in ['README', 'ChangeLog']:
                mime = 'text/plain'
            else:
                if ext in self._mimeType:
                    mime = self._mimeType[ext]

        # self.__debug('mime ' + os.path.basename(path), ext + ' ' + mime)
        return mime

    def __tmb(self, path, tmb):
        """Internal thumbnail create procedure"""
        try:
            im = self._im.open(path).copy()
            size = self._options['tmbSize'], self._options['tmbSize']
            box = self.__cropTuple(im.size)
            if box:
                im = im.crop(box)
            im.thumbnail(size, self._im.ANTIALIAS)
            im.save(tmb, 'PNG')
        except Exception, e:
            self.__debug('tmbFailed_' + path, str(e))
            return False
        return True

    def __rmTmb(self, path):
        tmb = self.__tmbPath(path)
        if self._options['tmbDir']:
            if os.path.exists(tmb):
                try:
                    os.unlink(tmb)
                except:
                    pass

    def __cropTuple(self, size):
        w, h = size
        if w > h:  # landscape
            l = int((w - h) / 2)
            u = 0
            r = l + h
            d = h
            return (l, u, r, d)
        elif h > w:  # portrait
            l = 0
            u = int((h - w) / 2)
            r = w
            d = u + w
            return (l, u, r, d)
        else:  # cube
            pass

        return False

    def __readlink(self, path):
        """Read link and return real path if not broken"""
        target = os.readlink(path)
        if not target[0] == '/':
            target = os.path.join(os.path.dirname(path), target)
        target = os.path.normpath(target)
        if os.path.exists(target):
            if not target.find(self._options['root']) == -1:
                return target
        return False

    def __dirSize(self, path):
        total_size = 0
        if self._options['dirSize']:
            for dirpath, dirnames, filenames in os.walk(path):
                for f in filenames:
                    fp = os.path.join(dirpath, f)
                    if os.path.exists(fp):
                        total_size += os.stat(fp).st_size
        else:
            total_size = os.lstat(path).st_size
        return total_size

    def __fbuffer(self, f, chunk_size=_options['uploadWriteChunk']):
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            yield chunk

    def __canCreateTmb(self, path=None):
        if self._options['imgLib'] and self._options['tmbDir']:
            if path is not None:
                mime = self.__mimetype(path)
                if not mime[0:5] == 'image':
                    return False
            return True
        else:
            return False

    def __tmbPath(self, path):
        tmb = False
        if self._options['tmbDir']:
            if not os.path.dirname(path) == self._options['tmbDir']:
                tmb = os.path.join(self._options[
                                   'tmbDir'], self.__hash(path) + '.png')
        return tmb

    def __isUploadAllow(self, name):
        allow = False
        deny = False
        mime = self.__mimetype(name)

        if 'all' in self._options['uploadAllow']:
            allow = True
        else:
            for a in self._options['uploadAllow']:
                if mime.find(a) == 0:
                    allow = True

        if 'all' in self._options['uploadDeny']:
            deny = True
        else:
            for d in self._options['uploadDeny']:
                if mime.find(d) == 0:
                    deny = True

        if self._options['uploadOrder'][0] == 'allow':  # ,deny
            if deny is True:
                return False
            elif allow is True:
                return True
            else:
                return False
        else:  # deny,allow
            if allow is True:
                return True
            elif deny is True:
                return False
            else:
                return True

    def __isAccepted(self, target):
        if target == '.' or target == '..':
            return False
        if target[0:1] == '.' and not self._options['dotFiles']:
            return False
        return True

    def __isAllowed(self, path, access):
        if not os.path.exists(path):
            return False

        if access == 'read':
            if not os.access(path, os.R_OK):
                self.__errorData(path, access)
                return False
        elif access == 'write':
            if not os.access(path, os.W_OK):
                self.__errorData(path, access)
                return False
        elif access == 'rm':
            if not os.access(os.path.dirname(path), os.W_OK):
                self.__errorData(path, access)
                return False
        else:
            return False

        path = path[len(os.path.normpath(self._options['root'])):]
        for ppath in self._options['perms']:
            regex = r'' + ppath
            if re.search(regex, path)\
                    and access in self._options['perms'][ppath]:
                return self._options['perms'][ppath][access]

        return self._options['defaults'][access]

    def __hash(self, path, u=False):
        """Hash of the path"""
        m = hashlib.md5()
        if u:
            path = path.encode('utf-8')
        m.update(path)
        return str(m.hexdigest())

    def __path2url(self, path):
        curDir = path
        length = len(self._options['root'])
        url = self.__checkUtf8(self._options[
                               'URL'] + curDir[length:]).replace(os.sep, '/')

        try:
            import urllib
            url = urllib.quote(url, '/:~')
        except:
            pass
        return url

    def __errorData(self, path, msg):
        """Collect error/warning messages"""
        self._errorData[path] = msg

    def __initImgLib(self):
        if not self._options['imgLib'] is False and self._im is None:
            try:
                import Image
                Image
                self._im = Image
                self._options['imgLib'] = 'PIL'
            except:
                self._options['imgLib'] = False
                self._im = False

        self.__debug('imgLib', self._options['imgLib'])
        return self._options['imgLib']

    def __getImgSize(self, path):
        self.__initImgLib()
        if self.__canCreateTmb():
            try:
                im = self._im.open(path)
                return str(im.size[0]) + 'x' + str(im.size[1])
            except:
                pass

        return False

    def __debug(self, k, v):
        if self._options['debug']:
            self._response['debug'].update({k: v})
        return

    def __checkArchivers(self):
        # import subprocess
        # sp = subprocess.Popen(['tar', '--version'], shell = False,
        # stdout = subprocess.PIPE, stderr=subprocess.PIPE)
        # out, err = sp.communicate()
        # print 'out:', out, '\nerr:', err, '\n'
        archive = {'create': {}, 'extract': {}}

        if 'archive' in self._options['disabled']\
                and 'extract' in self._options['disabled']:
            self._options['archiveMimes'] = []
            self._options['archivers'] = archive
            return

        tar = self.__runSubProcess(['tar', '--version'])
        gzip = self.__runSubProcess(['gzip', '--version'])
        bzip2 = self.__runSubProcess(['bzip2', '--version'])
        zipc = self.__runSubProcess(['zip', '--version'])
        unzip = self.__runSubProcess(['unzip', '--help'])
        rar = self.__runSubProcess(['rar', '--version'], validReturn=[0, 7])
        unrar = self.__runSubProcess(['unrar'], validReturn=[0, 7])
        p7z = self.__runSubProcess(['7z', '--help'])
        p7za = self.__runSubProcess(['7za', '--help'])
        p7zr = self.__runSubProcess(['7zr', '--help'])

        # tar = False
        # tar = gzip = bzip2 = zipc = unzip = rar = unrar = False
        # print tar, gzip, bzip2, zipc, unzip, rar, unrar, p7z, p7za, p7zr

        c = archive['create']
        e = archive['extract']

        if tar:
            mime = 'application/x-tar'
            c.update({mime: {'cmd': 'tar', 'argc': '-cf', 'ext': 'tar'}})
            e.update({mime: {'cmd': 'tar', 'argc': '-xf', 'ext': 'tar'}})

        if tar and gzip:
            mime = 'application/x-gzip'
            c.update({mime: {'cmd': 'tar', 'argc': '-czf', 'ext': 'tar.gz'}})
            e.update({mime: {'cmd': 'tar', 'argc': '-xzf', 'ext': 'tar.gz'}})

        if tar and bzip2:
            mime = 'application/x-bzip2'
            c.update({mime: {'cmd': 'tar', 'argc': '-cjf', 'ext': 'tar.bz2'}})
            e.update({mime: {'cmd': 'tar', 'argc': '-xjf', 'ext': 'tar.bz2'}})

        mime = 'application/zip'
        if zipc:
            c.update({mime: {'cmd': 'zip', 'argc': '-r9', 'ext': 'zip'}})
        if unzip:
            e.update({mime: {'cmd': 'unzip', 'argc': '', 'ext': 'zip'}})

        mime = 'application/x-rar'
        if rar:
            c.update({mime: {'cmd': 'rar', 'argc': 'a -inul', 'ext': 'rar'}})
            e.update({mime: {'cmd': 'rar', 'argc': 'x -y', 'ext': 'rar'}})
        elif unrar:
            e.update({mime: {'cmd': 'unrar', 'argc': 'x -y', 'ext': 'rar'}})

        p7zip = None
        if p7z:
            p7zip = '7z'
        elif p7za:
            p7zip = '7za'
        elif p7zr:
            p7zip = '7zr'

        if p7zip:
            mime = 'application/x-7z-compressed'
            c.update({mime: {'cmd': p7zip, 'argc': 'a -t7z', 'ext': '7z'}})
            e.update({mime: {'cmd': p7zip, 'argc': 'e -y', 'ext': '7z'}})

            mime = 'application/x-tar'
            if not mime in c:
                c.update({mime: {
                         'cmd': p7zip, 'argc': 'a -ttar', 'ext': 'tar'}})
            if not mime in e:
                e.update({mime: {'cmd': p7zip, 'argc': 'e -y', 'ext': 'tar'}})

            mime = 'application/x-gzip'
            if not mime in c:
                c.update({mime: {
                         'cmd': p7zip, 'argc': 'a -tgzip', 'ext': 'gz'}})
            if not mime in e:
                e.update({mime: {
                         'cmd': p7zip, 'argc': 'e -y', 'ext': 'tar.gz'}})

            mime = 'application/x-bzip2'
            if not mime in c:
                c.update({mime: {
                         'cmd': p7zip, 'argc': 'a -tbzip2', 'ext': 'bz2'}})
            if not mime in e:
                e.update({mime: {
                         'cmd': p7zip, 'argc': 'e -y', 'ext': 'tar.bz2'}})

            mime = 'application/zip'
            if not mime in c:
                c.update({mime: {
                         'cmd': p7zip, 'argc': 'a -tzip', 'ext': 'zip'}})
            if not mime in e:
                e.update({mime: {'cmd': p7zip, 'argc': 'e -y', 'ext': 'zip'}})

        if not self._options['archiveMimes']:
            self._options['archiveMimes'] = c.keys()
        else:
            pass

        self._options['archivers'] = archive
        pass

    def __runSubProcess(self, cmd, validReturn=[0]):
        if self._sp is None:
            import subprocess
            self._sp = subprocess

        try:
            sp = self._sp.Popen(cmd, shell=False, stdout=self._sp.PIPE,
                                stderr=self._sp.PIPE, stdin=self._sp.PIPE)
            out, err = sp.communicate('')
            ret = sp.returncode
            # print cmd, ret, out, err
        except:
            return False

        if not ret in validReturn:
            return False

        return True

    def __checkUtf8(self, name):
        try:
            name.decode('utf-8')
        except UnicodeDecodeError:
            name = unicode(name, 'utf-8', 'replace')
            self.__debug('invalid encoding', name)
            # name += ' (invalid encoding)'
        return name
