# Copyright 2020 <NAME>. All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

#    1. Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.

#    2. Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.

# THIS SOFTWARE IS PROVIDED BY MARCOS PONTES ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL MARCOS PONTES OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# The views and conclusions contained in the software and documentation are
# those of the authors and should not be interpreted as representing official
# policies, either expressed or implied, of MARCOS PONTES.

"""
Sistema de arquivos do atri. A raíz do sistema está baseada na constante root (MUP_ROOT) e na biblioteca core
da ferramenta. Este módulo provê funcionalidades de CRUD de coleções e documentos gerenciados pela ferramenta.
"""
import pickle
from typing import List, Dict
from pathlib import Path
from functools import lru_cache

from werkzeug.datastructures import FileStorage

from atri.config import MUP_ROOT
from atri.core import MCol, MDoc
from atri.core.index.index import MIndex
from atri.core.parsing import get_parser
from atri.core.ranking.search import MSearcher
from atri.core.metrics import get_metric
from atri.error import MupError
from atri.core.constants import DEFAULTS, INDEXING

"""
    Handle default de erros do file system do atri
"""


class FileSystemError(Exception):
    pass


"""
    Classe base responsável por gerenciar os diretórios da ferramenta.
"""


class FileSystem(object):
    __instance = None  # Singleton instance

    def __init__(self, root: str = MUP_ROOT):
        """
        Inicializa o sistema de arquivos do atri
        @param: root = Raíz do sistema de arquivos (default=Variável de ambiente MUP_ROOT)
        """
        if not FileSystem.__instance:
            self.root: Path = Path(root)  # MUP_root Path
            self.collections: Dict[str, MCol] = dict()  # nome -> MCol

            self._temporary = self.root.joinpath('_temp')
            self._initialize()
        else:
            raise FileSystemError("FileSystem already created")

    def __getitem__(self, name: str) -> MCol:
        """
            Obtém metadados das coleções com base em seu nome
        """
        if name not in self.collections:
            raise FileSystemError("Collection not found!")
        return self.collections[name]

    @classmethod
    def get(cls) -> "FileSystem":
        """
            Método responsável pela criação do FS.
        """
        if not cls.__instance:
            cls.__instance = FileSystem()
        return cls.__instance

    def _initialize(self):
        """
            Inicializa os diretórios e cria metadados.
        """
        try:
            self.root.mkdir(parents=True, exist_ok=True)
            self._temporary.mkdir(parents=True, exist_ok=True)
            self._update_collections()
        except FileExistsError:
            raise FileSystemError("Error creating root folder.")

    def _validate_collection(self, name: str):
        if name not in self.collections:
            raise FileSystemError("Collection not found!")

    def _update_collections(self):
        """
            Atualiza as coleções com base nas informações em memória.
        """
        folders = self.all_folders()
        for fold in folders:
            try:
                mcol = MCol.load(fold)
                self.collections[mcol.name] = mcol
            except MupError:  # ignore folders that aren't collections
                continue

    def refresh(self):
        """
            Atualiza as coleções
        """
        self._update_collections()

    def all_folders(self) -> List[Path]:
        """
            Obtém todos os diretórios a partir da raíz.
        """
        folders = []
        for path in self.root.iterdir():
            if path.is_dir() and path.absolute() != self._temporary.absolute():
                folders.append(path)
        return folders

    def all_collections(self) -> List[MCol]:
        """
            Obtém todas as coleções criadas neste sistema de arquivo.
        """
        folders = self.all_folders()
        cols = []
        for fold in folders:
            try:
                 cols.append(MCol.load(fold))
            except MupError:  # ignore folders that aren't collections
                continue
        return cols

    def add_collection(self, name: str, description: str):
        """
            Adiciona uma coleção no sistema de arquivos.
        """
        coll_path = self.root.joinpath(name)
        if coll_path.exists():
            raise FileSystemError(f"A collection with name {name} already exists.")

        self.collections[name] = MCol(
            name, description, coll_path, index_defaults=INDEXING, search_defaults=DEFAULTS
        )

    def update_collection(self, name: str, new_name: str = None, new_description: str = None,
                          new_index_defaults=None, new_search_defaults=None):
        """s
            Atualiza uma coleção já existente no sistema de arquivos
        """
        col = self.__getitem__(name)

        if new_name:
            if new_name != name and new_name in self.collections:
                raise FileSystemError(f"A collection with name {name} already exists.")
            col.name = new_name
            col.path = col.path.parent.joinpath(new_name)

        if new_description:
            col.description = new_description

        if new_index_defaults:
            col.index_defaults = new_index_defaults

        if new_search_defaults:
            col.search_defaults = new_search_defaults

        if new_name:
            self.collections[new_name] = self.collections.pop(name)

    def delete_collection(self, name):
        """
            Deleta uma coleção no sistema de arquivos.
        """
        self._validate_collection(name)

        col = self.collections.pop(name)
        MCol.delete(col)

    def index_collection(self, name: str):
        col = self.__getitem__(name)

        defaults = col.index_defaults
        indexer = MIndex(col, **defaults)

        defaults['clean'] = defaults.get('clean', False)
        try:
            indexer.index(**defaults)
        except Exception as e:
            raise FileSystemError(f"Index process failed ({str(e)})")

    def parse_multi_query(self, f: FileStorage):
         # create temporary file
        fp = self._temporary.joinpath(f.filename).absolute()
        f.save(fp)
        tmp_file = Path(fp)

        try:
            p = get_parser(tmp_file, True)
            queries_generator = p(tmp_file)
        finally:
            tmp_file.unlink()

        return queries_generator


    def parse_file_query(self, f: FileStorage):
        # create temporary file
        fp = self._temporary.joinpath(f.filename).absolute()
        f.save(fp)
        tmp_file = Path(fp)

        try:
            mdocs = MCol.doc_from_file(tmp_file)
        finally:
            tmp_file.unlink()

        return mdocs

    def keywords_file_query(self, mdocs, fields=None):
        keywords = ""
        if isinstance(mdocs, MDoc):
                fields = fields if fields else mdocs.fields.keys()
                keywords = '\n'.join([v for f, v in mdocs.fields.items() if f in fields])
        else:
            for mdoc in mdocs:
                fields = fields if fields else mdoc.fields.keys()
                keywords += '\n'.join([v for f, v in mdoc.fields.items() if f in fields])
        return keywords

    def add_file(self, colname: str, f: FileStorage):
        """
            Adiciona um arquivo indexável em uma coleção.
        """
        col: MCol = self.__getitem__(colname)

        # create temporary file
        fp = self._temporary.joinpath(f.filename).absolute()
        f.save(fp)

        tmp_file = Path(fp)

        try:
            # add to collection
            col.add_file(tmp_file)
        except MupError as er:
            raise FileSystemError(str(er))
        finally:
            # unlink
            tmp_file.unlink()

        col.commit()

    def delete_file(self, colname: str, doc: str):
        """
        Deleta um arquivo da coleção
        """
        col: MCol = self.__getitem__(colname)
        if not col.delete_document(doc):
            raise FileSystemError(f"The file {doc} doesn't exists or can't be deleted.")

    @staticmethod
    @lru_cache
    def file_info(path: Path):
        """
            Obtém informações (nome e tamanho) sobre todos os arquivos e/ou diretórios presentes em um dado caminho.
        """
        name = path.with_suffix('').name
        return \
            {
                "name": name,
                "size": path.stat().st_size / (1024 * 1024)
            }

    def multisearch(self, collection_name, query_gen, **kwargs):
        """
            Realiza uma busca múltipla em uma coleção.
        """
        col = self.__getitem__(collection_name)

        metrics = kwargs.pop('metrics', ['ndcg@5'])
        metrics_func = {m: self.metric_fn(m, **kwargs) for m in metrics}

        evaluation = {m: 0.0 for m in metrics}
        ratio = 0

        for qry, qrel in query_gen:
            try:
                indexer = MIndex(col) # temporary
                searcher = MSearcher(indexer)
                hits = searcher.search(qry, **kwargs)
            except Exception as e:
                raise FileSystemError(f"Search process failed ({str(e)})")
            if isinstance(qrel, dict):
                truth = lambda x: qrel[x]
            else:
                truth = lambda x: int(x in qrel)

            response = self._extract_hits(hits, fieldname=searcher.fieldname, summarization=False)

            hits_evaluated = [truth(name) for name, _ in response.items()]
            for m in metrics:
                fn, params = metrics_func[m]

                params["nrel"] = len(qrel) # some query-based stats

                evaluation[m] += fn(hits_evaluated, **params)

            ratio += 1

        for m in metrics:
            evaluation[m] = evaluation[m] / ratio
        
        return evaluation

    def search(self, collection_name, keywords, **kwargs):
        col = self.__getitem__(collection_name)
        indexer = MIndex(col)
        searcher = MSearcher(indexer)
        try:
            hits = searcher.search(keywords, **kwargs)
        except Exception as e:
            raise FileSystemError(f"Search process failed ({str(e)})")
            
        return self._extract_hits(hits, fieldname=searcher.fieldname,
                                  summarization=bool(kwargs.get('summarization', False)))

    def _extract_hits(self, hits, fieldname, summarization=False):
        hits_json = {}
        for hit in hits:
            summary = ""
            if summarization:
                try:
                    doc_path = Path(hit['path'])
                    with doc_path.open(mode="rb") as io:
                        mdoc: MDoc = pickle.load(io)
                    summary = hit.highlights(fieldname, mdoc.fields[fieldname])
                except KeyError as e:
                    raise FileSystemError(f"Search process failed ({str(e)})")

                hits_json[hit['name']] = {"score": hit.score, "position": hit.pos+1, "summary": summary}
            else:
                hits_json[hit['name']] = {"score": hit.score, "position": hit.pos+1}
        return hits_json

    def defaults(self, collection_name=None):
        col = self.__getitem__(collection_name)
        return col.index_defaults

    def similarities(self, collection_name=None):
        if collection_name:
            col = self.__getitem__(collection_name)
            return col.search_defaults
        return DEFAULTS
    
    def metric_fn(self, metric, **kwargs):
        def split_limit(metric):
            if "@" in metric:
                metric_and_k = metric.split("@")
                return metric_and_k[0], int(metric_and_k[1])
            return metric, None

        metric_name, k = split_limit(metric)
        if k:
            kwargs['limit'] = k
        
        return get_metric(metric_name), kwargs

    def metric(self, metric,  ground_truth, **kwargs):
        """
        Calculates  relevance metric given a ground_truth graded ranking.
        """
        metric_func, kwargs = self.metric_fn(metric, **kwargs)
        return metric_func(ground_truth, **kwargs)
    

    
