# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022 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 pymobius
import pymobius.extension
import mobius
import glob
import os
import os.path
import traceback
import shutil

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Resource list
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class ResourceList (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self):
    self.__resource_ids = []
    self.__resources = {}

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief iterate through resources
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __iter__ (self):
    for resource_id in self.__resource_ids:
      yield resource_id, self.__resources.get (resource_id)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief add resource
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def add (self, resource_id, resource):
    self.__resource_ids.append (resource_id)
    self.__resources[resource_id] = resource

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief remove resource
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def remove (self, resource_id):
    try:
      self.__resource_ids.remove (resource_id)
    except ValueError:
      mobius.core.logf ('WRN resource "' + resource_id + '" not found. ' + traceback.format_exc ())

    self.__resources.pop (resource_id, None)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief get resource
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def get (self, resource_id):
    return self.__resources.get (resource_id)


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Application class
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Application (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self):
    self.__resources = {}

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Run application
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def run (self):

    # resource services
    pymobius.mediator.advertise ('app.add-resource', self.svc_add_resource)
    pymobius.mediator.advertise ('app.remove-resource', self.svc_remove_resource)
    pymobius.mediator.advertise ('app.get-resource', self.svc_get_resource)
    pymobius.mediator.advertise ('app.list-resources', self.svc_list_resources)

    # extension data services
    pymobius.mediator.advertise ('extension.get-metadata', pymobius.extension.svc_extension_get_metadata)
    pymobius.mediator.advertise ('extension.get-metadata-from-file', pymobius.extension.svc_extension_get_metadata_from_file)
    pymobius.mediator.advertise ('extension.get-icon-data-from-file', pymobius.extension.svc_extension_get_icon_data_from_file)
    pymobius.mediator.advertise ('extension.get-icon-path', pymobius.extension.svc_extension_get_icon_path)
    pymobius.mediator.advertise ('extension.get-resource-path', pymobius.extension.svc_extension_get_resource_path)

    # extension running services
    pymobius.mediator.advertise ('extension.is-running', pymobius.extension.svc_extension_is_running)
    pymobius.mediator.advertise ('extension.start-api', pymobius.extension.svc_extension_start_api)
    pymobius.mediator.advertise ('extension.stop-api', pymobius.extension.svc_extension_stop_api)
    pymobius.mediator.advertise ('extension.start', pymobius.extension.svc_extension_start)
    pymobius.mediator.advertise ('extension.stop', pymobius.extension.svc_extension_stop)
    pymobius.mediator.advertise ('extension.start-all', pymobius.extension.svc_extension_start_all)
    pymobius.mediator.advertise ('extension.stop-all', pymobius.extension.svc_extension_stop_all)
    pymobius.mediator.advertise ('extension.list', pymobius.extension.svc_extension_list)
    pymobius.mediator.advertise ('extension.install', pymobius.extension.svc_extension_install)
    pymobius.mediator.advertise ('extension.uninstall', pymobius.extension.svc_extension_uninstall)

    # configure user account
    self.__configure_user ()
    self.__install_cpp_extensions ()

    app = mobius.core.application ()
    app.start ()

    # start extensions API
    pymobius.mediator.call ('extension.start-all')

    # start application
    pymobius.mediator.emit ('app.starting')
    pymobius.mediator.call ('app.start')

    # stop extensions
    pymobius.mediator.emit ('app.stopping')
    pymobius.mediator.call ('extension.stop-all')

    app.stop ()

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Configure application for current user
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __configure_user (self):
    app = mobius.core.application ()
    extensions_dir = app.get_config_path ('extensions')

    if not os.path.exists (extensions_dir):
      os.makedirs (extensions_dir)
      dist_dir = app.get_data_path ('extensions')

      # install Python extensions
      for path in sorted (glob.glob ('%s/*.mobius' % dist_dir)):
        mobius.core.logf ('INF Installing extension %s' % os.path.basename (path))
        pymobius.extension.install (path)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Install C++ extensions
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __install_cpp_extensions (self):
    app = mobius.core.application ()
    dist_dir = app.get_data_path ('extensions')
    install_dir = app.get_config_path ('extensions')

    for dist_path in sorted (glob.glob ('%s/*.so' % dist_dir)):
      filename = os.path.basename (dist_path)
      install_path = os.path.join (install_dir, filename)
      can_install = False

      # compare installed version and distribution version
      if os.path.exists (install_path):
        ext_dist = mobius.core.extension (dist_path)
        ext_install = mobius.core.extension (install_path)

        if ext_dist.version != ext_install.version:
          can_install = True

      else:
        can_install = True

      # install, if necessary
      if can_install:
        mobius.core.logf ('INF Installing C++ extension %s' % filename)
        shutil.copy (dist_path, install_path)

        ext = mobius.core.extension (install_path)
        ext.install ()

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief service: app.add-resource
  # @param resource_id: resource ID
  # @param resource: resource object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def svc_add_resource (self, resource_id, resource):
    if '.' in resource_id:
      group_id, item_id = resource_id.rsplit ('.', 1)
      resource_list = self.__resources.setdefault (group_id, ResourceList ())
      resource_list.add (item_id, resource)

      pymobius.mediator.emit ('app.resource-added', group_id, item_id, resource)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief service: app.remove-resource
  # @param resource_id: resource ID
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def svc_remove_resource (self, resource_id):
    if '.' in resource_id:
      group_id, item_id = resource_id.rsplit ('.', 1)
      resource_list = self.__resources.setdefault (group_id, ResourceList ())
      resource_list.remove (item_id)

      pymobius.mediator.emit ('app.resource-removed', group_id, item_id)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief service: app.get-resource
  # @param resource_id: resource ID
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def svc_get_resource (self, resource_id):
    value = None

    if '.' in resource_id:
      group_id, item_id = resource_id.rsplit ('.', 1)
      resource_list = self.__resources.setdefault (group_id, ResourceList ())
      value = resource_list.get (item_id)

    return value

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief service: app.list-resources
  # @param resource_id: group ID
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def svc_list_resources (self, group_id):
    resource_list = self.__resources.get (group_id) or []

    return list (iter (resource_list))
