// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mobius Forensic Toolkit
// Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024 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/>.
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// References:
//    . Ares Galaxy 246 source code
//    . Forensic Analysis of Ares Galaxy Peer-to-Peer Network (Kolenbrander)
//
// Ares Galaxy main files (* decoded by MobiusFT):
//
//  . DHTNodes.dat - DHT nodes
//       @see DHT_readnodeFile - DHT/dhtzones.pas (line 125)
//       (client ID, IP, udp_port, tcp_port, type)
//
//  . MDHTNodes.dat - MDHT nodes
//       @see MDHT_readnodeFile - BitTorrent/dht_zones.pas (line 124)
//       (client ID, IP, udp_port, type)
//
//  . PHashIdx.dat, PhashIdxTemp.dat, TempPHash.dat - PHash table
//       @see ICH_load_phash_indexs - helper_ICH.pas (line 1023)
//       (hash_sha1, Phash table)
//
//  * ShareH.dat - Trusted metadata
//       @see get_trusted_metas - helper_library_db.pas (line 542)
//
//  * ShareL.dat - Cached metadata
//       @see get_cached_metas - helper_library_db.pas (line 367)
//
//  . SNodes.dat
//       @see aresnodes_loadfromdisk - helper_ares_nodes (line 445)
//       (IP, port, reports, attempts, connects, first_seen, last_seen)
//
//  * TorrentH.dat - DHT magnet file history and metadata
//       @see tthread_dht.getMagnetFiles - DHT/thread_dht.pas (line 284)
//
//  . TempDL/PHash_XXX.dat - Downloading file pieces info
//       @see ICH_loadPieces - helper_ICH (line 528)
//       (flag_done, progress, hash_sha1)
//
//  . TempDL/PBTHash_XXX.dat - Downloading file (BitTorrent) metadata
//       @see BitTorrentDb_load - BitTorrent/BitTorrentDlDb.pas (line 88)
//
//  * ___ARESTRA___*.* - Downloading files, with metadata info
//       @see read_details_DB_Download - helper_download_disk.pas (line 722)
//
//  . __INCOMPLETE__*.* - Downloading files (BitTorrent)
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include "evidence_loader_impl.h"
#include <mobius/io/walker.h>
#include <mobius/core/log.h>
#include <mobius/core/file_decoder/decoder.h>
#include <mobius/decoder/hexstring.h>
#include <mobius/datasource/datasource_vfs.h>
#include <mobius/exception.inc>
#include <mobius/io/folder.h>
#include <mobius/io/path.h>
#include <mobius/model/evidence.h>
#include <mobius/os/win/registry/hive_file.h>
#include <mobius/os/win/registry/hive_data.h>
#include <mobius/pod/map.h>
#include <mobius/string_functions.h>
#include <stdexcept>