# Copyright 2020 <NAME>. All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

#    1. Redistributions of source code must retain the above copyright notice,
#       this list of conditions and the following disclaimer.

#    2. Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.

# THIS SOFTWARE IS PROVIDED BY MARCOS PONTES ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL MARCOS PONTES OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# The views and conclusions contained in the software and documentation are
# those of the authors and should not be interpreted as representing official
# policies, either expressed or implied, of MARCOS PONTES.

"""
Sistema de arquivos do atri. A raíz do sistema está baseada na constante root (MUP_ROOT) e na biblioteca core
da ferramenta. Este módulo provê funcionalidades de CRUD de coleções e documentos gerenciados pela ferramenta.
"""
import pickle
from typing import List, Dict
from pathlib import Path
from functools import lru_cache

from werkzeug.datastructures import FileStorage

from atri.config import MUP_ROOT
from atri.core import MCol, MDoc
from atri.core.index.index import MIndex
from atri.core.parsing import get_parser
from atri.core.ranking.search import MSearcher
from atri.core.metrics import get_metric
from atri.error import MupError
from atri.core.constants import DEFAULTS, INDEXING

"""
    Handle default de erros do file system do atri
"""


class FileSystemError(Exception):
    pass


"""
    Classe base responsável por gerenciar os diretórios da ferramenta.
"""


