# -*- coding: iso-8859-1 -*-
"""
    MoinMoin - AttachFile action

    This action lets a page have multiple attachment files.
    It creates a folder <data>/pages/<pagename>/attachments
    and keeps everything in there.

    Form values: action=Attachment
    1. with no 'do' key: returns file upload form
    2. do=attach: accept file upload and saves the file in
       ../attachment/pagename/
    3. /pagename/fname?action=Attachment&do=get[&mimetype=type]:
       return contents of the attachment file with the name fname.
    4. /pathname/fname, do=view[&mimetype=type]:create a page
       to view the content of the file

    To insert an attachment into the page, use the "attachment:" pseudo
    schema.

    @copyright: 2001 by Ken Sugino (sugino@mediaone.net)
    @copyright: 2001-2004 by Jürgen Hermann <jh@web.de>
    @copyright: 2005 MoinMoin:ReimarBauer
    @copyright: 2005 MoinMoin:AlexanderSchremmer
    @copyright: 2005 DiegoOngaro at ETSZONE (diego@etszone.com)
    @copyright: 2006 MoinMoin:ReimarBauer
    @license: GNU GPL, see COPYING for details.
"""

import os, mimetypes, time, zipfile
from MoinMoin import config, user, util, wikiutil, packages
from MoinMoin.Page import Page
from MoinMoin.util import MoinMoinNoFooter, filesys, timefuncs

action_name = __name__.split('.')[-1]

def htdocs_access(request):
    return isinstance(request.cfg.attachments, type({}))


#############################################################################
### External interface - these are called from the core code
#############################################################################

def getBasePath(request):
    """ Get base path where page dirs for attachments are stored.
    """
    if htdocs_access(request):
        return request.cfg.attachments['dir']
    else:
        return request.rootpage.getPagePath('pages')


def getAttachDir(request, pagename, create=0):
    """ Get directory where attachments for page `pagename` are stored.
    """
    if htdocs_access(request):
        # direct file access via webserver, from public htdocs area
        pagename = wikiutil.quoteWikinameFS(pagename)
        attach_dir = os.path.join(request.cfg.attachments['dir'], pagename, "attachments")
        if create and not os.path.isdir(attach_dir):
            filesys.makeDirs(attach_dir)
    else:
        # send file via CGI, from page storage area
        attach_dir = Page(request, pagename).getPagePath("attachments", check_create=create)

    return attach_dir

def absoluteName(url, pagename):
    """ Get (pagename, filename) of an attachment: link
        @param url: PageName/filename.ext or filename.ext (unicode)
        @param pagename: name of the currently processed page (unicode)
        @rtype: tuple of unicode
        @return: PageName, filename.ext
    """
    pieces = url.split(u'/')
    if len(pieces) == 1:
        return pagename, pieces[0]
    else:
        return u"/".join(pieces[:-1]), pieces[-1]

def getAttachUrl(pagename, filename, request, addts=0, escaped=0, do='get'):
    """ Get URL that points to attachment `filename` of page `pagename`.

        If 'addts' is true, a timestamp with the file's modification time
        is added, so that browsers reload a changed file.
    """
    if htdocs_access(request):
        # direct file access via webserver
        timestamp = ''
        if addts:
            try:
                timestamp = '?ts=%s' % os.path.getmtime(
                    getFilename(request, pagename, filename))
            except IOError:
                pass

        url = "%s/%s/attachments/%s%s" % (
            request.cfg.attachments['url'], wikiutil.quoteWikinameFS(pagename),
            wikiutil.url_quote(filename), timestamp)
    else:
        # send file via CGI
        if do not in ['get', 'view']:
            do = 'get'

        url = "%s/%s?action=%s&do=%s&target=%s" % (
            request.getScriptname(), wikiutil.quoteWikinameURL(pagename),
            action_name, do, wikiutil.url_quote_plus(filename))
    if escaped:
        url = wikiutil.escape(url)
    return url

def getIndicator(request, pagename):
    """ Get an attachment indicator for a page (linked clip image) or
        an empty string if not attachments exist.
    """
    _ = request.getText
    attach_dir = getAttachDir(request, pagename)
    if not os.path.exists(attach_dir): return ''

    files = os.listdir(attach_dir)
    if not files: return ''

    attach_count = _('[%d attachments]') % len(files)
    attach_icon = request.theme.make_icon('attach', vars={ 'attach_count': attach_count })
    attach_link = wikiutil.link_tag(request,
        "%s?action=AttachFile" % wikiutil.quoteWikinameURL(pagename),
        attach_icon)

    return attach_link


