# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# 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/>.
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import binascii
import traceback

import mobius
import pymobius.operating_system
import pymobius.p2p
import pymobius.p2p.account
import pymobius.p2p.application
import pymobius.p2p.remote_file
import pymobius.p2p.search
import pymobius.registry
import pymobius.registry.installed_programs
from pymobius.p2p.local_file import *
from pymobius.registry import *
from . import decoder_tempdl_phash_dat
from . import decoder_torrenth_dat
from . import profile


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# References:
#    . Ares Galaxy 246 source code
#    . Forensic Analysis of Ares Galaxy Peer-to-Peer Network (Kolenbrander)
#
# Ares Galaxy main files (* decoded):
#
#  . 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)
#
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Generic dataholder
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class dataholder(object):
    pass


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Iterate through Ares AppData folders
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def iter_ares_folders(item):
    for opsys in pymobius.operating_system.scan(item):
        for user_profile in opsys.get_profiles():
            try:
                folder = user_profile.get_entry_by_path('%localappdata%/Ares')
                if folder:
                    yield user_profile, folder
            except Exception as e:
                mobius.core.logf('WRN p2p.ares: ' + str(e))


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Retrieves Ares P2P activity data
# @author Eduardo Aguiar
# This function is planned to run in an independent thread. The idea here
# is to gather all activity data and only then write data to the model
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def retrieve(model):
    try:
        ant = Ant(model)
        ant.run()
    except Exception as e:
        mobius.core.logf(f'WRN {str(e)}\n{traceback.format_exc()}')


# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief forensics: P2P Ares
# @author Eduardo Aguiar
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Ant(object):

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Initialize object
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __init__(self, model):
        self.__model = model  # P2P model
        self.__item = model.item

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

        # create local data model
        self.__data = dataholder()
        self.__data.application = pymobius.p2p.application.application()
        self.__data.application.id = 'ares'
        self.__data.application.name = 'Ares Galaxy'
        self.__data.item = self.__model.item

        self.__data.accounts = []
        self.__data.searches = []
        self.__data.local_files = []
        self.__data.remote_files = []

        # retrieve data
        self.__retrieve()

        # update P2P model
        self.__model.applications.append(self.__data.application)
        self.__model.accounts += self.__data.accounts
        self.__model.searches += self.__data.searches
        self.__model.local_files += self.__data.local_files
        self.__model.remote_files += self.__data.remote_files

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Retrieve data
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __retrieve(self):
        ant = pymobius.registry.main.Ant(self.__item)

        for registry in ant.get_data():
            self.__retrieve_registry_data(registry)

        self.__retrieve_app_data()
        self.__normalize_data()

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Retrieve registry data
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __retrieve_registry_data(self, registry):

        # Application
        for program in pymobius.registry.installed_programs.get(registry):
            program_name = program.display_name.lower()

            if program_name == 'ares' or program_name.startswith('ares '):
                self.__data.application.versions.add(program.version)

        # Search History
        for username, user_key in pymobius.registry.iter_hkey_users(registry):
            for subkey in user_key.get_key_by_mask('Software\\Ares\\Search.History\\*'):
                for value in subkey.values:
                    keyword = binascii.unhexlify(value.name).decode('utf-8')
                    search = pymobius.p2p.search.search()
                    search.app_id = 'ares'
                    search.app = 'Ares Galaxy'
                    search.text = keyword
                    search.username = username
                    search.count = -1
                    search.add_metadata('Application', 'Ares Galaxy')
                    search.add_metadata('Search Term', search.text)
                    search.add_metadata('Category', subkey.name)
                    self.__data.searches.append(search)

        # Accounts
        for username, user_key in pymobius.registry.iter_hkey_users(registry):
            ares_key = user_key.get_key_by_path('Software\\Ares')

            if ares_key:
                guid = get_data_as_string(ares_key.get_data_by_name('Personal.GUID'))
                account = pymobius.p2p.account.account()
                account.username = username
                account.app_id = 'ares'
                account.app = 'Ares Galaxy'
                account.network = 'Ares'
                account.guid = guid
                self.__data.accounts.append(account)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Retrieve data from disk files
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __retrieve_app_data(self):

        # scan Ares profiles
        for user_profile, folder in iter_ares_folders(self.__item):
            profile = pymobius.p2p.ares.profile.profile(self.__item)
            profile.username = user_profile.username

            # 'Data' folder
            data_folder = folder.get_child_by_name('Data', False)
            profile.retrieve_data_folder(data_folder)

            # 'My Shared Folder'
            my_shared_folder = \
                user_profile.get_entry_by_path('%desktop%/My Shared Folder') or \
                user_profile.get_entry_by_path('%localappdata%/My Shared Folder')
            profile.retrieve_my_shared_folder(my_shared_folder)

            # Add to model
            self.__add_local_files(self.__item, self.__data, profile)
            self.__add_remote_files(self.__item, self.__data, profile)

            # self.__add_torrenth_dat (self.__item, self.__data, profile)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Add data from profile.get_local_files
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __add_local_files(self, item, model, profile):

        # add metadata if it is not None
        def add_metadata(f, name, value):
            if value is not None:
                f.add_metadata(name, value)

        # add local files
        for entry in profile.get_local_files():
            lf = local_file()
            lf.app_id = 'ares'
            lf.app = 'Ares Galaxy'
            lf.path = entry.path
            lf.size = entry.size if entry.size is not None else -1
            lf.name = entry.name or entry.title
            lf.username = profile.username

            # flags
            if entry.flag_corrupted == STATE_YES:
                lf.flag_shared = STATE_NO  # [Kolenbrander - Pg. 7]
            else:
                lf.flag_shared = entry.flag_shared

            lf.flag_downloaded = entry.flag_downloaded
            lf.flag_uploaded = entry.flag_uploaded
            lf.flag_completed = entry.flag_completed
            lf.flag_corrupted = entry.flag_corrupted

            # hashes
            if entry.hash_sha1:
                lf.set_hash('sha1', entry.hash_sha1)

            # metadata
            metadata = entry.metadata

            lf.add_metadata('Title', entry.title)
            lf.add_metadata('Artist', entry.artist)
            lf.add_metadata('Album', entry.album)
            lf.add_metadata('Category', entry.category)
            lf.add_metadata('Year', entry.year)
            lf.add_metadata('Vidinfo', entry.vidinfo)
            lf.add_metadata('Language', entry.language)
            lf.add_metadata('URL', entry.url)
            lf.add_metadata('Comment', entry.comment)
            lf.add_metadata('Filetype', entry.filetype)

            add_metadata(lf, 'Download start time', metadata.get('download_start_time'))
            add_metadata(lf, 'Download completed time', entry.download_completed_time)
            add_metadata(lf, 'Bytes downloaded', metadata.get('bytes_downloaded'))
            add_metadata(lf, 'Bytes uploaded', metadata.get('bytes_uploaded'))

            add_metadata(lf, 'Pieces', metadata.get('pieces'))
            add_metadata(lf, 'Piece size', metadata.get('piece_size'))
            add_metadata(lf, 'Pieces downloaded', metadata.get('pieces_downloaded'))
            add_metadata(lf, 'Pieces to download', metadata.get('pieces_to_download'))

            add_metadata(lf, 'ShareL.dat path', metadata.get('sharel_path'))
            add_metadata(lf, 'ShareL.dat creation time (UTC)', metadata.get('sharel_creation_time'))
            add_metadata(lf, 'ShareL.dat last modification time (UTC)', metadata.get('sharel_last_modification_time'))

            add_metadata(lf, 'ShareH.dat path', metadata.get('shareh_path'))
            add_metadata(lf, 'ShareH.dat creation time (UTC)', metadata.get('shareh_creation_time'))
            add_metadata(lf, 'ShareH.dat last modification time (UTC)', metadata.get('shareh_last_modification_time'))

            add_metadata(lf, 'Arestra subfolder', metadata.get('arestra_subfolder'))
            add_metadata(lf, 'Arestra creation time (UTC)', metadata.get('arestra_creation_time'))
            add_metadata(lf, 'Arestra last modification time (UTC)', metadata.get('arestra_last_modification_time'))
            add_metadata(lf, 'Param1', metadata.get('param1'))
            add_metadata(lf, 'Param2', metadata.get('param2'))
            add_metadata(lf, 'Param3', metadata.get('param3'))

            add_metadata(lf, 'PHash_.dat path', metadata.get('phash_path'))
            add_metadata(lf, 'PHash_.dat creation time (UTC)', metadata.get('phash_creation_time'))
            add_metadata(lf, 'PHash_.dat last modification time (UTC)', metadata.get('phash_last_modification_time'))

            add_metadata(lf, 'PHashIdx.dat path', metadata.get('phashidx_path'))
            add_metadata(lf, 'PHashIdx.dat creation time (UTC)', metadata.get('phashidx_creation_time'))
            add_metadata(lf, 'PHashIdx.dat last modification time (UTC)',
                         metadata.get('phashidx_last_modification_time'))

            add_metadata(lf, 'Torrent hash SHA-1', metadata.get('torrent_hash_sha1'))
            add_metadata(lf, 'Torrent piece length', metadata.get('torrent_piece_length'))
            add_metadata(lf, 'Torrent pieces', metadata.get('torrent_pieces'))
            add_metadata(lf, 'Torrent Files', metadata.get('torrent_files'))
            add_metadata(lf, 'File index', metadata.get('file_idx'))
            add_metadata(lf, 'Database time', metadata.get('db_time'))
            add_metadata(lf, 'PBTHash_.dat path', metadata.get('pbthash_path'))
            add_metadata(lf, 'PBTHash_.dat creation time (UTC)', metadata.get('pbthash_creation_time'))
            add_metadata(lf, 'PBTHash_.dat last modification time (UTC)',
                         metadata.get('pbthash_last_modification_time'))

            add_metadata(lf, 'DHT magnet file added date/time', metadata.get('dht_added_time'))
            add_metadata(lf, 'DHT magnet file SHA-1', metadata.get('dht_hash_sha1'))
            add_metadata(lf, 'DHT magnet file verified', metadata.get('dht_verified'))
            add_metadata(lf, 'Seeds', metadata.get('seeds'))
            add_metadata(lf, 'TorrentH.dat path', metadata.get('torrenth_path'))
            add_metadata(lf, 'TorrentH.dat creation time (UTC)', metadata.get('torrenth_creation_time'))
            add_metadata(lf, 'TorrentH.dat last modification time (UTC)',
                         metadata.get('torrenth_last_modification_time'))

            # add to model
            model.local_files.append(lf)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Add data from profile.get_remote_files
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __add_remote_files(self, item, model, profile):

        # add remote files
        for entry in profile.get_remote_files():
            lf = pymobius.p2p.remote_file.remote_file()
            lf.app_id = 'ares'
            lf.app = 'Ares Galaxy'
            lf.size = entry.size
            lf.name = entry.name
            lf.username = profile.username
            lf.timestamp = entry.timestamp

            # hash
            lf.set_hash('sha1', entry.hash_sha1)

            # metadata
            if 'local_timestamp' in entry.metadata:
                lf.add_metadata('Local timestamp', entry.metadata.get('local_timestamp'))

            lf.add_metadata('Title', entry.title)
            lf.add_metadata('Artist', entry.artist)
            lf.add_metadata('Album', entry.album)
            lf.add_metadata('Category', entry.category)
            lf.add_metadata('Year', entry.year)
            lf.add_metadata('Language', entry.language)
            lf.add_metadata('URL', entry.url)
            lf.add_metadata('Comment', entry.comment)
            lf.add_metadata('Filetype', entry.filetype)

            # peer data
            lf.peer.ip = entry.peer_ip
            lf.peer.port = entry.peer_port

            # add to model
            model.remote_files.append(lf)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Add data from TorrentH.dat to model
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __add_torrenth_dat(self, item, model, profile):

        # add local files
        for entry in profile.torrenth.values():
            lf = local_file()
            lf.app_id = 'ares'
            lf.app = 'Ares Galaxy'
            lf.path = entry.path
            lf.size = entry.size
            lf.name = entry.name
            lf.username = profile.username

            # flags
            lf.flag_shared = STATE_YES  # @see DHT/thread_dht.pas (line 412) STATE_NO
            lf.flag_downloaded = STATE_YES  # @see DHT/dhtkeywords.pas (line 355)
            # @todo f.flag_uploaded
            lf.flag_completed = STATE_YES  # @see DHT/dhtkeywords.pas (line 355)
            lf.flag_corrupted = STATE_NO  # @see DHT/dhtkeywords.pas (line 355)

            # metadata
            lf.add_metadata('Title', entry.title)
            lf.add_metadata('Comment', entry.comment)
            lf.add_metadata('Filetype', entry.filetype)
            lf.add_metadata('DHT magnet file added date/time', entry.timestamp)
            lf.add_metadata('DHT magnet file SHA-1', entry.hash_sha1)
            lf.add_metadata('DHT magnet file verified', 'yes' if entry.flag_verified else 'no')
            lf.add_metadata('URL', entry.url)
            lf.add_metadata('Seeds', entry.seeds)
            lf.add_metadata('TorrentH.dat path', entry.torrenth_path)
            lf.add_metadata('TorrentH.dat creation time (UTC)', entry.torrenth_creation_time)
            lf.add_metadata('TorrentH.dat last modification time (UTC)', entry.torrenth_last_modification_time)

            # add to model
            model.local_files.append(lf)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Retrieve data from TempDL/PHash_XXX.dat
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __retrieve_tempdl_phash_dat(self, item, model, f, username):

        # decode file
        data = decoder_tempdl_phash_dat.decode(f)

        if not data:
            return

        # set file handled
        pymobius.p2p.set_handled(item, f)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Add data from My Shared Folder/___ARESTRA___ files to model
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __add_arestra(self, item, model, profile):
        for entry in profile.arestra.values():

            # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
            # create local file
            # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
            lf = local_file()
            lf.app_id = 'ares'
            lf.app = 'Ares Galaxy'
            lf.path = entry.path
            lf.size = entry.size
            lf.name = entry.name
            lf.username = profile.username

            if entry.size > 0:
                pct_progress = float(entry.progress) * 100.0 / float(entry.size)
            else:
                pct_progress = 0.0

            # flags
            # lf.flag_shared = STATE_YES if entry.flag_shared else STATE_NO
            lf.flag_downloaded = STATE_YES
            # @todo f.flag_uploaded
            lf.flag_completed = STATE_YES if entry.progress == entry.size else STATE_NO
            lf.flag_corrupted = STATE_NO if entry.phash_verified == entry.progress else STATE_UNKNOWN

            # hashes
            lf.set_hash('sha1', entry.hash_sha1)

            # metadata
            lf.add_metadata('Signature', entry.signature)
            lf.add_metadata('Version', entry.version)
            lf.add_metadata('Bytes downloaded', '%d bytes (%.2f%%)' % (entry.progress, pct_progress))
            lf.add_metadata('Genre', entry.kwgenre)
            lf.add_metadata('Title', entry.title)
            lf.add_metadata('Comments', entry.comment)
            lf.add_metadata('Subfolder', entry.subfolder)
            lf.add_metadata('Start time', entry.begin_time)
            lf.add_metadata('File creation time (UTC)', entry.creation_time)
            lf.add_metadata('File last modification time (UTC)', entry.last_modification_time)
            lf.add_metadata('Flag paused', entry.flag_paused)
            lf.add_metadata('Filetype', entry.filetype)
            lf.add_metadata('Param1', entry.param1)
            lf.add_metadata('Param2', entry.param2)
            lf.add_metadata('Param3', entry.param3)

            # add to model
            model.local_files.append(lf)

            # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
            # create remote files
            # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
            for source in entry.sources:
                lf = pymobius.p2p.remote_file.remote_file()
                lf.app_id = 'ares'
                lf.app = 'Ares Galaxy'
                lf.size = entry.size
                lf.name = entry.name
                lf.username = profile.username
                lf.timestamp = entry.creation_time

                # hash
                lf.set_hash('sha1', entry.hash_sha1)

                # metadata
                lf.add_metadata('Local timestamp', entry.begin_time)
                lf.add_metadata('Genre', entry.kwgenre)
                lf.add_metadata('Title', entry.title)
                lf.add_metadata('Artist', entry.artist)
                lf.add_metadata('Album', entry.album)
                lf.add_metadata('Category', entry.category)
                lf.add_metadata('Year', entry.year)
                lf.add_metadata('Language', entry.language)
                lf.add_metadata('URL', entry.url)
                lf.add_metadata('Comments', entry.comment)
                lf.add_metadata('Filetype', entry.filetype)

                # peer data
                lf.peer.ip = source.ip
                lf.peer.port = source.port

                # add to model
                model.remote_files.append(lf)

    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    # @brief Normalize retrieved data
    # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    def __normalize_data(self):

        # sort and remove duplicated remote files
        remote_files = [(f.timestamp, f) for f in self.__data.remote_files]
        timestamp, peer_ip, size, name = None, None, None, None
        self.__data.remote_files = []

        for (timestamp, f) in sorted(remote_files):
            if (timestamp, peer_ip, size, name) != (f.timestamp, f.peer.ip, f.size, f.name):
                self.__data.remote_files.append(f)
                timestamp, peer_ip, size, name = f.timestamp, f.peer.ip, f.size, f.name