class FileSystem(object):
    __instance = None  # Singleton instance

    def __init__(self, root: str = MUP_ROOT):
        """
        Inicializa o sistema de arquivos do atri
        @param: root = Raíz do sistema de arquivos (default=Variável de ambiente MUP_ROOT)
        """
        if not FileSystem.__instance:
            self.root: Path = Path(root)  # MUP_root Path
            self.collections: Dict[str, MCol] = dict()  # nome -> MCol

            self._temporary = self.root.joinpath('_temp')
            self._initialize()
        else:
            raise FileSystemError("FileSystem already created")

    def __getitem__(self, name: str) -> MCol:
        """
            Obtém metadados das coleções com base em seu nome
        """
        if name not in self.collections:
            raise FileSystemError("Collection not found!")
        return self.collections[name]

    @classmethod
    def get(cls) -> "FileSystem":
        """
            Método responsável pela criação do FS.
        """
        if not cls.__instance:
            cls.__instance = FileSystem()
        return cls.__instance

    def _initialize(self):
        """
            Inicializa os diretórios e cria metadados.
        """
        try:
            self.root.mkdir(parents=True, exist_ok=True)
            self._temporary.mkdir(parents=True, exist_ok=True)
            self._update_collections()
        except FileExistsError:
            raise FileSystemError("Error creating root folder.")

    def _validate_collection(self, name: str):
        if name not in self.collections:
            raise FileSystemError("Collection not found!")

    def _update_collections(self):
        """
            Atualiza as coleções com base nas informações em memória.
        """
        folders = self.all_folders()
        for fold in folders:
            try:
                mcol = MCol.load(fold)
                self.collections[mcol.name] = mcol
            except MupError:  # ignore folders that aren't collections
                continue

    def refresh(self):
        """
            Atualiza as coleções
        """
        self._update_collections()

    def all_folders(self) -> List[Path]:
        """
            Obtém todos os diretórios a partir da raíz.
        """
        folders = []
        for path in self.root.iterdir():
            if path.is_dir() and path.absolute() != self._temporary.absolute():
                folders.append(path)
        return folders

    def all_collections(self) -> List[MCol]:
        """
            Obtém todas as coleções criadas neste sistema de arquivo.
        """
        folders = self.all_folders()
        cols = []
        for fold in folders:
            try:
                 cols.append(MCol.load(fold))
            except MupError:  # ignore folders that aren't collections
                continue
        return cols

    def add_collection(self, name: str, description: str):
        """
            Adiciona uma coleção no sistema de arquivos.
        """
        coll_path = self.root.joinpath(name)
        if coll_path.exists():
            raise FileSystemError(f"A collection with name {name} already exists.")

        self.collections[name] = MCol(
            name, description, coll_path, index_defaults=INDEXING, search_defaults=DEFAULTS
        )

    def update_collection(self, name: str, new_name: str = None, new_description: str = None,
                          new_index_defaults=None, new_search_defaults=None):
        """s
            Atualiza uma coleção já existente no sistema de arquivos
        """
        col = self.__getitem__(name)

        if new_name:
            if new_name != name and new_name in self.collections:
                raise FileSystemError(f"A collection with name {name} already exists.")
            col.name = new_name
            col.path = col.path.parent.joinpath(new_name)

        if new_description:
            col.description = new_description

        if new_index_defaults:
            col.index_defaults = new_index_defaults

        if new_search_defaults:
            col.search_defaults = new_search_defaults

        if new_name:
            self.collections[new_name] = self.collections.pop(name)

    def delete_collection(self, name):
        """
            Deleta uma coleção no sistema de arquivos.
        """
        self._validate_collection(name)

        col = self.collections.pop(name)
        MCol.delete(col)

    def index_collection(self, name: str):
        col = self.__getitem__(name)

        defaults = col.index_defaults
        indexer = MIndex(col, **defaults)

        defaults['clean'] = defaults.get('clean', False)
        try:
            indexer.index(**defaults)
        except Exception as e:
            raise FileSystemError(f"Index process failed ({str(e)})")

    def parse_multi_query(self, f: FileStorage):
         # create temporary file
        fp = self._temporary.joinpath(f.filename).absolute()
        f.save(fp)
        tmp_file = Path(fp)

        try:
            p = get_parser(tmp_file, True)
            queries_generator = p(tmp_file)
        finally:
            tmp_file.unlink()

        return queries_generator


    def parse_file_query(self, f: FileStorage):
        # create temporary file
        fp = self._temporary.joinpath(f.filename).absolute()
        f.save(fp)
        tmp_file = Path(fp)

        try:
            mdocs = MCol.doc_from_file(tmp_file)
        finally:
            tmp_file.unlink()

        return mdocs

    def keywords_file_query(self, mdocs, fields=None):
        keywords = ""
        if isinstance(mdocs, MDoc):
                fields = fields if fields else mdocs.fields.keys()
                keywords = '\n'.join([v for f, v in mdocs.fields.items() if f in fields])
        else:
            for mdoc in mdocs:
                fields = fields if fields else mdoc.fields.keys()
                keywords += '\n'.join([v for f, v in mdoc.fields.items() if f in fields])
        return keywords

    def add_file(self, colname: str, f: FileStorage):
        """
            Adiciona um arquivo indexável em uma coleção.
        """
        col: MCol = self.__getitem__(colname)

        # create temporary file
        fp = self._temporary.joinpath(f.filename).absolute()
        f.save(fp)

        tmp_file = Path(fp)

        try:
            # add to collection
            col.add_file(tmp_file)
        except MupError as er:
            raise FileSystemError(str(er))
        finally:
            # unlink
            tmp_file.unlink()

        col.commit()

    def delete_file(self, colname: str, doc: str):
        """
        Deleta um arquivo da coleção
        """
        col: MCol = self.__getitem__(colname)
        if not col.delete_document(doc):
            raise FileSystemError(f"The file {doc} doesn't exists or can't be deleted.")

    @staticmethod
    @lru_cache
    def file_info(path: Path):
        """
            Obtém informações (nome e tamanho) sobre todos os arquivos e/ou diretórios presentes em um dado caminho.
        """
        name = path.with_suffix('').name
        return \
            {
                "name": name,
                "size": path.stat().st_size / (1024 * 1024)
            }

    def multisearch(self, collection_name, query_gen, **kwargs):
        """
            Realiza uma busca múltipla em uma coleção.
        """
        col = self.__getitem__(collection_name)

        metrics = kwargs.pop('metrics', ['ndcg@5'])
        metrics_func = {m: self.metric_fn(m, **kwargs) for m in metrics}

        evaluation = {m: 0.0 for m in metrics}
        ratio = 0

        for qry, qrel in query_gen:
            try:
                indexer = MIndex(col) # temporary
                searcher = MSearcher(indexer)
                hits = searcher.search(qry, **kwargs)
            except Exception as e:
                raise FileSystemError(f"Search process failed ({str(e)})")
            if isinstance(qrel, dict):
                truth = lambda x: qrel[x]
            else:
                truth = lambda x: int(x in qrel)

            response = self._extract_hits(hits, fieldname=searcher.fieldname, summarization=False)

            hits_evaluated = [truth(name) for name, _ in response.items()]
            for m in metrics:
                fn, params = metrics_func[m]

                params["nrel"] = len(qrel) # some query-based stats

                evaluation[m] += fn(hits_evaluated, **params)

            ratio += 1

        for m in metrics:
            evaluation[m] = evaluation[m] / ratio
        
        return evaluation

    def search(self, collection_name, keywords, **kwargs):
        col = self.__getitem__(collection_name)
        indexer = MIndex(col)
        searcher = MSearcher(indexer)
        try:
            hits = searcher.search(keywords, **kwargs)
        except Exception as e:
            raise FileSystemError(f"Search process failed ({str(e)})")
            
        return self._extract_hits(hits, fieldname=searcher.fieldname,
                                  summarization=bool(kwargs.get('summarization', False)))

    def _extract_hits(self, hits, fieldname, summarization=False):
        hits_json = {}
        for hit in hits:
            summary = ""
            if summarization:
                try:
                    doc_path = Path(hit['path'])
                    with doc_path.open(mode="rb") as io:
                        mdoc: MDoc = pickle.load(io)
                    summary = hit.highlights(fieldname, mdoc.fields[fieldname])
                except KeyError as e:
                    raise FileSystemError(f"Search process failed ({str(e)})")

                hits_json[hit['name']] = {"score": hit.score, "position": hit.pos+1, "summary": summary}
            else:
                hits_json[hit['name']] = {"score": hit.score, "position": hit.pos+1}
        return hits_json

    def defaults(self, collection_name=None):
        col = self.__getitem__(collection_name)
        return col.index_defaults

    def similarities(self, collection_name=None):
        if collection_name:
            col = self.__getitem__(collection_name)
            return col.search_defaults
        return DEFAULTS
    
    def metric_fn(self, metric, **kwargs):
        def split_limit(metric):
            if "@" in metric:
                metric_and_k = metric.split("@")
                return metric_and_k[0], int(metric_and_k[1])
            return metric, None

        metric_name, k = split_limit(metric)
        if k:
            kwargs['limit'] = k
        
        return get_metric(metric_name), kwargs

    def metric(self, metric,  ground_truth, **kwargs):
        """
        Calculates  relevance metric given a ground_truth graded ranking.
        """
        metric_func, kwargs = self.metric_fn(metric, **kwargs)
        return metric_func(ground_truth, **kwargs)
    

    