def getFilename(request, pagename, filename):
    """ make complete pathfilename of file "name" attached to some page "pagename"
        @param request: request object
        @param pagename: name of page where the file is attached to (unicode)
        @param filename: filename of attached file (unicode)
        @rtype: string (in config.charset encoding)
        @return: complete path/filename of attached file
    """
    return os.path.join(getAttachDir(request, pagename), filename).encode(config.charset)


def info(pagename, request):
    """ Generate snippet with info on the attachment for page `pagename`.
    """
    _ = request.getText

    attach_dir = getAttachDir(request, pagename)
    files = []
    if os.path.isdir(attach_dir):
        files = os.listdir(attach_dir)
    page = Page(request, pagename)
    # TODO: remove escape=0 in 2.0
    link = page.url(request, {'action': 'AttachFile'}, escape=0)
    attach_info = _('There are <a href="%(link)s">%(count)s attachment(s)</a> stored for this page.', formatted=False) % {
        'count': len(files),
        'link': wikiutil.escape(link)
        }
    return "\n<p>\n%s\n</p>\n" % attach_info


#############################################################################
### Internal helpers
#############################################################################

def _addLogEntry(request, action, pagename, filename):
    """ Add an entry to the edit log on uploads and deletes.

        `action` should be "ATTNEW" or "ATTDEL"
    """
    from MoinMoin.logfile import editlog
    t = wikiutil.timestamp2version(time.time())
    fname = wikiutil.url_quote(filename, want_unicode=True)

    # TODO: for now we simply write 2 logs, maybe better use some multilog stuff
    # Write to global log
    log = editlog.EditLog(request)
    log.add(request, t, 99999999, action, pagename, request.remote_addr, fname)

    # Write to local log
    log = editlog.EditLog(request, rootpagename=pagename)
    log.add(request, t, 99999999, action, pagename, request.remote_addr, fname)


def _access_file(pagename, request):
    """ Check form parameter `target` and return a tuple of
        `(filename, filepath)` for an existing attachment.

        Return `(None, None)` if an error occurs.
    """
    _ = request.getText

    error = None
    if not request.form.get('target', [''])[0]:
        error = _("Filename of attachment not specified!")
    else:
        filename = wikiutil.taintfilename(request.form['target'][0])
        fpath = getFilename(request, pagename, filename)

        if os.path.isfile(fpath):
            return (filename, fpath)
        error = _("Attachment '%(filename)s' does not exist!") % {'filename': filename}

    error_msg(pagename, request, error)
    return (None, None)


def _build_filelist(request, pagename, showheader, readonly, mime_type='*'):
    _ = request.getText

    # access directory
    attach_dir = getAttachDir(request, pagename)
    files = _get_files(request, pagename)

    if mime_type != '*':
        files = [fname for fname in files if mime_type == mimetypes.guess_type(fname)[0]]

    str = ""
    if files:
        if showheader:
            str = str + _(
                "To refer to attachments on a page, use '''{{{attachment:filename}}}''', \n"
                "as shown below in the list of files. \n"
                "Do '''NOT''' use the URL of the {{{[get]}}} link, \n"
                "since this is subject to change and can break easily."
            )
        str = str + "<ul>"

        label_del = _("del")
        label_move = _("move")
        label_get = _("get")
        label_edit = _("edit")
        label_view = _("view")
        label_unzip = _("unzip")
        label_install = _("install")

        for file in files:
            fsize = float(os.stat(os.path.join(attach_dir,file).encode(config.charset))[6]) # in byte
            fsize = "%.1f" % (fsize / 1024)
            baseurl = request.getScriptname()
            action = action_name
            urlpagename = wikiutil.quoteWikinameURL(pagename)
            urlfile = wikiutil.url_quote_plus(file)

            base, ext = os.path.splitext(file)
            get_url = getAttachUrl(pagename, file, request, escaped=1)
            parmdict = {'baseurl': baseurl, 'urlpagename': urlpagename, 'action': action,
                        'urlfile': urlfile, 'label_del': label_del,
                        'label_move': label_move,
                        'base': base, 'label_edit': label_edit,
                        'label_view': label_view,
                        'label_unzip': label_unzip,
                        'label_install': label_install,
                        'get_url': get_url, 'label_get': label_get,
                        'file': wikiutil.escape(file).replace(' ', '%20'),
                        'fsize': fsize,
                        'pagename': pagename}

            del_link = ''
            if request.user.may.delete(pagename) and not readonly:
                del_link = '<a href="%(baseurl)s/%(urlpagename)s' \
                    '?action=%(action)s&amp;do=del&amp;target=%(urlfile)s">%(label_del)s</a>&nbsp;| ' % parmdict
            if request.user.may.delete(pagename) and not readonly:
                move_link = '<a href="%(baseurl)s/%(urlpagename)s' \
                    '?action=%(action)s&amp;do=move&amp;target=%(urlfile)s">%(label_move)s</a>&nbsp;| ' % parmdict
            else:
                move_link = ''
            if ext == '.draw':
                viewlink = '<a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;drawing=%(base)s">%(label_edit)s</a>' % parmdict
            else:
                viewlink = '<a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=view&amp;target=%(urlfile)s">%(label_view)s</a>' % parmdict

            if (packages.ZipPackage(request, os.path.join(attach_dir, file).encode(config.charset)).isPackage() and
                request.user.isSuperUser()):
                viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=install&amp;target=%(urlfile)s">%(label_install)s</a>' % parmdict
            elif (zipfile.is_zipfile(os.path.join(attach_dir,file).encode(config.charset)) and
                request.user.may.read(pagename) and request.user.may.delete(pagename)
                and request.user.may.write(pagename)):
                viewlink += ' | <a href="%(baseurl)s/%(urlpagename)s?action=%(action)s&amp;do=unzip&amp;target=%(urlfile)s">%(label_unzip)s</a>' % parmdict


            parmdict['viewlink'] = viewlink
            parmdict['del_link'] = del_link
            parmdict['move_link'] = move_link
            str = str + ('<li>[%(del_link)s%(move_link)s'
                '<a href="%(get_url)s">%(label_get)s</a>&nbsp;| %(viewlink)s]'
                ' (%(fsize)s KB) attachment:<strong>%(file)s</strong></li>') % parmdict
        str = str + "</ul>"
    else:
        if showheader:
            str = '%s<p>%s</p>' % (str, _("No attachments stored for %(pagename)s") % {'pagename': wikiutil.escape(pagename)})

    return str


