from mod_python import apache, util

import os
import os.path

import urllib
import urlparse

import mimetypes

import rr_config
import rr_util

import dav
import mimerepo

###

def do_post_auth ( request, form ):
    """Helper function to extract the authorization information
    out of the form and call rr_util.check_cms.

    The following form fields are used for authorization:
      object_id   : The ID of the object on the CMS side
      client_id   : The name (aka ID) of the client CMS
      user        : The user name of the CMS user doing the upload
      passwd      : The password for user

    This function attaches some attributes to the request object:
      client_name: name of the client cms
      user: name of the user who issued the request
      object_id: the id of the referring object on the cms side
    """
    try:
        user = form['user'].strip()
        passwd = form['passwd'].strip()
        object_id = form['object_id'].strip()
        client_name = form['client_id'].strip()
    except KeyError:
        # at least one of the needed values was not in the form
        return False
    # small sanity check of the form values
    if not user or not passwd or not object_id or not client_name:
        # at least one value was not set
        return False
    r_pw = request.get_basic_auth_pw()
    r_user = request.user
    rr_util.ap_debug(request, 'user: %s, pw: %s\n' % (r_user, r_pw))
    request.client_name = client_name
    request.user = user
    request.object_id = object_id
    if rr_config.do_auth:
        url = rr_config.DB.get_client_auth_url(request, client_name)
        if not url:
            # client not registered
            return False
        url = url + '?' + urllib.urlencode([('id', object_id), ('method', request.method)])
        rr_util.ap_debug(request, 'CALLING URL: %s\n' % url)
        if rr_util.call_cms(url, r_user, r_pw):
            return True
        else:
            return False
    return True
#

def store_local_file ( in_file, target ):
   """Store the data from in_file to the specified target_path on the local
   filesystem.
   """
   chunk_size = 65536 * 4 # 256kB Chunks
   target = urllib.unquote(target)
   out_file = file(target, 'wb')
   complete = False
   try:
      while True:
         data = in_file.read(chunk_size)
         if data:
            out_file.write(data)
         else:
            complete = True
            break
   finally:
      out_file.flush()
      out_file.close()
      if not complete:
         # remove incomplete file and signal failure
         os.unlink(target_path)
         return False
      pass
   return True
#

def error ( request, msg=None ):
    """Redirect to request.error_url.
    """
    rr_util.redirect(request, request.error_url, msg)
    pass
#

def ok_response ( request, file_url, msg=None ):
    url = request.ok_url
    if msg is None:
        msg = ''
    url += '?' + urllib.urlencode([('url', file_url), ('errormsg', msg)])
    util.redirect(request, url)
    pass
#

def create_file ( repo, name, mime_type, encoding ):
    """Create a file with the given name in the given repository.

    Uses a very simple overflow mechanism to avoid name collisions.
    """
    curr = 0
    data = ''
    while curr < 999:
        remotepath = '%03d/%s' % (curr, name)
        try:
            remotefile = repo.create_file(mime_type, remotepath, data, encoding)
            break
        except mimerepo.FileExistsError:
            curr += 1
            pass
    return remotefile
#

def process_form ( request, form ):
    """Processes the upload and does all the work.

    The following fields are expected to be in the form:
      field_file  : The field for the uploaded file
      ok_url      : The url to redirect to when everything went fine
      error_url   : The url to redirect to when an error occured
      link_url    : Set when this upload is ment to be an update, else empty
                    -- but present!
    """
    error_url = None
    try:
        error_url = form['error_url'].strip()
        ok_url = form['ok_url'].strip()
        link_url = form['link_url'].strip()
        fileobj = form['field_file']
    except KeyError:
        # at least one form field not found
        if error_url is not None:
            # fine, so we can redirect to the error page
            error(request, msg='Form was not filled completely!')
        else:
            # oh oh... nothing to redirect to
            # display an error text
            msg = 'Not all necessary form fields were given. Request processing aborted!'
            request.headers_out['Content-Type'] = 'text/plain'
            request.set_content_length(len(msg))
            request.write(msg)
            return
    request.ok_url = ok_url
    request.error_url = error_url
    if (not fileobj.filename or not fileobj.file
        or not rr_util.file_length(fileobj.file)):
        error(request, msg='No File attached!')
    target_name = fileobj.filename
    mime_type, encoding = mimetypes.guess_type(target_name)
    # XXX
    # what shall we do w- unknown mime-types?
    if not mime_type:
        mime_type = 'application/octet-stream'
    use_db = rr_config.do_auth or rr_config.do_search
    if use_db:
        master_user, master_pw, local_dir, host, path = rr_config.DB.get_upload_data(request, rr_config.repo_name)
    else:
        local_dir = rr_util.get_option('repo_dir', request)
        host = rr_util.get_option('repo_host', request)
        path = rr_util.get_option('repo_path', request)
        if not local_dir or not host or not path:
            error(request, msg='Misconfiguration detected! Call server admin!\n Incomplete repository configureation (repo_dir, repo_host, repo_path).')
    # connect to dav server
    dav_url = 'http://' + host + path
    conn = dav.DAVConnection(host)
    if rr_config.do_auth:
        # if authorization is enabled, use master account
        rr_util.ap_notice(request, '  setting master user/pw on DAV connection')
        conn.set_auth(master_user, master_pw)
    if link_url:
        # when link_url is set, it should be an update
        url_t = urlparse.urlsplit(link_url)
        up = urllib.url2pathname(url_t[2])
        base_name = os.path.basename(up)
        if target_name == base_name:
            # is this is an update!
            try:
                dav_file = dav.DAVFile(link_url, conn)
            except dav.DAVNoFileError:
                error(request, 'URL %s does not exist!' % link_url)
                pass
            lt = None
            try:
                lt = dav_file.lock()
            except dav.DAVLockedError:
                error(request, "Can't update %s, resource locked!" % link_url)
            except dav.DAVLockFailedError:
                error(request, "Locking %s failed!" % link_url)
            except:
                raise
            r_path = dav_file.path
            local_path = os.path.join(local_dir, r_path[len(path):])
            try:
                try:
                    if not store_local_file(fileobj.file, local_path):
                        raise IOError
                finally:
                    dav_file.unlock(lt)
            except IOError:
                error(request, 'Update file failed!')
            ok_response(request, dav_file.url, 'Update successful!')
        else:
            # this is not an update because the names differ
            error(request, msg='This is not an update because of different file names!')
    # new upload
    if local_dir[-1] != '/':
        local_dir += '/'
    if path[-1] != '/':
        path += '/'
    dav_coll = dav.DAVCollection(dav_url, conn)
    repo = mimerepo.Repository(dav_coll)
    r_file = create_file(repo, target_name, mime_type, encoding)
    r_path = r_file.path
    local_path = os.path.join(local_dir, r_path[len(path):])
    if not store_local_file(fileobj.file, local_path):
        rr_util.ap_error(request, 'storing local file failed on %s' % local_path)
        error(request, 'Store file failed!')
    if use_db:
        rr_config.DB.store_new_resource(request, r_path)
    # everything went fine
    # call all functions on upload hook
    request.new_uri = r_path
    rr_util.call_hooks('upload', request)
    ok_response(request, r_file.url, 'File created!')
    # NOT REACHED !
    return
#

def upload ( request, form ):
    """Entry point for uploads via form POST requests.
    """
    if not do_post_auth(request, form):
        raise apache.SERVER_RETURN, apache.HTTP_UNAUTHORIZED
    # authorized, move file to it's destination
    process_form(request, form)
    return apache.OK
#
###
