# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010 Eduardo Aguiar
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import os
import os.path
import shutil
import zipfile

import mobius.extension.pickle
import mobius.extension.compiler
import gtk.gdk
import libxml2

BASEDIR = os.path.expandvars ("$HOME/.mobiusft/extensions")

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Extension metadata
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Metadata (object):

  def __init__ (self):
    self.id = ''
    self.name = ''
    self.author = ''
    self.version = ''
    self.description = ''

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Get extension path
# @param extension_id Extension ID
# @param *args sub-paths
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_path (extension_id, *args):
  extension_path = os.path.normpath (os.path.join (BASEDIR, extension_id))

  if not extension_path.startswith (BASEDIR):
    raise Exception, 'Warning: bad crafted extension ID <%s>' % extension_id

  path = os.path.join (extension_path, *args)

  if not path.startswith (extension_path):
    raise Exception, 'Warning: bad crafted path. Extension ID <%s>, Path <%s>' % (extension_id, '/'.join (args))

  return path

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Get extension metadata
# @param extension_id Extension ID
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_metadata (extension_id):
  path = get_path (extension_id, "metadata.xml")

  if os.path.exists (path):
    doc = libxml2.parseFile (path)
    node = doc.getRootElement ()

    metadata = Metadata ()
    metadata.id = (node.prop ('id') or '').decode ('utf-8')
    metadata.name = (node.prop ('name') or '').decode ('utf-8')
    metadata.author = (node.prop ('author') or '').decode ('utf-8')
    metadata.version = (node.prop ('version') or '').decode ('utf-8')
    metadata.description = (node.prop ('description') or '').decode ('utf-8')
    doc.freeDoc ()

  else:
    metadata = None

  return metadata

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Get metadata from extension file
# @param path Extension path
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_metadata_from_file (path):

  if os.path.exists (path):
    # @begin-deprecated version=0.5.4
    file_ext = os.path.splitext (path)[1]

    if file_ext == '.xml':
      doc = libxml2.parseFile (path)

    else:
    # @end-deprecated
      zfile = zipfile.ZipFile (path)
      metadata = zfile.read ('metadata.xml')
      zfile.close ()
      doc = libxml2.parseDoc (metadata)

    node = doc.getRootElement ()
    metadata = Metadata ()
    metadata.id = (node.prop ('id') or '').decode ('utf-8')
    metadata.name = (node.prop ('name') or '').decode ('utf-8')
    metadata.author = (node.prop ('author') or '').decode ('utf-8')
    metadata.version = (node.prop ('version') or '').decode ('utf-8')
    metadata.description = (node.prop ('description') or '').decode ('utf-8')
    doc.freeDoc ()

  else:
    metadata = None

  return metadata

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Get icon data from extension file
# @param path Extension path
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_icon_data_from_file (path):
  icon_data = None

  if os.path.exists (path):
    # @begin-deprecated version=0.5.4
    file_ext = os.path.splitext (path)[1]

    if file_ext == '.xml':
      doc = libxml2.parseFile (path)
      node = doc.getRootElement ()
      node = node.children

      while node and not icon_data:
        if node.type == 'element' and node.name == 'icon':
          icon_data = node.getContent ().decode ('base64')
        else:
          node = node.next

      doc.freeDoc ()

    else:
    # @end-deprecated
      zfile = zipfile.ZipFile (path)
      icon_data = zfile.read ('icon.png')
      zfile.close ()

  return icon_data

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Install extension
# @param path Extension path
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def install (path):
  ext = os.path.splitext (path)[1]

  # @begin-deprecated
  if ext == '.xml':
    __install_xml (path)

  else:
  # @end-deprecated
    __install_mobius (path)

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Uninstall extension
# @param extension_id Extension ID
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def uninstall (extension_id):
  path = get_path (extension_id)
  shutil.rmtree (path)

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief List installed extensions
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def installed ():

  for path in os.listdir (BASEDIR):
    fullpath = os.path.join (BASEDIR, path)
    ext = os.path.splitext (fullpath)[1]

    if os.path.isdir (fullpath) and ext != '.install':
      yield path

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Install a .mobius extension
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def __install_mobius (path):
  zfile = zipfile.ZipFile (path)

  # read metadata
  metadata = zfile.read ('metadata.xml')

  doc = libxml2.parseDoc (metadata)
  node = doc.getRootElement ()
  extension_id = (node.prop ('id') or '').decode ('utf-8')
  doc.freeDoc ()

  # set directory names
  extension_dir = get_path (extension_id)	# extension directory
  install_dir = extension_dir + '.install'	# installation directory
  old_dir = extension_dir + '.old'		# old version directory

  # remove previous .install and .old directory, if any
  if os.path.exists (install_dir):
    shutil.rmtree (install_dir)

  if os.path.exists (old_dir):
    shutil.rmtree (old_dir)
  
  # extract files to .install directory
  zfile.extractall (install_dir)
  zfile.close ()

  # run setup.py if it exists
  setup_py = os.path.join (install_dir, 'build', 'setup.py')

  if os.path.exists (setup_py):
    oldpwd = os.getcwd ()
    os.chdir (install_dir)

    fp = open (setup_py)
    exec fp in {}
    fp.close ()

    os.chdir (oldpwd)

  # rename extension dir to .old
  if os.path.exists (extension_dir):
    shutil.move (extension_dir, old_dir)

  # rename .install directory to extension dir
  shutil.move (install_dir, extension_dir)

  # remove old version, if any
  if os.path.exists (old_dir):
    shutil.rmtree (old_dir)

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Install a .xml extension
# @deprecated version=0.5.4
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def __install_xml (path):

  # load extension
  pickle = mobius.extension.pickle.Pickle ()
  extension = pickle.load (path)

  if not extension.id:
    raise Exception, "Null extension ID. Path=%s" % path

  # set directory names
  extension_dir = get_path (extension.id)	# extension directory
  install_dir = extension_dir + '.install'	# installation directory
  old_dir = extension_dir + '.old'		# old version directory

  # remove previous .install and .old directory, if any
  if os.path.exists (install_dir):
    shutil.rmtree (install_dir)

  if os.path.exists (old_dir):
    shutil.rmtree (old_dir)

  # create .install directory
  os.makedirs (install_dir)

  # generate metadata.xml
  doc = libxml2.newDoc ('1.0')
  node = libxml2.newNode ('extension')
  node.setProp ('id', extension.id.encode ('utf-8'))
  node.setProp ('name', extension.name.encode ('utf-8'))
  node.setProp ('author', extension.author.encode ('utf-8'))
  node.setProp ('version', extension.version.encode ('utf-8'))
  node.setProp ('description', extension.description.encode ('utf-8'))
  doc.addChild (node)
  metadata_path = os.path.join (install_dir, 'metadata.xml')
  doc.saveFormatFileEnc (metadata_path, 'utf-8', 1)
  doc.freeDoc ()

  # generate icon.png
  loader = gtk.gdk.PixbufLoader ()
  loader.set_size (64, 64)
  loader.write (extension.icon_data.decode ('base64'))
  loader.close ()
  pixbuf = loader.get_pixbuf ()

  icon_path = os.path.join (install_dir, 'icon.png')
  pixbuf.save (icon_path, 'png')

  # create build directory
  build_dir = os.path.join (install_dir, "build")
  os.makedirs (build_dir)

  # generate build/main.py
  pyfile = os.path.join (build_dir, "main.py")
  compiler = mobius.extension.compiler.Compiler ()
  compiler.generate_py (extension, pyfile) 

  # rename extension dir to .old
  if os.path.exists (extension_dir):
    shutil.move (extension_dir, old_dir)

  # rename .install directory to extension dir
  shutil.move (install_dir, extension_dir)

  # remove old version, if any
  if os.path.exists (old_dir):
    shutil.rmtree (old_dir)