def _get_files(request, pagename):
    attach_dir = getAttachDir(request, pagename)
    if os.path.isdir(attach_dir):
        files = map(lambda a: a.decode(config.charset), os.listdir(attach_dir))
        files.sort()
        return files
    return []


def _get_filelist(request, pagename):
    return _build_filelist(request, pagename, 1, 0)

def _subdir_exception(zf):
    """
    Checks for the existance of one common subdirectory shared among
    all files in the zip file. If this is the case, returns a dict of
    original names to modified names so that such files can be unpacked
    as the user would expect.
    """

    b = zf.namelist()
    if not '/' in b[0]:
        return False #No directory
    slashoffset = b[0].index('/')
    directory = b[0][:slashoffset]
    for origname in b:
        if origname.rfind('/') != slashoffset or origname[:slashoffset] != directory:
            return False #Multiple directories or different directory
    names = {}
    for origname in b:
        names[origname] = origname[slashoffset+1:]
    return names #Returns dict of {origname: safename}

def error_msg(pagename, request, msg):
    Page(request, pagename).send_page(request, msg=msg)


#############################################################################
### Create parts of the Web interface
#############################################################################

def send_link_rel(request, pagename):
    files = _get_files(request, pagename)
    if len(files) > 0 and not htdocs_access(request):
        scriptName = request.getScriptname()
        pagename_quoted = wikiutil.quoteWikinameURL(pagename)

        for file in files:
            url = "%s/%s?action=%s&do=view&target=%s" % (
                scriptName, pagename_quoted,
                action_name, wikiutil.url_quote_plus(file))

            request.write(u'<link rel="Appendix" title="%s" href="%s">\n' % (
                wikiutil.escape(file), wikiutil.escape(url)))


def send_hotdraw(pagename, request):
    _ = request.getText

    now = time.time()
    pubpath = request.cfg.url_prefix + "/applets/TWikiDrawPlugin"
    basename = request.form['drawing'][0]
    drawpath = getAttachUrl(pagename, basename + '.draw', request, escaped=1)
    pngpath = getAttachUrl(pagename, basename + '.png', request, escaped=1)
    querystr = {'action': 'AttachFile', 'ts': now}
    querystr = wikiutil.escape(wikiutil.makeQueryString(querystr))
    pagelink = '%s/%s?%s' % (request.getScriptname(), wikiutil.quoteWikinameURL(pagename), querystr)
    helplink = Page(request, "HelpOnActions/AttachFile").url(request)
    savelink = Page(request, pagename).url(request) # XXX include target filename param here for twisted
                                           # request, {'savename': request.form['drawing'][0]+'.draw'}
    #savelink = '/cgi-bin/dumpform.bat'

    if htdocs_access(request):
        timestamp = '?ts=%s' % now
    else:
        timestamp = '&amp;ts=%s' % now

    request.write('<h2>' + _("Edit drawing") + '</h2>')
    request.write("""
<p>
<img src="%(pngpath)s%(timestamp)s">
<applet code="CH.ifa.draw.twiki.TWikiDraw.class"
        archive="%(pubpath)s/twikidraw.jar" width="640" height="480">
<param name="drawpath" value="%(drawpath)s">
<param name="pngpath"  value="%(pngpath)s">
<param name="savepath" value="%(savelink)s">
<param name="basename" value="%(basename)s">
<param name="viewpath" value="%(pagelink)s">
<param name="helppath" value="%(helplink)s">
<strong>NOTE:</strong> You need a Java enabled browser to edit the drawing example.
</applet>
</p>""" % {
    'pngpath': pngpath, 'timestamp': timestamp,
    'pubpath': pubpath, 'drawpath': drawpath,
    'savelink': savelink, 'pagelink': pagelink, 'helplink': helplink,
    'basename': basename
})


