<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * 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 3 of the License, 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 <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\executables;

use seekquarry\yioop\configs as C;
use seekquarry\yioop\library as L;
use seekquarry\yioop\library\media_jobs as MJ;
use seekquarry\yioop\library\CrawlConstants;
use seekquarry\yioop\library\storage_formats\IndexDocumentBundle;

if (php_sapi_name() != 'cli' ||
    defined("seekquarry\\yioop\\configs\\IS_OWN_WEB_SERVER")) {
    echo "BAD REQUEST"; exit();
}
/**  For crawlHash, crawlHashWord function */
require_once __DIR__."/../library/Utility.php";

ini_set("memory_limit", C\ARC_TOOL_MEMORY_LIMIT);   /*reading in a whole
    shard might take a fair bit of memory
*/
/*
 * We'll set up multi-byte string handling to use UTF-8
 */
mb_internal_encoding("UTF-8");
mb_regex_encoding("UTF-8");
/**
 *
 *
 * @author Chris Pollett
 */
class DictionaryUpdater implements CrawlConstants
{
    /**
     * The main code for the dictionary updater, updates the the
     * dictionary for the IndexDocumentBundle at $bundle_path running
     * on channel $channel from its current next_partition to process
     * to the current save partition. Partitions are groups of documents
     * that have been downloaded, but whose words ave not necessarily been
     * add to the dicitionary for the bundle.
     *
     * @param int $channel the channel the crawl is running on. Used in
     *  naming lock files
     * @param string $bundle_path the path to the IndexDocumentBundle or
     *  FeedDucumentBundle we are adding dictionary info for
     * @param string $rebuild_or_remerge either the string "rebuild',
     *  "rebuild_some_number", or "remerge". If it "rebuild", it will both
     *  recompute partition inverted indexes then build a global dictionary
     *  from these. If it "remerge", if a partition inverted index
     *  exists it is directly merged into a new global dictionary without
     *  recomputing it. "rebuild_some_number" just rebuild partition
     *  some_number's inverted index, but does not merge it into the
     *  global dictionary.
     */
    public static function run($channel, $bundle_path, $rebuild_or_remerge)
    {
        if (!isset($_SERVER["LOG_TO_FILES"])) {
            $_SERVER["LOG_TO_FILES"] = true;
            L\CrawlDaemon::$name = "DictionaryUpdater";
            L\CrawlDaemon::$subname = $channel;
            $process_name = L\CrawlDaemon::getNameString(L\CrawlDaemon::$name,
                L\CrawlDaemon::$subname);
            L\crawlLog("\n\nInitialize logger..", $process_name, true);
        }
        $archive_type = self::getArchiveKind($bundle_path);
        if (!$archive_type) {
            L\crawlLog("Unknown Archive Type Exiting...");
            exit();
        }
        $archive_name = C\NS_LIB . $archive_type;
        if ($archive_type == "FeedDocumentBundle") {
            $dbms_manager = C\NS_DATASOURCES . ucfirst(C\p('DBMS')) . "Manager";
            $db = new $dbms_manager();
            $db->connect();
            $index_archive = new $archive_name($bundle_path, $db,
                false, null, C\NUM_DOCS_PER_PARTITION);
            $sql = "SELECT * FROM MEDIA_SOURCE WHERE (TYPE='rss'
                 OR TYPE='html' OR TYPE='json' OR TYPE='regex')";
            $result = $db->execute($sql);
            $feeds = [];
            while ($feed = $db->fetchArray($result)) {
                MJ\FeedsUpdateJob::parseFeedAuxInfo($feed);
                $feeds[] = $feed;
            }
            $index_archive->feeds = $feeds;
        } else {
            $index_archive = new $archive_name($bundle_path,
                false, "", C\NUM_DOCS_PER_PARTITION);
        }
        $next_partition_path = $bundle_path . "/".
            IndexDocumentBundle::NEXT_PARTITION_FILE;
        $rebuild_path = "$bundle_path/rebuild_$channel";
        if (file_exists($next_partition_path)) {
            $index_archive->next_partition_to_add = intval(
                file_get_contents($next_partition_path));
        } else {
            $index_archive->next_partition_to_add = 0;
            file_put_contents($next_partition_path,
                $index_archive->next_partition_to_add);
        }
        //note the false parameter means will only update for one partition
        $index_archive->updateDictionary($next_partition_path, false,
            $rebuild_or_remerge);
        file_put_contents($rebuild_path, "done");
    }
    /**
     * Given a folder name, determines the kind of bundle (if any) it holds.
     * It does this based on the expected location of the description.txt file,
     * or arc_description.ini (in the case of a non-yioop archive)
     *
     * @param string $archive_path the path to archive folder
     * @return string the archive bundle type, either: WebArchiveBundle or
     *     IndexArchiveBundle
     */
    public static function getArchiveKind($archive_path)
    {
        if (file_exists("$archive_path/bundle0")) {
            if (file_exists("$archive_path/bundle0/summaries/description.txt")){
                return "DoubleIndexBundleOld";
            } else {
                return "DoubleIndexBundle";
            }
        }
        if (file_exists("$archive_path/description.txt")) {
            return "WebArchiveBundle";
        }
        if (file_exists("$archive_path/filter_a.ftr")) {
            if (file_exists("$archive_path/summaries/description.txt")) {
                return "FeedArchiveBundle";
            } else {
                return "FeedDocumentBundle";
            }
        }
        if (file_exists("$archive_path/summaries/description.txt")) {
            return "IndexArchiveBundle";
        } else if (file_exists("$archive_path/archive_info.txt")) {
            return "IndexDocumentBundle";
        }
        $desc_path = "$archive_path/arc_description.ini";
        if (file_exists($desc_path)) {
            $desc = L\parse_ini_with_fallback($desc_path);
            if (!isset($desc['arc_type'])) {
                return false;
            }
            return $desc['arc_type'];
        }
        return false;
    }
}

if (!empty($argv[3]) && in_array($argv[1], ["run", "terminal"])) {
    $argv[4] ??= "rebuild";
    /*
     * Instantiate and runs the QueueSever
     */
    if ($argv[1] == "terminal") {
        $_SERVER["LOG_TO_FILES"] = "terminal";
    }
    DictionaryUpdater::run($argv[2], $argv[3], $argv[4]);
}