namespace
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Constants
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static const std::string APP_ID = "ares";
static const std::string APP_NAME = "Ares Galaxy";
static const std::string ANT_ID = "evidence.app.ares";
static const std::string ANT_NAME = APP_NAME;
static const std::string ANT_VERSION = "1.0";

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Convert registry data into string
//! \param data Registry data
//! \param encoding Char encoding
//! \return String
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static std::string
to_string_from_hexstring (const mobius::os::win::registry::hive_data& data, const std::string& encoding = "utf-16le")
{
  std::string value;

  if (data)
    {
      mobius::bytearray d;
      d.from_hexstring (data.get_data ().to_string (encoding));
      value = d.to_string ();
    }

  return value;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Convert registry data into hex string
//! \param data Registry data
//! \return String
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static std::string
to_hex_string (const mobius::os::win::registry::hive_data& data)
{
  std::string value;

  if (data)
    value = mobius::string::toupper (data.get_data ().to_hexstring ());

  return value;
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Update metadata map, preferring non null values
//! \param metadata Metadata map
//! \param other Other metadata map
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
static void
update_metadata (mobius::pod::map& metadata, const mobius::pod::map& other)
{
  for (const auto& [k, v] : other)
    {
      auto old_v = metadata.get (k);

      if (!metadata.contains (k) || (old_v.is_null () && !v.is_null ()))
        metadata.set (k, v);
    }
}

} // namespace

namespace mobius::extension::app::ares
{
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Constructor
//! \param item Item object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
evidence_loader_impl::evidence_loader_impl (const mobius::model::item& item, scan_type type)
  : item_ (item),
    scan_type_ (type)
{
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Scan item files for evidences
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::run ()
{
  mobius::core::log log (__FILE__, __FUNCTION__);
  log.info (__LINE__, "Evidence loader <" + APP_ID + "> started");
  log.info (__LINE__, "Item UID: " + std::to_string (item_.get_uid ()));
  log.info (__LINE__, "Scan mode: " + std::to_string (static_cast <int> (scan_type_)));

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Check if loader has already run for item
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  if (item_.has_ant (ANT_ID) && (1 == 0))
    {
      log.info (__LINE__, "Evidence loader <" + APP_ID + "> has already run");
      return ;
    }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Check datasource
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  auto datasource = item_.get_datasource ();

  if (!datasource)
    throw std::runtime_error ("item has no datasource");

  if (datasource.get_type () != "vfs")
    throw std::runtime_error ("datasource type is not VFS");

  if (!datasource.is_available ())
    throw std::runtime_error ("datasource is not available");

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Scan item files, according to scan_type
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  switch (scan_type_)
    {
      case scan_type::canonical_folders:
        _scan_canonical_folders ();
        break;

      case scan_type::all_folders:
        _scan_all_folders ();
        break;

      default:
        log.warning (__LINE__, "invalid scan type: " + std::to_string (static_cast <int> (scan_type_)));
        return;
    }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Save evidences
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  _save_evidences ();
  log.info (__LINE__, "Evidence loader <" + APP_ID + "> ended");
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Scan canonical folders
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_scan_canonical_folders ()
{
  auto vfs_datasource = mobius::datasource::datasource_vfs (item_.get_datasource ());
  auto vfs = vfs_datasource.get_vfs ();

  for (const auto& entry : vfs.get_root_entries ())
    {
      if (entry.is_folder ())
        _scan_canonical_root_folder (entry.get_folder ());
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Scan root folder for evidences
//! \param folder Root folder
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_scan_canonical_root_folder (const mobius::io::folder& folder)
{
  auto w = mobius::io::walker (folder);

  for (const auto& f : w.get_folders_by_pattern ("users/*"))
    _scan_canonical_user_folder (f);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Scan user folder for evidences
//! \param folder User folder
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_scan_canonical_user_folder (const mobius::io::folder& folder)
{
  username_ = folder.get_name ();
  account_ = {};

  auto w = mobius::io::walker (folder);

  for (const auto& f : w.get_files_by_name ("ntuser.dat"))
    _decode_ntuser_dat_file (f);

  for (const auto& f : w.get_folders_by_path ("appdata/local/ares"))
    _scan_canonical_ares_folder (f);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Scan AppData/Local/Ares folder for evidences
//! \param folder Ares folder
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_scan_canonical_ares_folder (const mobius::io::folder& folder)
{
  account_files_.clear ();

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Scan folders
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  auto w = mobius::io::walker (folder);

  for (const auto& f : w.get_folders_by_name ("data"))
    _scan_canonical_ares_data_folder (f);

  for (const auto& f : w.get_folders_by_name ("my shared folder"))
    _scan_canonical_ares_my_shared_folder (f);

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Copy consolidated files to the list of all files found
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  std::transform (
    account_files_.cbegin (),
    account_files_.cend (),
    std::back_inserter (local_files_),
    [](const auto& p){ return p.second; }
  );

  account_files_.clear ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Scan Ares Data folder for evidences
//! \param folder Ares/Data folder
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_scan_canonical_ares_data_folder (const mobius::io::folder& folder)
{
  mobius::io::walker w (folder);

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Scan files
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  for (const auto& f : w.get_files ())
    {
      const std::string lname = mobius::string::tolower (f.get_name ());

      if (lname == "shareh.dat")
        _decode_shareh_dat_file (f);

      else if (lname == "sharel.dat")
        _decode_sharel_dat_file (f);

      else if (lname == "phashidx.dat" || lname == "phashidxtemp.dat" || lname == "tempphash.dat")
        _decode_phashidx_dat_file (f);
    }

  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  // Scan subfolders
  // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  for (const auto& f : w.get_files_by_pattern ("tempdl/phash_*.dat"))
    _scan_canonical_ares_data_tempdl_phash_file (f);

  //for (const auto& f : w.get_files_by_pattern ("tempdl/pbthash_*.dat"))
  //  _scan_canonical_ares_data_tempdl_pbthash_file (f);

  //for (const auto& f : w.get_files_by_pattern ("tempul/*"))
  //  _scan_canonical_ares_data_tempul_file (f);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Decode PHash_xxx.dat file
//! \param f File object
//! \todo Check pieces (progress, completed, pieces, piece)
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_scan_canonical_ares_data_tempdl_phash_file (const mobius::io::file& f)
{
  const std::string DECODER_ID = "app.ares.phash";
  mobius::core::log log (__FILE__, __FUNCTION__);

  try
    {
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Create decoder
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      auto decoder = mobius::core::file_decoder::new_decoder_by_id (DECODER_ID);

      if (!decoder)
        {
          log.warning (__LINE__, DECODER_ID + " decoder not found. " + f.get_path () + " file ignored");
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Decode file
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      decoder.decode (f.new_reader ());

      if (decoder.is_instance ())
        log.info (__LINE__, DECODER_ID + " file decoded. Path: " + f.get_path ());

      else
        {
          log.info (__LINE__, "File " + f.get_path () + " ignored. It is not an instance of " + DECODER_ID);
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Process entries
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      bool is_deleted = f.is_deleted ();

      for (auto& entry : decoder.get_entries ())
        {
          auto hash_sha1 = entry.get_metadata <std::string> ("hash_sha1");
          auto is_entry_completed = entry.get_metadata <int64_t> ("is_completed");
          auto pieces_count = entry.get_metadata <int64_t> ("pieces_count");
          auto [iter, success] = account_files_.try_emplace (hash_sha1, local_file {});
          std::ignore = success;
          auto& fobj = iter->second;

          if (!fobj.tempdl_phash_f || (fobj.tempdl_phash_f.is_deleted () && !is_deleted))
            {
              fobj.hash_sha1 = hash_sha1;
              fobj.account_guid = account_.guid;
              fobj.username = username_;
              fobj.flag_downloaded = true;
              fobj.tempdl_phash_f = f;
              fobj.metadata.set ("pieces_count", pieces_count);

              if (is_entry_completed != 2)  // STATE_UNKNOWN
                {
                  auto progress = entry.get_metadata <int64_t> ("progress");
                  auto pieces_completed = entry.get_metadata <int64_t> ("pieces_completed");
                  auto pieces_to_go = entry.get_metadata <int64_t> ("pieces_to_go");
                  auto piece_size = entry.get_metadata <int64_t> ("piece_size");

                  fobj.flag_completed = bool (is_entry_completed);
                  fobj.metadata.set ("downloaded_bytes", progress);
                  fobj.metadata.set ("pieces_completed", pieces_completed);
                  fobj.metadata.set ("pieces_to_go", pieces_to_go);
                  fobj.metadata.set ("piece_size", piece_size);
                }
            }
        }
    }
  catch (const std::exception& e)
    {
      log.warning (__LINE__, e.what ());
    }
}

void
evidence_loader_impl::_scan_canonical_ares_data_tempul_file (const mobius::io::file&)
{
  mobius::core::log log (__FILE__, __FUNCTION__);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Decode PHashIdx.dat file
//! \param f File object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_decode_phashidx_dat_file (const mobius::io::file& f)
{
  const std::string DECODER_ID = "app.ares.phash";
  mobius::core::log log (__FILE__, __FUNCTION__);

  try
    {
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Create decoder
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      auto decoder = mobius::core::file_decoder::new_decoder_by_id (DECODER_ID);

      if (!decoder)
        {
          log.warning (__LINE__, DECODER_ID + " decoder not found. " + f.get_path () + " file ignored");
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Decode file
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      decoder.decode (f.new_reader ());

      if (decoder.is_instance ())
        log.info (__LINE__, DECODER_ID + " file decoded. Path: " + f.get_path ());

      else
        {
          log.info (__LINE__, "File " + f.get_path () + " ignored. It is not an instance of " + DECODER_ID);
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Process entries
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      bool is_deleted = f.is_deleted ();

      for (auto& entry : decoder.get_entries ())
        {
          auto hash_sha1 = entry.get_metadata <std::string> ("hash_sha1");
          auto [iter, success] = account_files_.try_emplace (hash_sha1, local_file {});
          std::ignore = success;
          auto& fobj = iter->second;

          if (!fobj.phashidx_f || (fobj.phashidx_f.is_deleted () && !is_deleted))
            {
              fobj.hash_sha1 = hash_sha1;
              fobj.account_guid = account_.guid;
              fobj.username = username_;
              fobj.phashidx_idx = entry.get_idx ();
              fobj.flag_completed = true;  // PHashIdx.dat entries are always completed
              fobj.flag_downloaded = true;
              fobj.phashidx_f = f;

              if (fobj.size)
                fobj.metadata.set ("downloaded_bytes", fobj.size);
            }
        }
    }
  catch (const std::exception& e)
    {
      log.warning (__LINE__, e.what ());
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Decode ShareH.dat file
//! \param f File object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_decode_shareh_dat_file (const mobius::io::file& f)
{
  const std::string DECODER_ID = "app.ares.shareh";
  mobius::core::log log (__FILE__, __FUNCTION__);

  try
    {
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Create decoder
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      auto decoder = mobius::core::file_decoder::new_decoder_by_id (DECODER_ID);

      if (!decoder)
        {
          log.warning (__LINE__, DECODER_ID + " decoder not found. " + f.get_path () + " file ignored");
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Decode file
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      decoder.decode (f.new_reader ());

      if (decoder.is_instance ())
        log.info (__LINE__, DECODER_ID + " file decoded. Path: " + f.get_path ());

      else
        {
          log.info (__LINE__, "File " + f.get_path () + " ignored. It is not an instance of " + DECODER_ID);
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Process entries
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      bool is_deleted = f.is_deleted ();

      for (auto& entry : decoder.get_entries ())
        {
          auto hash_sha1 = entry.get_metadata <std::string> ("hash_sha1");

          auto [iter, success] = account_files_.try_emplace (hash_sha1, local_file {});
          std::ignore = success;
          auto& fobj = iter->second;

          if (!fobj.shareh_f || (fobj.shareh_f.is_deleted () && !is_deleted))
            {
              fobj.hash_sha1 = hash_sha1;
              fobj.account_guid = account_.guid;
              fobj.username = username_;
              fobj.download_completed_time = entry.get_metadata <mobius::datetime::datetime> ("download_completed_time");
              fobj.shareh_idx = entry.get_idx ();
              fobj.shareh_f = f;

              fobj.flag_shared = entry.get_metadata <bool> ("is_shared");
              fobj.flag_completed = true;  // ShareH entries are always completed
              fobj.flag_corrupted = entry.get_metadata <bool> ("is_corrupted");

              if (fobj.download_completed_time)
                fobj.flag_downloaded = true;

              update_metadata (fobj.metadata, entry.get_all_metadata ());
            }
        }
    }
  catch (const std::exception& e)
    {
      log.warning (__LINE__, e.what ());
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Decode ShareL.dat file
//! \param f File object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_decode_sharel_dat_file (const mobius::io::file& f)
{
  const std::string DECODER_ID = "app.ares.sharel";
  mobius::core::log log (__FILE__, __FUNCTION__);

  try
    {
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Create decoder
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      auto decoder = mobius::core::file_decoder::new_decoder_by_id (DECODER_ID);

      if (!decoder)
        {
          log.warning (__LINE__, DECODER_ID + " decoder not found. " + f.get_path () + " file ignored");
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Decode file
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      decoder.decode (f.new_reader ());

      if (decoder.is_instance ())
        log.info (__LINE__, DECODER_ID + " file decoded. Path: " + f.get_path ());

      else
        {
          log.info (__LINE__, "File " + f.get_path () + " ignored. It is not an instance of " + DECODER_ID);
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Process entries
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      bool is_deleted = f.is_deleted ();

      for (auto& entry : decoder.get_entries ())
        {
          auto hash_sha1 = entry.get_metadata <std::string> ("hash_sha1");

          auto [iter, success] = account_files_.try_emplace (hash_sha1, local_file {});
          std::ignore = success;
          auto& fobj = iter->second;

          if (!fobj.sharel_f || (fobj.sharel_f.is_deleted () && !is_deleted))
            {
              // attributes
              fobj.hash_sha1 = hash_sha1;
              fobj.account_guid = account_.guid;
              fobj.username = username_;
              fobj.path = entry.get_metadata <std::string> ("path");
              fobj.download_started_time = entry.get_metadata <mobius::datetime::datetime> ("download_started_time");
              fobj.size = entry.get_metadata <std::int64_t> ("size");
              fobj.sharel_idx = entry.get_idx ();
              fobj.sharel_f = f;

              if (!fobj.path.empty ())
                {
                  auto cpath = mobius::string::replace (fobj.path, "\\", "/");
                  fobj.filename = mobius::io::path (cpath).get_filename ();
                }

              // flags
              if (fobj.download_started_time)
                fobj.flag_downloaded = true;

              fobj.flag_corrupted.set_if_unknown (entry.get_metadata <bool> ("is_corrupted"));
              fobj.flag_shared.set_if_unknown (true);   // ShareL is shared by default, unless it is flagged
                                                        // "no" in the corresponding ShareH entry.

              fobj.flag_completed = true;  // ShareL entries are always completed

              // metadata
              update_metadata (fobj.metadata, entry.get_all_metadata ());
            }
        }
    }
  catch (const std::exception& e)
    {
      log.warning (__LINE__, e.what ());
    }
}

void
evidence_loader_impl::_scan_canonical_ares_my_shared_folder (const mobius::io::folder& folder)
{
  mobius::io::walker w (folder);

  auto filter = [](const auto& f){
     return mobius::string::startswith (f.get_name (), "___ARESTRA___");
  };

  for (const auto& f : w.find_files (filter))
    _decode_arestra_file (f);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Decode ___ARESTRA___ file
//! \param f File object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_decode_arestra_file (const mobius::io::file& f)
{
  const std::string DECODER_ID = "app.ares.arestra";
  mobius::core::log log (__FILE__, __FUNCTION__);

  try
    {
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Create decoder
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      auto decoder = mobius::core::file_decoder::new_decoder_by_id (DECODER_ID);

      if (!decoder)
        {
          log.warning (__LINE__, DECODER_ID + " decoder not found. " + f.get_path () + " file ignored");
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Decode file
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      decoder.decode (f.new_reader ());

      if (decoder.is_instance ())
        log.info (__LINE__, DECODER_ID + " file decoded. Path: " + f.get_path ());

      else
        {
          log.info (__LINE__, "File " + f.get_path () + " ignored. It is not an instance of " + DECODER_ID);
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Generate local_file
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      auto general = decoder.get_metadata_group ("general");
      auto metadata = decoder.get_metadata_group ("metadata");
      auto hash_sha1 = metadata.get <std::string> ("hash_sha1");
      bool is_deleted = f.is_deleted ();

      auto [iter, success] = account_files_.try_emplace (hash_sha1, local_file {});
      std::ignore = success;
      auto& fobj = iter->second;

      if (!fobj.arestra_f || (fobj.arestra_f.is_deleted () && !is_deleted))
        {
          // set attributes
          fobj.hash_sha1 = hash_sha1;
          fobj.account_guid = account_.guid;
          fobj.username = username_;
          fobj.download_started_time = metadata.get <mobius::datetime::datetime> ("download_started_time");
          fobj.size = general.get <std::int64_t> ("file_size");
          fobj.arestra_f = f;

          // set filename
          fobj.filename = mobius::io::path (f.get_path ()).get_filename ();
          fobj.filename.erase (0, 13);  // remove "___ARESTRA___"

          // set flags
          fobj.flag_downloaded = true;
          fobj.flag_corrupted.set_if_unknown (metadata.get <bool> ("is_corrupted"));
          fobj.flag_shared.set_if_unknown (false);   //! \see thread_share.pas (line 1065)
          fobj.flag_completed = general.get <bool> ("is_completed");

          // set metadata
          fobj.metadata.set ("arestra_file_version", general.get <std::int64_t> ("file_version"));
          fobj.metadata.set ("downloaded_bytes", general.get <std::int64_t> ("progress"));
          update_metadata (fobj.metadata, metadata);
        }
    }
  catch (const std::exception& e)
    {
      log.warning (__LINE__, e.what ());
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Decode data from NTUSER.dat file
//! \param f File object
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_decode_ntuser_dat_file (const mobius::io::file& f)
{
  mobius::core::log log (__FILE__, __FUNCTION__);

  try
    {
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Create decoder
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      auto decoder = mobius::os::win::registry::hive_file (f.new_reader ());

      if (!decoder.is_instance ())
        {
          log.info (__LINE__, "File " + f.get_path () + " ignored.");
          return;
        }

      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      // Get evidences from Ares key
      // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      const auto& root_key = decoder.get_root_key ();
      const auto& ares_key = root_key.get_key_by_path ("Software\\Ares");

      if (ares_key)
        {
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Load account
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          account acc;

          acc.guid = ares_key.get_data_by_name ("Personal.GUID").get_data_as_string ("utf-16le");
          acc.nickname = to_string_from_hexstring (ares_key.get_data_by_name ("Personal.Nickname"));
          acc.dht_id = to_hex_string (ares_key.get_data_by_name ("Network.DHTID"));
          acc.mdht_id = to_hex_string (ares_key.get_data_by_name ("Network.MDHTID"));
          acc.username = username_;
          acc.is_deleted = f.is_deleted ();
          acc.f = f;

          if (account_.guid.empty () ||
              (account_.is_deleted && !acc.is_deleted))
            account_ = acc;

          accounts_.push_back (acc);

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Load autofill values
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          for (const auto& key : ares_key.get_keys_by_mask ("Search.History\\*"))
            {
              std::string category = key.get_name ();

              for (const auto& value : key.get_values ())
                {
                  autofill af;

                  af.value = mobius::decoder::hexstring (value.get_name ()).to_string ();
                  af.username = username_;
                  af.category = category;
                  af.account_guid = acc.guid;
                  af.is_deleted = acc.is_deleted;
                  af.f = f;

                  autofills_.push_back (af);
                }
            }
        }
    }
  catch (const std::exception& e)
    {
      log.warning (__LINE__, e.what ());
    }
}


void
evidence_loader_impl::_scan_all_folders ()
{
  auto vfs_datasource = mobius::datasource::datasource_vfs (item_.get_datasource ());
  auto vfs = vfs_datasource.get_vfs ();

  for (const auto& entry : vfs.get_root_entries ())
    {
      if (entry.is_folder ())
        _scan_generic_folder (entry.get_folder ());
    }
}

void
evidence_loader_impl::_scan_generic_folder (const mobius::io::folder& folder)
{
  mobius::io::walker w (folder);

  for (const auto& f : w.get_files ())
    {
      const std::string lname = mobius::string::tolower (f.get_name ());

      if (lname == "shareh.dat")
        _decode_shareh_dat_file (f);

      else if (lname == "sharel.dat")
        _decode_sharel_dat_file (f);

      else if (lname == "phashidx.dat")
        _decode_phashidx_dat_file (f);

      else if (mobius::string::startswith (lname, "___arestra___"))
         _decode_arestra_file (f);
    }

  for (const auto& child : w.get_folders ())
    _scan_generic_folder (child);
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Save evidences
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_save_evidences ()
{
  auto transaction = item_.new_transaction ();

  _save_accounts ();
  _save_autofills ();
  _save_local_files ();
  _save_received_files ();
  _save_shared_files ();

  item_.set_ant (ANT_ID, ANT_NAME, ANT_VERSION);
  transaction.commit ();
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Save accounts
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_save_accounts ()
{
  for (const auto& a : accounts_)
    {
      mobius::pod::map metadata;
      metadata.set ("app_id", APP_ID);
      metadata.set ("app_name", APP_NAME);
      metadata.set ("username", a.username);
      metadata.set ("network", "Ares");
      metadata.set ("guid", a.guid);
      metadata.set ("dht_id", a.dht_id);
      metadata.set ("mdht_id", a.mdht_id);

      auto e = item_.new_evidence ("user-account");

      e.set_attribute ("account_type", "app.ares");
      e.set_attribute ("id", a.guid);
      e.set_attribute ("name", a.nickname);
      e.set_attribute ("password", {});
      e.set_attribute ("password_found", "no");
      e.set_attribute ("is_deleted", a.is_deleted);
      e.set_attribute ("metadata", metadata);
      e.set_tag ("p2p");
      e.add_source (a.f);
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Save autofill entries
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_save_autofills ()
{
  for (const auto& a : autofills_)
    {
      mobius::pod::map metadata;
      metadata.set ("category", a.category);
      metadata.set ("network", "Ares");
      metadata.set ("ares_account_guid", a.account_guid);

      auto e = item_.new_evidence ("autofill");

      e.set_attribute ("field_name", "search");
      e.set_attribute ("value", a.value);
      e.set_attribute ("app_id", APP_ID);
      e.set_attribute ("app_name", APP_NAME);
      e.set_attribute ("username", a.username);
      e.set_attribute ("is_deleted", a.is_deleted);
      e.set_attribute ("metadata", metadata);
      e.set_tag ("p2p");
      e.add_source (a.f);
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Save received files
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_save_received_files ()
{
  for (const auto& f : local_files_)
    {
      if (f.flag_downloaded.is_yes ())
        {
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Create evidence
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          auto e = item_.new_evidence ("received-file");

          if (f.download_completed_time)
            e.set_attribute ("timestamp", f.download_completed_time);

          else if (f.download_started_time)
            e.set_attribute ("timestamp", f.download_started_time);

          e.set_attribute ("filename", f.filename);
          e.set_attribute ("path", f.path);
          e.set_attribute ("username", f.username);
          e.set_attribute ("app_id", APP_ID);
          e.set_attribute ("app_name", APP_NAME);

          std::vector <mobius::pod::data> hashes = {{"sha1", f.hash_sha1}};
          e.set_attribute ("hashes", hashes);

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Metadata
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          mobius::pod::map metadata;
          metadata.set ("flag_downloaded", to_string (f.flag_downloaded));
          metadata.set ("flag_uploaded", to_string (f.flag_uploaded));
          metadata.set ("flag_shared", to_string (f.flag_shared));
          metadata.set ("flag_corrupted", to_string (f.flag_corrupted));
          metadata.set ("flag_completed", to_string (f.flag_completed));

          if (f.shareh_idx)
            metadata.set ("shareh_idx", f.shareh_idx);

          if (f.sharel_idx)
            metadata.set ("sharel_idx", f.sharel_idx);

          if (f.phashidx_idx)
            metadata.set ("phashidx_idx", f.phashidx_idx);

          metadata.set ("network", "Ares");
          update_metadata (metadata, f.metadata);

          e.set_attribute ("metadata", metadata);

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Tags
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          e.set_tag ("p2p");

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Sources
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          e.add_source (f.shareh_f);
          e.add_source (f.sharel_f);
          e.add_source (f.phashidx_f);
          e.add_source (f.arestra_f);
        }
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Save local files
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_save_local_files ()
{
  for (const auto& f : local_files_)
    {
      if (!f.path.empty ())
        {
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Create evidence
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          auto e = item_.new_evidence ("local-file");

          e.set_attribute ("username", f.username);
          e.set_attribute ("path", f.path);
          e.set_attribute ("app_id", APP_ID);
          e.set_attribute ("app_name", APP_NAME);

          std::vector <mobius::pod::data> hashes = {{"sha1", f.hash_sha1}};
          e.set_attribute ("hashes", hashes);

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Metadata
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          mobius::pod::map metadata;

          metadata.set ("size", f.size);
          metadata.set ("flag_downloaded", to_string (f.flag_downloaded));
          metadata.set ("flag_uploaded", to_string (f.flag_uploaded));
          metadata.set ("flag_shared", to_string (f.flag_shared));
          metadata.set ("flag_corrupted", to_string (f.flag_corrupted));
          metadata.set ("flag_completed", to_string (f.flag_completed));

          if (f.shareh_idx)
            metadata.set ("shareh_idx", f.shareh_idx);

          if (f.sharel_idx)
            metadata.set ("sharel_idx", f.sharel_idx);

          if (f.phashidx_idx)
            metadata.set ("phashidx_idx", f.phashidx_idx);

          metadata.set ("network", "Ares");
          update_metadata (metadata, f.metadata);

          e.set_attribute ("metadata", metadata);

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Tags
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          e.set_tag ("p2p");

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Sources
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          e.add_source (f.shareh_f);
          e.add_source (f.sharel_f);
          e.add_source (f.phashidx_f);
          e.add_source (f.arestra_f);
        }
    }
}

// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//! \brief Save shared files
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void
evidence_loader_impl::_save_shared_files ()
{
  for (const auto& f : local_files_)
    {
      if (f.flag_shared.is_yes () || f.flag_shared.is_always ())
        {
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Create evidence
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          auto e = item_.new_evidence ("shared-file");

          e.set_attribute ("username", f.username);
          e.set_attribute ("filename", f.filename);
          e.set_attribute ("path", f.path);
          e.set_attribute ("app_id", APP_ID);
          e.set_attribute ("app_name", APP_NAME);

          std::vector <mobius::pod::data> hashes = {{"sha1", f.hash_sha1}};
          e.set_attribute ("hashes", hashes);

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Metadata
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          mobius::pod::map metadata;

          metadata.set ("size", f.size);
          metadata.set ("flag_downloaded", to_string (f.flag_downloaded));
          metadata.set ("flag_uploaded", to_string (f.flag_uploaded));
          metadata.set ("flag_shared", to_string (f.flag_shared));
          metadata.set ("flag_corrupted", to_string (f.flag_corrupted));
          metadata.set ("flag_completed", to_string (f.flag_completed));

          if (f.shareh_idx)
            metadata.set ("shareh_idx", f.shareh_idx);

          if (f.sharel_idx)
            metadata.set ("sharel_idx", f.sharel_idx);

          if (f.phashidx_idx)
            metadata.set ("phashidx_idx", f.phashidx_idx);

          metadata.set ("network", "Ares");
          update_metadata (metadata, f.metadata);

          e.set_attribute ("metadata", metadata);

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Tags
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          e.set_tag ("p2p");

          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          // Sources
          // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
          e.add_source (f.shareh_f);
          e.add_source (f.sharel_f);
          e.add_source (f.phashidx_f);
          e.add_source (f.arestra_f);
        }
    }
}

} // namespace mobius::extension::app::ares