def send_uploadform(pagename, request):
    """ Send the HTML code for the list of already stored attachments and
        the file upload form.
    """
    _ = request.getText

    if not request.user.may.read(pagename):
        request.write('<p>%s</p>' % _('You are not allowed to view this page.'))
        return

    request.write('<h2>' + _("Attached Files") + '</h2>')
    request.write(_get_filelist(request, pagename))

    if not request.user.may.write(pagename):
        request.write('<p>%s</p>' % _('You are not allowed to attach a file to this page.'))
        return

    if request.form.get('drawing', [None])[0]:
        send_hotdraw(pagename, request)
        return

    request.write('<h2>' + _("New Attachment") + '</h2><p>' +
_("""An upload will never overwrite an existing file. If there is a name
conflict, you have to rename the file that you want to upload.
Otherwise, if "Rename to" is left blank, the original filename will be used.""") + '</p>')
    request.write("""
<form action="%(baseurl)s/%(pagename)s" method="POST" enctype="multipart/form-data">
<dl>
<dt>%(upload_label_file)s</dt>
<dd><input type="file" name="file" size="50"></dd>
<dt>%(upload_label_rename)s</dt>
<dd><input type="text" name="rename" size="50" value="%(rename)s"></dd>
<dt>%(upload_label_overwrite)s</dt>
<dd><input type="checkbox" name="overwrite" value="1" %(overwrite_checked)s></dd>
</dl>
<p>
<input type="hidden" name="action" value="%(action_name)s">
<input type="hidden" name="do" value="upload">
<input type="submit" value="%(upload_button)s">
</p>
</form>
""" % {
    'baseurl': request.getScriptname(),
    'pagename': wikiutil.quoteWikinameURL(pagename),
    'action_name': action_name,
    'upload_label_file': _('File to upload'),
    'upload_label_rename': _('Rename to'),
    'rename': request.form.get('rename', [''])[0],
    'upload_label_overwrite': _('Overwrite existing attachment of same name'),
    'overwrite_checked': ('', 'checked')[request.form.get('overwrite', ['0'])[0] == '1'],
    'upload_button': _('Upload'),
})

#<dt>%(upload_label_mime)s</dt>
#<dd><input type="text" name="mime" size="50"></dd>
#    'upload_label_mime': _('MIME Type (optional)'),


#############################################################################
### Web interface for file upload, viewing and deletion
#############################################################################