# Copyright 2020 <NAME>. All rights reserved. # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: #    1. Redistributions of source code must retain the above copyright notice, #       this list of conditions and the following disclaimer. #    2. Redistributions in binary form must reproduce the above copyright #       notice, this list of conditions and the following disclaimer in the #       documentation and/or other materials provided with the distribution. # THIS SOFTWARE IS PROVIDED BY MARCOS PONTES ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL MARCOS PONTES OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # The views and conclusions contained in the software and documentation are # those of the authors and should not be interpreted as representing official # policies, either expressed or implied, of MARCOS PONTES. Sistema de arquivos do atri. A raíz do sistema está baseada na constante root (MUP_ROOT) e na biblioteca core da ferramenta. Este módulo provê funcionalidades de CRUD de coleções e documentos gerenciados pela ferramenta. Handle default de erros do file system do atri Classe base responsável por gerenciar os diretórios da ferramenta. # Singleton instance Inicializa o sistema de arquivos do atri         @param: root = Raíz do sistema de arquivos (default=Variável de ambiente MUP_ROOT) # MUP_root Path # nome -> MCol Obtém metadados das coleções com base em seu nome Método responsável pela criação do FS. Inicializa os diretórios e cria metadados. Atualiza as coleções com base nas informações em memória. # ignore folders that aren't collections Atualiza as coleções Obtém todos os diretórios a partir da raíz. Obtém todas as coleções criadas neste sistema de arquivo. # ignore folders that aren't collections Adiciona uma coleção no sistema de arquivos. s             Atualiza uma coleção já existente no sistema de arquivos Deleta uma coleção no sistema de arquivos. # create temporary file # create temporary file Adiciona um arquivo indexável em uma coleção. # create temporary file # add to collection # unlink Deleta um arquivo da coleção Obtém informações (nome e tamanho) sobre todos os arquivos e/ou diretórios presentes em um dado caminho. Realiza uma busca múltipla em uma coleção. # temporary # some query-based stats Calculates  relevance metric given a ground_truth graded ranking.