def execute(pagename, request):
    """ Main dispatcher for the 'AttachFile' action.
    """
    _ = request.getText

    msg = None
    if action_name in request.cfg.actions_excluded:
        msg = _('File attachments are not allowed in this wiki!')
    elif request.form.has_key('filepath'):
        if request.user.may.write(pagename):
            save_drawing(pagename, request)
            request.http_headers()
            request.write("OK")
        else:
            msg = _('You are not allowed to save a drawing on this page.')
    elif not request.form.has_key('do'):
        upload_form(pagename, request)
    elif request.form['do'][0] == 'upload':
        if request.user.may.write(pagename):
            if request.form.has_key('file'):
                do_upload(pagename, request)
            else:
                # This might happen when trying to upload file names
                # with non-ascii characters on Safari.
                msg = _("No file content. Delete non ASCII characters from the file name and try again.")
        else:
            msg = _('You are not allowed to attach a file to this page.')
    elif request.form['do'][0] == 'del':
        if request.user.may.delete(pagename):
            del_file(pagename, request)
        else:
            msg = _('You are not allowed to delete attachments on this page.')
    elif request.form['do'][0] == 'move':
        if request.user.may.delete(pagename):
            send_moveform(pagename, request)
        else:
            msg = _('You are not allowed to move attachments from this page.')
    elif request.form['do'][0] == 'attachment_move':
        if request.form.has_key('cancel'):
            msg = _('Move aborted!')
            error_msg(pagename, request, msg)
            return
        if not wikiutil.checkTicket(request, request.form['ticket'][0]):
            msg = _('Please use the interactive user interface to move attachments!')
            error_msg(pagename, request, msg)
            return
        if request.user.may.delete(pagename):
            attachment_move(pagename, request)
        else:
            msg = _('You are not allowed to move attachments from this page.')
    elif request.form['do'][0] == 'get':
        if request.user.may.read(pagename):
            get_file(pagename, request)
        else:
            msg = _('You are not allowed to get attachments from this page.')
    elif request.form['do'][0] == 'unzip':
         if request.user.may.delete(pagename) and request.user.may.read(pagename) and request.user.may.write(pagename):
            unzip_file(pagename, request)
         else:
            msg = _('You are not allowed to unzip attachments of this page.')
    elif request.form['do'][0] == 'install':
         if request.user.isSuperUser():
            install_package(pagename, request)
         else:
            msg = _('You are not allowed to install files.')
    elif request.form['do'][0] == 'view':
        if request.user.may.read(pagename):
            view_file(pagename, request)
        else:
            msg = _('You are not allowed to view attachments of this page.')
    else:
        msg = _('Unsupported upload action: %s') % (request.form['do'][0],)

    if msg:
        error_msg(pagename, request, msg)


def upload_form(pagename, request, msg=''):
    _ = request.getText

    request.http_headers()
    # Use user interface language for this generated page
    request.setContentLanguage(request.lang)
    wikiutil.send_title(request, _('Attachments for "%(pagename)s"') % {'pagename': pagename}, pagename=pagename, msg=msg)
    request.write('<div id="content">\n') # start content div
    send_uploadform(pagename, request)
    request.write('</div>\n') # end content div
    wikiutil.send_footer(request, pagename)


def do_upload(pagename, request):
    _ = request.getText

    # make filename
    filename = None
    if request.form.has_key('file__filename__'):
        filename = request.form['file__filename__']
    rename = None
    if request.form.has_key('rename'):
        rename = request.form['rename'][0].strip()
    overwrite = 0
    if request.form.has_key('overwrite'):
        try:
            overwrite = int(request.form['overwrite'][0])
        except:
            pass

    # if we use twisted, "rename" field is NOT optional, because we
    # can't access the client filename
    if rename:
        target = rename
    elif filename:
        target = filename
    else:
        error_msg(pagename, request, _("Filename of attachment not specified!"))
        return

    # get file content
    filecontent = request.form['file'][0]

    # preprocess the filename
    # 1. strip leading drive and path (IE misbehaviour)
    if len(target) > 1 and (target[1] == ':' or target[0] == '\\'): # C:.... or \path... or \\server\...
        bsindex = target.rfind('\\')
        if bsindex >= 0:
            target = target[bsindex+1:]
        
    # 2. replace illegal chars
    target = wikiutil.taintfilename(target)

    # set mimetype from extension, or from given mimetype
    #type, encoding = mimetypes.guess_type(target)
    #if not type:
    #    ext = None
    #    if request.form.has_key('mime'):
    #        ext = mimetypes.guess_extension(request.form['mime'][0])
    #    if not ext:
    #        type, encoding = mimetypes.guess_type(filename)
    #        if type:
    #            ext = mimetypes.guess_extension(type)
    #        else:
    #            ext = ''
    #    target = target + ext

    # get directory, and possibly create it
    attach_dir = getAttachDir(request, pagename, create=1)
    # save file
    fpath = os.path.join(attach_dir, target).encode(config.charset)
    exists = os.path.exists(fpath)
    if exists and not overwrite:
        msg = _("Attachment '%(target)s' (remote name '%(filename)s') already exists.") % {
            'target': target, 'filename': filename}
    else:
        if exists:
            try:
                os.remove(fpath)
            except:
                pass
        stream = open(fpath, 'wb')
        try:
            stream.write(filecontent)
        finally:
            stream.close()

        bytes = len(filecontent)
        msg = _("Attachment '%(target)s' (remote name '%(filename)s')"
                " with %(bytes)d bytes saved.") % {
                'target': target, 'filename': filename, 'bytes': bytes}
        _addLogEntry(request, 'ATTNEW', pagename, target)

    # return attachment list
    upload_form(pagename, request, msg)


def save_drawing(pagename, request):

    filename = request.form['filename'][0]
    filecontent = request.form['filepath'][0]

    # there should be no difference in filename parsing with or without
    # htdocs_access, cause the filename param is used
    basepath, basename = os.path.split(filename)
    basename, ext = os.path.splitext(basename)

    # get directory, and possibly create it
    attach_dir = getAttachDir(request, pagename, create=1)

    if ext == '.draw':
        _addLogEntry(request, 'ATTDRW', pagename, basename + ext)
        filecontent = filecontent.replace("\r","")

    savepath = os.path.join(getAttachDir(request, pagename), basename + ext)
    if ext == '.map' and filecontent.strip()=='':
        # delete map file if it is empty
        os.unlink(savepath)
    else:
        stream = open(savepath, 'wb')
        try:
            stream.write(filecontent)
        finally:
            stream.close()

    # touch attachment directory to invalidate cache if new map is saved
    if ext == '.map':
        os.utime(getAttachDir(request, pagename), None)

def del_file(pagename, request):
    _ = request.getText

    filename, fpath = _access_file(pagename, request)
    if not filename: return # error msg already sent in _access_file

    # delete file
    os.remove(fpath)
    _addLogEntry(request, 'ATTDEL', pagename, filename)

    upload_form(pagename, request, msg=_("Attachment '%(filename)s' deleted.") % {'filename': filename})

def move_file(request, pagename, new_pagename, attachment, new_attachment):
    _ = request.getText

    newpage = Page(request, new_pagename)
    if newpage.exists(includeDeleted=1) and request.user.may.write(new_pagename) and request.user.may.delete(pagename):
        new_attachment_path = os.path.join(getAttachDir(request, new_pagename,
                              create=1), new_attachment).encode(config.charset)
        attachment_path = os.path.join(getAttachDir(request, pagename),
                          attachment).encode(config.charset)

        if os.path.exists(new_attachment_path):
            upload_form(pagename, request, msg=_("Attachment '%(filename)s' already exists.") % {
                                   'filename': new_attachment})
            return

        if new_attachment_path != attachment_path:
        # move file  
            filesys.rename(attachment_path, new_attachment_path)
            _addLogEntry(request, 'ATTDEL', pagename, attachment)
            _addLogEntry(request, 'ATTNEW', new_pagename, new_attachment)
            upload_form(pagename, request, msg=_("Attachment '%(filename)s' moved to %(page)s.") % {
                                                 'filename': new_attachment,
                                                 'page': new_pagename})
        else:
            upload_form(pagename, request, msg=_("Nothing changed"))
    else:
         upload_form(pagename, request, msg=_("Page %(newpagename)s does not exists or you don't have enough rights.") % {
             'newpagename': new_pagename})

def attachment_move(pagename, request):
    _ = request.getText
    if request.form.has_key('newpagename'):
        new_pagename = request.form.get('newpagename')[0]
    else:
        upload_form(pagename, request, msg=_("Move aborted because empty page name"))
    if request.form.has_key('newattachmentname'):
        new_attachment = request.form.get('newattachmentname')[0]
        if new_attachment != wikiutil.taintfilename(new_attachment):
            upload_form(pagename, request, msg=_("Please use proper signs in attachment '%(filename)s'.") % {
                                  'filename': new_attachment})
            return
    else:
        upload_form(pagename, request, msg=_("Move aborted because empty attachment name"))

    attachment = request.form.get('oldattachmentname')[0]
    move_file(request, pagename, new_pagename, attachment, new_attachment)

def send_moveform(pagename, request):
    _ = request.getText

    filename, fpath = _access_file(pagename, request)
    if not filename: return # error msg already sent in _access_file

    # move file
    d = {'action': 'AttachFile',
         'do': 'attachment_move',
         'ticket': wikiutil.createTicket(request),
         'pagename': pagename,
         'attachment_name': filename,
         'move': _('Move'),
         'cancel': _('Cancel'),
         'newname_label': _("New page name"),
         'attachment_label': _("New attachment name"),
        }
    formhtml = '''
<form method="post" action="">
<input type="hidden" name="action" value="%(action)s">
<input type="hidden" name="do" value="%(do)s">
<input type="hidden" name="ticket" value="%(ticket)s">
<table>
    <tr>
        <td class="label"><label>%(newname_label)s</label></td>
        <td class="content">
            <input type="text" name="newpagename" value="%(pagename)s">
        </td>
    </tr>
    <tr>
        <td class="label"><label>%(attachment_label)s</label></td>
        <td class="content">
            <input type="text" name="newattachmentname" value="%(attachment_name)s">
        </td>
    </tr>
    <tr>
        <td></td>
        <td class="buttons">
            <input type="hidden" name="oldattachmentname" value="%(attachment_name)s">
            <input type="submit" name="move" value="%(move)s">
            <input type="submit" name="cancel" value="%(cancel)s">
        </td>
    </tr>
</table>
</form>''' % d
    thispage = Page(request, pagename)
    return thispage.send_page(request, msg=formhtml)

def get_file(pagename, request):
    import shutil

    filename, fpath = _access_file(pagename, request)
    if not filename: return # error msg already sent in _access_file

    timestamp = timefuncs.formathttpdate(int(os.path.getmtime(fpath)))
    if request.if_modified_since == timestamp:
        request.http_headers(["Status: 304 Not modified"])
        request.setResponseCode(304)
    else:
        # get mimetype
        mt, enc = mimetypes.guess_type(filename)
        if not mt:
            mt = "application/octet-stream"

        # TODO: fix the encoding here, plain 8 bit is not allowed according to the RFCs
        # There is no solution that is compatible to IE except stripping non-ascii chars
        filename_enc = filename.encode(config.charset)

        # for dangerous files (like .html), when we are in danger of cross-site-scripting attacks,
        # we just let the user store them to disk ('attachment').
        # For safe files, we directly show them inline (this also works better for IE).
        dangerous = mt in request.cfg.mimetypes_xss_protect
        content_dispo = dangerous and 'attachment' or 'inline'

        request.http_headers([
            'Content-Type: %s' % mt,
            'Last-Modified: %s' % timestamp, # TODO maybe add a short Expires: header here?
            'Content-Length: %d' % os.path.getsize(fpath),
            'Content-Disposition: %s; filename="%s"' % (content_dispo, filename_enc),
        ])

        # send data
        shutil.copyfileobj(open(fpath, 'rb'), request, 8192)

    raise MoinMoinNoFooter

def install_package(pagename, request):
    _ = request.getText

    target, targetpath = _access_file(pagename, request)
    if not target:
        return

    package = packages.ZipPackage(request, targetpath)

    if package.isPackage():
        if package.installPackage():
            msg=_("Attachment '%(filename)s' installed.") % {'filename': wikiutil.escape(target)}
        else:
            msg=_("Installation of '%(filename)s' failed.") % {'filename': wikiutil.escape(target)}
        if package.msg != "":
            msg += "<br><pre>" + wikiutil.escape(package.msg) + "</pre>"
    else:
        msg = _('The file %s is not a MoinMoin package file.' % wikiutil.escape(target))

    upload_form(pagename, request, msg=msg)

def unzip_file(pagename, request):
    _ = request.getText
    valid_pathname = lambda name: (name.find('/') == -1) and (name.find('\\') == -1)

    filename, fpath = _access_file(pagename, request)
    if not filename:
        return # error msg already sent in _access_file

    attachment_path = getAttachDir(request, pagename)
    single_file_size = request.cfg.unzip_single_file_size
    attachments_file_space = request.cfg.unzip_attachments_space
    attachments_file_count = request.cfg.unzip_attachments_count

    files = _get_files(request, pagename)

    msg = ""
    if files:
        fsize = 0.0
        fcount = 0
        for file in files:
            fsize += float(os.stat(getFilename(request, pagename, file))[6]) # in byte
            fcount += 1

        available_attachments_file_space = attachments_file_space - fsize
        available_attachments_file_count = attachments_file_count - fcount
        
        if zipfile.is_zipfile(fpath):
            zf = zipfile.ZipFile(fpath)
            sum_size_over_all_valid_files = 0.0
            count_valid_files = 0
            namelist = _subdir_exception(zf)
            if not namelist: #if it's not handled by _subdir_exception()
                #Convert normal zf.namelist() to {origname:finalname} dict
                namelist = {}
                for name in zf.namelist():
                    namelist[name] = name
            for (origname, finalname) in namelist.iteritems():
                if valid_pathname(finalname):
                    sum_size_over_all_valid_files += zf.getinfo(origname).file_size
                    count_valid_files += 1

            if sum_size_over_all_valid_files > available_attachments_file_space:
                msg=_("Attachment '%(filename)s' could not be unzipped because"
                      " the resulting files would be too large (%(space)d kB"
                      " missing).") % {
                        'filename': filename,
                        'space': (sum_size_over_all_valid_files -
                              available_attachments_file_space) / 1000 }
            elif count_valid_files > available_attachments_file_count:
                msg=_("Attachment '%(filename)s' could not be unzipped because"
                      " the resulting files would be too many (%(count)d "
                      "missing).") % {
                        'filename': filename,
                        'count': (count_valid_files -
                                  available_attachments_file_count) }
            else:
                valid_name = False
                for (origname, finalname) in namelist.iteritems():
                    if valid_pathname(finalname):
                        zi = zf.getinfo(origname)
                        if zi.file_size < single_file_size:
                            new_file = getFilename(request, pagename, finalname)
                            if not os.path.exists(new_file):
                                outfile = open(new_file, 'wb')
                                outfile.write(zf.read(origname))
                                outfile.close()
                                # it's not allowed to zip a zip file so it is dropped
                                if zipfile.is_zipfile(new_file):
                                    os.unlink(new_file)
                                else:
                                    valid_name = True
                                    _addLogEntry(request, 'ATTNEW', pagename, finalname)

                if valid_name:
                    msg=_("Attachment '%(filename)s' unzipped.") % {'filename': filename}
                else:
                    msg=_("Attachment '%(filename)s' not unzipped because the "
                          "files are too big, .zip files only, exist already or "
                          "reside in folders.") % {'filename': filename}
        else:
            msg = _('The file %(target)s is not a .zip file.' % target)

    upload_form(pagename, request, msg=wikiutil.escape(msg))

def send_viewfile(pagename, request):
    _ = request.getText

    filename, fpath = _access_file(pagename, request)
    if not filename: return

    request.write('<h2>' + _("Attachment '%(filename)s'") % {'filename': filename} + '</h2>')

    type, enc = mimetypes.guess_type(filename)
    if type:
        if type[:5] == 'image':
            timestamp = htdocs_access(request) and "?%s" % time.time() or ''
            request.write('<img src="%s%s" alt="%s">' % (
                getAttachUrl(pagename, filename, request, escaped=1), timestamp, wikiutil.escape(filename, 1)))
            return
        elif type[:4] == 'text':
            # TODO: should use formatter here!
            request.write("<pre>")
            # Try to decode file contents. It may return junk, but we
            # don't have enough information on attachments.
            content = open(fpath, 'r').read()
            content = wikiutil.decodeUnknownInput(content)
            content = wikiutil.escape(content)
            request.write(content)
            request.write("</pre>")
            return

    package = packages.ZipPackage(request, fpath)
    if package.isPackage():
        request.write("<pre><b>%s</b>\n%s</pre>" % (_("Package script:"),wikiutil.escape(package.getScript())))
        return

    import zipfile
    if zipfile.is_zipfile(fpath):
        zf = zipfile.ZipFile(fpath, mode='r')
        request.write("<pre>%-46s %19s %12s\n" % (_("File Name"), _("Modified")+" "*5, _("Size")))
        for zinfo in zf.filelist:
            date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
            request.write(wikiutil.escape("%-46s %s %12d\n" % (zinfo.filename, date, zinfo.file_size)))
        request.write("</pre>")
        return

    request.write('<p>' + _("Unknown file type, cannot display this attachment inline.") + '</p>')
    request.write('<a href="%s">%s</a>' % (
        getAttachUrl(pagename, filename, request, escaped=1), wikiutil.escape(filename)))


def view_file(pagename, request):
    _ = request.getText

    filename, fpath = _access_file(pagename, request)
    if not filename: return

    # send header & title
    request.http_headers()
    # Use user interface language for this generated page
    request.setContentLanguage(request.lang)
    title = _('attachment:%(filename)s of %(pagename)s', formatted=True) % {
        'filename': filename, 'pagename': pagename}
    wikiutil.send_title(request, title, pagename=pagename)

    # send body
    # TODO: use formatter startContent?
    request.write('<div id="content">\n') # start content div
    send_viewfile(pagename, request)
    send_uploadform(pagename, request)
    request.write('</div>\n') # end content div

    # send footer
    wikiutil.send_footer(request, pagename)


#############################################################################
### File attachment administration
#############################################################################

def do_admin_browser(request):
    """ Browser for SystemAdmin macro.
    """
    from MoinMoin.util.dataset import TupleDataset, Column
    _ = request.getText

    data = TupleDataset()
    data.columns = [
        Column('page', label=('Page')),
        Column('file', label=('Filename')),
        Column('size',  label=_('Size'), align='right'),
        #Column('action', label=_('Action')),
    ]

    # iterate over pages that might have attachments
    pages = request.rootpage.getPageList()
    for pagename in pages:
        # check for attachments directory
        page_dir = getAttachDir(request, pagename)
        if os.path.isdir(page_dir):
            # iterate over files of the page
            files = os.listdir(page_dir)
            for filename in files:
                filepath = os.path.join(page_dir, filename)
                data.addRow((
                    Page(request, pagename).link_to(request, querystr="action=AttachFile"),
                    wikiutil.escape(filename.decode(config.charset)),
                    os.path.getsize(filepath),
                    # '',
                ))

    if data:
        from MoinMoin.widget.browser import DataBrowserWidget

        browser = DataBrowserWidget(request)
        browser.setData(data)
        return browser.toHTML()

    return ''

