From 4fdaaa16bacbaef0aeef8074e68f38649640d5f5 Mon Sep 17 00:00:00 2001 From: Viktor Lofgren Date: Sat, 4 Mar 2023 13:19:01 +0100 Subject: [PATCH] Restructuring the git repo --- .gitignore | 3 + README.md | 33 +- api/assistant-api/build.gradle | 45 ++ .../assistant/client/AssistantClient.java | 28 +- .../client/model}/DictionaryEntry.java | 2 +- .../client/model}/DictionaryResponse.java | 2 +- api/index-api/build.gradle | 48 ++ .../index/client/EdgeIndexClient.java | 45 ++ .../domain/EdgeDomainSearchResults.java | 6 +- .../domain/EdgeDomainSearchSpecification.java | 2 +- .../model/query}/EdgeSearchSpecification.java | 9 +- .../model/query}/EdgeSearchSubquery.java | 2 +- .../model/query}/SearchSetIdentifier.java | 4 +- .../model/results}/EdgeSearchResultItem.java | 14 +- .../EdgeSearchResultKeywordScore.java | 10 +- .../model/results}/EdgeSearchResultSet.java | 2 +- .../model/results}/EdgeSearchResults.java | 2 +- api/readme.md | 7 + api/search-api/build.gradle | 46 ++ .../search/client/EdgeSearchClient.java | 19 +- .../search/client/model/ApiSearchResult.java | 18 + .../model/ApiSearchResultQueryDetails.java | 2 +- .../client}/model/ApiSearchResults.java | 2 +- build.gradle | 52 +- common/config/build.gradle | 32 ++ .../java/nu/marginalia/LanguageModels.java | 22 + .../main/java/nu/marginalia}/UserAgent.java | 2 +- .../main/java/nu/marginalia}/WebsiteUrl.java | 2 +- .../main/java/nu/marginalia}/WmsaHome.java | 5 +- common/model/build.gradle | 53 ++ .../java/nu/marginalia}/model/EdgeDomain.java | 5 +- .../java/nu/marginalia}/model/EdgeUrl.java | 12 +- .../model/crawl}/DocumentKeywords.java | 6 +- .../model/crawl/EdgeContentType.java | 2 +- .../model/crawl/EdgeDomainIndexingState.java | 2 +- .../model/crawl/EdgeDomainLink.java | 4 +- .../model/crawl/EdgeHtmlStandard.java | 7 +- .../model/crawl}/EdgePageDocumentFlags.java | 4 +- .../model/crawl}/EdgePageWordFlags.java | 6 +- .../model/crawl/EdgePageWords.java | 6 +- .../marginalia}/model/crawl/EdgeUrlState.java | 2 +- .../marginalia/model/crawl}/HtmlFeature.java | 2 +- .../nu/marginalia/model/crawl}/PubDate.java | 2 +- .../model/dbcommon/DbDomainQueries.java | 64 +++ .../model}/dbcommon/EdgeDomainBlacklist.java | 6 +- .../dbcommon/EdgeDomainBlacklistImpl.java | 4 +- .../marginalia/model/gson}/GsonFactory.java | 10 +- .../java/nu/marginalia}/model/id/EdgeId.java | 2 +- .../nu/marginalia}/model/id/EdgeIdArray.java | 2 +- .../model/id/EdgeIdCollection.java | 2 +- .../model/id/EdgeIdCollectionMutable.java | 2 +- .../nu/marginalia}/model/id/EdgeIdList.java | 2 +- .../nu/marginalia}/model/id/EdgeIdSet.java | 2 +- .../model/idx}/EdgePageDocumentsMetadata.java | 6 +- .../model/idx}/EdgePageWordMetadata.java | 3 +- .../java/nu/marginalia/util/DenseBitMap.java | 0 .../marginalia/util}/KeywordListChunker.java | 4 +- .../java/nu/marginalia/util/LineUtils.java | 47 ++ .../java/nu/marginalia/util/ParallelPipe.java | 13 +- .../java/nu/marginalia/util}/QueryParams.java | 2 +- .../java/nu/marginalia/util/StringPool.java | 0 .../main/resources/sql/edge-crawler-cache.sql | 10 +- .../nu/marginalia}/model/EdgeDomainTest.java | 3 +- .../model}/EdgePageWordMetadataTest.java | 6 +- .../nu/marginalia}/model/EdgeUrlTest.java | 3 +- .../util/BrailleBlockPunchCardsTest.java | 0 .../nu/marginalia/util/DenseBitMapTest.java | 1 + .../nu/marginalia/util/LineUtilsTest.java | 16 + common/readme.md | 9 + common/service-client/build.gradle | 56 ++ .../marginalia}/client/AbortingScheduler.java | 5 +- .../nu/marginalia}/client/AbstractClient.java | 235 +++----- .../client/AbstractDynamicClient.java | 20 +- .../java/nu/marginalia/client/Context.java | 89 +++ .../nu/marginalia}/client/HttpStatusCode.java | 2 +- .../client/exception/LocalException.java | 2 +- .../client/exception/MessagingException.java | 2 +- .../client/exception/NetworkException.java | 2 +- .../client/exception/RemoteException.java | 2 +- .../RouteNotConfiguredException.java | 2 +- .../client/exception/TimeoutException.java | 2 +- .../marginalia/client/AbstractClientTest.java | 180 ++++++ .../java/nu/marginalia/client/TestServer.java | 59 ++ common/service-discovery/build.gradle | 22 + .../service/SearchServiceDescriptors.java | 17 + .../service/descriptor}/HostsFile.java | 11 +- .../service/descriptor/ServiceDescriptor.java | 23 + .../descriptor/ServiceDescriptors.java | 31 + .../nu/marginalia/service/id/ServiceId.java | 25 + common/service/build.gradle | 35 ++ .../java/nu/marginalia/service/MainClass.java | 62 ++ .../service/module/ConfigurationModule.java | 35 ++ .../service}/module/DatabaseModule.java | 47 +- .../service}/module/LoggerConfiguration.java | 2 +- .../service}/module/MetricsPortProvider.java | 2 +- .../service}/server/Initialization.java | 9 +- .../service}/server/MetricsServer.java | 2 +- .../service}/server/RateLimiter.java | 10 +- .../marginalia/service}/server/Service.java | 36 +- .../service/server}/StaticResources.java | 2 +- .../src/main/resources/log4j2.properties | 28 + crawl/common/build.gradle | 55 ++ .../crawling/common}/AbortMonitor.java | 2 +- .../crawling/common}/TaskStats.java | 2 +- .../marginalia/crawling/common}/WorkLog.java | 9 +- .../common}/blocklist/GeoIpBlocklist.java | 6 +- .../common}/blocklist/InetAddressCache.java | 4 +- .../common}/blocklist/IpBlockList.java | 4 +- .../common}/blocklist/UrlBlocklist.java | 6 +- .../crawling/common/link}/LinkParser.java | 5 +- .../common/plan}/CrawlPlanLoader.java | 3 +- .../plan}/CrawlerSpecificationLoader.java | 21 +- .../crawling/common/plan}/EdgeCrawlPlan.java | 40 +- .../src/main}/resources/log4j2.properties | 13 +- crawl/converting-model/build.gradle | 41 ++ .../converting/instruction}/Instruction.java | 2 +- .../instruction}/InstructionTag.java | 4 +- .../converting/instruction/Interpreter.java | 25 + .../instruction/instructions/DomainLink.java | 6 + .../instruction/instructions}/LoadDomain.java | 10 +- .../instructions}/LoadDomainLink.java | 8 +- .../instructions}/LoadDomainRedirect.java | 8 +- .../instructions}/LoadKeywords.java | 13 +- .../instructions}/LoadProcessedDocument.java | 17 +- .../LoadProcessedDocumentWithError.java | 12 +- .../instructions}/LoadProcessedDomain.java | 12 +- .../instructions}/LoadRssFeed.java | 10 +- .../instruction/instructions}/LoadUrl.java | 10 +- crawl/converting-process/build.gradle | 70 +++ .../marginalia}/converting/ConversionLog.java | 23 +- .../marginalia}/converting/ConverterMain.java | 48 +- .../converting/ConverterModule.java | 24 +- .../converting/InstructionWriter.java | 28 +- .../converting/UpdateDomainStatistics.java | 4 +- .../compiler/DocumentsCompiler.java | 16 +- .../converting/compiler/FeedsCompiler.java | 10 +- .../compiler/InstructionsCompiler.java | 8 +- .../converting/compiler/LinksCompiler.java | 12 +- .../converting/compiler/RedirectCompiler.java | 19 + .../converting/compiler/UrlsCompiler.java | 14 +- .../model/DisqualifiedException.java | 4 +- .../converting/model/ProcessedDocument.java | 21 +- .../model/ProcessedDocumentDetails.java | 10 +- .../converting/model/ProcessedDomain.java | 6 +- .../converting/processor/AcceptableAds.java | 4 +- .../processor/DocumentProcessor.java | 136 +++++ .../converting/processor/DomainProcessor.java | 33 +- .../converting/processor/SiteWords.java | 14 +- .../keywords}/DocumentKeywordExtractor.java | 18 +- .../processor/keywords}/KeywordCounter.java | 17 +- .../processor/keywords}/NameCounter.java | 9 +- .../processor/keywords}/SubjectCounter.java | 13 +- .../logic/CommonKeywordExtractor.java | 6 +- .../processor/logic/DocumentValuator.java | 14 +- .../processor/logic/DomPruningFilter.java | 2 +- .../processor/logic/FeatureExtractor.java | 9 +- .../processor/logic/FeedExtractor.java | 5 +- .../logic/HtmlStandardExtractor.java | 48 +- .../processor/logic/InternalLinkGraph.java | 8 +- .../processor/logic/LinkProcessor.java | 10 +- .../logic/LshDocumentDeduplicator.java | 60 ++ .../processor/logic/PlainTextLogic.java | 115 ++++ .../processor/logic/SalientImageDetector.java | 2 +- .../logic/SummaryExtractionFilter.java | 2 +- .../processor/logic/SummaryExtractor.java | 2 +- .../processor/logic/TitleExtractor.java | 4 +- .../logic/pubdate/PubDateEffortLevel.java | 6 + .../logic/pubdate/PubDateHeuristic.java | 7 +- .../logic/pubdate/PubDateParser.java | 5 +- .../logic/pubdate/PubDateSniffer.java | 9 +- .../PubDateHeuristicDOMParsingPass1.java | 14 +- .../PubDateHeuristicDOMParsingPass2.java | 14 +- ...PubDateHeuristicGuessFromHtmlStandard.java | 23 + .../PubDateHeuristicHtml5AnyTimeTag.java | 14 +- .../PubDateHeuristicHtml5ArticleDateTag.java | 14 +- .../PubDateHeuristicHtml5ItempropDateTag.java | 14 +- .../heuristic/PubDateHeuristicJSONLD.java | 14 +- .../PubDateHeuristicLastModified.java | 14 +- .../heuristic/PubDateHeuristicMicrodata.java | 14 +- .../heuristic/PubDateHeuristicOpenGraph.java | 14 +- .../heuristic/PubDateHeuristicRDFaTag.java | 14 +- .../PubDateHeuristicUrlPatternPass1.java | 14 +- .../PubDateHeuristicUrlPatternPass2.java | 14 +- .../logic/topic/AdblockSimulator.java | 4 +- .../logic/topic/GoogleAnwersSpamDetector.java | 2 +- .../processor/logic/topic/RecipeDetector.java | 4 +- .../logic/topic/TextileCraftDetector.java | 4 +- .../logic/topic/WoodworkingDetector.java | 4 +- .../AbstractDocumentProcessorPlugin.java | 88 +++ .../plugin/HtmlDocumentProcessorPlugin.java | 270 +++++++++ .../PlainTextDocumentProcessorPlugin.java | 118 ++++ .../converting/tool}/DocumentDebugger.java | 20 +- .../logic/DomPruningFilterTest.java | 2 +- .../converting/logic/PlainTextLogicTest.java | 267 +++++++++ .../converting}/logic/PubDateSnifferTest.java | 14 +- .../logic/SummaryExtractorTest.java | 6 +- .../logic/pubdate/PubDateTest.java | 3 +- .../keywords}/SentenceExtractorTest.java | 53 +- .../test/util/TestLanguageModels.java | 37 ++ .../src/test/resources/html/monadnock.html | 0 .../src/test/resources/html/readme.md | 0 .../resources/html/summarization/187.shtml | 0 .../resources/html/summarization/surrey.html | 0 .../html/summarization/surrey.html.1 | 0 .../src/test/resources/html/work-set/index | 0 .../resources/html/work-set/url--1021546012 | 0 .../resources/html/work-set/url--1028592943 | 0 .../resources/html/work-set/url--1081293162 | 0 .../resources/html/work-set/url--1105046394 | 0 .../resources/html/work-set/url--1146923296 | 0 .../resources/html/work-set/url--1194694074 | 0 .../resources/html/work-set/url--1207898281 | 0 .../resources/html/work-set/url--1268145073 | 0 .../resources/html/work-set/url--1294876331 | 0 .../resources/html/work-set/url--1314767420 | 0 .../resources/html/work-set/url--1316269786 | 0 .../resources/html/work-set/url--1316766580 | 0 .../resources/html/work-set/url--1319968043 | 0 .../resources/html/work-set/url--1338576987 | 0 .../resources/html/work-set/url--1341909571 | 0 .../resources/html/work-set/url--1369578579 | 0 .../resources/html/work-set/url--1437315645 | 0 .../resources/html/work-set/url--1458954960 | 0 .../resources/html/work-set/url--1475681345 | 0 .../resources/html/work-set/url--1498328446 | 0 .../resources/html/work-set/url--1507779664 | 0 .../resources/html/work-set/url--1540303379 | 0 .../resources/html/work-set/url--154898476 | 0 .../resources/html/work-set/url--1552059399 | 0 .../resources/html/work-set/url--1557688340 | 0 .../resources/html/work-set/url--1584145751 | 0 .../resources/html/work-set/url--1605151204 | 0 .../resources/html/work-set/url--162269247 | 0 .../resources/html/work-set/url--1624294488 | 0 .../resources/html/work-set/url--164108285 | 0 .../resources/html/work-set/url--1645688243 | 0 .../resources/html/work-set/url--1658004609 | 0 .../resources/html/work-set/url--1658558834 | 0 .../resources/html/work-set/url--1698664879 | 0 .../resources/html/work-set/url--169975195 | 0 .../resources/html/work-set/url--1701203332 | 0 .../resources/html/work-set/url--17281998 | 0 .../resources/html/work-set/url--1742070028 | Bin .../resources/html/work-set/url--1745376814 | 0 .../resources/html/work-set/url--1749889035 | 0 .../resources/html/work-set/url--176177364 | 0 .../resources/html/work-set/url--177014197 | 0 .../resources/html/work-set/url--1794527707 | 0 .../resources/html/work-set/url--1797740201 | 0 .../resources/html/work-set/url--1799098579 | 0 .../resources/html/work-set/url--1959637826 | 0 .../resources/html/work-set/url--1971916964 | 0 .../resources/html/work-set/url--1985840368 | 0 .../resources/html/work-set/url--2012610859 | 0 .../resources/html/work-set/url--202178680 | 0 .../resources/html/work-set/url--2043528727 | 0 .../resources/html/work-set/url--2081757477 | 0 .../resources/html/work-set/url--2103982576 | 0 .../resources/html/work-set/url--2111558769 | 0 .../resources/html/work-set/url--213168798 | 0 .../resources/html/work-set/url--232544032 | 0 .../resources/html/work-set/url--253010011 | 0 .../resources/html/work-set/url--274250994 | 0 .../resources/html/work-set/url--332442790 | 0 .../resources/html/work-set/url--353437903 | 0 .../resources/html/work-set/url--364546777 | 0 .../resources/html/work-set/url--379129416 | 0 .../resources/html/work-set/url--399428149 | 0 .../resources/html/work-set/url--425233170 | 0 .../resources/html/work-set/url--434612307 | 0 .../resources/html/work-set/url--439772328 | 0 .../resources/html/work-set/url--458002611 | 0 .../resources/html/work-set/url--506010305 | 0 .../resources/html/work-set/url--546773534 | 0 .../resources/html/work-set/url--551288516 | 0 .../resources/html/work-set/url--602577763 | 0 .../resources/html/work-set/url--611668054 | 0 .../resources/html/work-set/url--634771245 | 0 .../resources/html/work-set/url--639320493 | 0 .../resources/html/work-set/url--643179018 | 0 .../resources/html/work-set/url--663772351 | 0 .../resources/html/work-set/url--670789152 | 0 .../test/resources/html/work-set/url--6797317 | 0 .../resources/html/work-set/url--700978490 | 0 .../resources/html/work-set/url--708035332 | 0 .../resources/html/work-set/url--804917062 | 0 .../resources/html/work-set/url--819771302 | 0 .../resources/html/work-set/url--840796372 | 0 .../resources/html/work-set/url--841445362 | 0 .../resources/html/work-set/url--862385354 | 0 .../resources/html/work-set/url--879796466 | 0 .../resources/html/work-set/url--89134993 | 0 .../resources/html/work-set/url--905197876 | 0 .../resources/html/work-set/url--920328354 | 0 .../resources/html/work-set/url--952827759 | 0 .../resources/html/work-set/url--964018507 | 0 .../resources/html/work-set/url--972614909 | 0 .../test/resources/html/work-set/url-10088520 | 0 .../resources/html/work-set/url-1013281103 | 0 .../resources/html/work-set/url-1019241851 | 0 .../resources/html/work-set/url-1059944953 | 0 .../resources/html/work-set/url-1118681302 | 0 .../resources/html/work-set/url-1179298706 | 0 .../resources/html/work-set/url-1191749784 | 0 .../resources/html/work-set/url-1207094790 | 0 .../resources/html/work-set/url-1213989666 | 0 .../resources/html/work-set/url-1222442301 | 0 .../resources/html/work-set/url-130332455 | 0 .../resources/html/work-set/url-1311055461 | 0 .../resources/html/work-set/url-1391842722 | 0 .../resources/html/work-set/url-1457388763 | 0 .../resources/html/work-set/url-1506356272 | 0 .../resources/html/work-set/url-1511762169 | 0 .../resources/html/work-set/url-1534640058 | 0 .../resources/html/work-set/url-1551513871 | 0 .../resources/html/work-set/url-1567632447 | 0 .../resources/html/work-set/url-1623049502 | 0 .../resources/html/work-set/url-163919330 | 0 .../resources/html/work-set/url-1661398327 | 0 .../resources/html/work-set/url-1724309925 | 0 .../resources/html/work-set/url-1736807128 | 0 .../resources/html/work-set/url-1739031345 | 0 .../resources/html/work-set/url-1755745765 | 0 .../resources/html/work-set/url-1802811100 | 0 .../resources/html/work-set/url-1805364707 | 0 .../resources/html/work-set/url-1832702370 | 0 .../resources/html/work-set/url-1853114311 | 0 .../resources/html/work-set/url-1924872844 | 0 .../resources/html/work-set/url-197772804 | 0 .../resources/html/work-set/url-1984259912 | 0 .../resources/html/work-set/url-1990903988 | 0 .../resources/html/work-set/url-2039310951 | 0 .../resources/html/work-set/url-2040857056 | 0 .../resources/html/work-set/url-2052613093 | 0 .../resources/html/work-set/url-2063899866 | 0 .../resources/html/work-set/url-2115548255 | 0 .../resources/html/work-set/url-2127148436 | 0 .../resources/html/work-set/url-2133781904 | 0 .../resources/html/work-set/url-225690385 | 0 .../resources/html/work-set/url-226401955 | 0 .../resources/html/work-set/url-262970770 | 0 .../test/resources/html/work-set/url-30106798 | 0 .../resources/html/work-set/url-302167335 | 0 .../resources/html/work-set/url-327999153 | 0 .../resources/html/work-set/url-332568225 | 0 .../resources/html/work-set/url-343223418 | 0 .../resources/html/work-set/url-383103932 | 0 .../resources/html/work-set/url-412929678 | 0 .../resources/html/work-set/url-475213997 | 0 .../resources/html/work-set/url-483403121 | 0 .../resources/html/work-set/url-488667993 | 0 .../test/resources/html/work-set/url-50815201 | 0 .../resources/html/work-set/url-522685905 | 0 .../resources/html/work-set/url-570714305 | 0 .../test/resources/html/work-set/url-58733529 | 0 .../resources/html/work-set/url-616518304 | 0 .../resources/html/work-set/url-662169426 | 0 .../resources/html/work-set/url-677278788 | 0 .../resources/html/work-set/url-690486170 | 0 .../resources/html/work-set/url-709693331 | 0 .../resources/html/work-set/url-734531556 | 0 .../resources/html/work-set/url-767530276 | 0 .../resources/html/work-set/url-783154014 | 0 .../resources/html/work-set/url-796905237 | 0 .../resources/html/work-set/url-800099955 | 0 .../resources/html/work-set/url-804101946 | 0 .../resources/html/work-set/url-830664902 | 0 .../resources/html/work-set/url-876060686 | 0 .../resources/html/work-set/url-892584998 | 0 .../resources/html/work-set/url-942458463 | 0 .../resources/html/work-set/url-952036171 | 0 .../resources/html/work-set/url-968207276 | 0 .../crawl-job-extractor-process/build.gradle | 47 ++ .../crawl/CrawlJobDomainExtractor.java | 156 ++---- .../crawl/CrawlJobExtractorMain.java | 49 ++ .../marginalia/crawl/CrawlJobSpecWriter.java | 27 + .../crawl/CrawlJobSpecWriterTest.java | 44 ++ crawl/crawling-model/build.gradle | 47 ++ .../crawling/io}/CrawledDomainReader.java | 14 +- .../crawling/io}/CrawledDomainWriter.java | 6 +- .../crawling/model/CrawlLogEntry.java | 2 +- .../crawling/model/CrawledDocument.java | 5 +- .../crawling/model/CrawledDomain.java | 2 +- .../crawling/model/CrawlerDocumentStatus.java | 2 +- .../crawling/model/CrawlerDomainStatus.java | 2 +- .../crawling/model/CrawlingSpecification.java | 7 +- .../crawling/model/SerializableCrawlData.java | 2 +- crawl/crawling-process/build.gradle | 62 ++ .../nu/marginalia/crawl}/CrawlerMain.java | 85 +-- .../marginalia/crawl}/retreival/Cookies.java | 2 +- .../crawl}/retreival/CrawlerRetreiver.java | 17 +- .../FastTerminatingSocketFactory.java | 2 +- .../crawl}/retreival/HttpFetcher.java | 18 +- .../retreival/HttpRedirectResolver.java | 8 +- .../crawl}/retreival/NoSecuritySSL.java | 3 +- .../crawl}/retreival/RateLimitException.java | 2 +- .../retreival/logic/ContentTypeLogic.java | 4 +- .../retreival/logic/ContentTypeParser.java | 4 +- .../src/main/resources/ip-banned-cidr.txt | 0 .../crawling/CrawlPlanLoaderTest.java | 5 +- .../crawling/DomainCrawlerRobotsTxtTest.java | 2 +- .../crawling/HtmlTagCleanerTest.java | 4 +- .../marginalia}/crawling/HttpFetcherTest.java | 12 +- .../crawling/LanguageFilterTest.java | 4 +- .../marginalia}/crawling/LinkParserTest.java | 6 +- .../marginalia}/crawling/RssCrawlerTest.java | 6 +- .../crawling/UrlBlocklistTest.java | 6 +- .../nu/marginalia}/crawling/WorkLogTest.java | 6 +- .../retreival/CrawlerRetreiverTest.java | 10 +- crawl/experimental/build.gradle | 50 ++ .../experimental}/AdblockTesterTool.java | 16 +- .../experimental}/ConverterLogicTestTool.java | 24 +- .../experimental}/CrawlDataExtractorTool.java | 18 +- crawl/loading-process/build.gradle | 68 +++ .../loading}/ConvertedDomainReader.java | 9 +- .../nu/marginalia/loading}/LoaderMain.java | 61 +- .../nu/marginalia/loading/LoaderModule.java | 38 ++ .../loading}/loader/IndexLoadKeywords.java | 31 +- .../nu/marginalia/loading}/loader/Loader.java | 30 +- .../loading}/loader/LoaderData.java | 6 +- .../loading}/loader/LoaderFactory.java | 2 +- .../loader/LoaderIndexJournalWriter.java | 87 +++ .../loading}/loader/SqlLoadDomainLinks.java | 4 +- .../loading}/loader/SqlLoadDomains.java | 4 +- .../loader/SqlLoadProcessedDocument.java | 10 +- .../loader/SqlLoadProcessedDomain.java | 8 +- .../loading}/loader/SqlLoadUrls.java | 6 +- .../java/nu/marginalia/loader/DbTestUtil.java | 6 +- .../loader/SqlLoadDomainLinksTest.java | 13 +- .../loader/SqlLoadDomainsTest.java | 11 +- .../loader/SqlLoadProcessedDocumentTest.java | 39 +- .../loader/SqlLoadProcessedDomainTest.java | 14 +- .../marginalia}/loader/SqlLoadUrlsTest.java | 12 +- crawl/readme.md | 74 +++ doc/language-models.md | 15 - docker-compose.yml | 128 +++++ docker-service.gradle | 41 ++ env/mariadb.env | 4 + env/service.env | 1 + features/domain-ranking/build.gradle | 45 ++ .../marginalia/ranking}/DomainRankings.java | 2 +- .../marginalia}/ranking/RankingAlgorithm.java | 12 +- .../marginalia}/ranking/ReversePageRank.java | 4 +- .../marginalia}/ranking/StandardPageRank.java | 16 +- .../accumulator/RankingResultAccumulator.java | 2 +- .../RankingResultBitSetAccumulator.java | 2 +- .../RankingResultHashMapAccumulator.java | 2 +- .../RankingResultListAccumulator.java | 2 +- .../ranking/data/RankingDomainData.java | 4 +- .../ranking/data/RankingDomainFetcher.java | 6 +- ...RankingDomainFetcherForSimilarityData.java | 4 +- .../tool/CreateBrowseDomainRanksTool.java | 12 +- .../ranking/tool/PerusePageRankV2.java | 12 +- .../ranking/tool/PrintDomainRanksTool.java | 14 +- .../ranking/tool/UpdateDomainRanksTool.java | 16 +- features/query-parser/build.gradle | 43 ++ .../marginalia/query_parser/QueryParser.java | 105 ++++ .../query_parser/QueryPermutation.java | 220 ++++++++ .../query_parser/QueryTokenizer.java | 65 +++ .../query_parser}/QueryVariants.java | 27 +- .../marginalia/query_parser/token/Token.java | 32 ++ .../query_parser/token/TokenType.java | 34 ++ .../query_parser}/BodyQueryParserTest.java | 22 +- .../query_parser}/QueryParserTest.java | 34 +- .../query_parser}/QueryVariantsTest.java | 14 +- .../marginalia/util/TestLanguageModels.java | 4 +- features/random-websites/build.gradle | 55 ++ .../browse/DbBrowseDomainsFromUrlId.java | 71 +++ .../browse/DbBrowseDomainsRandom.java | 60 ++ .../browse/DbBrowseDomainsSimilarCosine.java | 66 +++ .../browse/DbBrowseDomainsSimilarOldAlgo.java | 199 +------ .../browse/experimental}/AndCardIntSet.java | 6 +- .../EdgeDomainLinkConsineSimilarityMain.java | 15 +- .../EdgeWordWordConsineSimilarityMain.java | 7 +- .../browse}/model/BrowseResult.java | 4 +- .../browse}/model/BrowseResultSet.java | 2 +- .../experimental}/AndCardIntSetTest.java | 4 +- features/renderer/build.gradle | 35 ++ .../marginalia/renderer/MustacheRenderer.java | 60 ++ .../marginalia/renderer/RendererFactory.java | 13 + .../renderer/RenderingException.java | 12 + features/screenshots/build.gradle | 44 ++ .../screenshot/ScreenshotService.java | 16 +- gradle.properties | 2 + index/index-forward/build.gradle | 46 ++ .../index}/forward/ForwardIndexConverter.java | 31 +- .../forward/ForwardIndexParameters.java | 3 +- .../index}/forward/ForwardIndexReader.java | 25 +- .../forward/ParamMatchingQueryFilter.java | 10 +- .../forward/ForwardIndexConverterTest.java | 43 +- .../java/nu/marginalia/test/TestUtil.java | 50 ++ index/index-journal/build.gradle | 44 ++ .../journal/model/IndexJournalEntry.java | 28 + .../model/IndexJournalEntryBuilder.java | 34 ++ .../journal/model/IndexJournalEntryData.java | 15 +- .../model/IndexJournalEntryHeader.java | 20 + .../journal/model/IndexJournalFileHeader.java | 4 + .../journal/model/IndexJournalStatistics.java | 3 + .../journal/reader/IndexJournalReadEntry.java | 60 ++ .../journal/reader/IndexJournalReader.java | 48 ++ ...ndexJournalReaderSingleCompressedFile.java | 197 +++++++ .../journal/writer/IndexJournalWriter.java | 20 + .../writer/IndexJournalWriterImpl.java | 73 +++ .../index/journal/IndexJournalTest.java | 133 +++++ index/index-query/build.gradle | 36 ++ .../index/query/EmptyEntrySource.java | 4 +- .../marginalia}/index/query/EntrySource.java | 4 +- .../query/EntrySourceFromArrayRange.java | 6 +- .../marginalia}/index/query/IndexQuery.java | 8 +- .../index/query/IndexQueryBuilder.java | 12 + .../index/query/IndexQueryParams.java | 8 +- .../index/query/IndexSearchBudget.java | 2 +- .../index/query/filter/QueryFilterAnyOf.java | 4 +- .../query/filter/QueryFilterLetThrough.java | 2 +- .../index/query/filter/QueryFilterNoPass.java | 4 +- .../QueryFilterStepExcludeFromPredicate.java | 2 +- .../filter/QueryFilterStepFromPredicate.java | 2 +- .../index/query/filter/QueryFilterStepIf.java | 4 +- .../index/query/limit}/QueryLimits.java | 2 +- .../index/query/limit}/QueryStrategy.java | 2 +- .../query/limit}/SpecificationLimit.java | 2 +- .../query/limit}/SpecificationLimitType.java | 2 +- .../index}/searchset/SearchSet.java | 2 +- index/index-reverse/build.gradle | 43 ++ .../index}/reverse/ReverseIndexConverter.java | 49 +- .../reverse/ReverseIndexParameters.java | 4 +- .../ReverseIndexPrefixEntrySource.java | 8 +- .../reverse/ReverseIndexPrioReader.java | 14 +- .../ReverseIndexPriorityParameters.java | 8 +- .../index}/reverse/ReverseIndexReader.java | 24 +- .../query/ReverseIndexEntrySource.java | 8 +- .../ReverseIndexEntrySourceBehavior.java | 11 + .../query/ReverseIndexRejectFilter.java | 8 +- .../query/ReverseIndexRetainFilter.java | 8 +- .../reverse/ReverseIndexConverterTest.java | 54 +- .../reverse/ReverseIndexConverterTest2.java | 44 +- .../java/nu/marginalia/test/TestUtil.java | 50 ++ index/lexicon/build.gradle | 38 ++ .../marginalia}/lexicon/KeywordLexicon.java | 25 +- .../lexicon/KeywordLexiconReadOnlyView.java | 2 +- .../journal/KeywordLexiconJournal.java | 2 +- .../KeywordLexiconJournalCommitQueue.java | 16 +- .../journal/KeywordLexiconJournalFile.java | 16 +- .../lexicon/KeywordLexiconTest.java | 77 +++ index/readme.md | 17 + libraries/array/build.gradle | 30 + .../java/nu/marginalia}/array/IntArray.java | 18 +- .../java/nu/marginalia}/array/LongArray.java | 18 +- .../array/algo/BulkTransferArray.java | 2 +- .../marginalia}/array/algo/IntArrayBase.java | 2 +- .../array/algo/IntArraySearch.java | 8 +- .../marginalia}/array/algo/IntArraySort.java | 2 +- .../array/algo/IntArrayTransformations.java | 10 +- .../marginalia}/array/algo/LongArrayBase.java | 2 +- .../array/algo/LongArraySearch.java | 4 +- .../marginalia}/array/algo/LongArraySort.java | 2 +- .../array/algo/LongArrayTransformations.java | 10 +- .../array/algo/SortingContext.java | 2 +- .../array/buffer/IntQueryBuffer.java | 2 +- .../array/buffer/LongQueryBuffer.java | 2 +- .../ReferenceImplIntArrayDelegate.java | 4 +- .../ReferenceImplLongArrayDelegate.java | 4 +- .../array/delegate/ShiftedIntArray.java | 16 +- .../array/delegate/ShiftedLongArray.java | 18 +- .../array/functional/AddressRangeCall.java | 2 +- .../array/functional/AddressRangeCallIO.java | 2 +- .../functional/AddressRangeIntFunction.java | 2 +- .../functional/AddressRangeLongFunction.java | 2 +- .../functional/IntBinaryIOOperation.java | 2 +- .../array/functional/IntIOTransformer.java | 2 +- .../array/functional/IntTransformer.java | 2 +- .../functional/LongBinaryIOOperation.java | 2 +- .../array/functional/LongIOTransformer.java | 2 +- .../array/functional/LongIntConsumer.java | 2 +- .../array/functional/LongLongConsumer.java | 2 +- .../array/functional/LongTransformer.java | 2 +- .../array/functor/IntIOFolder.java | 8 +- .../array/functor/LongIOFolder.java | 8 +- .../array/page/AbstractPagingArray.java | 14 +- .../marginalia}/array/page/IntArrayPage.java | 4 +- .../marginalia}/array/page/LongArrayPage.java | 6 +- .../array/page/PagingIntArray.java | 22 +- .../array/page/PagingLongArray.java | 22 +- .../marginalia}/array/page/PartitionPage.java | 2 +- .../array/scheme/ArrayPartitioningScheme.java | 2 +- .../scheme/PowerOf2PartitioningScheme.java | 2 +- .../scheme/SequentialPartitioningScheme.java | 2 +- .../marginalia}/array/trace/ArrayTrace.java | 4 +- .../array/trace/ArrayTraceViz.java | 2 +- .../nu/marginalia}/array/trace/FileTrace.java | 4 +- .../nu/marginalia}/array/trace/NullTrace.java | 2 +- .../IntLowBitPartitioningSchemeTest.java | 4 +- .../marginalia}/array/PagingIntArrayTest.java | 8 +- .../array/algo/IntArraySearchTest.java | 10 +- .../array/algo/IntArraySortTest.java | 29 +- .../algo/IntArrayTransformationsTest.java | 10 +- .../array/algo/LongArraySearchTest.java | 10 +- .../array/algo/LongArraySortTest.java | 46 +- .../algo/LongArrayTransformationsTest.java | 10 +- .../scheme/ArrayPartitioningSchemeTest.java | 2 +- .../nu/marginalia/util/test/TestUtil.java | 20 +- libraries/btree/build.gradle | 26 + .../java/nu/marginalia/btree/BTreeDogEar.java | 46 ++ .../nu/marginalia}/btree/BTreeReader.java | 124 ++-- .../marginalia/btree/BTreeWriteCallback.java | 9 + .../nu/marginalia}/btree/BTreeWriter.java | 61 +- .../marginalia}/btree/model/BTreeContext.java | 24 +- .../marginalia}/btree/model/BTreeHeader.java | 25 +- .../nu/marginalia}/btree/BTreeWriterTest.java | 143 ++--- libraries/language-processing/build.gradle | 50 ++ .../marginalia}/language/LanguageFilter.java | 5 +- .../nu/marginalia}/language/WordPatterns.java | 2 +- .../language/encoding/AsciiFlattener.java | 130 +++++ .../language/encoding}/HtmlTagCleaner.java | 2 +- .../language/encoding}/UnicodeRanges.java | 2 +- .../language/keywords}/KeywordExtractor.java | 10 +- .../language}/model/DocumentLanguageData.java | 16 +- .../language}/model/DocumentSentence.java | 68 ++- .../language}/model/KeywordMetadata.java | 13 +- .../language/model/WordFrequencyData.java | 4 + .../marginalia/language}/model/WordRep.java | 2 +- .../language/model}/WordSeparator.java | 2 +- .../marginalia/language}/model/WordSpan.java | 2 +- .../language}/sentence/SentenceExtractor.java | 14 +- .../SentenceExtractorStringUtils.java | 2 +- .../sentence/SentenceSegmentSplitter.java | 6 +- .../statistics}/EnglishDictionary.java | 3 +- .../statistics}/NGramBloomFilter.java | 48 +- .../statistics/TermFrequencyDict.java | 206 +++++++ .../src/main/resources/dictionary/en-1000 | 0 .../main/resources/dictionary/en-stopwords | 0 .../src/main/resources/dictionary/en-words | 0 .../src/main/resources/dictionary/latin-1000 | 0 .../src/main/resources/dictionary/swe-1000 | 0 .../main/resources/dictionary/word-frequency | 0 .../language/encoding/AsciiFlattenerTest.java | 38 ++ libraries/misc/build.gradle | 30 + .../nu/marginalia}/bigstring/BigString.java | 2 +- .../bigstring/CompressedBigString.java | 2 +- .../marginalia}/bigstring/PlainBigString.java | 2 +- .../nu/marginalia/dict/DictionaryData.java | 39 ++ .../marginalia/dict/DictionaryDataBank.java | 63 +++ .../nu/marginalia}/dict/DictionaryMap.java | 4 +- .../dict/OffHeapDictionaryHashMap.java | 30 +- .../marginalia}/dict/OnHeapDictionaryMap.java | 2 +- .../nu/marginalia}/gregex/GuardedRegex.java | 2 +- .../gregex/GuardedRegexFactory.java | 2 +- .../main/java/nu/marginalia/lsh}/EasyLSH.java | 27 +- .../java/nu/marginalia/util/FileSizeUtil.java | 0 .../java/nu/marginalia/util/PrimeUtil.java | 0 .../nu/marginalia/util/RandomWriteFunnel.java | 7 +- .../nu/marginalia/util/TransformList.java | 33 +- .../java/nu/marginalia/lsh}/EasyLSHTest.java | 11 +- .../java/nu/marginalia/test/TestUtil.java | 50 ++ .../nu/marginalia/util/TransformListTest.java | 32 +- libraries/readme.md | 9 + marginalia_nu/data/.gitignore | 1 - marginalia_nu/data/models/.gitignore | 1 - marginalia_nu/data/test/.gitignore | 1 - .../nu/marginalia/wmsa/edge/E2ETestBase.java | 86 --- .../wmsa/edge/EdgeCrawlBehaviorE2ETest.java | 105 ---- .../wmsa/edge/EdgeSearchE2ETest.java | 284 ---------- .../wmsa/edge/EncyclopediaE2ETest.java | 154 ----- .../nu/marginalia/wmsa/edge/MemexE2ETest.java | 95 ---- marginalia_nu/src/e2e/resources/crawl-mock.sh | 19 - marginalia_nu/src/e2e/resources/crawl.sh | 79 --- marginalia_nu/src/e2e/resources/init.sh | 73 --- .../src/e2e/resources/load-encyclopedia.sh | 32 -- marginalia_nu/src/e2e/resources/memex.sh | 39 -- .../src/e2e/resources/memex/index.gmi | 6 - .../src/e2e/resources/memex/log/a.gmi | 7 - .../src/e2e/resources/memex/log/b.gmi | 6 - .../src/e2e/resources/memex/log/index.gmi | 7 - .../src/e2e/resources/nginx/encyclopedia.conf | 40 -- .../src/e2e/resources/nginx/memex.conf | 27 - .../src/e2e/resources/nginx/search.conf | 20 - .../jmh/java/nu/marginalia/BitSetTest.java | 313 ----------- .../nu/marginalia/util/SeekDictionary.java | 73 --- .../nu/marginalia/util/btree/BTreeDogEar.java | 35 -- .../marginalia/util/btree/BTreeReaderIf.java | 21 - .../marginalia/util/btree/WriteCallback.java | 9 - .../marginalia/util/dict/DictionaryData.java | 100 ---- .../util/language/conf/LanguageModels.java | 16 - .../language/processing/AsciiFlattener.java | 58 -- .../language/processing/model/WordRef.java | 46 -- .../processing/model/tag/WordTag.java | 8 - .../WikipediaInternalLinkExtractorMain.java | 43 -- .../java/nu/marginalia/wmsa/api/ApiMain.java | 27 - .../wmsa/api/model/ApiSearchResult.java | 55 -- .../wmsa/configuration/MainClass.java | 56 -- .../wmsa/configuration/ServiceDescriptor.java | 107 ---- .../wmsa/configuration/command/Command.java | 31 - .../configuration/command/ConvertCommand.java | 24 - .../configuration/command/CrawlCommand.java | 24 - .../command/IndexDataDumpCommand.java | 24 - .../configuration/command/ListCommand.java | 23 - .../configuration/command/LoadCommand.java | 24 - .../configuration/command/ReindexCommand.java | 24 - .../configuration/command/StartCommand.java | 30 - .../configuration/command/VersionCommand.java | 20 - .../module/ConfigurationModule.java | 27 - .../wmsa/configuration/server/Context.java | 139 ----- .../edge/assistant/EdgeAssistantMain.java | 33 -- .../assistant/dict/TermFrequencyDict.java | 218 -------- .../edge/assistant/dict/WikiArticles.java | 23 - .../wmsa/edge/assistant/dict/WikiCleaner.java | 386 ------------- .../edge/assistant/dict/WikiSearchResult.java | 55 -- .../converting/LinkKeywordExtractorMain.java | 186 ------ .../converting/LinkKeywordLoaderMain.java | 110 ---- .../edge/converting/ReindexTriggerMain.java | 59 -- .../converting/atags/AnchorTextExtractor.java | 264 --------- .../converting/compiler/RedirectCompiler.java | 19 - .../converting/interpreter/Interpreter.java | 25 - .../interpreter/instruction/DomainLink.java | 6 - .../processor/DocumentProcessor.java | 425 -------------- .../logic/pubdate/PubDateEffortLevel.java | 6 - ...PubDateHeuristicGuessFromHtmlStandard.java | 23 - .../wmsa/edge/crawling/CrawlerTestMain.java | 116 ---- .../wmsa/edge/dbcommon/EdgeDataStoreDao.java | 31 - .../wmsa/edge/index/EdgeIndexControl.java | 30 - .../wmsa/edge/index/EdgeIndexMain.java | 34 -- .../wmsa/edge/index/EdgeIndexModule.java | 23 - .../wmsa/edge/index/EdgeIndexService.java | 84 --- .../edge/index/EdgeIndexTablesModule.java | 29 - .../edge/index/client/EdgeIndexClient.java | 87 --- .../index/client/EdgeIndexLocalService.java | 88 --- .../index/client/EdgeIndexWriterClient.java | 14 - .../index/config/RankingSettingsEntry.java | 8 - .../index/postings/IndexMetadataService.java | 22 - .../index/postings/SearchIndexControl.java | 77 --- .../model/SearchIndexJournalEntryHeader.java | 22 - .../model/SearchIndexJournalFileHeader.java | 4 - .../model/SearchIndexJournalStatistics.java | 3 - .../reader/SearchIndexJournalCleaner.java | 71 --- .../reader/SearchIndexJournalReadEntry.java | 98 ---- .../reader/SearchIndexJournalReader.java | 52 -- .../SearchIndexJournalReaderSingleFile.java | 180 ------ .../writer/SearchIndexJournalWriter.java | 13 - .../writer/SearchIndexJournalWriterImpl.java | 134 ----- .../ReverseIndexEntrySourceBehavior.java | 6 - .../wmsa/edge/index/query/IndexQueryIf.java | 12 - .../ranking/old/OldReversePageRankV2.java | 261 --------- .../index/ranking/old/StandardPageRank.java | 266 --------- .../svc/EdgeIndexDomainQueryService.java | 110 ---- .../index/svc/EdgeIndexLexiconService.java | 126 ----- .../edge/index/svc/EdgeIndexOpsService.java | 42 -- .../edge/index/svc/EdgeIndexQueryService.java | 305 ---------- .../edge/index/svc/EdgeOpsLockService.java | 42 -- .../edge/integration/arxiv/ArxivParser.java | 29 - .../arxiv/model/ArxivMetadata.java | 21 - .../integration/model/BasicDocumentData.java | 22 - .../StackOverflowPostProcessor.java | 83 --- .../StackOverflowPostsReader.java | 123 ---- .../model/StackOverflowPost.java | 14 - .../model/StackOverflowQuestionData.java | 14 - .../wikipedia/WikipediaProcessor.java | 81 --- .../wikipedia/WikipediaReader.java | 46 -- .../wikipedia/model/WikipediaArticle.java | 12 - .../wmsa/edge/model/WideHashable.java | 5 - .../wmsa/edge/model/crawl/EdgeIndexTask.java | 33 -- .../edge/model/crawl/EdgePageContent.java | 25 - .../edge/model/crawl/EdgePageMetadata.java | 50 -- .../edge/model/crawl/EdgeRawPageContents.java | 24 - .../wmsa/edge/model/crawl/EdgeRobotsTxt.java | 10 - .../wmsa/edge/model/crawl/EdgeUrlVisit.java | 21 - .../model/search/EdgeSearchResultsKey.java | 13 - .../wmsa/edge/search/EdgeSearchMain.java | 38 -- .../edge/search/command/IndexCommand.java | 40 -- .../edge/search/command/SearchParameters.java | 9 - .../command/commands/ConvertCommand.java | 35 -- .../command/commands/SearchCommand.java | 59 -- .../wmsa/edge/search/query/QueryParser.java | 529 ------------------ .../query/model/EdgeUserSearchParameters.java | 7 - .../edge/search/results/UrlDeduplicator.java | 39 -- .../search/svc/EdgeSearchApiQueryService.java | 55 -- .../svc/EdgeSearchDomainSearchService.java | 65 --- .../svc/EdgeSearchWikiArticlesService.java | 41 -- .../edge/tools/EncyclopediaLoaderTool.java | 85 --- .../wmsa/edge/tools/FeaturesLoaderTool.java | 85 --- .../wmsa/edge/tools/IndexJournalDumpTool.java | 47 -- .../edge/tools/SearchIndexScrubberMain.java | 69 --- .../StripSimpleJournalEntriesToolMain.java | 28 - .../wmsa/encyclopedia/EncyclopediaClient.java | 35 -- .../wmsa/encyclopedia/EncyclopediaDao.java | 154 ----- .../wmsa/encyclopedia/EncyclopediaMain.java | 29 - .../wmsa/encyclopedia/EncyclopediaModule.java | 9 - .../encyclopedia/EncyclopediaService.java | 94 ---- .../wmsa/memex/client/MemexApiClient.java | 14 - .../model/render/MemexRendererableDirect.java | 7 - .../wmsa/renderer/StatusRendererService.java | 81 --- .../src/main/resources/data/smhi/stader.csv | 134 ----- .../src/main/resources/fonts/LM-regular.ttf | Bin 150124 -> 0 bytes .../resources/fonts/STIXTwoMath-Regular.ttf | Bin 1518116 -> 0 bytes .../src/main/resources/log4j2.properties | 29 - .../main/resources/static/dating/index.html | 71 --- .../main/resources/static/dating/robots.txt | 4 - .../src/main/resources/static/edge/index.html | 164 ------ .../resources/static/encyclopedia/index.html | 24 - .../resources/static/encyclopedia/robots.txt | 2 - .../static/encyclopedia/wiki-clean.html | 71 --- .../static/encyclopedia/wiki-start.html | 26 - .../main/resources/static/explore/style.css | 20 - .../resources/static/fonts/LM-bold-italic.ttf | Bin 191324 -> 0 bytes .../static/fonts/LM-bold-italic.woff | Bin 66172 -> 0 bytes .../static/fonts/LM-bold-italic.woff2 | Bin 39392 -> 0 bytes .../main/resources/static/fonts/LM-bold.ttf | Bin 144572 -> 0 bytes .../main/resources/static/fonts/LM-bold.woff | Bin 52888 -> 0 bytes .../main/resources/static/fonts/LM-bold.woff2 | Bin 33548 -> 0 bytes .../main/resources/static/fonts/LM-italic.ttf | Bin 191540 -> 0 bytes .../resources/static/fonts/LM-italic.woff | Bin 66180 -> 0 bytes .../resources/static/fonts/LM-italic.woff2 | Bin 39148 -> 0 bytes .../resources/static/fonts/LM-regular.ttf | Bin 150124 -> 0 bytes .../resources/static/fonts/LM-regular.woff | Bin 53760 -> 0 bytes .../resources/static/fonts/LM-regular.woff2 | Bin 33664 -> 0 bytes .../main/resources/static/smhi/favicon.ico | Bin 1211 -> 0 bytes .../src/main/resources/static/smhi/font.css | 50 -- .../main/resources/static/smhi/responsive.css | 74 --- .../src/main/resources/static/smhi/style.css | 192 ------- .../src/main/resources/static/style.css | 246 -------- .../templates/encyclopedia/wiki-error.hdb | 28 - .../templates/encyclopedia/wiki-search.hdb | 35 -- .../main/resources/templates/smhi/index.hdb | 44 -- .../main/resources/templates/smhi/prognos.hdb | 73 --- .../templates/status/server-status.hdb | 25 - .../marginalia/util/SeekDictionaryTest.java | 31 - .../wmsa/configuration/HostsFileTest.java | 69 --- .../assistant/suggest/SuggestionsTest.java | 43 -- .../index/service/util/PrimeUtilTest.java | 31 - .../integration/arxiv/ArxivParserTest.java | 41 -- .../stackoverflow/StackOverflowPostsTest.java | 47 -- .../integration/wikipedia/WikipediaTest.java | 73 --- .../wmsa/podcasts/PodcastFetcherTest.java | 15 - .../src/test/resources/log4j2.properties | 15 - .../src/test/resources/model-data.json | 1 - {marginalia_nu => other/memex}/build.gradle | 16 +- {marginalia_nu => other/memex}/lombok.config | 0 .../memex/MemexServiceDescriptors.java | 15 + .../memex}/auth/AuthConfigurationModule.java | 4 +- .../nu/marginalia/memex}/auth/AuthMain.java | 17 +- .../marginalia/memex}/auth/AuthService.java | 14 +- .../memex}/auth/client/AuthClient.java | 19 +- .../memex}/auth/model/LoginFormModel.java | 2 +- .../marginalia/memex}/gemini/BadBotList.java | 2 +- .../gemini/GeminiConfigurationModule.java | 2 +- .../memex}/gemini/GeminiService.java | 2 +- .../memex}/gemini/GeminiServiceDummy.java | 2 +- .../memex}/gemini/GeminiServiceImpl.java | 16 +- .../memex}/gemini/client/GeminiClient.java | 2 +- .../marginalia/memex}/gemini/gmi/Gemtext.java | 12 +- .../memex}/gemini/gmi/GemtextDatabase.java | 10 +- .../memex}/gemini/gmi/GemtextDocument.java | 16 +- .../gemini/gmi/line/AbstractGemtextLine.java | 4 +- .../memex}/gemini/gmi/line/GemtextAside.java | 4 +- .../gemini/gmi/line/GemtextHeading.java | 4 +- .../gemini/gmi/line/GemtextLineVisitor.java | 2 +- .../gmi/line/GemtextLineVisitorAdapter.java | 2 +- .../memex}/gemini/gmi/line/GemtextLink.java | 6 +- .../memex}/gemini/gmi/line/GemtextList.java | 4 +- .../memex}/gemini/gmi/line/GemtextPragma.java | 4 +- .../gemini/gmi/line/GemtextPreformat.java | 4 +- .../memex}/gemini/gmi/line/GemtextQuote.java | 4 +- .../memex}/gemini/gmi/line/GemtextTask.java | 10 +- .../memex}/gemini/gmi/line/GemtextText.java | 4 +- .../gemini/gmi/line/GemtextTextLiteral.java | 4 +- .../gemini/gmi/parser/GemtextAsideParser.java | 6 +- .../gmi/parser/GemtextHeadingParser.java | 10 +- .../gemini/gmi/parser/GemtextLinkParser.java | 16 +- .../gemini/gmi/parser/GemtextListParser.java | 2 +- .../gemini/gmi/parser/GemtextParser.java | 8 +- .../gmi/parser/GemtextPragmaParser.java | 10 +- .../gemini/gmi/parser/GemtextQuoteParser.java | 2 +- .../gemini/gmi/parser/GemtextTaskParser.java | 14 +- .../gemini/gmi/renderer/GemtextRenderer.java | 4 +- .../gmi/renderer/GemtextRendererFactory.java | 8 +- .../memex}/gemini/io/GeminiConnection.java | 6 +- .../memex}/gemini/io/GeminiSSLSetUp.java | 2 +- .../memex}/gemini/io/GeminiStatusCode.java | 2 +- .../memex}/gemini/io/GeminiUserException.java | 2 +- .../gemini/plugins/BareStaticPagePlugin.java | 6 +- .../memex}/gemini/plugins/FileType.java | 2 +- .../memex}/gemini/plugins/Plugin.java | 6 +- .../memex}/gemini/plugins/SearchPlugin.java | 6 +- .../nu/marginalia/memex}/memex/Memex.java | 28 +- .../memex/MemexConfigurationModule.java | 16 +- .../nu/marginalia/memex}/memex/MemexData.java | 14 +- .../marginalia/memex}/memex/MemexLinks.java | 6 +- .../marginalia/memex}/memex/MemexLoader.java | 16 +- .../nu/marginalia/memex}/memex/MemexMain.java | 17 +- .../marginalia/memex}/memex/MemexService.java | 28 +- .../memex}/memex/change/GemtextAppend.java | 12 +- .../memex}/memex/change/GemtextCreate.java | 6 +- .../memex/change/GemtextCreateOrMutate.java | 6 +- .../memex}/memex/change/GemtextMutation.java | 8 +- .../memex}/memex/change/GemtextPrepend.java | 12 +- .../memex}/memex/change/GemtextReplace.java | 12 +- .../GemtextTombstoneUpdateCaclulator.java | 6 +- .../GemtextDocumentUpdateCalculator.java | 22 +- .../change/update/GemtextTaskExtractor.java | 6 +- .../change/update/GemtextTasksRewrite.java | 12 +- .../memex}/memex/model/GemtextSection.java | 2 +- .../memex/model/GemtextSectionAction.java | 2 +- .../memex}/memex/model/MemexExternalUrl.java | 2 +- .../memex}/memex/model/MemexImage.java | 2 +- .../memex}/memex/model/MemexIndexTask.java | 2 +- .../memex}/memex/model/MemexLink.java | 2 +- .../memex}/memex/model/MemexNode.java | 2 +- .../memex/model/MemexNodeHeadingId.java | 2 +- .../memex}/memex/model/MemexNodeTaskId.java | 2 +- .../memex}/memex/model/MemexNodeType.java | 2 +- .../memex}/memex/model/MemexNodeUrl.java | 2 +- .../memex}/memex/model/MemexTaskState.java | 2 +- .../memex}/memex/model/MemexTaskTags.java | 2 +- .../memex}/memex/model/MemexUrl.java | 2 +- .../memex}/memex/model/fs/MemexDirectory.java | 8 +- .../memex/model/fs/MemexFileSystem.java | 8 +- .../render/MemexRenderCreateFormModel.java | 8 +- .../render/MemexRenderUpdateFormModel.java | 6 +- .../render/MemexRenderUploadFormModel.java | 8 +- .../render/MemexRendererDeleteFormModel.java | 6 +- .../model/render/MemexRendererImageModel.java | 8 +- .../model/render/MemexRendererIndexModel.java | 11 +- .../render/MemexRendererRenameFormModel.java | 6 +- .../render/MemexRendererTombstoneModel.java | 6 +- .../model/render/MemexRendererViewModel.java | 6 +- .../model/render/MemexRendererableDirect.java | 7 + .../memex/renderer/MemexGmiRenderer.java | 12 +- .../memex/renderer/MemexHtmlRenderer.java | 38 +- .../memex/renderer/MemexRendererers.java | 4 +- .../system/MemexFileSystemModifiedTimes.java | 2 +- .../memex/system/MemexFileSystemMonitor.java | 5 +- .../memex}/memex/system/MemexFileWriter.java | 4 +- .../memex/system/MemexSourceFileSystem.java | 6 +- .../memex}/memex/system/git/MemexGitRepo.java | 4 +- .../memex/system/git/MemexGitRepoDummy.java | 4 +- .../memex/system/git/MemexGitRepoImpl.java | 4 +- .../memex/renderer}/MustacheRenderer.java | 10 +- .../memex/renderer/RendererFactory.java | 13 + .../util}/dithering/FloydSteinbergDither.java | 2 +- .../memex/util}/dithering/Palettes.java | 2 +- .../main/resources/static/memex/ico/dir.png | Bin .../main/resources/static/memex/ico/doc32.png | Bin .../main/resources/static/memex/ico/file.png | Bin .../resources/static/memex/ico/folder32.png | Bin .../resources/static/memex/ico/folderup16.png | Bin .../main/resources/static/memex/ico/nav16.png | Bin .../main/resources/static/memex/ico/pic16.png | Bin .../main/resources/static/memex/ico/pic32.png | Bin .../main/resources/static/memex/ico/root.png | Bin .../resources/static/memex/ico/shiba16.png | Bin .../resources/static/memex/ico/world16.png | Bin .../main/resources/static/memex/style-new.css | 0 .../main/resources/templates/auth/login.hdb | 0 .../templates/memex/memex-create-form.hdb | 0 .../templates/memex/memex-delete-form.hdb | 0 .../resources/templates/memex/memex-image.hdb | 0 .../templates/memex/memex-index-feed.hdb | 0 .../resources/templates/memex/memex-index.hdb | 0 .../templates/memex/memex-rename-form.hdb | 0 .../templates/memex/memex-tombstone.hdb | 0 .../templates/memex/memex-update-form.hdb | 0 .../templates/memex/memex-upload-form.hdb | 0 .../resources/templates/memex/memex-view.hdb | 0 .../memex/partial/memex-backlinks-inline.hdb | 0 .../memex/partial/memex-backlinks.hdb | 0 .../memex/partial/memex-directories.hdb | 0 .../memex/partial/memex-documents-inline.hdb | 0 .../memex/partial/memex-documents.hdb | 0 .../templates/memex/partial/memex-footer.hdb | 0 .../templates/memex/partial/memex-head.hdb | 0 .../templates/memex/partial/memex-images.hdb | 0 .../memex/partial/memex-task-listing.hdb | 0 .../templates/memex/partial/memex-topbar.hdb | 0 .../marginalia}/gmi/GemtextDatabaseTest.java | 5 +- .../marginalia}/gmi/GemtextDocumentTest.java | 10 +- .../gmi/parser/GemtextTaskParserTest.java | 7 +- .../dithering/FloydSteinbergDitherTest.java | 4 +- .../memex}/memex/MemexFileWriterTest.java | 6 +- .../nu/marginalia/memex}/memex/MemexTest.java | 2 +- .../memex/change/GemtextChangeTest.java | 24 +- .../memex/change/GemtextTaskUpdateTest.java | 28 +- .../GemtextTombstoneUpdateCaclulatorTest.java | 22 +- .../memex/model/MemexNodeHeadingIdTest.java | 2 +- .../nu/marginalia/util/test/TestUtil.java | 50 ++ {third_party => other/wmsa_old}/build.gradle | 95 ++-- .../java/nu/marginalia/wmsa/WmsaHome.java | 56 ++ .../wmsa/podcasts/PodcastFetcher.java | 0 .../wmsa/podcasts/PodcastScraperMain.java | 16 +- .../wmsa/podcasts/PodcastScraperService.java | 8 +- .../wmsa/podcasts/model/Podcast.java | 0 .../wmsa/podcasts/model/PodcastEpisode.java | 0 .../wmsa/podcasts/model/PodcastListing.java | 0 .../wmsa/podcasts/model/PodcastMetadata.java | 0 .../podcasts/model/PodcastNewEpisodes.java | 0 .../wmsa/renderer/PodcastRendererService.java | 7 +- .../wmsa/renderer/RendererMain.java | 14 +- .../wmsa/renderer/RendererModule.java | 0 .../wmsa/renderer/RendererService.java | 8 +- .../wmsa/renderer/ServerStatusModel.java | 0 .../wmsa/renderer/WmsaServiceDescriptors.java | 17 + .../wmsa/renderer/client/RendererClient.java | 19 +- .../renderer/mustache/MustacheRenderer.java | 61 ++ .../renderer/mustache/RendererFactory.java | 0 .../resource_store/ResourceEntityStore.java | 3 +- .../resource_store/ResourceStoreClient.java | 19 +- .../resource_store/ResourceStoreMain.java | 16 +- .../resource_store/ResourceStoreModule.java | 2 +- .../resource_store/ResourceStoreService.java | 32 +- .../model/RenderedResource.java | 0 .../main/resources/static/podcast/style.css | 0 .../resources/templates/podcast/episode.hdb | 0 .../resources/templates/podcast/listing.hdb | 0 .../main/resources/templates/podcast/new.hdb | 0 .../resources/templates/podcast/podcast.hdb | 0 .../ResourceStoreServiceTest.java | 19 +- run/.gitignore | 1 + run/reconvert.sh | 58 ++ services-core/assistant-service/build.gradle | 67 +++ .../marginalia/assistant/AssistantMain.java | 34 ++ .../marginalia/assistant/AssistantModule.java | 8 +- .../assistant/AssistantService.java | 41 +- .../assistant/dict/DictionaryService.java | 5 +- .../assistant/dict/SpellChecker.java | 3 +- .../assistant/eval/MathParser.java | 4 +- .../nu/marginalia}/assistant/eval/Unit.java | 2 +- .../nu/marginalia}/assistant/eval/Units.java | 2 +- .../assistant/suggest/Suggestions.java | 12 +- .../src/main/resources/units.csv | 0 .../assistant/dict/WikiCleanerTest.java | 2 +- .../assistant/eval/MathParserTest.java | 2 +- .../marginalia}/assistant/eval/UnitsTest.java | 2 +- services-core/index-service/build.gradle | 76 +++ .../java/nu/marginalia/index/IndexMain.java | 35 ++ .../java/nu/marginalia/index/IndexModule.java | 38 ++ .../nu/marginalia/index/IndexService.java | 76 +++ .../index/IndexServicesFactory.java | 79 +-- .../marginalia/index/IndexTablesModule.java | 20 + .../index/config/RankingSettings.java | 3 +- .../index/config/RankingSettingsEntry.java | 11 + .../marginalia/index/index}/SearchIndex.java | 50 +- .../index/index/SearchIndexQueryBuilder.java | 42 ++ .../index/index}/SearchIndexReader.java | 62 +- .../index/index/SearchIndexSearchTerms.java | 6 +- .../index/results/IndexMetadataService.java | 26 + .../IndexResultDomainDeduplicator.java | 18 +- .../index/results}/IndexResultValuator.java | 41 +- .../marginalia/index/svc/IndexOpsService.java | 77 +++ .../index/svc/IndexQueryService.java | 294 ++++++++++ .../index/svc/IndexSearchSetsService.java | 38 +- .../index/svc/SearchTermsService.java | 67 +++ .../index/svc/searchset/RankingSearchSet.java | 15 +- .../index/svc/searchset/SearchSetAny.java | 4 +- .../index/svc/searchset/SmallSearchSet.java | 3 +- .../model/EdgePageDocumentsMetadataTest.java | 4 +- .../index/model/RankingSettingsTest.java | 6 +- .../service/EdgeIndexIntegrationTest.java | 40 +- .../EdgeIndexIntegrationTestModule.java | 18 +- .../service/util/DictionaryDataTest.java | 2 +- .../service/util/DictionaryHashMapTest.java | 2 +- .../index/service/util/PrimeUtilTest.java | 30 + .../service/util/RandomWriteFunnelTest.java | 2 +- .../svc/searchset/RankingSearchSetTest.java | 5 +- .../nu/marginalia/index/util/TestUtil.java | 50 ++ services-core/readme.md | 12 + services-core/search-service/build.gradle | 76 +++ .../java/nu/marginalia/search/SearchMain.java | 38 ++ .../nu/marginalia/search/SearchModule.java | 10 +- .../nu/marginalia/search/SearchOperator.java | 96 ++-- .../nu/marginalia/search/SearchService.java | 50 +- .../search/command/CommandEvaluator.java | 6 +- .../search/command/IndexCommand.java | 29 + .../command/SearchCommandInterface.java | 5 +- .../search/command/SearchJsParameter.java | 4 +- .../search/command/SearchParameters.java | 9 + .../search/command/commands/BangCommand.java | 10 +- .../command/commands/BrowseCommand.java | 51 +- .../command/commands/ConvertCommand.java | 35 ++ .../command/commands/DefinitionCommand.java | 18 +- .../command/commands/SearchCommand.java | 51 ++ .../command/commands/SiteListCommand.java | 42 +- .../search/db/DbUrlDetailsQuery.java | 111 ++++ .../search/exceptions/RedirectException.java | 2 +- .../search/model/DecoratedSearchResults.java | 13 +- .../search/model/DomainInformation.java | 4 +- .../search/model/PageScoreAdjustment.java | 8 +- .../search/model/SearchProfile.java | 18 +- .../search/model/SearchRankingSymbols.java | 4 +- .../marginalia/search/model/UrlDetails.java | 20 +- .../search/query/NearQueryProcessor.java | 2 +- .../search/query/QueryFactory.java | 53 +- .../search/query/model/SearchQuery.java | 8 +- .../query/model/UserSearchParameters.java | 7 + .../search/results/BrowseResultCleaner.java | 8 +- .../search/results/SearchResultDecorator.java | 51 +- .../search/results/UrlDeduplicator.java | 67 +++ .../siteinfo/DomainInformationService.java | 22 +- .../svc/SearchAddToCrawlQueueService.java | 28 +- .../search/svc/SearchApiQueryService.java | 98 ++++ .../search/svc/SearchErrorPageService.java | 11 +- .../search/svc/SearchFlagSiteService.java | 14 +- .../search/svc/SearchQueryIndexService.java | 76 +-- .../search/svc/SearchQueryService.java | 28 +- .../svc/SearchUnitConversionService.java | 12 +- .../valuation/SearchResultValuator.java | 14 +- .../main/resources/static/search}/about.html | 0 .../resources/static/search}/changelog.html | 0 .../resources/static/search}/crawler-ips.txt | 0 .../main/resources/static/search}/error.html | 0 .../main/resources/static/search}/favicon.ico | Bin .../static/search}/known-issues.html | 0 .../resources/static/search}/maintenance.html | 0 .../main/resources/static/search}/notes.html | 0 .../resources/static/search}/opensearch.xml | 0 .../main/resources/static/search}/robots.txt | 0 .../resources/static/search}/style-new.css | 0 .../src/main/resources/static/search}/tts.js | 0 .../resources/static/search}/wiki-clean.html | 0 .../templates/search}/browse-result.hdb | 0 .../templates/search}/browse-results.hdb | 8 +- .../templates/search}/conversion-results.hdb | 6 +- .../templates/search}/dictionary-results.hdb | 6 +- .../templates/search}/error-page.hdb | 0 .../resources/templates/search}/index.hdb | 20 +- .../templates/search}/indict/indict-form.hdb | 6 +- .../templates/search}/parts/search-footer.hdb | 0 .../templates/search}/parts/search-form.hdb | 2 +- .../templates/search}/parts/search-header.hdb | 0 .../search}/parts/site-info-index.hdb | 0 .../search}/parts/site-info-links.hdb | 0 .../search}/search-result-metadata.hdb | 0 .../templates/search}/search-result.hdb | 2 +- .../templates/search}/search-results.hdb | 10 +- .../resources/templates/search}/site-info.hdb | 12 +- .../command/commands/BangCommandTest.java | 4 +- .../search/query/QueryFactoryTest.java | 23 +- .../marginalia/util/TestLanguageModels.java | 37 ++ services-satellite/api-service/build.gradle | 63 +++ .../main/java/nu/marginalia/api/ApiMain.java | 28 + .../java/nu/marginalia}/api/ApiService.java | 16 +- .../nu/marginalia}/api/model/ApiLicense.java | 2 +- .../dating-service/build.gradle | 66 +++ .../nu/marginalia}/dating/DatingMain.java | 19 +- .../nu/marginalia}/dating/DatingModule.java | 2 +- .../nu/marginalia}/dating/DatingService.java | 40 +- .../dating/DatingSessionObject.java | 21 +- .../main/resources/static/dating/index.html | 0 .../main/resources/static/dating/robots.txt | 0 .../templates/dating/dating-view.hdb | 0 .../explorer-service/build.gradle | 61 ++ .../nu/marginalia}/explorer/ExplorerMain.java | 17 +- .../marginalia}/explorer/ExplorerService.java | 19 +- .../main/resources/static/explore/style.css | 0 .../templates/explorer/explorer-about.hdb | 0 .../templates/explorer/explorer-messages.hdb | 0 .../templates/explorer/explorer-results.hdb | 0 .../templates/explorer/explorer-search.hdb | 0 .../resources/templates/explorer/explorer.hdb | 0 services-satellite/readme.md | 7 + settings.gradle | 164 +++++- {third_party => third-party}/README.md | 1 + third-party/build.gradle | 31 + .../ca/rmen/porterstemmer/PorterStemmer.java | 0 .../com/github/datquocnguyen/FWObject.java | 0 .../github/datquocnguyen/InitialTagger.java | 0 .../java/com/github/datquocnguyen/Node.java | 0 .../github/datquocnguyen/RDRPOSTagger.java | 0 .../java/com/github/datquocnguyen/Utils.java | 0 .../com/github/datquocnguyen/WordTag.java | 0 .../com/google/gson/stream/JsonReader.java | 0 .../com/upserve/uppend/blobs/NativeIO.java | 0 .../jdkoverride/LargeLineBufferedReader.java | 0 .../sentdetect/DefaultSDContextGenerator.java | 1 + .../tools/sentdetect/SentenceDetectorME.java | 10 +- .../org/openzim/ZIMTypes/ArticleEntry.java | 0 .../org/openzim/ZIMTypes/DirectoryEntry.java | 0 .../org/openzim/ZIMTypes/RedirectEntry.java | 0 .../java/org/openzim/ZIMTypes/ZIMFile.java | 0 .../java/org/openzim/ZIMTypes/ZIMReader.java | 11 +- .../util/RandomAcessFileZIMInputStream.java | 0 .../main/java/org/openzim/util/Utilities.java | 0 .../java/org/tukaani/xz/BlockInputStream.java | 0 .../org/tukaani/xz/BlockOutputStream.java | 0 .../tukaani/xz/CorruptedInputException.java | 0 .../org/tukaani/xz/CountingInputStream.java | 0 .../org/tukaani/xz/CountingOutputStream.java | 0 .../main/java/org/tukaani/xz/DeltaCoder.java | 0 .../java/org/tukaani/xz/DeltaDecoder.java | 0 .../java/org/tukaani/xz/DeltaInputStream.java | 0 .../main/java/org/tukaani/xz/FilterCoder.java | 0 .../java/org/tukaani/xz/FilterDecoder.java | 0 .../java/org/tukaani/xz/FilterEncoder.java | 0 .../java/org/tukaani/xz/FilterOptions.java | 0 .../tukaani/xz/FinishableOutputStream.java | 0 .../tukaani/xz/IndexIndicatorException.java | 0 .../main/java/org/tukaani/xz/LZMA2Coder.java | 0 .../java/org/tukaani/xz/LZMA2Decoder.java | 0 .../java/org/tukaani/xz/LZMA2Encoder.java | 0 .../java/org/tukaani/xz/LZMA2InputStream.java | 0 .../java/org/tukaani/xz/LZMA2Options.java | 0 .../org/tukaani/xz/LZMA2OutputStream.java | 0 .../org/tukaani/xz/MemoryLimitException.java | 0 .../main/java/org/tukaani/xz/RawCoder.java | 0 .../org/tukaani/xz/SingleXZInputStream.java | 0 .../xz/UnsupportedOptionsException.java | 0 .../src/main/java/org/tukaani/xz/XZ.java | 0 .../org/tukaani/xz/XZFormatException.java | 0 .../java/org/tukaani/xz/XZIOException.java | 0 .../java/org/tukaani/xz/XZInputStream.java | 0 .../java/org/tukaani/xz/XZOutputStream.java | 0 .../main/java/org/tukaani/xz/check/CRC32.java | 0 .../main/java/org/tukaani/xz/check/CRC64.java | 0 .../main/java/org/tukaani/xz/check/Check.java | 0 .../main/java/org/tukaani/xz/check/None.java | 0 .../java/org/tukaani/xz/check/SHA256.java | 0 .../org/tukaani/xz/common/DecoderUtil.java | 0 .../org/tukaani/xz/common/EncoderUtil.java | 0 .../org/tukaani/xz/common/StreamFlags.java | 0 .../main/java/org/tukaani/xz/common/Util.java | 0 .../java/org/tukaani/xz/delta/DeltaCoder.java | 0 .../org/tukaani/xz/delta/DeltaDecoder.java | 0 .../java/org/tukaani/xz/index/IndexBase.java | 0 .../org/tukaani/xz/index/IndexEncoder.java | 0 .../java/org/tukaani/xz/index/IndexHash.java | 0 .../org/tukaani/xz/index/IndexRecord.java | 0 .../java/org/tukaani/xz/lz/LZDecoder.java | 0 .../java/org/tukaani/xz/lzma/LZMACoder.java | 0 .../java/org/tukaani/xz/lzma/LZMADecoder.java | 0 .../main/java/org/tukaani/xz/lzma/State.java | 0 .../java/org/tukaani/xz/package-info.java | 0 .../org/tukaani/xz/rangecoder/RangeCoder.java | 0 .../tukaani/xz/rangecoder/RangeDecoder.java | 0 .../src/main/java/symspell}/SymSpell.java | 2 +- tools/screenshot/build.gradle | 46 ++ .../ScreenshotCaptureToolMain.java | 6 +- .../screenshot/ScreenshotLoaderMain.java | 5 +- 1234 files changed, 11606 insertions(+), 14306 deletions(-) create mode 100644 api/assistant-api/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => api/assistant-api/src/main/java/nu/marginalia}/assistant/client/AssistantClient.java (56%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict => api/assistant-api/src/main/java/nu/marginalia/assistant/client/model}/DictionaryEntry.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict => api/assistant-api/src/main/java/nu/marginalia/assistant/client/model}/DictionaryResponse.java (86%) create mode 100644 api/index-api/build.gradle create mode 100644 api/index-api/src/main/java/nu/marginalia/index/client/EdgeIndexClient.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search => api/index-api/src/main/java/nu/marginalia/index/client/model}/domain/EdgeDomainSearchResults.java (61%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search => api/index-api/src/main/java/nu/marginalia/index/client/model}/domain/EdgeDomainSearchSpecification.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search => api/index-api/src/main/java/nu/marginalia/index/client/model/query}/EdgeSearchSpecification.java (65%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search => api/index-api/src/main/java/nu/marginalia/index/client/model/query}/EdgeSearchSubquery.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset => api/index-api/src/main/java/nu/marginalia/index/client/model/query}/SearchSetIdentifier.java (64%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search => api/index-api/src/main/java/nu/marginalia/index/client/model/results}/EdgeSearchResultItem.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search => api/index-api/src/main/java/nu/marginalia/index/client/model/results}/EdgeSearchResultKeywordScore.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search => api/index-api/src/main/java/nu/marginalia/index/client/model/results}/EdgeSearchResultSet.java (85%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search => api/index-api/src/main/java/nu/marginalia/index/client/model/results}/EdgeSearchResults.java (91%) create mode 100644 api/readme.md create mode 100644 api/search-api/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => api/search-api/src/main/java/nu/marginalia}/search/client/EdgeSearchClient.java (56%) create mode 100644 api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResult.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/api => api/search-api/src/main/java/nu/marginalia/search/client}/model/ApiSearchResultQueryDetails.java (84%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/api => api/search-api/src/main/java/nu/marginalia/search/client}/model/ApiSearchResults.java (86%) create mode 100644 common/config/build.gradle create mode 100644 common/config/src/main/java/nu/marginalia/LanguageModels.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/config/src/main/java/nu/marginalia}/UserAgent.java (52%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/config/src/main/java/nu/marginalia}/WebsiteUrl.java (73%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/config/src/main/java/nu/marginalia}/WmsaHome.java (97%) create mode 100644 common/model/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/EdgeDomain.java (99%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/EdgeUrl.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => common/model/src/main/java/nu/marginalia/model/crawl}/DocumentKeywords.java (85%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/crawl/EdgeContentType.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/crawl/EdgeDomainIndexingState.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/crawl/EdgeDomainLink.java (68%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/crawl/EdgeHtmlStandard.java (71%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model => common/model/src/main/java/nu/marginalia/model/crawl}/EdgePageDocumentFlags.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model => common/model/src/main/java/nu/marginalia/model/crawl}/EdgePageWordFlags.java (78%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/crawl/EdgePageWords.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/crawl/EdgeUrlState.java (73%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic => common/model/src/main/java/nu/marginalia/model/crawl}/HtmlFeature.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate => common/model/src/main/java/nu/marginalia/model/crawl}/PubDate.java (95%) create mode 100644 common/model/src/main/java/nu/marginalia/model/dbcommon/DbDomainQueries.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia/model}/dbcommon/EdgeDomainBlacklist.java (74%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia/model}/dbcommon/EdgeDomainBlacklistImpl.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/client => common/model/src/main/java/nu/marginalia/model/gson}/GsonFactory.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/id/EdgeId.java (74%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/id/EdgeIdArray.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/id/EdgeIdCollection.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/id/EdgeIdCollectionMutable.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/id/EdgeIdList.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => common/model/src/main/java/nu/marginalia}/model/id/EdgeIdSet.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model => common/model/src/main/java/nu/marginalia/model/idx}/EdgePageDocumentsMetadata.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model => common/model/src/main/java/nu/marginalia/model/idx}/EdgePageWordMetadata.java (97%) rename {marginalia_nu => common/model}/src/main/java/nu/marginalia/util/DenseBitMap.java (100%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => common/model/src/main/java/nu/marginalia/util}/KeywordListChunker.java (91%) create mode 100644 common/model/src/main/java/nu/marginalia/util/LineUtils.java rename {marginalia_nu => common/model}/src/main/java/nu/marginalia/util/ParallelPipe.java (87%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic => common/model/src/main/java/nu/marginalia/util}/QueryParams.java (97%) rename {marginalia_nu => common/model}/src/main/java/nu/marginalia/util/StringPool.java (100%) rename {marginalia_nu => common/model}/src/main/resources/sql/edge-crawler-cache.sql (98%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => common/model/src/test/java/nu/marginalia}/model/EdgeDomainTest.java (98%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/crawl => common/model/src/test/java/nu/marginalia/model}/EdgePageWordMetadataTest.java (93%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => common/model/src/test/java/nu/marginalia}/model/EdgeUrlTest.java (96%) rename {marginalia_nu => common/model}/src/test/java/nu/marginalia/util/BrailleBlockPunchCardsTest.java (100%) rename {marginalia_nu => common/model}/src/test/java/nu/marginalia/util/DenseBitMapTest.java (97%) create mode 100644 common/model/src/test/java/nu/marginalia/util/LineUtilsTest.java create mode 100644 common/readme.md create mode 100644 common/service-client/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/AbortingScheduler.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/AbstractClient.java (64%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/AbstractDynamicClient.java (66%) create mode 100644 common/service-client/src/main/java/nu/marginalia/client/Context.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/HttpStatusCode.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/exception/LocalException.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/exception/MessagingException.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/exception/NetworkException.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/exception/RemoteException.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/exception/RouteNotConfiguredException.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => common/service-client/src/main/java/nu/marginalia}/client/exception/TimeoutException.java (88%) create mode 100644 common/service-client/src/test/java/nu/marginalia/client/AbstractClientTest.java create mode 100644 common/service-client/src/test/java/nu/marginalia/client/TestServer.java create mode 100644 common/service-discovery/build.gradle create mode 100644 common/service-discovery/src/main/java/nu/marginalia/service/SearchServiceDescriptors.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/service-discovery/src/main/java/nu/marginalia/service/descriptor}/HostsFile.java (75%) create mode 100644 common/service-discovery/src/main/java/nu/marginalia/service/descriptor/ServiceDescriptor.java create mode 100644 common/service-discovery/src/main/java/nu/marginalia/service/descriptor/ServiceDescriptors.java create mode 100644 common/service-discovery/src/main/java/nu/marginalia/service/id/ServiceId.java create mode 100644 common/service/build.gradle create mode 100644 common/service/src/main/java/nu/marginalia/service/MainClass.java create mode 100644 common/service/src/main/java/nu/marginalia/service/module/ConfigurationModule.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/service/src/main/java/nu/marginalia/service}/module/DatabaseModule.java (70%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/service/src/main/java/nu/marginalia/service}/module/LoggerConfiguration.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/service/src/main/java/nu/marginalia/service}/module/MetricsPortProvider.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/service/src/main/java/nu/marginalia/service}/server/Initialization.java (80%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/service/src/main/java/nu/marginalia/service}/server/MetricsServer.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/service/src/main/java/nu/marginalia/service}/server/RateLimiter.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration => common/service/src/main/java/nu/marginalia/service}/server/Service.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store => common/service/src/main/java/nu/marginalia/service/server}/StaticResources.java (97%) create mode 100644 common/service/src/main/resources/log4j2.properties create mode 100644 crawl/common/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/common/src/main/java/nu/marginalia/crawling/common}/AbortMonitor.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/common/src/main/java/nu/marginalia/crawling/common}/TaskStats.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/common/src/main/java/nu/marginalia/crawling/common}/WorkLog.java (91%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/common/src/main/java/nu/marginalia/crawling/common}/blocklist/GeoIpBlocklist.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/common/src/main/java/nu/marginalia/crawling/common}/blocklist/InetAddressCache.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/common/src/main/java/nu/marginalia/crawling/common}/blocklist/IpBlockList.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/common/src/main/java/nu/marginalia/crawling/common}/blocklist/UrlBlocklist.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic => crawl/common/src/main/java/nu/marginalia/crawling/common/link}/LinkParser.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/common/src/main/java/nu/marginalia/crawling/common/plan}/CrawlPlanLoader.java (85%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/common/src/main/java/nu/marginalia/crawling/common/plan}/CrawlerSpecificationLoader.java (60%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model => crawl/common/src/main/java/nu/marginalia/crawling/common/plan}/EdgeCrawlPlan.java (78%) rename {marginalia_nu/src/e2e => crawl/common/src/main}/resources/log4j2.properties (57%) create mode 100644 crawl/converting-model/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction}/Instruction.java (68%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction}/InstructionTag.java (80%) create mode 100644 crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/Interpreter.java create mode 100644 crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/DomainLink.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions}/LoadDomain.java (61%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions}/LoadDomainLink.java (65%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions}/LoadDomainRedirect.java (63%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions}/LoadKeywords.java (57%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions}/LoadProcessedDocument.java (64%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions}/LoadProcessedDocumentWithError.java (57%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions}/LoadProcessedDomain.java (52%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions}/LoadRssFeed.java (61%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction => crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions}/LoadUrl.java (60%) create mode 100644 crawl/converting-process/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/ConversionLog.java (72%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/ConverterMain.java (51%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/ConverterModule.java (52%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LoadInstructionWriter.java => crawl/converting-process/src/main/java/nu/marginalia/converting/InstructionWriter.java (79%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/UpdateDomainStatistics.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/compiler/DocumentsCompiler.java (64%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/compiler/FeedsCompiler.java (59%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/compiler/InstructionsCompiler.java (85%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/compiler/LinksCompiler.java (59%) create mode 100644 crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/RedirectCompiler.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/compiler/UrlsCompiler.java (75%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/model/DisqualifiedException.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/model/ProcessedDocument.java (52%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/model/ProcessedDocumentDetails.java (65%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/model/ProcessedDomain.java (82%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/AcceptableAds.java (85%) create mode 100644 crawl/converting-process/src/main/java/nu/marginalia/converting/processor/DocumentProcessor.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/DomainProcessor.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/SiteWords.java (85%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords}/DocumentKeywordExtractor.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords}/KeywordCounter.java (85%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords}/NameCounter.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords}/SubjectCounter.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/CommonKeywordExtractor.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/DocumentValuator.java (80%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/DomPruningFilter.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/FeatureExtractor.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/FeedExtractor.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/HtmlStandardExtractor.java (70%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/InternalLinkGraph.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/LinkProcessor.java (89%) create mode 100644 crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/LshDocumentDeduplicator.java create mode 100644 crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/PlainTextLogic.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/SalientImageDetector.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/SummaryExtractionFilter.java (99%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/SummaryExtractor.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/TitleExtractor.java (91%) create mode 100644 crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateEffortLevel.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/PubDateHeuristic.java (56%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/PubDateParser.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/PubDateSniffer.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass1.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass2.java (86%) create mode 100644 crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicGuessFromHtmlStandard.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5AnyTimeTag.java (60%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ArticleDateTag.java (54%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ItempropDateTag.java (54%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicJSONLD.java (70%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicLastModified.java (57%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicMicrodata.java (53%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicOpenGraph.java (54%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicRDFaTag.java (53%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass1.java (70%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass2.java (67%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/topic/AdblockSimulator.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/topic/GoogleAnwersSpamDetector.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/topic/RecipeDetector.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/topic/TextileCraftDetector.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/converting-process/src/main/java/nu/marginalia}/converting/processor/logic/topic/WoodworkingDetector.java (97%) create mode 100644 crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/AbstractDocumentProcessorPlugin.java create mode 100644 crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/HtmlDocumentProcessorPlugin.java create mode 100644 crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/PlainTextDocumentProcessorPlugin.java rename {marginalia_nu/src/main/java/nu/marginalia/util/language => crawl/converting-process/src/main/java/nu/marginalia/converting/tool}/DocumentDebugger.java (86%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor => crawl/converting-process/src/test/java/nu/marginalia/converting}/logic/DomPruningFilterTest.java (72%) create mode 100644 crawl/converting-process/src/test/java/nu/marginalia/converting/logic/PlainTextLogicTest.java rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor => crawl/converting-process/src/test/java/nu/marginalia/converting}/logic/PubDateSnifferTest.java (96%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor => crawl/converting-process/src/test/java/nu/marginalia/converting}/logic/SummaryExtractorTest.java (96%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor => crawl/converting-process/src/test/java/nu/marginalia/converting}/logic/pubdate/PubDateTest.java (80%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling => crawl/converting-process/src/test/java/nu/marginalia/converting/processor/keywords}/SentenceExtractorTest.java (80%) create mode 100644 crawl/converting-process/src/test/java/nu/marginalia/test/util/TestLanguageModels.java rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/monadnock.html (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/readme.md (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/summarization/187.shtml (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/summarization/surrey.html (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/summarization/surrey.html.1 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/index (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1021546012 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1028592943 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1081293162 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1105046394 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1146923296 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1194694074 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1207898281 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1268145073 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1294876331 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1314767420 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1316269786 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1316766580 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1319968043 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1338576987 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1341909571 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1369578579 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1437315645 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1458954960 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1475681345 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1498328446 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1507779664 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1540303379 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--154898476 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1552059399 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1557688340 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1584145751 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1605151204 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--162269247 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1624294488 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--164108285 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1645688243 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1658004609 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1658558834 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1698664879 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--169975195 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1701203332 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--17281998 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1742070028 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1745376814 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1749889035 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--176177364 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--177014197 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1794527707 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1797740201 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1799098579 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1959637826 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1971916964 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--1985840368 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--2012610859 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--202178680 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--2043528727 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--2081757477 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--2103982576 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--2111558769 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--213168798 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--232544032 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--253010011 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--274250994 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--332442790 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--353437903 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--364546777 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--379129416 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--399428149 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--425233170 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--434612307 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--439772328 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--458002611 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--506010305 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--546773534 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--551288516 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--602577763 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--611668054 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--634771245 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--639320493 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--643179018 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--663772351 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--670789152 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--6797317 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--700978490 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--708035332 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--804917062 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--819771302 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--840796372 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--841445362 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--862385354 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--879796466 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--89134993 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--905197876 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--920328354 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--952827759 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--964018507 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url--972614909 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-10088520 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1013281103 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1019241851 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1059944953 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1118681302 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1179298706 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1191749784 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1207094790 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1213989666 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1222442301 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-130332455 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1311055461 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1391842722 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1457388763 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1506356272 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1511762169 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1534640058 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1551513871 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1567632447 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1623049502 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-163919330 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1661398327 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1724309925 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1736807128 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1739031345 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1755745765 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1802811100 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1805364707 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1832702370 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1853114311 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1924872844 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-197772804 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1984259912 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-1990903988 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-2039310951 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-2040857056 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-2052613093 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-2063899866 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-2115548255 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-2127148436 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-2133781904 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-225690385 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-226401955 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-262970770 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-30106798 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-302167335 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-327999153 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-332568225 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-343223418 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-383103932 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-412929678 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-475213997 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-483403121 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-488667993 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-50815201 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-522685905 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-570714305 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-58733529 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-616518304 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-662169426 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-677278788 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-690486170 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-709693331 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-734531556 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-767530276 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-783154014 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-796905237 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-800099955 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-804101946 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-830664902 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-876060686 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-892584998 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-942458463 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-952036171 (100%) rename {marginalia_nu => crawl/converting-process}/src/test/resources/html/work-set/url-968207276 (100%) create mode 100644 crawl/crawl-job-extractor-process/build.gradle rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlJobExtractorMain.java => crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobDomainExtractor.java (65%) create mode 100644 crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobExtractorMain.java create mode 100644 crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobSpecWriter.java create mode 100644 crawl/crawl-job-extractor-process/src/test/java/nu/marginalia/crawl/CrawlJobSpecWriterTest.java create mode 100644 crawl/crawling-model/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-model/src/main/java/nu/marginalia/crawling/io}/CrawledDomainReader.java (87%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-model/src/main/java/nu/marginalia/crawling/io}/CrawledDomainWriter.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/crawling-model/src/main/java/nu/marginalia}/crawling/model/CrawlLogEntry.java (61%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/crawling-model/src/main/java/nu/marginalia}/crawling/model/CrawledDocument.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/crawling-model/src/main/java/nu/marginalia}/crawling/model/CrawledDomain.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/crawling-model/src/main/java/nu/marginalia}/crawling/model/CrawlerDocumentStatus.java (76%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/crawling-model/src/main/java/nu/marginalia}/crawling/model/CrawlerDomainStatus.java (59%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/crawling-model/src/main/java/nu/marginalia}/crawling/model/CrawlingSpecification.java (62%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => crawl/crawling-model/src/main/java/nu/marginalia}/crawling/model/SerializableCrawlData.java (61%) create mode 100644 crawl/crawling-process/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/CrawlerMain.java (74%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/retreival/Cookies.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/retreival/CrawlerRetreiver.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/retreival/FastTerminatingSocketFactory.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/retreival/HttpFetcher.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/retreival/HttpRedirectResolver.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/retreival/NoSecuritySSL.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/retreival/RateLimitException.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/retreival/logic/ContentTypeLogic.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling => crawl/crawling-process/src/main/java/nu/marginalia/crawl}/retreival/logic/ContentTypeParser.java (96%) rename {marginalia_nu => crawl/crawling-process}/src/main/resources/ip-banned-cidr.txt (100%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/CrawlPlanLoaderTest.java (89%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/DomainCrawlerRobotsTxtTest.java (96%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/HtmlTagCleanerTest.java (88%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/HttpFetcherTest.java (88%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/LanguageFilterTest.java (92%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/LinkParserTest.java (94%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/RssCrawlerTest.java (92%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/UrlBlocklistTest.java (90%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/WorkLogTest.java (88%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => crawl/crawling-process/src/test/java/nu/marginalia}/crawling/retreival/CrawlerRetreiverTest.java (75%) create mode 100644 crawl/experimental/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools => crawl/experimental/src/main/java/nu/marginalia/experimental}/AdblockTesterTool.java (67%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools => crawl/experimental/src/main/java/nu/marginalia/experimental}/ConverterLogicTestTool.java (77%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools => crawl/experimental/src/main/java/nu/marginalia/experimental}/CrawlDataExtractorTool.java (80%) create mode 100644 crawl/loading-process/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/ConvertedDomainReader.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/LoaderMain.java (68%) create mode 100644 crawl/loading-process/src/main/java/nu/marginalia/loading/LoaderModule.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/loader/IndexLoadKeywords.java (63%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/loader/Loader.java (84%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/loader/LoaderData.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/loader/LoaderFactory.java (96%) create mode 100644 crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderIndexJournalWriter.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/loader/SqlLoadDomainLinks.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/loader/SqlLoadDomains.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/loader/SqlLoadProcessedDocument.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/loader/SqlLoadProcessedDomain.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/main/java/nu/marginalia/loading}/loader/SqlLoadUrls.java (96%) rename marginalia_nu/src/test/java/nu/marginalia/util/TestUtil.java => crawl/loading-process/src/test/java/nu/marginalia/loader/DbTestUtil.java (93%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/test/java/nu/marginalia}/loader/SqlLoadDomainLinksTest.java (80%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/test/java/nu/marginalia}/loader/SqlLoadDomainsTest.java (82%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/test/java/nu/marginalia}/loader/SqlLoadProcessedDocumentTest.java (67%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/test/java/nu/marginalia}/loader/SqlLoadProcessedDomainTest.java (80%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting => crawl/loading-process/src/test/java/nu/marginalia}/loader/SqlLoadUrlsTest.java (81%) create mode 100644 crawl/readme.md delete mode 100644 doc/language-models.md create mode 100644 docker-compose.yml create mode 100644 docker-service.gradle create mode 100644 env/mariadb.env create mode 100644 env/service.env create mode 100644 features/domain-ranking/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => features/domain-ranking/src/main/java/nu/marginalia/ranking}/DomainRankings.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/RankingAlgorithm.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/ReversePageRank.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/StandardPageRank.java (73%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/accumulator/RankingResultAccumulator.java (63%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/accumulator/RankingResultBitSetAccumulator.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/accumulator/RankingResultHashMapAccumulator.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/accumulator/RankingResultListAccumulator.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/data/RankingDomainData.java (84%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/data/RankingDomainFetcher.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/data/RankingDomainFetcherForSimilarityData.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/tool/CreateBrowseDomainRanksTool.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/tool/PerusePageRankV2.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/tool/PrintDomainRanksTool.java (78%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => features/domain-ranking/src/main/java/nu/marginalia}/ranking/tool/UpdateDomainRanksTool.java (83%) create mode 100644 features/query-parser/build.gradle create mode 100644 features/query-parser/src/main/java/nu/marginalia/query_parser/QueryParser.java create mode 100644 features/query-parser/src/main/java/nu/marginalia/query_parser/QueryPermutation.java create mode 100644 features/query-parser/src/main/java/nu/marginalia/query_parser/QueryTokenizer.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query => features/query-parser/src/main/java/nu/marginalia/query_parser}/QueryVariants.java (94%) create mode 100644 features/query-parser/src/main/java/nu/marginalia/query_parser/token/Token.java create mode 100644 features/query-parser/src/main/java/nu/marginalia/query_parser/token/TokenType.java rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query => features/query-parser/src/test/java/nu/marginalia/query_parser}/BodyQueryParserTest.java (78%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query => features/query-parser/src/test/java/nu/marginalia/query_parser}/QueryParserTest.java (62%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query => features/query-parser/src/test/java/nu/marginalia/query_parser}/QueryVariantsTest.java (83%) rename {marginalia_nu => features/query-parser}/src/test/java/nu/marginalia/util/TestLanguageModels.java (92%) create mode 100644 features/random-websites/build.gradle create mode 100644 features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsFromUrlId.java create mode 100644 features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsRandom.java create mode 100644 features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsSimilarCosine.java rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDataStoreDaoImpl.java => features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsSimilarOldAlgo.java (53%) rename {marginalia_nu/src/main/java/nu/marginalia/util => features/random-websites/src/main/java/nu/marginalia/browse/experimental}/AndCardIntSet.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/util/tool => features/random-websites/src/main/java/nu/marginalia/browse/experimental}/EdgeDomainLinkConsineSimilarityMain.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/util/tool => features/random-websites/src/main/java/nu/marginalia/browse/experimental}/EdgeWordWordConsineSimilarityMain.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search => features/random-websites/src/main/java/nu/marginalia/browse}/model/BrowseResult.java (75%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search => features/random-websites/src/main/java/nu/marginalia/browse}/model/BrowseResultSet.java (80%) rename {marginalia_nu/src/test/java/nu/marginalia/util => features/random-websites/src/test/java/nu/marginalia/experimental}/AndCardIntSetTest.java (87%) create mode 100644 features/renderer/build.gradle create mode 100644 features/renderer/src/main/java/nu/marginalia/renderer/MustacheRenderer.java create mode 100644 features/renderer/src/main/java/nu/marginalia/renderer/RendererFactory.java create mode 100644 features/renderer/src/main/java/nu/marginalia/renderer/RenderingException.java create mode 100644 features/screenshots/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant => features/screenshots/src/main/java/nu/marginalia}/screenshot/ScreenshotService.java (89%) create mode 100644 gradle.properties create mode 100644 index/index-forward/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-forward/src/main/java/nu/marginalia/index}/forward/ForwardIndexConverter.java (70%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-forward/src/main/java/nu/marginalia/index}/forward/ForwardIndexParameters.java (75%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-forward/src/main/java/nu/marginalia/index}/forward/ForwardIndexReader.java (70%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-forward/src/main/java/nu/marginalia/index}/forward/ParamMatchingQueryFilter.java (87%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings => index/index-forward/src/test/java/nu/marginalia/index}/forward/ForwardIndexConverterTest.java (60%) create mode 100644 index/index-forward/src/test/java/nu/marginalia/test/TestUtil.java create mode 100644 index/index-journal/build.gradle create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntry.java create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryBuilder.java rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalEntry.java => index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryData.java (77%) create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryHeader.java create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalFileHeader.java create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalStatistics.java create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReadEntry.java create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReader.java create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReaderSingleCompressedFile.java create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/writer/IndexJournalWriter.java create mode 100644 index/index-journal/src/main/java/nu.marginalia.index/journal/writer/IndexJournalWriterImpl.java create mode 100644 index/index-journal/src/test/java/nu/marginalia/index/journal/IndexJournalTest.java create mode 100644 index/index-query/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/EmptyEntrySource.java (73%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/EntrySource.java (54%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/EntrySourceFromArrayRange.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/IndexQuery.java (88%) create mode 100644 index/index-query/src/main/java/nu/marginalia/index/query/IndexQueryBuilder.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/IndexQueryParams.java (61%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/IndexSearchBudget.java (85%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/filter/QueryFilterAnyOf.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/filter/QueryFilterLetThrough.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/filter/QueryFilterNoPass.java (79%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/filter/QueryFilterStepExcludeFromPredicate.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/filter/QueryFilterStepFromPredicate.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => index/index-query/src/main/java/nu/marginalia}/index/query/filter/QueryFilterStepIf.java (91%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model => index/index-query/src/main/java/nu/marginalia/index/query/limit}/QueryLimits.java (68%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model => index/index-query/src/main/java/nu/marginalia/index/query/limit}/QueryStrategy.java (76%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain => index/index-query/src/main/java/nu/marginalia/index/query/limit}/SpecificationLimit.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain => index/index-query/src/main/java/nu/marginalia/index/query/limit}/SpecificationLimitType.java (63%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc => index/index-query/src/main/java/nu/marginalia/index}/searchset/SearchSet.java (55%) create mode 100644 index/index-reverse/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/main/java/nu/marginalia/index}/reverse/ReverseIndexConverter.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/main/java/nu/marginalia/index}/reverse/ReverseIndexParameters.java (60%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/main/java/nu/marginalia/index}/reverse/ReverseIndexPrefixEntrySource.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/main/java/nu/marginalia/index}/reverse/ReverseIndexPrioReader.java (75%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/main/java/nu/marginalia/index}/reverse/ReverseIndexPriorityParameters.java (62%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/main/java/nu/marginalia/index}/reverse/ReverseIndexReader.java (77%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/main/java/nu/marginalia/index}/reverse/query/ReverseIndexEntrySource.java (87%) create mode 100644 index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexEntrySourceBehavior.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/main/java/nu/marginalia/index}/reverse/query/ReverseIndexRejectFilter.java (68%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/main/java/nu/marginalia/index}/reverse/query/ReverseIndexRetainFilter.java (68%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/test/java/nu/marginalia/index}/reverse/ReverseIndexConverterTest.java (67%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings => index/index-reverse/src/test/java/nu/marginalia/index}/reverse/ReverseIndexConverterTest2.java (67%) create mode 100644 index/index-reverse/src/test/java/nu/marginalia/test/TestUtil.java create mode 100644 index/lexicon/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => index/lexicon/src/main/java/nu/marginalia}/lexicon/KeywordLexicon.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => index/lexicon/src/main/java/nu/marginalia}/lexicon/KeywordLexiconReadOnlyView.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => index/lexicon/src/main/java/nu/marginalia}/lexicon/journal/KeywordLexiconJournal.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => index/lexicon/src/main/java/nu/marginalia}/lexicon/journal/KeywordLexiconJournalCommitQueue.java (72%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index => index/lexicon/src/main/java/nu/marginalia}/lexicon/journal/KeywordLexiconJournalFile.java (90%) create mode 100644 index/lexicon/src/test/java/nu/marginalia/lexicon/KeywordLexiconTest.java create mode 100644 index/readme.md create mode 100644 libraries/array/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/IntArray.java (78%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/LongArray.java (78%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/BulkTransferArray.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/IntArrayBase.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/IntArraySearch.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/IntArraySort.java (99%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/IntArrayTransformations.java (77%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/LongArrayBase.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/LongArraySearch.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/LongArraySort.java (99%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/LongArrayTransformations.java (77%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/algo/SortingContext.java (71%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/buffer/IntQueryBuffer.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/buffer/LongQueryBuffer.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/delegate/ReferenceImplIntArrayDelegate.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/delegate/ReferenceImplLongArrayDelegate.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/delegate/ShiftedIntArray.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/delegate/ShiftedLongArray.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/AddressRangeCall.java (65%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/AddressRangeCallIO.java (75%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/AddressRangeIntFunction.java (67%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/AddressRangeLongFunction.java (67%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/IntBinaryIOOperation.java (73%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/IntIOTransformer.java (73%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/IntTransformer.java (62%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/LongBinaryIOOperation.java (74%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/LongIOTransformer.java (73%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/LongIntConsumer.java (62%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/LongLongConsumer.java (62%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functional/LongTransformer.java (63%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functor/IntIOFolder.java (67%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/functor/LongIOFolder.java (67%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/page/AbstractPagingArray.java (87%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/page/IntArrayPage.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/page/LongArrayPage.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/page/PagingIntArray.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/page/PagingLongArray.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/page/PartitionPage.java (91%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/scheme/ArrayPartitioningScheme.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/scheme/PowerOf2PartitioningScheme.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/scheme/SequentialPartitioningScheme.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/trace/ArrayTrace.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/trace/ArrayTraceViz.java (99%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/trace/FileTrace.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/array/src/main/java/nu/marginalia}/array/trace/NullTrace.java (80%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/array/src/test/java/nu/marginalia}/array/IntLowBitPartitioningSchemeTest.java (83%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/array/src/test/java/nu/marginalia}/array/PagingIntArrayTest.java (93%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/array/src/test/java/nu/marginalia}/array/algo/IntArraySearchTest.java (93%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/array/src/test/java/nu/marginalia}/array/algo/IntArraySortTest.java (84%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/array/src/test/java/nu/marginalia}/array/algo/IntArrayTransformationsTest.java (89%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/array/src/test/java/nu/marginalia}/array/algo/LongArraySearchTest.java (94%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/array/src/test/java/nu/marginalia}/array/algo/LongArraySortTest.java (78%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/array/src/test/java/nu/marginalia}/array/algo/LongArrayTransformationsTest.java (89%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/array/src/test/java/nu/marginalia}/array/scheme/ArrayPartitioningSchemeTest.java (93%) rename {marginalia_nu => libraries/array}/src/test/java/nu/marginalia/util/test/TestUtil.java (69%) create mode 100644 libraries/btree/build.gradle create mode 100644 libraries/btree/src/main/java/nu/marginalia/btree/BTreeDogEar.java rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/btree/src/main/java/nu/marginalia}/btree/BTreeReader.java (69%) create mode 100644 libraries/btree/src/main/java/nu/marginalia/btree/BTreeWriteCallback.java rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/btree/src/main/java/nu/marginalia}/btree/BTreeWriter.java (64%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/btree/src/main/java/nu/marginalia}/btree/model/BTreeContext.java (63%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/btree/src/main/java/nu/marginalia}/btree/model/BTreeHeader.java (60%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/btree/src/test/java/nu/marginalia}/btree/BTreeWriterTest.java (56%) create mode 100644 libraries/language-processing/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/language-processing/src/main/java/nu/marginalia}/language/LanguageFilter.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/language-processing/src/main/java/nu/marginalia}/language/WordPatterns.java (99%) create mode 100644 libraries/language-processing/src/main/java/nu/marginalia/language/encoding/AsciiFlattener.java rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language/encoding}/HtmlTagCleaner.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language => libraries/language-processing/src/main/java/nu/marginalia/language/encoding}/UnicodeRanges.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language/keywords}/KeywordExtractor.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language}/model/DocumentLanguageData.java (68%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language}/model/DocumentSentence.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language}/model/KeywordMetadata.java (72%) create mode 100644 libraries/language-processing/src/main/java/nu/marginalia/language/model/WordFrequencyData.java rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language}/model/WordRep.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/tag => libraries/language-processing/src/main/java/nu/marginalia/language/model}/WordSeparator.java (66%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language}/model/WordSpan.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language}/sentence/SentenceExtractor.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language}/sentence/SentenceExtractorStringUtils.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/util/language/processing => libraries/language-processing/src/main/java/nu/marginalia/language}/sentence/SentenceSegmentSplitter.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query => libraries/language-processing/src/main/java/nu/marginalia/language/statistics}/EnglishDictionary.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict => libraries/language-processing/src/main/java/nu/marginalia/language/statistics}/NGramBloomFilter.java (61%) create mode 100644 libraries/language-processing/src/main/java/nu/marginalia/language/statistics/TermFrequencyDict.java rename {marginalia_nu => libraries/language-processing}/src/main/resources/dictionary/en-1000 (100%) rename {marginalia_nu => libraries/language-processing}/src/main/resources/dictionary/en-stopwords (100%) rename {marginalia_nu => libraries/language-processing}/src/main/resources/dictionary/en-words (100%) rename {marginalia_nu => libraries/language-processing}/src/main/resources/dictionary/latin-1000 (100%) rename {marginalia_nu => libraries/language-processing}/src/main/resources/dictionary/swe-1000 (100%) rename {marginalia_nu => libraries/language-processing}/src/main/resources/dictionary/word-frequency (100%) create mode 100644 libraries/language-processing/src/test/java/nu/marginalia/language/encoding/AsciiFlattenerTest.java create mode 100644 libraries/misc/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/misc/src/main/java/nu/marginalia}/bigstring/BigString.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/misc/src/main/java/nu/marginalia}/bigstring/CompressedBigString.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/misc/src/main/java/nu/marginalia}/bigstring/PlainBigString.java (92%) create mode 100644 libraries/misc/src/main/java/nu/marginalia/dict/DictionaryData.java create mode 100644 libraries/misc/src/main/java/nu/marginalia/dict/DictionaryDataBank.java rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/misc/src/main/java/nu/marginalia}/dict/DictionaryMap.java (80%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/misc/src/main/java/nu/marginalia}/dict/OffHeapDictionaryHashMap.java (76%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/misc/src/main/java/nu/marginalia}/dict/OnHeapDictionaryMap.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/misc/src/main/java/nu/marginalia}/gregex/GuardedRegex.java (73%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/misc/src/main/java/nu/marginalia}/gregex/GuardedRegexFactory.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/util => libraries/misc/src/main/java/nu/marginalia/lsh}/EasyLSH.java (81%) rename {marginalia_nu => libraries/misc}/src/main/java/nu/marginalia/util/FileSizeUtil.java (100%) rename {marginalia_nu => libraries/misc}/src/main/java/nu/marginalia/util/PrimeUtil.java (100%) rename {marginalia_nu => libraries/misc}/src/main/java/nu/marginalia/util/RandomWriteFunnel.java (96%) rename {marginalia_nu => libraries/misc}/src/main/java/nu/marginalia/util/TransformList.java (79%) rename {marginalia_nu/src/test/java/nu/marginalia/util => libraries/misc/src/test/java/nu/marginalia/lsh}/EasyLSHTest.java (93%) create mode 100644 libraries/misc/src/test/java/nu/marginalia/test/TestUtil.java rename {marginalia_nu => libraries/misc}/src/test/java/nu/marginalia/util/TransformListTest.java (81%) create mode 100644 libraries/readme.md delete mode 100644 marginalia_nu/data/.gitignore delete mode 100644 marginalia_nu/data/models/.gitignore delete mode 100644 marginalia_nu/data/test/.gitignore delete mode 100644 marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/E2ETestBase.java delete mode 100644 marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeCrawlBehaviorE2ETest.java delete mode 100644 marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeSearchE2ETest.java delete mode 100644 marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EncyclopediaE2ETest.java delete mode 100644 marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/MemexE2ETest.java delete mode 100644 marginalia_nu/src/e2e/resources/crawl-mock.sh delete mode 100644 marginalia_nu/src/e2e/resources/crawl.sh delete mode 100644 marginalia_nu/src/e2e/resources/init.sh delete mode 100644 marginalia_nu/src/e2e/resources/load-encyclopedia.sh delete mode 100644 marginalia_nu/src/e2e/resources/memex.sh delete mode 100644 marginalia_nu/src/e2e/resources/memex/index.gmi delete mode 100644 marginalia_nu/src/e2e/resources/memex/log/a.gmi delete mode 100644 marginalia_nu/src/e2e/resources/memex/log/b.gmi delete mode 100644 marginalia_nu/src/e2e/resources/memex/log/index.gmi delete mode 100644 marginalia_nu/src/e2e/resources/nginx/encyclopedia.conf delete mode 100644 marginalia_nu/src/e2e/resources/nginx/memex.conf delete mode 100644 marginalia_nu/src/e2e/resources/nginx/search.conf delete mode 100644 marginalia_nu/src/jmh/java/nu/marginalia/BitSetTest.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/SeekDictionary.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeDogEar.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeReaderIf.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/btree/WriteCallback.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/dict/DictionaryData.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/language/conf/LanguageModels.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/language/processing/AsciiFlattener.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordRef.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/tag/WordTag.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/util/tool/WikipediaInternalLinkExtractorMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/api/ApiMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResult.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/MainClass.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/ServiceDescriptor.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/Command.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ConvertCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/CrawlCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/IndexDataDumpCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ListCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/LoadCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ReindexCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/StartCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/VersionCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/ConfigurationModule.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Context.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/TermFrequencyDict.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiArticles.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiCleaner.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiSearchResult.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LinkKeywordExtractorMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LinkKeywordLoaderMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ReindexTriggerMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/atags/AnchorTextExtractor.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/RedirectCompiler.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/Interpreter.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/DomainLink.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/DocumentProcessor.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateEffortLevel.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicGuessFromHtmlStandard.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerTestMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDataStoreDao.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexControl.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexModule.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexTablesModule.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexClient.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexLocalService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexWriterClient.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/config/RankingSettingsEntry.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/IndexMetadataService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndexControl.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalEntryHeader.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalFileHeader.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalStatistics.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalCleaner.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReadEntry.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReader.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReaderSingleFile.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/writer/SearchIndexJournalWriter.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/writer/SearchIndexJournalWriterImpl.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexEntrySourceBehavior.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQueryIf.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/old/OldReversePageRankV2.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/old/StandardPageRank.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexDomainQueryService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexLexiconService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexOpsService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexQueryService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeOpsLockService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/arxiv/ArxivParser.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/arxiv/model/ArxivMetadata.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/model/BasicDocumentData.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostProcessor.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostsReader.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/model/StackOverflowPost.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/model/StackOverflowQuestionData.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaProcessor.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaReader.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/model/WikipediaArticle.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/WideHashable.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeIndexTask.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageContent.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageMetadata.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeRawPageContents.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeRobotsTxt.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeUrlVisit.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultsKey.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/IndexCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchParameters.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/ConvertCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SearchCommand.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryParser.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/model/EdgeUserSearchParameters.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/UrlDeduplicator.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchApiQueryService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchDomainSearchService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchWikiArticlesService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/EncyclopediaLoaderTool.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/FeaturesLoaderTool.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/IndexJournalDumpTool.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/SearchIndexScrubberMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/StripSimpleJournalEntriesToolMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaClient.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaDao.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaMain.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaModule.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaService.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/client/MemexApiClient.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererableDirect.java delete mode 100644 marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/StatusRendererService.java delete mode 100644 marginalia_nu/src/main/resources/data/smhi/stader.csv delete mode 100644 marginalia_nu/src/main/resources/fonts/LM-regular.ttf delete mode 100644 marginalia_nu/src/main/resources/fonts/STIXTwoMath-Regular.ttf delete mode 100644 marginalia_nu/src/main/resources/log4j2.properties delete mode 100644 marginalia_nu/src/main/resources/static/dating/index.html delete mode 100644 marginalia_nu/src/main/resources/static/dating/robots.txt delete mode 100644 marginalia_nu/src/main/resources/static/edge/index.html delete mode 100644 marginalia_nu/src/main/resources/static/encyclopedia/index.html delete mode 100644 marginalia_nu/src/main/resources/static/encyclopedia/robots.txt delete mode 100644 marginalia_nu/src/main/resources/static/encyclopedia/wiki-clean.html delete mode 100644 marginalia_nu/src/main/resources/static/encyclopedia/wiki-start.html delete mode 100644 marginalia_nu/src/main/resources/static/explore/style.css delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-bold-italic.ttf delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-bold-italic.woff delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-bold-italic.woff2 delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-bold.ttf delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-bold.woff delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-bold.woff2 delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-italic.ttf delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-italic.woff delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-italic.woff2 delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-regular.ttf delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-regular.woff delete mode 100644 marginalia_nu/src/main/resources/static/fonts/LM-regular.woff2 delete mode 100644 marginalia_nu/src/main/resources/static/smhi/favicon.ico delete mode 100644 marginalia_nu/src/main/resources/static/smhi/font.css delete mode 100644 marginalia_nu/src/main/resources/static/smhi/responsive.css delete mode 100644 marginalia_nu/src/main/resources/static/smhi/style.css delete mode 100644 marginalia_nu/src/main/resources/static/style.css delete mode 100644 marginalia_nu/src/main/resources/templates/encyclopedia/wiki-error.hdb delete mode 100644 marginalia_nu/src/main/resources/templates/encyclopedia/wiki-search.hdb delete mode 100644 marginalia_nu/src/main/resources/templates/smhi/index.hdb delete mode 100644 marginalia_nu/src/main/resources/templates/smhi/prognos.hdb delete mode 100644 marginalia_nu/src/main/resources/templates/status/server-status.hdb delete mode 100644 marginalia_nu/src/test/java/nu/marginalia/util/SeekDictionaryTest.java delete mode 100644 marginalia_nu/src/test/java/nu/marginalia/wmsa/configuration/HostsFileTest.java delete mode 100644 marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/suggest/SuggestionsTest.java delete mode 100644 marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/PrimeUtilTest.java delete mode 100644 marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/arxiv/ArxivParserTest.java delete mode 100644 marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostsTest.java delete mode 100644 marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaTest.java delete mode 100644 marginalia_nu/src/test/java/nu/marginalia/wmsa/podcasts/PodcastFetcherTest.java delete mode 100644 marginalia_nu/src/test/resources/log4j2.properties delete mode 100644 marginalia_nu/src/test/resources/model-data.json rename {marginalia_nu => other/memex}/build.gradle (94%) rename {marginalia_nu => other/memex}/lombok.config (100%) create mode 100644 other/memex/src/main/java/nu/marginalia/memex/MemexServiceDescriptors.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/auth/AuthConfigurationModule.java (69%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/auth/AuthMain.java (52%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/auth/AuthService.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/auth/client/AuthClient.java (65%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/auth/model/LoginFormModel.java (82%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/BadBotList.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/GeminiConfigurationModule.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/GeminiService.java (72%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/GeminiServiceDummy.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/GeminiServiceImpl.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/client/GeminiClient.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/Gemtext.java (78%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/GemtextDatabase.java (87%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/GemtextDocument.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/AbstractGemtextLine.java (84%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextAside.java (80%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextHeading.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextLineVisitor.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextLineVisitorAdapter.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextLink.java (82%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextList.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextPragma.java (80%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextPreformat.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextQuote.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextTask.java (76%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextText.java (80%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/line/GemtextTextLiteral.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/parser/GemtextAsideParser.java (72%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/parser/GemtextHeadingParser.java (66%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/parser/GemtextLinkParser.java (67%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/parser/GemtextListParser.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/parser/GemtextParser.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/parser/GemtextPragmaParser.java (62%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/parser/GemtextQuoteParser.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/parser/GemtextTaskParser.java (62%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/renderer/GemtextRenderer.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/gmi/renderer/GemtextRendererFactory.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/io/GeminiConnection.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/io/GeminiSSLSetUp.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/io/GeminiStatusCode.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/io/GeminiUserException.java (82%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/plugins/BareStaticPagePlugin.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/plugins/FileType.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/plugins/Plugin.java (72%) rename {marginalia_nu/src/main/java/nu/marginalia => other/memex/src/main/java/nu/marginalia/memex}/gemini/plugins/SearchPlugin.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/Memex.java (91%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/MemexConfigurationModule.java (88%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/MemexData.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/MemexLinks.java (92%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/MemexLoader.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/MemexMain.java (54%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/MemexService.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/GemtextAppend.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/GemtextCreate.java (72%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/GemtextCreateOrMutate.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/GemtextMutation.java (74%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/GemtextPrepend.java (84%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/GemtextReplace.java (84%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/GemtextTombstoneUpdateCaclulator.java (91%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/update/GemtextDocumentUpdateCalculator.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/update/GemtextTaskExtractor.java (81%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/change/update/GemtextTasksRewrite.java (91%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/GemtextSection.java (85%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/GemtextSectionAction.java (60%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexExternalUrl.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexImage.java (83%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexIndexTask.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexLink.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexNode.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexNodeHeadingId.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexNodeTaskId.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexNodeType.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexNodeUrl.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexTaskState.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexTaskTags.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/MemexUrl.java (90%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/fs/MemexDirectory.java (75%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/fs/MemexFileSystem.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/render/MemexRenderCreateFormModel.java (76%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/render/MemexRenderUpdateFormModel.java (71%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/render/MemexRenderUploadFormModel.java (76%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/render/MemexRendererDeleteFormModel.java (72%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/render/MemexRendererImageModel.java (77%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/render/MemexRendererIndexModel.java (82%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/render/MemexRendererRenameFormModel.java (72%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/render/MemexRendererTombstoneModel.java (66%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/model/render/MemexRendererViewModel.java (75%) create mode 100644 other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererableDirect.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/renderer/MemexGmiRenderer.java (95%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/renderer/MemexHtmlRenderer.java (82%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/renderer/MemexRendererers.java (84%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/system/MemexFileSystemModifiedTimes.java (93%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/system/MemexFileSystemMonitor.java (96%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/system/MemexFileWriter.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/system/MemexSourceFileSystem.java (94%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/system/git/MemexGitRepo.java (68%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/system/git/MemexGitRepoDummy.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => other/memex/src/main/java/nu/marginalia/memex}/memex/system/git/MemexGitRepoImpl.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/mustache => other/memex/src/main/java/nu/marginalia/memex/renderer}/MustacheRenderer.java (95%) create mode 100644 other/memex/src/main/java/nu/marginalia/memex/renderer/RendererFactory.java rename {marginalia_nu/src/main/java/nu/marginalia/util/graphics => other/memex/src/main/java/nu/marginalia/memex/util}/dithering/FloydSteinbergDither.java (99%) rename {marginalia_nu/src/main/java/nu/marginalia/util/graphics => other/memex/src/main/java/nu/marginalia/memex/util}/dithering/Palettes.java (92%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/dir.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/doc32.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/file.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/folder32.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/folderup16.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/nav16.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/pic16.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/pic32.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/root.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/shiba16.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/ico/world16.png (100%) rename {marginalia_nu => other/memex}/src/main/resources/static/memex/style-new.css (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/auth/login.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-create-form.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-delete-form.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-image.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-index-feed.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-index.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-rename-form.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-tombstone.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-update-form.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-upload-form.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/memex-view.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-backlinks-inline.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-backlinks.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-directories.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-documents-inline.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-documents.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-footer.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-head.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-images.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-task-listing.hdb (100%) rename {marginalia_nu => other/memex}/src/main/resources/templates/memex/partial/memex-topbar.hdb (100%) rename {marginalia_nu/src/test/java/nu/marginalia/gemini => other/memex/src/test/java/nu/marginalia}/gmi/GemtextDatabaseTest.java (88%) rename {marginalia_nu/src/test/java/nu/marginalia/gemini => other/memex/src/test/java/nu/marginalia}/gmi/GemtextDocumentTest.java (90%) rename {marginalia_nu/src/test/java/nu/marginalia/gemini => other/memex/src/test/java/nu/marginalia}/gmi/parser/GemtextTaskParserTest.java (78%) rename {marginalia_nu/src/test/java/nu/marginalia/util/graphics => other/memex/src/test/java/nu/marginalia/memex}/dithering/FloydSteinbergDitherTest.java (89%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa => other/memex/src/test/java/nu/marginalia/memex}/memex/MemexFileWriterTest.java (88%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa => other/memex/src/test/java/nu/marginalia/memex}/memex/MemexTest.java (78%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa => other/memex/src/test/java/nu/marginalia/memex}/memex/change/GemtextChangeTest.java (93%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa => other/memex/src/test/java/nu/marginalia/memex}/memex/change/GemtextTaskUpdateTest.java (90%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa => other/memex/src/test/java/nu/marginalia/memex}/memex/change/GemtextTombstoneUpdateCaclulatorTest.java (84%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa => other/memex/src/test/java/nu/marginalia/memex}/memex/model/MemexNodeHeadingIdTest.java (96%) create mode 100644 other/memex/src/test/java/nu/marginalia/util/test/TestUtil.java rename {third_party => other/wmsa_old}/build.gradle (53%) create mode 100644 other/wmsa_old/src/main/java/nu/marginalia/wmsa/WmsaHome.java rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/podcasts/PodcastFetcher.java (100%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperMain.java (58%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperService.java (92%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/podcasts/model/Podcast.java (100%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastEpisode.java (100%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastListing.java (100%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastMetadata.java (100%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastNewEpisodes.java (100%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/renderer/PodcastRendererService.java (96%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/renderer/RendererMain.java (63%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/renderer/RendererModule.java (100%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/renderer/RendererService.java (76%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/renderer/ServerStatusModel.java (100%) create mode 100644 other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/WmsaServiceDescriptors.java rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/renderer/client/RendererClient.java (73%) create mode 100644 other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/mustache/MustacheRenderer.java rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/renderer/mustache/RendererFactory.java (100%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/resource_store/ResourceEntityStore.java (98%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreClient.java (71%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreMain.java (60%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreModule.java (87%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreService.java (80%) rename {marginalia_nu => other/wmsa_old}/src/main/java/nu/marginalia/wmsa/resource_store/model/RenderedResource.java (100%) rename {marginalia_nu => other/wmsa_old}/src/main/resources/static/podcast/style.css (100%) rename {marginalia_nu => other/wmsa_old}/src/main/resources/templates/podcast/episode.hdb (100%) rename {marginalia_nu => other/wmsa_old}/src/main/resources/templates/podcast/listing.hdb (100%) rename {marginalia_nu => other/wmsa_old}/src/main/resources/templates/podcast/new.hdb (100%) rename {marginalia_nu => other/wmsa_old}/src/main/resources/templates/podcast/podcast.hdb (100%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa => other/wmsa_old/src/test/java/nu/marginalia}/resource_store/ResourceStoreServiceTest.java (87%) create mode 100644 run/.gitignore create mode 100755 run/reconvert.sh create mode 100644 services-core/assistant-service/build.gradle create mode 100644 services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantMain.java rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantModule.java => services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantModule.java (63%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantService.java => services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantService.java (70%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/assistant-service/src/main/java/nu/marginalia}/assistant/dict/DictionaryService.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/assistant-service/src/main/java/nu/marginalia}/assistant/dict/SpellChecker.java (87%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/assistant-service/src/main/java/nu/marginalia}/assistant/eval/MathParser.java (99%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/assistant-service/src/main/java/nu/marginalia}/assistant/eval/Unit.java (84%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/assistant-service/src/main/java/nu/marginalia}/assistant/eval/Units.java (98%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/assistant-service/src/main/java/nu/marginalia}/assistant/suggest/Suggestions.java (93%) rename {marginalia_nu => services-core/assistant-service}/src/main/resources/units.csv (100%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/assistant-service/src/test/java/nu/marginalia}/assistant/dict/WikiCleanerTest.java (98%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/assistant-service/src/test/java/nu/marginalia}/assistant/eval/MathParserTest.java (96%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/assistant-service/src/test/java/nu/marginalia}/assistant/eval/UnitsTest.java (96%) create mode 100644 services-core/index-service/build.gradle create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/IndexMain.java create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/IndexModule.java create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/IndexService.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/index-service/src/main/java/nu/marginalia}/index/IndexServicesFactory.java (68%) create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/IndexTablesModule.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/index-service/src/main/java/nu/marginalia}/index/config/RankingSettings.java (90%) create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/config/RankingSettingsEntry.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => services-core/index-service/src/main/java/nu/marginalia/index/index}/SearchIndex.java (75%) create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexQueryBuilder.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => services-core/index-service/src/main/java/nu/marginalia/index/index}/SearchIndexReader.java (60%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/EdgeIndexQuerySearchTerms.java => services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexSearchTerms.java (76%) create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/results/IndexMetadataService.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query => services-core/index-service/src/main/java/nu/marginalia/index/results}/IndexResultDomainDeduplicator.java (70%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings => services-core/index-service/src/main/java/nu/marginalia/index/results}/IndexResultValuator.java (84%) create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexOpsService.java create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexQueryService.java rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexSearchSetsService.java => services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexSearchSetsService.java (76%) create mode 100644 services-core/index-service/src/main/java/nu/marginalia/index/svc/SearchTermsService.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/index-service/src/main/java/nu/marginalia}/index/svc/searchset/RankingSearchSet.java (79%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/index-service/src/main/java/nu/marginalia}/index/svc/searchset/SearchSetAny.java (71%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/index-service/src/main/java/nu/marginalia}/index/svc/searchset/SmallSearchSet.java (85%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/index-service/src/test/java/nu/marginalia}/index/model/EdgePageDocumentsMetadataTest.java (94%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/index-service/src/test/java/nu/marginalia}/index/model/RankingSettingsTest.java (93%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/index-service/src/test/java/nu/marginalia}/index/service/EdgeIndexIntegrationTest.java (76%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/index-service/src/test/java/nu/marginalia}/index/service/EdgeIndexIntegrationTestModule.java (73%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/index-service/src/test/java/nu/marginalia}/index/service/util/DictionaryDataTest.java (91%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/index-service/src/test/java/nu/marginalia}/index/service/util/DictionaryHashMapTest.java (96%) create mode 100644 services-core/index-service/src/test/java/nu/marginalia/index/service/util/PrimeUtilTest.java rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/index-service/src/test/java/nu/marginalia}/index/service/util/RandomWriteFunnelTest.java (98%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/index-service/src/test/java/nu/marginalia}/index/svc/searchset/RankingSearchSetTest.java (83%) create mode 100644 services-core/index-service/src/test/java/nu/marginalia/index/util/TestUtil.java create mode 100644 services-core/readme.md create mode 100644 services-core/search-service/build.gradle create mode 100644 services-core/search-service/src/main/java/nu/marginalia/search/SearchMain.java rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchModule.java => services-core/search-service/src/main/java/nu/marginalia/search/SearchModule.java (53%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchOperator.java => services-core/search-service/src/main/java/nu/marginalia/search/SearchOperator.java (53%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchService.java => services-core/search-service/src/main/java/nu/marginalia/search/SearchService.java (61%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/command/CommandEvaluator.java (88%) create mode 100644 services-core/search-service/src/main/java/nu/marginalia/search/command/IndexCommand.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/command/SearchCommandInterface.java (60%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/command/SearchJsParameter.java (88%) create mode 100644 services-core/search-service/src/main/java/nu/marginalia/search/command/SearchParameters.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/command/commands/BangCommand.java (85%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/command/commands/BrowseCommand.java (61%) create mode 100644 services-core/search-service/src/main/java/nu/marginalia/search/command/commands/ConvertCommand.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/command/commands/DefinitionCommand.java (74%) create mode 100644 services-core/search-service/src/main/java/nu/marginalia/search/command/commands/SearchCommand.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/command/commands/SiteListCommand.java (66%) create mode 100644 services-core/search-service/src/main/java/nu/marginalia/search/db/DbUrlDetailsQuery.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/exceptions/RedirectException.java (84%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/model/DecoratedSearchResults.java (56%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/model/DomainInformation.java (82%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgePageScoreAdjustment.java => services-core/search-service/src/main/java/nu/marginalia/search/model/PageScoreAdjustment.java (71%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/EdgeSearchProfile.java => services-core/search-service/src/main/java/nu/marginalia/search/model/SearchProfile.java (80%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/EdgeSearchRankingSymbols.java => services-core/search-service/src/main/java/nu/marginalia/search/model/SearchRankingSymbols.java (92%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeUrlDetails.java => services-core/search-service/src/main/java/nu/marginalia/search/model/UrlDetails.java (91%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/query/NearQueryProcessor.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/query/QueryFactory.java (84%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/model/EdgeSearchQuery.java => services-core/search-service/src/main/java/nu/marginalia/search/query/model/SearchQuery.java (72%) create mode 100644 services-core/search-service/src/main/java/nu/marginalia/search/query/model/UserSearchParameters.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/results/BrowseResultCleaner.java (75%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/results/SearchResultDecorator.java (60%) create mode 100644 services-core/search-service/src/main/java/nu/marginalia/search/results/UrlDeduplicator.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/siteinfo/DomainInformationService.java (93%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchAddToCrawlQueueService.java => services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchAddToCrawlQueueService.java (66%) create mode 100644 services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchApiQueryService.java rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchErrorPageService.java => services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchErrorPageService.java (94%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchFlagSiteService.java => services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchFlagSiteService.java (91%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchQueryIndexService.java => services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryIndexService.java (61%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchQueryService.java => services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryService.java (67%) rename marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchUnitConversionService.java => services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchUnitConversionService.java (86%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-core/search-service/src/main/java/nu/marginalia}/search/valuation/SearchResultValuator.java (96%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/about.html (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/changelog.html (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/crawler-ips.txt (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/error.html (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/favicon.ico (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/known-issues.html (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/maintenance.html (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/notes.html (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/opensearch.xml (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/robots.txt (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/style-new.css (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/tts.js (100%) rename {marginalia_nu/src/main/resources/static/edge => services-core/search-service/src/main/resources/static/search}/wiki-clean.html (100%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/browse-result.hdb (100%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/browse-results.hdb (86%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/conversion-results.hdb (90%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/dictionary-results.hdb (92%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/error-page.hdb (100%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/index.hdb (86%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/indict/indict-form.hdb (95%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/parts/search-footer.hdb (100%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/parts/search-form.hdb (95%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/parts/search-header.hdb (100%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/parts/site-info-index.hdb (100%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/parts/site-info-links.hdb (100%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/search-result-metadata.hdb (100%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/search-result.hdb (92%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/search-results.hdb (89%) rename {marginalia_nu/src/main/resources/templates/edge => services-core/search-service/src/main/resources/templates/search}/site-info.hdb (74%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/search-service/src/test/java/nu/marginalia}/search/command/commands/BangCommandTest.java (90%) rename {marginalia_nu/src/test/java/nu/marginalia/wmsa/edge => services-core/search-service/src/test/java/nu/marginalia}/search/query/QueryFactoryTest.java (82%) create mode 100644 services-core/search-service/src/test/java/nu/marginalia/util/TestLanguageModels.java create mode 100644 services-satellite/api-service/build.gradle create mode 100644 services-satellite/api-service/src/main/java/nu/marginalia/api/ApiMain.java rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => services-satellite/api-service/src/main/java/nu/marginalia}/api/ApiService.java (89%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa => services-satellite/api-service/src/main/java/nu/marginalia}/api/model/ApiLicense.java (89%) create mode 100644 services-satellite/dating-service/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-satellite/dating-service/src/main/java/nu/marginalia}/dating/DatingMain.java (58%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-satellite/dating-service/src/main/java/nu/marginalia}/dating/DatingModule.java (70%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-satellite/dating-service/src/main/java/nu/marginalia}/dating/DatingService.java (82%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-satellite/dating-service/src/main/java/nu/marginalia}/dating/DatingSessionObject.java (72%) create mode 100644 services-satellite/dating-service/src/main/resources/static/dating/index.html create mode 100644 services-satellite/dating-service/src/main/resources/static/dating/robots.txt rename {marginalia_nu => services-satellite/dating-service}/src/main/resources/templates/dating/dating-view.hdb (100%) create mode 100644 services-satellite/explorer-service/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-satellite/explorer-service/src/main/java/nu/marginalia}/explorer/ExplorerMain.java (57%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge => services-satellite/explorer-service/src/main/java/nu/marginalia}/explorer/ExplorerService.java (95%) create mode 100644 services-satellite/explorer-service/src/main/resources/static/explore/style.css rename {marginalia_nu => services-satellite/explorer-service}/src/main/resources/templates/explorer/explorer-about.hdb (100%) rename {marginalia_nu => services-satellite/explorer-service}/src/main/resources/templates/explorer/explorer-messages.hdb (100%) rename {marginalia_nu => services-satellite/explorer-service}/src/main/resources/templates/explorer/explorer-results.hdb (100%) rename {marginalia_nu => services-satellite/explorer-service}/src/main/resources/templates/explorer/explorer-search.hdb (100%) rename {marginalia_nu => services-satellite/explorer-service}/src/main/resources/templates/explorer/explorer.hdb (100%) create mode 100644 services-satellite/readme.md rename {third_party => third-party}/README.md (92%) create mode 100644 third-party/build.gradle rename {third_party => third-party}/src/main/java/ca/rmen/porterstemmer/PorterStemmer.java (100%) rename {third_party => third-party}/src/main/java/com/github/datquocnguyen/FWObject.java (100%) rename {third_party => third-party}/src/main/java/com/github/datquocnguyen/InitialTagger.java (100%) rename {third_party => third-party}/src/main/java/com/github/datquocnguyen/Node.java (100%) rename {third_party => third-party}/src/main/java/com/github/datquocnguyen/RDRPOSTagger.java (100%) rename {third_party => third-party}/src/main/java/com/github/datquocnguyen/Utils.java (100%) rename {third_party => third-party}/src/main/java/com/github/datquocnguyen/WordTag.java (100%) rename {third_party => third-party}/src/main/java/com/google/gson/stream/JsonReader.java (100%) rename {third_party => third-party}/src/main/java/com/upserve/uppend/blobs/NativeIO.java (100%) rename {third_party => third-party}/src/main/java/jdkoverride/LargeLineBufferedReader.java (100%) rename {third_party => third-party}/src/main/java/opennlp/tools/sentdetect/DefaultSDContextGenerator.java (99%) rename {third_party => third-party}/src/main/java/opennlp/tools/sentdetect/SentenceDetectorME.java (98%) rename {third_party => third-party}/src/main/java/org/openzim/ZIMTypes/ArticleEntry.java (100%) rename {third_party => third-party}/src/main/java/org/openzim/ZIMTypes/DirectoryEntry.java (100%) rename {third_party => third-party}/src/main/java/org/openzim/ZIMTypes/RedirectEntry.java (100%) rename {third_party => third-party}/src/main/java/org/openzim/ZIMTypes/ZIMFile.java (100%) rename {third_party => third-party}/src/main/java/org/openzim/ZIMTypes/ZIMReader.java (98%) rename {third_party => third-party}/src/main/java/org/openzim/util/RandomAcessFileZIMInputStream.java (100%) rename {third_party => third-party}/src/main/java/org/openzim/util/Utilities.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/BlockInputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/BlockOutputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/CorruptedInputException.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/CountingInputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/CountingOutputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/DeltaCoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/DeltaDecoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/DeltaInputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/FilterCoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/FilterDecoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/FilterEncoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/FilterOptions.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/FinishableOutputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/IndexIndicatorException.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/LZMA2Coder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/LZMA2Decoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/LZMA2Encoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/LZMA2InputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/LZMA2Options.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/LZMA2OutputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/MemoryLimitException.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/RawCoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/SingleXZInputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/UnsupportedOptionsException.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/XZ.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/XZFormatException.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/XZIOException.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/XZInputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/XZOutputStream.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/check/CRC32.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/check/CRC64.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/check/Check.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/check/None.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/check/SHA256.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/common/DecoderUtil.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/common/EncoderUtil.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/common/StreamFlags.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/common/Util.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/delta/DeltaCoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/delta/DeltaDecoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/index/IndexBase.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/index/IndexEncoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/index/IndexHash.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/index/IndexRecord.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/lz/LZDecoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/lzma/LZMACoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/lzma/LZMADecoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/lzma/State.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/package-info.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/rangecoder/RangeCoder.java (100%) rename {third_party => third-party}/src/main/java/org/tukaani/xz/rangecoder/RangeDecoder.java (100%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict => third-party/src/main/java/symspell}/SymSpell.java (99%) create mode 100644 tools/screenshot/build.gradle rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools => tools/screenshot/src/main/java/nu/marginalia/screenshot}/ScreenshotCaptureToolMain.java (97%) rename {marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant => tools/screenshot/src/main/java/nu/marginalia}/screenshot/ScreenshotLoaderMain.java (92%) diff --git a/.gitignore b/.gitignore index 5e039277..a970eb30 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ build/ *~ .gradle/ .idea/ +lombok.config +Dockerfile +run diff --git a/README.md b/README.md index 91f9f67e..ba8bf42b 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,38 @@ # marginalia.nu -This is the source code for marginalia.nu, including the [search engine](https://search.marginalia.nu), -the [MEMEX/gemini server](https://memex.marginalia.nu), the and the [encyclopedia service](https://encyclopedia.marginalia.nu). +This is the source code for [Marginalia Search](https://search.marginalia.nu). The aim of the project is to develop new and alternative discovery methods for the Internet. It's an experimental workshop as much as it is a public service, the overarching goal is to elevate the more human, non-commercial sides of the Internet. A side-goal is to do this without requiring datacenters and expensive enterprise hardware, to run this operation on affordable hardware. -The canonical git server for this project is [https://git.marginalia.nu](https://git.marginalia.nu). -It is fine to mirror it on other hosts, but if you have issues or questions -git.marginalia.nu is where you want to go. +## Set up instructions -## Important note about wmsa.local +For local development, you're strongly encouraged to use docker or podman. +From a fresh to running system, you'll need to do this: -This project has a [sister repository called wmsa.local](https://git.marginalia.nu/marginalia/wmsa.local) -that contains scripts and configuration files for running and developing the code. +``` +$ ./gradlew assemble -Without it, development is very unpleasant. +$ ./gradlew docker -While developing the code, you will want an environment variable WMSA_HOME pointing to -the directory in which wmsa.local is checked out, otherwise the code will not run and -several tests will fail. +$ vim run/settings.profile + +(follow instructions in file) + +$ run/setup.sh + +$ run/reconvert.sh + +$ docker-compose up +``` + +Wait a moment and check out [https://localhost:8080](https://localhost:8080). ## Documentation -Documentation is a work in progress. See the [wiki](https://git.marginalia.nu/marginalia/marginalia.nu/wiki). +Documentation is a work in progress. ## Contributing diff --git a/api/assistant-api/build.gradle b/api/assistant-api/build.gradle new file mode 100644 index 00000000..9ec260b1 --- /dev/null +++ b/api/assistant-api/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:config') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.guice + implementation libs.rxjava + implementation libs.gson + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/client/AssistantClient.java b/api/assistant-api/src/main/java/nu/marginalia/assistant/client/AssistantClient.java similarity index 56% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/client/AssistantClient.java rename to api/assistant-api/src/main/java/nu/marginalia/assistant/client/AssistantClient.java index 63f8e255..94677317 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/client/AssistantClient.java +++ b/api/assistant-api/src/main/java/nu/marginalia/assistant/client/AssistantClient.java @@ -1,28 +1,32 @@ -package nu.marginalia.wmsa.edge.assistant.client; +package nu.marginalia.assistant.client; import com.google.inject.Inject; import com.google.inject.Singleton; import io.reactivex.rxjava3.core.Observable; -import nu.marginalia.wmsa.client.AbstractDynamicClient; -import nu.marginalia.wmsa.client.exception.RouteNotConfiguredException; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.assistant.dict.DictionaryResponse; -import org.eclipse.jetty.util.UrlEncoded; +import nu.marginalia.assistant.client.model.DictionaryResponse; +import nu.marginalia.client.AbstractDynamicClient; +import nu.marginalia.client.exception.RouteNotConfiguredException; +import nu.marginalia.WmsaHome; +import nu.marginalia.model.gson.GsonFactory; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.client.Context; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.List; @Singleton public class AssistantClient extends AbstractDynamicClient { @Inject - public AssistantClient() { - super(ServiceDescriptor.EDGE_ASSISTANT); + public AssistantClient(ServiceDescriptors descriptors) { + super(descriptors.forId(ServiceId.Assistant), WmsaHome.getHostsFile(), GsonFactory::get); } public Observable dictionaryLookup(Context ctx, String word) { try { - return super.get(ctx, "/dictionary/" + UrlEncoded.encodeString(word), DictionaryResponse.class); + return super.get(ctx, "/dictionary/" + URLEncoder.encode(word, StandardCharsets.UTF_8), DictionaryResponse.class); } catch (RouteNotConfiguredException ex) { return Observable.empty(); @@ -32,7 +36,7 @@ public class AssistantClient extends AbstractDynamicClient { @SuppressWarnings("unchecked") public Observable> spellCheck(Context ctx, String word) { try { - return (Observable>) (Object) super.get(ctx, "/spell-check/" + UrlEncoded.encodeString(word), List.class); + return (Observable>) (Object) super.get(ctx, "/spell-check/" + URLEncoder.encode(word, StandardCharsets.UTF_8), List.class); } catch (RouteNotConfiguredException ex) { return Observable.empty(); @@ -49,7 +53,7 @@ public class AssistantClient extends AbstractDynamicClient { public Observable evalMath(Context ctx, String expression) { try { - return super.get(ctx, "/eval-expression?value=" + UrlEncoded.encodeString(expression)); + return super.get(ctx, "/eval-expression?value=" + URLEncoder.encode(expression, StandardCharsets.UTF_8)); } catch (RouteNotConfiguredException ex) { return Observable.empty(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/DictionaryEntry.java b/api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/DictionaryEntry.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/DictionaryEntry.java rename to api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/DictionaryEntry.java index 88c48986..c40ea97f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/DictionaryEntry.java +++ b/api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/DictionaryEntry.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.assistant.dict; +package nu.marginalia.assistant.client.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/DictionaryResponse.java b/api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/DictionaryResponse.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/DictionaryResponse.java rename to api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/DictionaryResponse.java index 624782a6..03fbd2e6 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/DictionaryResponse.java +++ b/api/assistant-api/src/main/java/nu/marginalia/assistant/client/model/DictionaryResponse.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.assistant.dict; +package nu.marginalia.assistant.client.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/api/index-api/build.gradle b/api/index-api/build.gradle new file mode 100644 index 00000000..35616eee --- /dev/null +++ b/api/index-api/build.gradle @@ -0,0 +1,48 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:config') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + + implementation project(':index:index-query') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.guice + implementation libs.rxjava + implementation libs.protobuf + implementation libs.gson + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} \ No newline at end of file diff --git a/api/index-api/src/main/java/nu/marginalia/index/client/EdgeIndexClient.java b/api/index-api/src/main/java/nu/marginalia/index/client/EdgeIndexClient.java new file mode 100644 index 00000000..5ed45885 --- /dev/null +++ b/api/index-api/src/main/java/nu/marginalia/index/client/EdgeIndexClient.java @@ -0,0 +1,45 @@ +package nu.marginalia.index.client; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import io.prometheus.client.Summary; +import io.reactivex.rxjava3.core.Observable; +import nu.marginalia.WmsaHome; +import nu.marginalia.client.AbstractDynamicClient; +import nu.marginalia.client.Context; +import nu.marginalia.index.client.model.query.EdgeSearchSpecification; +import nu.marginalia.index.client.model.results.EdgeSearchResultItem; +import nu.marginalia.index.client.model.results.EdgeSearchResultSet; +import nu.marginalia.model.gson.GsonFactory; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; + +import javax.annotation.CheckReturnValue; +import java.util.List; + +@Singleton +public class EdgeIndexClient extends AbstractDynamicClient { + + private static final Summary wmsa_search_index_api_time = Summary.build().name("wmsa_search_index_api_time").help("-").register(); + + @Inject + public EdgeIndexClient(ServiceDescriptors descriptors) { + super(descriptors.forId(ServiceId.Index), WmsaHome.getHostsFile(), GsonFactory::get); + + setTimeout(30); + } + + @CheckReturnValue + public List query(Context ctx, EdgeSearchSpecification specs) { + return wmsa_search_index_api_time.time( + () -> this.postGet(ctx, "/search/", specs, EdgeSearchResultSet.class).blockingFirst().getResults() + ); + } + + + @CheckReturnValue + public Observable isBlocked(Context ctx) { + return super.get(ctx, "/is-blocked", Boolean.class); + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/EdgeDomainSearchResults.java b/api/index-api/src/main/java/nu/marginalia/index/client/model/domain/EdgeDomainSearchResults.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/EdgeDomainSearchResults.java rename to api/index-api/src/main/java/nu/marginalia/index/client/model/domain/EdgeDomainSearchResults.java index e9079bb7..0b994f81 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/EdgeDomainSearchResults.java +++ b/api/index-api/src/main/java/nu/marginalia/index/client/model/domain/EdgeDomainSearchResults.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.model.search.domain; +package nu.marginalia.index.client.model.domain; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeIdList; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.id.EdgeIdList; @AllArgsConstructor @Getter @ToString public class EdgeDomainSearchResults { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/EdgeDomainSearchSpecification.java b/api/index-api/src/main/java/nu/marginalia/index/client/model/domain/EdgeDomainSearchSpecification.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/EdgeDomainSearchSpecification.java rename to api/index-api/src/main/java/nu/marginalia/index/client/model/domain/EdgeDomainSearchSpecification.java index 2c4738f9..29748632 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/EdgeDomainSearchSpecification.java +++ b/api/index-api/src/main/java/nu/marginalia/index/client/model/domain/EdgeDomainSearchSpecification.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.search.domain; +package nu.marginalia.index.client.model.domain; import lombok.AllArgsConstructor; import lombok.ToString; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchSpecification.java b/api/index-api/src/main/java/nu/marginalia/index/client/model/query/EdgeSearchSpecification.java similarity index 65% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchSpecification.java rename to api/index-api/src/main/java/nu/marginalia/index/client/model/query/EdgeSearchSpecification.java index 84f133d7..bfafb75b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchSpecification.java +++ b/api/index-api/src/main/java/nu/marginalia/index/client/model/query/EdgeSearchSpecification.java @@ -1,10 +1,9 @@ -package nu.marginalia.wmsa.edge.model.search; +package nu.marginalia.index.client.model.query; import lombok.*; -import nu.marginalia.wmsa.edge.index.model.QueryLimits; -import nu.marginalia.wmsa.edge.index.model.QueryStrategy; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSetIdentifier; -import nu.marginalia.wmsa.edge.model.search.domain.SpecificationLimit; +import nu.marginalia.index.query.limit.QueryLimits; +import nu.marginalia.index.query.limit.QueryStrategy; +import nu.marginalia.index.query.limit.SpecificationLimit; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchSubquery.java b/api/index-api/src/main/java/nu/marginalia/index/client/model/query/EdgeSearchSubquery.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchSubquery.java rename to api/index-api/src/main/java/nu/marginalia/index/client/model/query/EdgeSearchSubquery.java index b171c5a3..33416001 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchSubquery.java +++ b/api/index-api/src/main/java/nu/marginalia/index/client/model/query/EdgeSearchSubquery.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.search; +package nu.marginalia.index.client.model.query; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SearchSetIdentifier.java b/api/index-api/src/main/java/nu/marginalia/index/client/model/query/SearchSetIdentifier.java similarity index 64% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SearchSetIdentifier.java rename to api/index-api/src/main/java/nu/marginalia/index/client/model/query/SearchSetIdentifier.java index 040cd32c..aca5c291 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SearchSetIdentifier.java +++ b/api/index-api/src/main/java/nu/marginalia/index/client/model/query/SearchSetIdentifier.java @@ -1,6 +1,4 @@ -package nu.marginalia.wmsa.edge.index.svc.searchset; - -import nu.marginalia.wmsa.edge.search.model.EdgeSearchProfile; +package nu.marginalia.index.client.model.query; /** Identifies a RankingSearchSet, associated with an EdgeSearchProfile * diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultItem.java b/api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResultItem.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultItem.java rename to api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResultItem.java index 517c1975..e9f1e1be 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultItem.java +++ b/api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResultItem.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.model.search; +package nu.marginalia.index.client.model.results; import lombok.AllArgsConstructor; import lombok.Getter; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeId; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.id.EdgeId; import java.util.ArrayList; import java.util.List; @@ -41,7 +41,7 @@ public class EdgeSearchResultItem { return scoreValue; } - private transient int domainId = 0; + private transient int domainId = Integer.MIN_VALUE; public void setDomainId(int domainId) { this.domainId = domainId; } @@ -69,12 +69,12 @@ public class EdgeSearchResultItem { } public long deduplicationKey() { - final int ranking = getDomainId(); + final int domainId = getDomainId(); - if (ranking == Integer.MAX_VALUE || ranking == Integer.MIN_VALUE) { + if (domainId == Integer.MAX_VALUE || domainId == Integer.MIN_VALUE) { return 0; } - return ranking; + return domainId; } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultKeywordScore.java b/api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResultKeywordScore.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultKeywordScore.java rename to api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResultKeywordScore.java index 6d97192c..0fed4a7f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultKeywordScore.java +++ b/api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResultKeywordScore.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.model.search; +package nu.marginalia.index.client.model.results; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentFlags; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordMetadata; +import nu.marginalia.model.crawl.EdgePageWordFlags; +import nu.marginalia.model.idx.EdgePageWordMetadata; +import nu.marginalia.model.crawl.EdgePageDocumentFlags; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; import static java.lang.Integer.lowestOneBit; import static java.lang.Integer.numberOfTrailingZeros; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultSet.java b/api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResultSet.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultSet.java rename to api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResultSet.java index be12ab31..e69fd34d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultSet.java +++ b/api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResultSet.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.search; +package nu.marginalia.index.client.model.results; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResults.java b/api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResults.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResults.java rename to api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResults.java index 00b51df4..2e54a25c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResults.java +++ b/api/index-api/src/main/java/nu/marginalia/index/client/model/results/EdgeSearchResults.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.search; +package nu.marginalia.index.client.model.results; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/api/readme.md b/api/readme.md new file mode 100644 index 00000000..a49ddd63 --- /dev/null +++ b/api/readme.md @@ -0,0 +1,7 @@ +# Core Service Clients + +These are clients for the [core services](../services-core/), along with what models +are necessary for speaking to them. + +All that is necessary is to `@Inject` them into the constructor and then +requests can be sent. \ No newline at end of file diff --git a/api/search-api/build.gradle b/api/search-api/build.gradle new file mode 100644 index 00000000..613cea82 --- /dev/null +++ b/api/search-api/build.gradle @@ -0,0 +1,46 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:config') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.guice + implementation libs.rxjava + implementation libs.gson + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/client/EdgeSearchClient.java b/api/search-api/src/main/java/nu/marginalia/search/client/EdgeSearchClient.java similarity index 56% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/client/EdgeSearchClient.java rename to api/search-api/src/main/java/nu/marginalia/search/client/EdgeSearchClient.java index 3e38c3f6..c643cbe5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/client/EdgeSearchClient.java +++ b/api/search-api/src/main/java/nu/marginalia/search/client/EdgeSearchClient.java @@ -1,11 +1,15 @@ -package nu.marginalia.wmsa.edge.search.client; +package nu.marginalia.search.client; +import com.google.inject.Inject; import com.google.inject.Singleton; import io.reactivex.rxjava3.core.Observable; -import nu.marginalia.wmsa.api.model.ApiSearchResults; -import nu.marginalia.wmsa.client.AbstractDynamicClient; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.server.Context; +import nu.marginalia.client.AbstractDynamicClient; +import nu.marginalia.model.gson.GsonFactory; +import nu.marginalia.search.client.model.ApiSearchResults; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.WmsaHome; +import nu.marginalia.client.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,8 +21,9 @@ import java.nio.charset.StandardCharsets; public class EdgeSearchClient extends AbstractDynamicClient { private final Logger logger = LoggerFactory.getLogger(getClass()); - public EdgeSearchClient() { - super(ServiceDescriptor.EDGE_SEARCH); + @Inject + public EdgeSearchClient(ServiceDescriptors descriptors) { + super(descriptors.forId(ServiceId.Search), WmsaHome.getHostsFile(), GsonFactory::get); } @CheckReturnValue diff --git a/api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResult.java b/api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResult.java new file mode 100644 index 00000000..bedc3046 --- /dev/null +++ b/api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResult.java @@ -0,0 +1,18 @@ +package nu.marginalia.search.client.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@AllArgsConstructor @Getter +public class ApiSearchResult { + public String url; + public String title; + public String description; + public double quality; + + public List> details = new ArrayList<>(); + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResultQueryDetails.java b/api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResultQueryDetails.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResultQueryDetails.java rename to api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResultQueryDetails.java index ad146ca8..ee13c219 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResultQueryDetails.java +++ b/api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResultQueryDetails.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.api.model; +package nu.marginalia.search.client.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResults.java b/api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResults.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResults.java rename to api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResults.java index db696c73..688e9e91 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResults.java +++ b/api/search-api/src/main/java/nu/marginalia/search/client/model/ApiSearchResults.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.api.model; +package nu.marginalia.search.client.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/build.gradle b/build.gradle index ffe47e69..d6740cf8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,42 +1,16 @@ plugins { id 'java' - - id 'com.github.johnrengelman.shadow' version '6.0.0' } -group 'nu.marginalia' +group 'marginalia' version 'SNAPSHOT' + compileJava.options.encoding = "UTF-8" compileTestJava.options.encoding = "UTF-8" -repositories { - mavenLocal() - maven { url "https://artifactory.cronapp.io/public-release/" } - maven { url "https://repo1.maven.org/maven2/" } - maven { url "https://www2.ph.ed.ac.uk/maven2/" } - maven { url "https://jitpack.io/" } - exclusiveContent { - forRepository { - maven { - url = uri("https://jitpack.io") - } - } - filter { - // Only use JitPack for the `gson-record-type-adapter-factory` library - includeModule("com.github.Marcono1234", "gson-record-type-adapter-factory") - } - } -} -shadowJar { - zip64 true -} -jar { - manifest { - attributes 'Main-Class': "nu.marginalia.wmsa.configuration.ServiceDescriptor" - } - from { - configurations.shadow.collect { it.isDirectory() ? it : zipTree(it) } - } +task dist(type: Copy) { + from subprojects.collect { it.tasks.withType(Tar) } + into "$buildDir/dist" } java { @@ -44,19 +18,3 @@ java { languageVersion.set(JavaLanguageVersion.of(17)) } } - -dependencies { - implementation project(':marginalia_nu') -} -task version() { // -} - -test { - maxParallelForks = 16 - forkEvery = 1 - maxHeapSize = "8G" - useJUnitPlatform { - excludeTags "nobuild" - } -} - diff --git a/common/config/build.gradle b/common/config/build.gradle new file mode 100644 index 00000000..211a3465 --- /dev/null +++ b/common/config/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':libraries:misc') +} + +test { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + maxHeapSize = "8G" + useJUnitPlatform() +} + +task fastTests(type: Test) { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + maxHeapSize = "8G" + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/common/config/src/main/java/nu/marginalia/LanguageModels.java b/common/config/src/main/java/nu/marginalia/LanguageModels.java new file mode 100644 index 00000000..0220e7a2 --- /dev/null +++ b/common/config/src/main/java/nu/marginalia/LanguageModels.java @@ -0,0 +1,22 @@ +package nu.marginalia; + +import java.nio.file.Path; + +public class LanguageModels { + public final Path ngramBloomFilter; + public final Path termFrequencies; + + public final Path openNLPSentenceDetectionData; + public final Path posRules; + public final Path posDict; + public final Path openNLPTokenData; + + public LanguageModels(Path ngramBloomFilter, Path termFrequencies, Path openNLPSentenceDetectionData, Path posRules, Path posDict, Path openNLPTokenData) { + this.ngramBloomFilter = ngramBloomFilter; + this.termFrequencies = termFrequencies; + this.openNLPSentenceDetectionData = openNLPSentenceDetectionData; + this.posRules = posRules; + this.posDict = posDict; + this.openNLPTokenData = openNLPTokenData; + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/UserAgent.java b/common/config/src/main/java/nu/marginalia/UserAgent.java similarity index 52% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/UserAgent.java rename to common/config/src/main/java/nu/marginalia/UserAgent.java index d4dfd54c..75c1aa7d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/UserAgent.java +++ b/common/config/src/main/java/nu/marginalia/UserAgent.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.configuration; +package nu.marginalia; public record UserAgent(String uaString) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/WebsiteUrl.java b/common/config/src/main/java/nu/marginalia/WebsiteUrl.java similarity index 73% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/WebsiteUrl.java rename to common/config/src/main/java/nu/marginalia/WebsiteUrl.java index 8e3f8c4c..5772cc56 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/WebsiteUrl.java +++ b/common/config/src/main/java/nu/marginalia/WebsiteUrl.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.configuration; +package nu.marginalia; public record WebsiteUrl(String url) { public String withPath(String path) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/WmsaHome.java b/common/config/src/main/java/nu/marginalia/WmsaHome.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/WmsaHome.java rename to common/config/src/main/java/nu/marginalia/WmsaHome.java index bbf7ccbc..6c9eb537 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/WmsaHome.java +++ b/common/config/src/main/java/nu/marginalia/WmsaHome.java @@ -1,6 +1,7 @@ -package nu.marginalia.wmsa.configuration; +package nu.marginalia; -import nu.marginalia.util.language.conf.LanguageModels; + +import nu.marginalia.service.descriptor.HostsFile; import java.io.FileNotFoundException; import java.io.IOException; diff --git a/common/model/build.gradle b/common/model/build.gradle new file mode 100644 index 00000000..072227e1 --- /dev/null +++ b/common/model/build.gradle @@ -0,0 +1,53 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':libraries:misc') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.guice + implementation libs.bundles.gson + + implementation libs.notnull + + implementation libs.commons.lang3 + + implementation libs.trove + implementation libs.fastutil + + implementation libs.rxjava + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + maxHeapSize = "8G" + useJUnitPlatform() +} + +task fastTests(type: Test) { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + maxHeapSize = "8G" + useJUnitPlatform { + excludeTags "slow" + } +} + diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/EdgeDomain.java b/common/model/src/main/java/nu/marginalia/model/EdgeDomain.java similarity index 99% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/EdgeDomain.java rename to common/model/src/main/java/nu/marginalia/model/EdgeDomain.java index 79e65476..96a44718 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/EdgeDomain.java +++ b/common/model/src/main/java/nu/marginalia/model/EdgeDomain.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model; +package nu.marginalia.model; import lombok.*; @@ -10,8 +10,6 @@ import java.util.regex.Pattern; @AllArgsConstructor @Getter @Setter @Builder public class EdgeDomain { - - @Nonnull public final String subDomain; @Nonnull @@ -109,6 +107,7 @@ public class EdgeDomain { } return domain.substring(0, cutPoint).toLowerCase(); } + public String getLongDomainKey() { StringBuilder ret = new StringBuilder(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/EdgeUrl.java b/common/model/src/main/java/nu/marginalia/model/EdgeUrl.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/EdgeUrl.java rename to common/model/src/main/java/nu/marginalia/model/EdgeUrl.java index 84246c4e..7c5f3df0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/EdgeUrl.java +++ b/common/model/src/main/java/nu/marginalia/model/EdgeUrl.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.model; +package nu.marginalia.model; import lombok.Builder; import lombok.Getter; import lombok.Setter; -import nu.marginalia.wmsa.edge.converting.processor.logic.QueryParams; +import nu.marginalia.util.QueryParams; import java.net.URI; import java.net.URISyntaxException; @@ -12,7 +12,7 @@ import java.util.Optional; import java.util.regex.Pattern; @Getter @Setter @Builder -public class EdgeUrl implements WideHashable { +public class EdgeUrl { public final String proto; public final EdgeDomain domain; public final Integer port; @@ -158,12 +158,6 @@ public class EdgeUrl implements WideHashable { return path.replaceAll(".*/", ""); } - public long wideHash() { - long domainHash = domain.hashCode(); - long thisHash = hashCode(); - return (domainHash << 32) | thisHash; - } - public int depth() { return (int) path.chars().filter(c -> c=='/').count(); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/DocumentKeywords.java b/common/model/src/main/java/nu/marginalia/model/crawl/DocumentKeywords.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/DocumentKeywords.java rename to common/model/src/main/java/nu/marginalia/model/crawl/DocumentKeywords.java index 7faefb2e..ce154aa5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/DocumentKeywords.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/DocumentKeywords.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.model.crawl; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordMetadata; -import nu.marginalia.wmsa.edge.model.crawl.EdgePageWords; + +import nu.marginalia.model.idx.EdgePageWordMetadata; import java.util.Arrays; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeContentType.java b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeContentType.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeContentType.java rename to common/model/src/main/java/nu/marginalia/model/crawl/EdgeContentType.java index 70978166..4d447038 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeContentType.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeContentType.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.crawl; +package nu.marginalia.model.crawl; import lombok.*; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeDomainIndexingState.java b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeDomainIndexingState.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeDomainIndexingState.java rename to common/model/src/main/java/nu/marginalia/model/crawl/EdgeDomainIndexingState.java index 6a57a871..448641fb 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeDomainIndexingState.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeDomainIndexingState.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.crawl; +package nu.marginalia.model.crawl; public enum EdgeDomainIndexingState { ACTIVE("Active"), diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeDomainLink.java b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeDomainLink.java similarity index 68% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeDomainLink.java rename to common/model/src/main/java/nu/marginalia/model/crawl/EdgeDomainLink.java index 7486fec3..b66064bf 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeDomainLink.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeDomainLink.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.model.crawl; +package nu.marginalia.model.crawl; import lombok.*; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.model.EdgeDomain; @AllArgsConstructor @EqualsAndHashCode @Getter @Setter @Builder @ToString public class EdgeDomainLink { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeHtmlStandard.java b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeHtmlStandard.java similarity index 71% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeHtmlStandard.java rename to common/model/src/main/java/nu/marginalia/model/crawl/EdgeHtmlStandard.java index dd3d0cec..17788cca 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeHtmlStandard.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeHtmlStandard.java @@ -1,4 +1,5 @@ -package nu.marginalia.wmsa.edge.model.crawl; +package nu.marginalia.model.crawl; + public enum EdgeHtmlStandard { PLAIN(0, 1, 1993), @@ -8,9 +9,13 @@ public enum EdgeHtmlStandard { XHTML(-0.1, 1.05, 2006), HTML5(0.5, 1.1, 2018); + /** Used to tune quality score */ public final double offset; + /** Used to tune quality score */ public final double scale; + /** This parameter is used to bias publish date heuristics + * */ public final int yearGuess; EdgeHtmlStandard(double offset, double scale, int yearGuess) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageDocumentFlags.java b/common/model/src/main/java/nu/marginalia/model/crawl/EdgePageDocumentFlags.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageDocumentFlags.java rename to common/model/src/main/java/nu/marginalia/model/crawl/EdgePageDocumentFlags.java index 0f7a68fa..04f55edf 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageDocumentFlags.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/EdgePageDocumentFlags.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.model; +package nu.marginalia.model.crawl; import java.util.EnumSet; @@ -6,7 +6,7 @@ public enum EdgePageDocumentFlags { /** Simple processing was done, this document should be de-prioritized as a search result */ Simple, - UnusedBit1, + PlainText, UnusedBit2, UnusedBit3, UnusedBit4, diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageWordFlags.java b/common/model/src/main/java/nu/marginalia/model/crawl/EdgePageWordFlags.java similarity index 78% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageWordFlags.java rename to common/model/src/main/java/nu/marginalia/model/crawl/EdgePageWordFlags.java index a3f443c5..089e02af 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageWordFlags.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/EdgePageWordFlags.java @@ -1,9 +1,5 @@ -package nu.marginalia.wmsa.edge.index.model; +package nu.marginalia.model.crawl; -import nu.marginalia.util.language.processing.KeywordCounter; -import nu.marginalia.util.language.processing.NameCounter; -import nu.marginalia.util.language.processing.SubjectCounter; -import nu.marginalia.wmsa.edge.converting.processor.SiteWords; import java.util.EnumSet; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageWords.java b/common/model/src/main/java/nu/marginalia/model/crawl/EdgePageWords.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageWords.java rename to common/model/src/main/java/nu/marginalia/model/crawl/EdgePageWords.java index 0db772da..60b02c56 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageWords.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/EdgePageWords.java @@ -1,10 +1,8 @@ -package nu.marginalia.wmsa.edge.model.crawl; +package nu.marginalia.model.crawl; import gnu.trove.list.array.TLongArrayList; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordMetadata; import java.util.ArrayList; import java.util.Collection; @@ -39,7 +37,7 @@ public class EdgePageWords { List emptyMeta = new ArrayList<>(entries.size()); for (int i = 0; i < entries.size(); i++) { - emptyMeta.add(EdgePageWordMetadata.emptyValue()); + emptyMeta.add(0L); } return new EdgePageWords(entries, emptyMeta); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeUrlState.java b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeUrlState.java similarity index 73% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeUrlState.java rename to common/model/src/main/java/nu/marginalia/model/crawl/EdgeUrlState.java index f9b57be3..07802e5c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeUrlState.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/EdgeUrlState.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.crawl; +package nu.marginalia.model.crawl; /** This should correspond to EC_URL.STATE */ public enum EdgeUrlState { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/HtmlFeature.java b/common/model/src/main/java/nu/marginalia/model/crawl/HtmlFeature.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/HtmlFeature.java rename to common/model/src/main/java/nu/marginalia/model/crawl/HtmlFeature.java index c4d0181c..4bdb5ca1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/HtmlFeature.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/HtmlFeature.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.model.crawl; import java.util.Collection; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDate.java b/common/model/src/main/java/nu/marginalia/model/crawl/PubDate.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDate.java rename to common/model/src/main/java/nu/marginalia/model/crawl/PubDate.java index 7913e710..67f67105 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDate.java +++ b/common/model/src/main/java/nu/marginalia/model/crawl/PubDate.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate; +package nu.marginalia.model.crawl; import java.time.LocalDate; import java.time.format.DateTimeFormatter; diff --git a/common/model/src/main/java/nu/marginalia/model/dbcommon/DbDomainQueries.java b/common/model/src/main/java/nu/marginalia/model/dbcommon/DbDomainQueries.java new file mode 100644 index 00000000..dc1f015c --- /dev/null +++ b/common/model/src/main/java/nu/marginalia/model/dbcommon/DbDomainQueries.java @@ -0,0 +1,64 @@ +package nu.marginalia.model.dbcommon; + + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.zaxxer.hikari.HikariDataSource; +import lombok.SneakyThrows; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.id.EdgeId; + +import java.util.NoSuchElementException; +import java.util.Optional; + +@Singleton +public class DbDomainQueries { + private final HikariDataSource dataSource; + + private final Cache> domainIdCache = CacheBuilder.newBuilder().maximumSize(10_000).build(); + + @Inject + public DbDomainQueries(HikariDataSource dataSource) + { + this.dataSource = dataSource; + } + + + @SneakyThrows + public EdgeId getDomainId(EdgeDomain domain) { + try (var connection = dataSource.getConnection()) { + + return domainIdCache.get(domain, () -> { + try (var stmt = connection.prepareStatement("SELECT ID FROM EC_DOMAIN WHERE DOMAIN_NAME=?")) { + stmt.setString(1, domain.toString()); + var rsp = stmt.executeQuery(); + if (rsp.next()) { + return new EdgeId<>(rsp.getInt(1)); + } + } + throw new NoSuchElementException(); + }); + } + catch (UncheckedExecutionException ex) { + throw ex.getCause(); + } + } + + @SneakyThrows + public Optional getDomain(EdgeId id) { + try (var connection = dataSource.getConnection()) { + + try (var stmt = connection.prepareStatement("SELECT DOMAIN_NAME FROM EC_DOMAIN WHERE ID=?")) { + stmt.setInt(1, id.id()); + var rsp = stmt.executeQuery(); + if (rsp.next()) { + return Optional.of(new EdgeDomain(rsp.getString(1))); + } + return Optional.empty(); + } + } + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDomainBlacklist.java b/common/model/src/main/java/nu/marginalia/model/dbcommon/EdgeDomainBlacklist.java similarity index 74% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDomainBlacklist.java rename to common/model/src/main/java/nu/marginalia/model/dbcommon/EdgeDomainBlacklist.java index 5fa6f193..f659a57a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDomainBlacklist.java +++ b/common/model/src/main/java/nu/marginalia/model/dbcommon/EdgeDomainBlacklist.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.dbcommon; +package nu.marginalia.model.dbcommon; import com.google.inject.ImplementedBy; import gnu.trove.set.hash.TIntHashSet; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.id.EdgeId; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.id.EdgeId; @ImplementedBy(EdgeDomainBlacklistImpl.class) public interface EdgeDomainBlacklist { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDomainBlacklistImpl.java b/common/model/src/main/java/nu/marginalia/model/dbcommon/EdgeDomainBlacklistImpl.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDomainBlacklistImpl.java rename to common/model/src/main/java/nu/marginalia/model/dbcommon/EdgeDomainBlacklistImpl.java index 13d7080f..053ced8e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDomainBlacklistImpl.java +++ b/common/model/src/main/java/nu/marginalia/model/dbcommon/EdgeDomainBlacklistImpl.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.dbcommon; +package nu.marginalia.model.dbcommon; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -6,6 +6,7 @@ import com.zaxxer.hikari.HikariDataSource; import gnu.trove.set.hash.TIntHashSet; import io.reactivex.rxjava3.schedulers.Schedulers; import lombok.SneakyThrows; +import nu.marginalia.model.EdgeDomain; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,5 +68,4 @@ public class EdgeDomainBlacklistImpl implements EdgeDomainBlacklist { return false; } - } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/GsonFactory.java b/common/model/src/main/java/nu/marginalia/model/gson/GsonFactory.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/GsonFactory.java rename to common/model/src/main/java/nu/marginalia/model/gson/GsonFactory.java index 393b2ea5..04496ff8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/GsonFactory.java +++ b/common/model/src/main/java/nu/marginalia/model/gson/GsonFactory.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.client; +package nu.marginalia.model.gson; import com.google.gson.*; import marcono1234.gson.recordadapter.RecordTypeAdapterFactory; -import nu.marginalia.util.bigstring.BigString; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeId; +import nu.marginalia.bigstring.BigString; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.id.EdgeId; import java.net.URISyntaxException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeId.java b/common/model/src/main/java/nu/marginalia/model/id/EdgeId.java similarity index 74% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeId.java rename to common/model/src/main/java/nu/marginalia/model/id/EdgeId.java index 90f978ed..9e45c78f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeId.java +++ b/common/model/src/main/java/nu/marginalia/model/id/EdgeId.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.id; +package nu.marginalia.model.id; /** diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdArray.java b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdArray.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdArray.java rename to common/model/src/main/java/nu/marginalia/model/id/EdgeIdArray.java index d7e1f0f1..078dcdb6 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdArray.java +++ b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdArray.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.id; +package nu.marginalia.model.id; import java.util.Arrays; import java.util.stream.IntStream; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdCollection.java b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdCollection.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdCollection.java rename to common/model/src/main/java/nu/marginalia/model/id/EdgeIdCollection.java index ef30b78d..a8403cc6 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdCollection.java +++ b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdCollection.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.id; +package nu.marginalia.model.id; import java.util.Arrays; import java.util.stream.IntStream; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdCollectionMutable.java b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdCollectionMutable.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdCollectionMutable.java rename to common/model/src/main/java/nu/marginalia/model/id/EdgeIdCollectionMutable.java index 24da0714..0056cb28 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdCollectionMutable.java +++ b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdCollectionMutable.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.id; +package nu.marginalia.model.id; import gnu.trove.TIntCollection; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdList.java b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdList.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdList.java rename to common/model/src/main/java/nu/marginalia/model/id/EdgeIdList.java index e61076a6..295854ec 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdList.java +++ b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdList.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.id; +package nu.marginalia.model.id; import gnu.trove.TIntCollection; import gnu.trove.list.array.TIntArrayList; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdSet.java b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdSet.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdSet.java rename to common/model/src/main/java/nu/marginalia/model/id/EdgeIdSet.java index 98ffd2ac..5119b5c7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/id/EdgeIdSet.java +++ b/common/model/src/main/java/nu/marginalia/model/id/EdgeIdSet.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.id; +package nu.marginalia.model.id; import gnu.trove.TIntCollection; import gnu.trove.set.hash.TIntHashSet; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageDocumentsMetadata.java b/common/model/src/main/java/nu/marginalia/model/idx/EdgePageDocumentsMetadata.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageDocumentsMetadata.java rename to common/model/src/main/java/nu/marginalia/model/idx/EdgePageDocumentsMetadata.java index 4331131f..1100a49f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageDocumentsMetadata.java +++ b/common/model/src/main/java/nu/marginalia/model/idx/EdgePageDocumentsMetadata.java @@ -1,6 +1,7 @@ -package nu.marginalia.wmsa.edge.index.model; +package nu.marginalia.model.idx; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; +import nu.marginalia.model.crawl.EdgePageDocumentFlags; +import nu.marginalia.model.crawl.PubDate; import java.util.EnumSet; import java.util.Set; @@ -107,7 +108,6 @@ public record EdgePageDocumentsMetadata(int rank, return (int) ((encoded >>> TOPOLOGY_SHIFT) & TOPOLOGY_MASK); } public static int decodeYear(long encoded) { - return PubDate.fromYearByte((int) ((encoded >>> YEAR_SHIFT) & YEAR_MASK)); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageWordMetadata.java b/common/model/src/main/java/nu/marginalia/model/idx/EdgePageWordMetadata.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageWordMetadata.java rename to common/model/src/main/java/nu/marginalia/model/idx/EdgePageWordMetadata.java index 579dda92..7f8aff2a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/EdgePageWordMetadata.java +++ b/common/model/src/main/java/nu/marginalia/model/idx/EdgePageWordMetadata.java @@ -1,5 +1,6 @@ -package nu.marginalia.wmsa.edge.index.model; +package nu.marginalia.model.idx; +import nu.marginalia.model.crawl.EdgePageWordFlags; import nu.marginalia.util.BrailleBlockPunchCards; import java.util.EnumSet; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/DenseBitMap.java b/common/model/src/main/java/nu/marginalia/util/DenseBitMap.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/util/DenseBitMap.java rename to common/model/src/main/java/nu/marginalia/util/DenseBitMap.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/KeywordListChunker.java b/common/model/src/main/java/nu/marginalia/util/KeywordListChunker.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/KeywordListChunker.java rename to common/model/src/main/java/nu/marginalia/util/KeywordListChunker.java index 1e30055f..1f7af342 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/KeywordListChunker.java +++ b/common/model/src/main/java/nu/marginalia/util/KeywordListChunker.java @@ -1,4 +1,6 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.util; + +import nu.marginalia.model.crawl.DocumentKeywords; import java.util.ArrayList; import java.util.Collections; diff --git a/common/model/src/main/java/nu/marginalia/util/LineUtils.java b/common/model/src/main/java/nu/marginalia/util/LineUtils.java new file mode 100644 index 00000000..0bb785a0 --- /dev/null +++ b/common/model/src/main/java/nu/marginalia/util/LineUtils.java @@ -0,0 +1,47 @@ +package nu.marginalia.util; + +import java.util.ArrayList; +import java.util.List; + +public class LineUtils { + + /** LF, CR, CRLF, LFCR-agnostic string-line splitter that preserves empty lines + * that does not create a huge blob of a last item like String$split(regex, n) + * + */ + public static List firstNLines(String documentBody, int numLines) { + List lines = new ArrayList<>(numLines); + + boolean eatCr = false; + boolean eatLf = false; + int startPos = 0; + + for (int pos = 0; pos < documentBody.length() && lines.size() < numLines; pos++) { + int cp = documentBody.charAt(pos); + if (cp == '\r') { + if (eatCr) { + eatCr = false; + } + else { + eatLf = true; + lines.add(documentBody.substring(startPos, pos)); + } + startPos = pos + 1; + } else if (cp == '\n') { + if (eatLf) { + eatLf = false; + } + else { + eatCr = true; + lines.add(documentBody.substring(startPos, pos)); + } + startPos = pos + 1; + } else { + eatCr = eatLf = false; + } + } + + return lines; + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/ParallelPipe.java b/common/model/src/main/java/nu/marginalia/util/ParallelPipe.java similarity index 87% rename from marginalia_nu/src/main/java/nu/marginalia/util/ParallelPipe.java rename to common/model/src/main/java/nu/marginalia/util/ParallelPipe.java index 853af8fb..fc95debe 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/ParallelPipe.java +++ b/common/model/src/main/java/nu/marginalia/util/ParallelPipe.java @@ -9,6 +9,12 @@ import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +/** Generalization of the workflow
+ * -- single provider thread reading sequentially from disk
+ * -> multiple independent CPU-bound processing tasks
+ * -> single consumer thread writing to network/disk
+ *

+ */ public abstract class ParallelPipe { private final LinkedBlockingQueue inputs; private final LinkedBlockingQueue intermediates; @@ -61,8 +67,9 @@ public abstract class ParallelPipe { } } - logger.debug("Terminating {}", Thread.currentThread().getName()); + logger.info("Terminating {}", Thread.currentThread().getName()); } + @SneakyThrows private void runReceiverThread() { while (expectingOutput || !inputs.isEmpty() || !intermediates.isEmpty()) { @@ -80,12 +87,16 @@ public abstract class ParallelPipe { logger.info("Terminating {}", Thread.currentThread().getName()); } + /** Begin processing an item */ @SneakyThrows public void accept(INPUT input) { inputs.put(input); } + /** The meat of the processor thread runtime */ protected abstract INTERMEDIATE onProcess(INPUT input) throws Exception; + + /** The meat of the consumer thread runtime */ protected abstract void onReceive(INTERMEDIATE intermediate) throws Exception; public void join() throws InterruptedException { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/QueryParams.java b/common/model/src/main/java/nu/marginalia/util/QueryParams.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/QueryParams.java rename to common/model/src/main/java/nu/marginalia/util/QueryParams.java index 8b37e4c9..430758fd 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/QueryParams.java +++ b/common/model/src/main/java/nu/marginalia/util/QueryParams.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.util; import org.apache.commons.lang3.StringUtils; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/StringPool.java b/common/model/src/main/java/nu/marginalia/util/StringPool.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/util/StringPool.java rename to common/model/src/main/java/nu/marginalia/util/StringPool.java diff --git a/marginalia_nu/src/main/resources/sql/edge-crawler-cache.sql b/common/model/src/main/resources/sql/edge-crawler-cache.sql similarity index 98% rename from marginalia_nu/src/main/resources/sql/edge-crawler-cache.sql rename to common/model/src/main/resources/sql/edge-crawler-cache.sql index f0dc851c..3f93a4df 100644 --- a/marginalia_nu/src/main/resources/sql/edge-crawler-cache.sql +++ b/common/model/src/main/resources/sql/edge-crawler-cache.sql @@ -74,7 +74,7 @@ CREATE TABLE IF NOT EXISTS EC_PAGE_DATA ( FORMAT ENUM('PLAIN', 'UNKNOWN', 'HTML123', 'HTML4', 'XHTML', 'HTML5', 'MARKDOWN') NOT NULL, FEATURES INT COMMENT "Bit-encoded feature set of document, @see HtmlFeature" NOT NULL, - DATA_HASH INTEGER NOT NULL, + DATA_HASH BIGINT NOT NULL, QUALITY DOUBLE NOT NULL, PUB_YEAR SMALLINT, @@ -212,6 +212,13 @@ CREATE INDEX IF NOT EXISTS EC_DOMAIN_TOP_DOMAIN ON EC_DOMAIN (DOMAIN_TOP); ---; +CREATE TABLE IF NOT EXISTS EC_RANDOM_DOMAINS ( + DOMAIN_ID INT PRIMARY KEY, + DOMAIN_SET INT NOT NULL +); + +---; + DROP TABLE IF EXISTS REF_DICTIONARY; CREATE TABLE IF NOT EXISTS REF_DICTIONARY ( @@ -287,3 +294,4 @@ CREATE TABLE WMSA_PROCESS( MUTEX VARCHAR(255), TIMEOUT INT NOT NULL DEFAULT 60 ); + diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/EdgeDomainTest.java b/common/model/src/test/java/nu/marginalia/model/EdgeDomainTest.java similarity index 98% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/EdgeDomainTest.java rename to common/model/src/test/java/nu/marginalia/model/EdgeDomainTest.java index 13686997..9fbf6890 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/EdgeDomainTest.java +++ b/common/model/src/test/java/nu/marginalia/model/EdgeDomainTest.java @@ -1,5 +1,6 @@ -package nu.marginalia.wmsa.edge.model; +package nu.marginalia.model; +import nu.marginalia.model.EdgeUrl; import org.junit.jupiter.api.Test; import java.net.URISyntaxException; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageWordMetadataTest.java b/common/model/src/test/java/nu/marginalia/model/EdgePageWordMetadataTest.java similarity index 93% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageWordMetadataTest.java rename to common/model/src/test/java/nu/marginalia/model/EdgePageWordMetadataTest.java index f3cbfa77..dcbc83e5 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageWordMetadataTest.java +++ b/common/model/src/test/java/nu/marginalia/model/EdgePageWordMetadataTest.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.model.crawl; +package nu.marginalia.model; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordMetadata; +import nu.marginalia.model.crawl.EdgePageWordFlags; +import nu.marginalia.model.idx.EdgePageWordMetadata; import org.junit.jupiter.api.Test; import java.util.EnumSet; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/EdgeUrlTest.java b/common/model/src/test/java/nu/marginalia/model/EdgeUrlTest.java similarity index 96% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/EdgeUrlTest.java rename to common/model/src/test/java/nu/marginalia/model/EdgeUrlTest.java index 61444c69..d746ec3b 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/model/EdgeUrlTest.java +++ b/common/model/src/test/java/nu/marginalia/model/EdgeUrlTest.java @@ -1,5 +1,6 @@ -package nu.marginalia.wmsa.edge.model; +package nu.marginalia.model; +import nu.marginalia.model.EdgeUrl; import org.junit.jupiter.api.Test; import java.net.URISyntaxException; diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/BrailleBlockPunchCardsTest.java b/common/model/src/test/java/nu/marginalia/util/BrailleBlockPunchCardsTest.java similarity index 100% rename from marginalia_nu/src/test/java/nu/marginalia/util/BrailleBlockPunchCardsTest.java rename to common/model/src/test/java/nu/marginalia/util/BrailleBlockPunchCardsTest.java diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/DenseBitMapTest.java b/common/model/src/test/java/nu/marginalia/util/DenseBitMapTest.java similarity index 97% rename from marginalia_nu/src/test/java/nu/marginalia/util/DenseBitMapTest.java rename to common/model/src/test/java/nu/marginalia/util/DenseBitMapTest.java index 20857947..5f6d6aec 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/DenseBitMapTest.java +++ b/common/model/src/test/java/nu/marginalia/util/DenseBitMapTest.java @@ -1,5 +1,6 @@ package nu.marginalia.util; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; diff --git a/common/model/src/test/java/nu/marginalia/util/LineUtilsTest.java b/common/model/src/test/java/nu/marginalia/util/LineUtilsTest.java new file mode 100644 index 00000000..e63ca38f --- /dev/null +++ b/common/model/src/test/java/nu/marginalia/util/LineUtilsTest.java @@ -0,0 +1,16 @@ +package nu.marginalia.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +class LineUtilsTest { + + @Test + void firstNLines() { + String text = "a\nb\r\ncd\r\re\n\rffgg\n\n"; + List expected = List.of("a", "b", "cd", "", "e", "ffgg", ""); + Assertions.assertEquals(expected, LineUtils.firstNLines(text, 10)); + } +} \ No newline at end of file diff --git a/common/readme.md b/common/readme.md new file mode 100644 index 00000000..3860c029 --- /dev/null +++ b/common/readme.md @@ -0,0 +1,9 @@ +# Common + +These are packages containing the basic building blocks for running a service as well +as shared models. + +* [config](config/) contains some `@Inject`ables. +* [service](service/) is the shared base classes for main methods and web services. +* [service-client](service-client/) is the shared base class for RPC. +* [service-discovery](service-discovery) contains tools that lets the services find each other. \ No newline at end of file diff --git a/common/service-client/build.gradle b/common/service-client/build.gradle new file mode 100644 index 00000000..916eeaeb --- /dev/null +++ b/common/service-client/build.gradle @@ -0,0 +1,56 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id "de.undercouch.download" version "5.1.0" + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:service-discovery') + + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.commons.lang3 + implementation libs.spark + implementation libs.guice + implementation libs.rxjava + + implementation libs.okhttp3 + implementation libs.bundles.httpcomponents + + implementation libs.bundles.gson + implementation libs.protobuf + + implementation libs.bundles.prometheus + + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + maxHeapSize = "8G" + useJUnitPlatform() +} + +task fastTests(type: Test) { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + maxHeapSize = "8G" + useJUnitPlatform { + excludeTags "slow" + } +} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/AbortingScheduler.java b/common/service-client/src/main/java/nu/marginalia/client/AbortingScheduler.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/AbortingScheduler.java rename to common/service-client/src/main/java/nu/marginalia/client/AbortingScheduler.java index fc06bd26..2ec196e6 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/AbortingScheduler.java +++ b/common/service-client/src/main/java/nu/marginalia/client/AbortingScheduler.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.client; +package nu.marginalia.client; import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.reactivex.rxjava3.core.Scheduler; @@ -12,7 +12,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; -public class AbortingScheduler implements AutoCloseable { +public class AbortingScheduler { private final String name; private final ThreadFactory threadFactory; @@ -54,7 +54,6 @@ public class AbortingScheduler implements AutoCloseable { return executorService; } - @Override public synchronized void close() { if (null != executorService) { executorService.shutdown(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/AbstractClient.java b/common/service-client/src/main/java/nu/marginalia/client/AbstractClient.java similarity index 64% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/AbstractClient.java rename to common/service-client/src/main/java/nu/marginalia/client/AbstractClient.java index eac18abf..5be58520 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/AbstractClient.java +++ b/common/service-client/src/main/java/nu/marginalia/client/AbstractClient.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.client; +package nu.marginalia.client; import com.google.gson.Gson; import com.google.protobuf.GeneratedMessageV3; @@ -6,11 +6,10 @@ import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.ObservableSource; import io.reactivex.rxjava3.plugins.RxJavaPlugins; import lombok.SneakyThrows; -import nu.marginalia.wmsa.client.exception.LocalException; -import nu.marginalia.wmsa.client.exception.NetworkException; -import nu.marginalia.wmsa.client.exception.RemoteException; -import nu.marginalia.wmsa.client.exception.RouteNotConfiguredException; -import nu.marginalia.wmsa.configuration.server.Context; +import nu.marginalia.client.exception.LocalException; +import nu.marginalia.client.exception.NetworkException; +import nu.marginalia.client.exception.RemoteException; +import nu.marginalia.client.exception.RouteNotConfiguredException; import okhttp3.*; import org.apache.http.HttpHost; import org.apache.logging.log4j.ThreadContext; @@ -24,32 +23,33 @@ import java.net.ConnectException; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.zip.GZIPOutputStream; public abstract class AbstractClient implements AutoCloseable { - public static final String CONTEXT_OUTBOUND_REQUEST = "outbound-request"; - - private final Gson gson = GsonFactory.get(); - private final Logger logger = LoggerFactory.getLogger(getClass()); + public static final String CONTEXT_OUTBOUND_REQUEST = "outbound-request"; + private final Gson gson; private final OkHttpClient client; private boolean quiet; - private String url; + private String serviceRoute; + private int timeout; + + + private volatile boolean alive; + private final Thread livenessMonitor; public void setTimeout(int timeout) { this.timeout = timeout; } - private int timeout; - private volatile boolean alive; - - private final Thread livenessMonitor; - - public AbstractClient(String host, int port, int timeout) { + public AbstractClient(String host, int port, int timeout, Supplier gsonProvider) { logger.info("Creating client for {}[{}:{}]", getClass().getSimpleName(), host, port); + this.gson = gsonProvider.get(); + this.timeout = timeout; client = new OkHttpClient.Builder() .connectTimeout(100, TimeUnit.MILLISECONDS) @@ -57,7 +57,7 @@ public abstract class AbstractClient implements AutoCloseable { .retryOnConnectionFailure(true) .followRedirects(true) .build(); - url = new HttpHost(host, port).toURI(); + serviceRoute = new HttpHost(host, port).toURI(); RxJavaPlugins.setErrorHandler(e -> { if (e.getMessage() == null) { @@ -76,28 +76,37 @@ public abstract class AbstractClient implements AutoCloseable { public void setServiceRoute(String hostname, int port) { scheduler().abort(); - url = new HttpHost(hostname, port).toURI(); + serviceRoute = new HttpHost(hostname, port).toURI(); + } + + protected String getServiceRoute() { + return serviceRoute; } @SneakyThrows private void monitorLiveness() { Thread.sleep(100); // Wait for initialization - for (;;) { - try { - alive = isResponsive(); - } - // - catch (Exception ex) { - logger.warn("Oops", ex); - } - synchronized (livenessMonitor) { - if (alive) { - livenessMonitor.wait(1000); + try { + for (; ; ) { + try { + alive = isResponsive(); + } + // + catch (Exception ex) { + logger.warn("Oops", ex); + } + synchronized (livenessMonitor) { + if (alive) { + livenessMonitor.wait(1000); + } + } + if (!alive) { + Thread.sleep(100); } } - if (!alive) { - Thread.sleep(100); - } + } + catch (InterruptedException ex) { + // nothing to see here } } @@ -117,11 +126,9 @@ public abstract class AbstractClient implements AutoCloseable { public synchronized boolean isResponsive() { Context ctx = Context.internal("ping"); - var req = ctx.paint(new Request.Builder()).url(url + "/internal/ping").get().build(); + var req = ctx.paint(new Request.Builder()).url(serviceRoute + "/internal/ping").get().build(); - var call = client.newCall(req); - - return Observable.just(call) + return Observable.just(client.newCall(req)) .subscribeOn(scheduler().get()) .map(Call::execute) .map(this::getResponseStatus) @@ -135,11 +142,9 @@ public abstract class AbstractClient implements AutoCloseable { public synchronized boolean isAccepting() { Context ctx = Context.internal("ready"); - var req = ctx.paint(new Request.Builder()).url(url + "/internal/ready").get().build(); + var req = ctx.paint(new Request.Builder()).url(serviceRoute + "/internal/ready").get().build(); - var call = client.newCall(req); - - return Observable.just(call) + return Observable.just(client.newCall(req)) .subscribeOn(scheduler().get()) .map(Call::execute) .map(this::getResponseStatus) @@ -156,19 +161,12 @@ public abstract class AbstractClient implements AutoCloseable { ensureAlive(); - RequestBody body = RequestBody.create( - MediaType.parse("application/json; charset=utf-8"), - json(data)); + RequestBody body = RequestBody.create(json(data), MediaType.parse("application/json; charset=utf-8")); - var req = ctx.paint(new Request.Builder()).url(url + endpoint).post(body).build(); - var call = client.newCall(req); + var req = ctx.paint(new Request.Builder()).url(serviceRoute + endpoint).post(body).build(); return Observable - .just(call) - .map((c) -> { - ThreadContext.put("outbound-request", url + endpoint); - return c; - }) + .just(client.newCall(req)) .subscribeOn(scheduler().get()) .map(this::logInbound) .map(Call::execute) @@ -186,15 +184,13 @@ public abstract class AbstractClient implements AutoCloseable { ensureAlive(); - RequestBody body = RequestBody.create( - MediaType.parse("application/protobuf"), - data.toByteArray()); + RequestBody body = RequestBody.create(data.toByteArray(), MediaType.parse("application/protobuf")); - var req = ctx.paint(new Request.Builder()).url(url + endpoint).post(body).build(); + var req = ctx.paint(new Request.Builder()).url(serviceRoute + endpoint).post(body).build(); var call = client.newCall(req); logInbound(call); - ThreadContext.put("outbound-request", url + endpoint); + ThreadContext.put("outbound-request", serviceRoute + endpoint); try (var rsp = call.execute()) { logOutbound(rsp); int code = rsp.code(); @@ -212,18 +208,10 @@ public abstract class AbstractClient implements AutoCloseable { ensureAlive(); - RequestBody body = RequestBody.create( - MediaType.parse("application/json"), - json(data)); + RequestBody body = RequestBody.create(json(data), MediaType.parse("application/json")); + var req = ctx.paint(new Request.Builder()).url(serviceRoute + endpoint).post(body).build(); - var req = ctx.paint(new Request.Builder()).url(url + endpoint).post(body).build(); - var call = client.newCall(req); - - return Observable.just(call) - .map((c) -> { - ThreadContext.put("outbound-request", url + endpoint); - return c; - }) + return Observable.just(client.newCall(req)) .subscribeOn(scheduler().get()) .map(this::logInbound) .map(Call::execute) @@ -238,15 +226,15 @@ public abstract class AbstractClient implements AutoCloseable { protected synchronized Observable post(Context ctx, String endpoint, String data, MediaType mediaType) { ensureAlive(); - var body = RequestBody.create(mediaType, data); + var body = RequestBody.create(data, mediaType); - var req = ctx.paint(new Request.Builder()).url(url + endpoint).post(body).build(); + var req = ctx.paint(new Request.Builder()).url(serviceRoute + endpoint).post(body).build(); var call = client.newCall(req); return Observable.just(call) .map((c) -> { - ThreadContext.put(CONTEXT_OUTBOUND_REQUEST, url + endpoint); + ThreadContext.put(CONTEXT_OUTBOUND_REQUEST, serviceRoute + endpoint); return c; }) .subscribeOn(scheduler().get()) @@ -264,14 +252,9 @@ public abstract class AbstractClient implements AutoCloseable { protected synchronized Observable get(Context ctx, String endpoint, Class type) { ensureAlive(); - var req = ctx.paint(new Request.Builder()).url(url + endpoint).get().build(); - var call = client.newCall(req); + var req = ctx.paint(new Request.Builder()).url(serviceRoute + endpoint).get().build(); - return Observable.just(call) - .map((c) -> { - ThreadContext.put("outbound-request", url + endpoint); - return c; - }) + return Observable.just(client.newCall(req)) .subscribeOn(scheduler().get()) .map(this::logInbound) .map(Call::execute) @@ -284,62 +267,12 @@ public abstract class AbstractClient implements AutoCloseable { } @SuppressWarnings("unchecked") - protected synchronized Observable> getList(Context ctx, String endpoint, Class type) { - ensureAlive(); - - var req = ctx.paint(new Request.Builder()).url(url + endpoint).get().build(); - var call = client.newCall(req); - - return Observable.just(call) - .map((c) -> { - ThreadContext.put("outbound-request", url + endpoint); - return c; - }) - .subscribeOn(scheduler().get()) - .map(this::logInbound) - .map(Call::execute) - .map(this::logOutbound) - .map(rsp -> validateResponseStatus(rsp, req, 200)) - .map(rsp -> Arrays.asList((T[])getEntity(rsp, type.arrayType()))) - .retryWhen(this::retryHandler) - .timeout(timeout, TimeUnit.SECONDS) - .doFinally(() -> ThreadContext.remove("outbound-request")); - } - - - protected synchronized Observable getBinary(Context ctx, String endpoint) { - ensureAlive(); - - var req = ctx.paint(new Request.Builder()).url(url + endpoint).get().build(); - var call = client.newCall(req); - - return Observable.just(call) - .map((c) -> { - ThreadContext.put("outbound-request", url + endpoint); - return c; - }) - .subscribeOn(scheduler().get()) - .map(this::logInbound) - .map(Call::execute) - .map(this::logOutbound) - .map(rsp -> validateResponseStatus(rsp, req, 200)) - .map(this::getBinaryEntity) - .retryWhen(this::retryHandler) - .timeout(timeout, TimeUnit.SECONDS) - .doFinally(() -> ThreadContext.remove("outbound-request")); - } - protected synchronized Observable get(Context ctx, String endpoint) { ensureAlive(); - var req = ctx.paint(new Request.Builder()).url(url + endpoint).get().build(); - var call = client.newCall(req); + var req = ctx.paint(new Request.Builder()).url(serviceRoute + endpoint).get().build(); - return Observable.just(call) - .map((c) -> { - ThreadContext.put("outbound-request", url + endpoint); - return c; - }) + return Observable.just(client.newCall(req)) .subscribeOn(scheduler().get()) .map(this::logInbound) .map(Call::execute) @@ -354,14 +287,9 @@ public abstract class AbstractClient implements AutoCloseable { protected synchronized Observable delete(Context ctx, String endpoint) { ensureAlive(); - var req = ctx.paint(new Request.Builder()).url(url + endpoint).delete().build(); - var call = client.newCall(req); + var req = ctx.paint(new Request.Builder()).url(serviceRoute + endpoint).delete().build(); - return Observable.just(call) - .map((c) -> { - ThreadContext.put("outbound-request", url + endpoint); - return c; - }) + return Observable.just(client.newCall(req)) .subscribeOn(scheduler().get()) .map(this::logInbound) .map(Call::execute) @@ -390,26 +318,12 @@ public abstract class AbstractClient implements AutoCloseable { if (!isAlive()) { wait(2000); if (!isAlive()) { - throw new RouteNotConfiguredException("Route not configured for " + name()); + throw new RouteNotConfiguredException("Route not configured for " + name() + " -- tried " + serviceRoute); } } } - @SneakyThrows - public void waitReady() { - boolean accepting = isAccepting(); - if (accepting) { - return; - } - - logger.info("Waiting for " + name()); - do { - Thread.sleep(1000); - } while (!isAccepting()); - } - - private ObservableSource retryHandler(Observable error) { return error.flatMap(this::filterRetryableExceptions); } @@ -488,12 +402,6 @@ public abstract class AbstractClient implements AutoCloseable { } - @SneakyThrows - private byte[] getBinaryEntity(Response response) { - try (response) { - return response.body().bytes(); - } - } public boolean isAlive() { return alive; } @@ -507,17 +415,4 @@ public abstract class AbstractClient implements AutoCloseable { } } - private byte[] compressedJson(Object o) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - GZIPOutputStream gos = new GZIPOutputStream(baos); - try { - gson.toJson(o, new OutputStreamWriter(gos)); - gos.finish(); - return baos.toByteArray(); - } - catch (Exception ex) { - throw new LocalException(ex); - } - } - } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/AbstractDynamicClient.java b/common/service-client/src/main/java/nu/marginalia/client/AbstractDynamicClient.java similarity index 66% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/AbstractDynamicClient.java rename to common/service-client/src/main/java/nu/marginalia/client/AbstractDynamicClient.java index 974a939e..f85c2898 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/AbstractDynamicClient.java +++ b/common/service-client/src/main/java/nu/marginalia/client/AbstractDynamicClient.java @@ -1,21 +1,23 @@ -package nu.marginalia.wmsa.client; +package nu.marginalia.client; +import com.google.gson.Gson; import io.reactivex.rxjava3.core.Observable; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.server.Context; +import nu.marginalia.service.descriptor.ServiceDescriptor; +import nu.marginalia.service.descriptor.HostsFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; +import java.util.function.Supplier; public class AbstractDynamicClient extends AbstractClient { private final ServiceDescriptor service; private final Logger logger = LoggerFactory.getLogger(getClass()); private final AbortingScheduler scheduler; - public AbstractDynamicClient(@Nonnull ServiceDescriptor service) { - super(service.getHost(), service.port, 10); + public AbstractDynamicClient(@Nonnull ServiceDescriptor service, HostsFile hosts, Supplier gsonProvider) { + super(hosts.getHost(service), service.port, 10, gsonProvider); this.service = service; this.scheduler = new AbortingScheduler(name()); @@ -32,7 +34,7 @@ public class AbstractDynamicClient extends AbstractClient { @SneakyThrows public void blockingWait() { - logger.info("Waiting for route to {}", service); + logger.info("Waiting for route to {} ({})", service, getServiceRoute()); while (!isAlive()) { Thread.sleep(1000); } @@ -43,10 +45,4 @@ public class AbstractDynamicClient extends AbstractClient { return scheduler; } - public Observable who(Context ctx) { - return get(ctx, "/public/who"); - } - public Observable ping(Context ctx) { - return get(ctx, "/internal/ping"); - } } diff --git a/common/service-client/src/main/java/nu/marginalia/client/Context.java b/common/service-client/src/main/java/nu/marginalia/client/Context.java new file mode 100644 index 00000000..9449d20f --- /dev/null +++ b/common/service-client/src/main/java/nu/marginalia/client/Context.java @@ -0,0 +1,89 @@ +package nu.marginalia.client; + +import io.reactivex.rxjava3.schedulers.Schedulers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spark.Request; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class Context { + public static final String CONTEXT_HEADER = "X-Context"; + public static final String SESSION_HEADER = "Cookie"; + public static final String PUBLIC_HEADER = "X-Public"; + + private final String id; + private final String session; + private boolean treatAsPublic; + + private Context(String id, String session) { + this.id = Objects.requireNonNull(id, "Context missing"); + this.session = session; + } + + public Context treatAsPublic() { + this.treatAsPublic = true; + return this; + } + + public static Context internal() { + return new Context(UUID.randomUUID().toString(), null); + } + public static Context internal(String why) { + return new Context(why + ":" + System.nanoTime(), null); + } + + public static Context fromRequest(Request request) { + + if (Boolean.getBoolean("unit-test")) { + return Context.internal(); + } + + final var ctxHeader = anonymizeContext(request); + final var sessHeader = request.headers(SESSION_HEADER); + + return new Context(ctxHeader, sessHeader); + } + + private static String anonymizeContext(Request request) { + String header = request.headers(CONTEXT_HEADER); + if (header != null && header.contains("-")) { + // The public X-Context header contains info that traces to the + // external user's IP. Anonymize this by running it through a + // hash code blender with rotating salt + + return ContextScrambler.anonymize(header); + } + else if (header != null) { + return header; + } + else { + // When no X-Context is provided, synthesize one from path + return request.pathInfo() + ":" + Thread.currentThread().getId(); + } + } + + public okhttp3.Request.Builder paint(okhttp3.Request.Builder requestBuilder) { + requestBuilder.addHeader(CONTEXT_HEADER, id); + + if (session != null) { + requestBuilder.addHeader(SESSION_HEADER, session); + } + + if (treatAsPublic) { + requestBuilder.header(PUBLIC_HEADER, "1"); + } + + return requestBuilder; + } + + public String getContextId() { + return id; + } + + public boolean isPublic() { + return id.startsWith("#"); + } + +} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/HttpStatusCode.java b/common/service-client/src/main/java/nu/marginalia/client/HttpStatusCode.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/HttpStatusCode.java rename to common/service-client/src/main/java/nu/marginalia/client/HttpStatusCode.java index 3b39ae84..aa23e71d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/HttpStatusCode.java +++ b/common/service-client/src/main/java/nu/marginalia/client/HttpStatusCode.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.client; +package nu.marginalia.client; public final class HttpStatusCode { public final int code; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/LocalException.java b/common/service-client/src/main/java/nu/marginalia/client/exception/LocalException.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/LocalException.java rename to common/service-client/src/main/java/nu/marginalia/client/exception/LocalException.java index f721de69..bcaa2982 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/LocalException.java +++ b/common/service-client/src/main/java/nu/marginalia/client/exception/LocalException.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.client.exception; +package nu.marginalia.client.exception; public class LocalException extends MessagingException { public LocalException() { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/MessagingException.java b/common/service-client/src/main/java/nu/marginalia/client/exception/MessagingException.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/MessagingException.java rename to common/service-client/src/main/java/nu/marginalia/client/exception/MessagingException.java index f08b47b7..6151381e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/MessagingException.java +++ b/common/service-client/src/main/java/nu/marginalia/client/exception/MessagingException.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.client.exception; +package nu.marginalia.client.exception; public class MessagingException extends RuntimeException { public MessagingException() { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/NetworkException.java b/common/service-client/src/main/java/nu/marginalia/client/exception/NetworkException.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/NetworkException.java rename to common/service-client/src/main/java/nu/marginalia/client/exception/NetworkException.java index e39028fb..593333ad 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/NetworkException.java +++ b/common/service-client/src/main/java/nu/marginalia/client/exception/NetworkException.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.client.exception; +package nu.marginalia.client.exception; public class NetworkException extends MessagingException { public NetworkException() { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/RemoteException.java b/common/service-client/src/main/java/nu/marginalia/client/exception/RemoteException.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/RemoteException.java rename to common/service-client/src/main/java/nu/marginalia/client/exception/RemoteException.java index ed2c8645..d26df9b3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/RemoteException.java +++ b/common/service-client/src/main/java/nu/marginalia/client/exception/RemoteException.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.client.exception; +package nu.marginalia.client.exception; public class RemoteException extends MessagingException { public RemoteException() { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/RouteNotConfiguredException.java b/common/service-client/src/main/java/nu/marginalia/client/exception/RouteNotConfiguredException.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/RouteNotConfiguredException.java rename to common/service-client/src/main/java/nu/marginalia/client/exception/RouteNotConfiguredException.java index 7f2a4c40..c3155dcf 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/RouteNotConfiguredException.java +++ b/common/service-client/src/main/java/nu/marginalia/client/exception/RouteNotConfiguredException.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.client.exception; +package nu.marginalia.client.exception; public class RouteNotConfiguredException extends MessagingException { public RouteNotConfiguredException() { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/TimeoutException.java b/common/service-client/src/main/java/nu/marginalia/client/exception/TimeoutException.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/TimeoutException.java rename to common/service-client/src/main/java/nu/marginalia/client/exception/TimeoutException.java index 35adc152..17632758 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/client/exception/TimeoutException.java +++ b/common/service-client/src/main/java/nu/marginalia/client/exception/TimeoutException.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.client.exception; +package nu.marginalia.client.exception; public class TimeoutException extends MessagingException { public TimeoutException() { diff --git a/common/service-client/src/test/java/nu/marginalia/client/AbstractClientTest.java b/common/service-client/src/test/java/nu/marginalia/client/AbstractClientTest.java new file mode 100644 index 00000000..db590adf --- /dev/null +++ b/common/service-client/src/test/java/nu/marginalia/client/AbstractClientTest.java @@ -0,0 +1,180 @@ +package nu.marginalia.client; + +import com.google.gson.Gson; +import io.reactivex.rxjava3.core.Observable; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.SneakyThrows; +import org.junit.jupiter.api.*; +import spark.Request; +import spark.Response; +import spark.Spark; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +public class AbstractClientTest { + + static TestServer testServer; + static AbstractClient client; + Gson gson = new Gson(); + + @Data @AllArgsConstructor + private static class DummyObject { + public int num; + public String str; + } + + @BeforeAll + public static void setUp() { + int port = new Random().nextInt(6000, 10000); + testServer = new TestServer(port); + + client = new AbstractClient("localhost", port, 1, Gson::new) { + @Override + public AbortingScheduler scheduler() { + return new AbortingScheduler(name()); + } + + @Override + public String name() { + return "test"; + } + }; + client.setTimeout(1); + } + + + @AfterAll + public static void tearDown() { + testServer.close(); + client.close(); + } + + private void assertError(Observable observable) { + try { + observable.blockingSubscribe(); + } + catch (RuntimeException ex) { + System.out.println("Got exception " + ex.getClass().getSimpleName() + " -- as expected!" ); + return; + } + Assertions.fail("Expected exception"); + } + @SneakyThrows + private Object timeout(Request request, Response response) { + Thread.sleep(5000); + return "yawn"; + } + @SneakyThrows + private Object error404(Request request, Response response) { + Spark.halt(404); + return ""; + } + + @Test + public void testGetTimeout() { + testServer.get(this::timeout); + + assertError(client.get(Context.internal(), "/get")); + } + + @Test + public void testPostTimeout() { + testServer.post(this::timeout); + + assertError(client.post(Context.internal(), "/post", "test")); + } + + @Test + public void testDeleteTimeout() { + testServer.delete(this::timeout); + + assertError(client.delete(Context.internal(), "/post")); + } + + @Test + public void testPost404() { + testServer.post(this::error404); + + assertError(client.post(Context.internal(), "/post", "test")); + } + @Test + public void testGet404() { + testServer.get(this::error404); + + assertError(client.get(Context.internal(), "/get")); + } + @Test + public void testDelete404() { + testServer.delete(this::error404); + + assertError(client.delete(Context.internal(), "/delete")); + } + + @Test + public void testGet() { + testServer.get((req, rsp) -> "Hello World"); + + assertEquals("Hello World", client.get(Context.internal(), "/get").blockingFirst()); + } + + @Test + public void testAcceptingUp() { + testServer.setReady(true); + assertTrue(client.isAccepting()); + } + + @Test + public void testAcceptingDown() { + testServer.setReady(false); + assertFalse(client.isAccepting()); + } + + @Test + public void testGetJson() { + testServer.get((req, rsp) -> new DummyObject(5, "23"), new Gson()::toJson); + + assertEquals(client.get(Context.internal(), "/get", DummyObject.class).blockingFirst(), + new DummyObject(5, "23")); + } + + + @Test + public void testDelete() { + testServer.delete((req, rsp) -> "Hello World"); + + assertTrue(client.delete(Context.internal(), "/delete").blockingFirst().isGood()); + } + + + @Test + public void testPost() { + List inbox = new ArrayList<>(); + testServer.post((req, rsp) -> { + inbox.add(gson.fromJson(req.body(), DummyObject.class)); + return "ok"; + }); + + client.post(Context.internal(), "/post", new DummyObject(5, "23")).blockingSubscribe(); + assertEquals(1, inbox.size()); + assertEquals(new DummyObject(5, "23"), inbox.get(0)); + } + + @Test + public void testPostGet() { + List inbox = new ArrayList<>(); + testServer.post((req, rsp) -> { + inbox.add(gson.fromJson(req.body(), DummyObject.class)); + return new DummyObject(1, "ret"); + }, gson::toJson); + + var ret = client.postGet(Context.internal(), "/post", new DummyObject(5, "23"), DummyObject.class).blockingFirst(); + assertEquals(1, inbox.size()); + assertEquals(new DummyObject(5, "23"), inbox.get(0)); + assertEquals(new DummyObject(1, "ret"), ret); + } +} diff --git a/common/service-client/src/test/java/nu/marginalia/client/TestServer.java b/common/service-client/src/test/java/nu/marginalia/client/TestServer.java new file mode 100644 index 00000000..cbfe1075 --- /dev/null +++ b/common/service-client/src/test/java/nu/marginalia/client/TestServer.java @@ -0,0 +1,59 @@ +package nu.marginalia.client; + +import spark.Request; +import spark.Response; +import spark.Spark; + +import java.util.function.BiFunction; +import java.util.function.Function; + +public class TestServer { + BiFunction onGet; + BiFunction onPost; + BiFunction onDelete; + + + boolean isReady; + + public TestServer(int port) { + Spark.port(port); + Spark.get("/internal/ping", (r,q) -> "pong"); + Spark.get("/internal/ready", this::ready); + Spark.get("/get", (request, response) -> onGet.apply(request, response)); + Spark.post("/post", (request, response) -> onPost.apply(request, response)); + Spark.delete("/delete", (request, response) -> onDelete.apply(request, response)); + } + + private Object ready(Request request, Response response) { + if (isReady) { + return ""; + } + else { + response.status(401); + return "bad"; + } + } + + public void close() { + Spark.stop(); + } + + public boolean isReady() { + return isReady; + } + + public void setReady(boolean ready) { + isReady = ready; + } + + public TestServer get(BiFunction onGet) { this.onGet = onGet; return this; } + public TestServer get(BiFunction onGet, Function transform) { + this.onGet = onGet.andThen(transform); + return this; + } + public TestServer delete(BiFunction onDelete) { this.onDelete = onDelete; return this; } + public TestServer post(BiFunction onPost) { this.onPost = onPost; return this; } + public TestServer post(BiFunction onPost, Function transform) { + this.onPost = onPost.andThen(transform); return this; + } +} diff --git a/common/service-discovery/build.gradle b/common/service-discovery/build.gradle new file mode 100644 index 00000000..b870a937 --- /dev/null +++ b/common/service-discovery/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'java' + +} + +repositories { + mavenLocal() + mavenCentral() +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} +dependencies { + implementation libs.bundles.slf4j + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} diff --git a/common/service-discovery/src/main/java/nu/marginalia/service/SearchServiceDescriptors.java b/common/service-discovery/src/main/java/nu/marginalia/service/SearchServiceDescriptors.java new file mode 100644 index 00000000..a1f2bf13 --- /dev/null +++ b/common/service-discovery/src/main/java/nu/marginalia/service/SearchServiceDescriptors.java @@ -0,0 +1,17 @@ +package nu.marginalia.service; + +import nu.marginalia.service.descriptor.ServiceDescriptor; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; + +import java.util.List; + +public class SearchServiceDescriptors { + public static ServiceDescriptors descriptors = new ServiceDescriptors( + List.of(new ServiceDescriptor(ServiceId.Api, 5004), + new ServiceDescriptor(ServiceId.Index, 5021), + new ServiceDescriptor(ServiceId.Search, 5023), + new ServiceDescriptor(ServiceId.Assistant, 5025), + new ServiceDescriptor(ServiceId.Dating, 5070), + new ServiceDescriptor(ServiceId.Explorer, 5071))); +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/HostsFile.java b/common/service-discovery/src/main/java/nu/marginalia/service/descriptor/HostsFile.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/HostsFile.java rename to common/service-discovery/src/main/java/nu/marginalia/service/descriptor/HostsFile.java index 1a0341d7..ef46749b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/HostsFile.java +++ b/common/service-discovery/src/main/java/nu/marginalia/service/descriptor/HostsFile.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.configuration; +package nu.marginalia.service.descriptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,7 +13,7 @@ import java.util.Map; * * */ public class HostsFile { - private final Map hostsMap = new HashMap<>(ServiceDescriptor.values().length); + private final Map hostsMap = new HashMap<>(); private static final Logger logger = LoggerFactory.getLogger(HostsFile.class); public HostsFile(Path fileName) throws IOException { var lines = Files.readAllLines(fileName); @@ -27,7 +27,7 @@ public class HostsFile { String hostName = parts[1]; try { - hostsMap.put(ServiceDescriptor.byName(descriptorName), hostName); + hostsMap.put(descriptorName, hostName); } catch (IllegalArgumentException ex) { logger.warn("Hosts file contains entry for unknown service {}", descriptorName); @@ -36,13 +36,10 @@ public class HostsFile { } public HostsFile() { - for (var sd : ServiceDescriptor.values()) { - hostsMap.put(sd, "localhost"); - } } public String getHost(ServiceDescriptor sd) { - return hostsMap.get(sd); + return hostsMap.getOrDefault(sd.name, sd.name); } } diff --git a/common/service-discovery/src/main/java/nu/marginalia/service/descriptor/ServiceDescriptor.java b/common/service-discovery/src/main/java/nu/marginalia/service/descriptor/ServiceDescriptor.java new file mode 100644 index 00000000..89f3fe22 --- /dev/null +++ b/common/service-discovery/src/main/java/nu/marginalia/service/descriptor/ServiceDescriptor.java @@ -0,0 +1,23 @@ +package nu.marginalia.service.descriptor; + +import nu.marginalia.service.id.ServiceId; + +public class ServiceDescriptor { + public final ServiceId id; + public final String name; + public final int port; + + public ServiceDescriptor(ServiceId id, int port) { + this.id = id; + this.name = id.name; + this.port = port; + } + + public String toString() { + return name; + } + + public String describeService() { + return String.format("%s", name); + } +} diff --git a/common/service-discovery/src/main/java/nu/marginalia/service/descriptor/ServiceDescriptors.java b/common/service-discovery/src/main/java/nu/marginalia/service/descriptor/ServiceDescriptors.java new file mode 100644 index 00000000..7fdbce0d --- /dev/null +++ b/common/service-discovery/src/main/java/nu/marginalia/service/descriptor/ServiceDescriptors.java @@ -0,0 +1,31 @@ +package nu.marginalia.service.descriptor; + +import nu.marginalia.service.id.ServiceId; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ServiceDescriptors { + private final Map descriptorsAll = new LinkedHashMap<>(); + + public ServiceDescriptors() { + + } + + public ServiceDescriptors(List descriptors) { + descriptors.forEach(d -> descriptorsAll.put(d.id, d)); + } + + public ServiceDescriptor[] values() { + return descriptorsAll.values().toArray(ServiceDescriptor[]::new); + } + + public ServiceDescriptor forId(ServiceId id) { + return Objects.requireNonNull(descriptorsAll.get(id), + "No service descriptor defined for " + id + " -- did you forget to " + + "bind(ServiceDescriptors.class).toInstance(SearchServiceDescriptors.descriptors); ?"); + } + +} diff --git a/common/service-discovery/src/main/java/nu/marginalia/service/id/ServiceId.java b/common/service-discovery/src/main/java/nu/marginalia/service/id/ServiceId.java new file mode 100644 index 00000000..92ffb4a7 --- /dev/null +++ b/common/service-discovery/src/main/java/nu/marginalia/service/id/ServiceId.java @@ -0,0 +1,25 @@ +package nu.marginalia.service.id; + +public enum ServiceId { + + Assistant("assistant-service"), + Api("api-service"), + Search("search-service"), + Index("index-service"), + + Dating("dating-service"), + Explorer("explorer-service"), + + Other_Auth("auth"), + Other_Memex("memex"), + + + Other_ResourceStore("resource-store"), + Other_Renderer("renderer"), + Other_PodcastScraper("podcast-scraper"); + + public final String name; + ServiceId(String name) { + this.name = name; + } +} diff --git a/common/service/build.gradle b/common/service/build.gradle new file mode 100644 index 00000000..45f9a740 --- /dev/null +++ b/common/service/build.gradle @@ -0,0 +1,35 @@ +plugins { + id 'java' +} + + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':common:service-client') + implementation project(':common:service-discovery') + implementation project(':libraries:misc') + + implementation libs.lombok + annotationProcessor libs.lombok + + implementation libs.spark + implementation libs.guice + implementation libs.rxjava + + implementation libs.bundles.prometheus + implementation libs.bundles.slf4j + implementation libs.bucket4j + + testImplementation libs.bundles.slf4j.test + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + +} \ No newline at end of file diff --git a/common/service/src/main/java/nu/marginalia/service/MainClass.java b/common/service/src/main/java/nu/marginalia/service/MainClass.java new file mode 100644 index 00000000..26343581 --- /dev/null +++ b/common/service/src/main/java/nu/marginalia/service/MainClass.java @@ -0,0 +1,62 @@ +package nu.marginalia.service; + +import io.prometheus.client.hotspot.DefaultExports; +import io.reactivex.rxjava3.exceptions.UndeliverableException; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.client.exception.NetworkException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + +public abstract class MainClass { + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public MainClass() { + RxJavaPlugins.setErrorHandler(this::handleError); + } + + protected void handleError(Throwable ex) { + if (ex instanceof UndeliverableException) { + ex = ex.getCause(); + } + + if (ex instanceof SocketTimeoutException) { + logger.warn("SocketTimeoutException"); + } + else if (ex instanceof UnknownHostException) { + logger.warn("UnknownHostException"); + } + else if (ex instanceof NetworkException) { + logger.warn("NetworkException", ex); + } + else { + logger.error("Uncaught exception", ex); + } + } + + + protected static void init(ServiceId id, String... args) { + + System.setProperty("log4j2.isThreadContextMapInheritable", "true"); + System.setProperty("isThreadContextMapInheritable", "true"); + System.setProperty("service-name", id.name); + + initJdbc(); + initPrometheus(); + } + + private static void initJdbc() { + // This looks weird, but it's just for running the static block + // in the driver class so that it registers itself + + new org.mariadb.jdbc.Driver(); + } + + private static void initPrometheus() { + DefaultExports.initialize(); + } + +} diff --git a/common/service/src/main/java/nu/marginalia/service/module/ConfigurationModule.java b/common/service/src/main/java/nu/marginalia/service/module/ConfigurationModule.java new file mode 100644 index 00000000..a0d763d0 --- /dev/null +++ b/common/service/src/main/java/nu/marginalia/service/module/ConfigurationModule.java @@ -0,0 +1,35 @@ +package nu.marginalia.service.module; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; + +import java.util.Objects; + +public class ConfigurationModule extends AbstractModule { + private static final String SERVICE_NAME = System.getProperty("service-name"); + private final ServiceDescriptors descriptors; + private final ServiceId id; + + public ConfigurationModule(ServiceDescriptors descriptors, ServiceId id) { + this.descriptors = descriptors; + this.id = id; + } + + public void configure() { + bind(ServiceDescriptors.class).toInstance(descriptors); + bind(String.class).annotatedWith(Names.named("service-name")).toInstance(Objects.requireNonNull(SERVICE_NAME)); + bind(String.class).annotatedWith(Names.named("service-host")).toInstance(System.getProperty("service-host", "127.0.0.1")); + bind(Integer.class).annotatedWith(Names.named("service-port")).toInstance(descriptors.forId(id).port); + } + + @Provides + @Named("metrics-server-port") + public Integer provideMetricsServerPort(@Named("service-port") Integer servicePort) { + return servicePort + 1000; + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/DatabaseModule.java b/common/service/src/main/java/nu/marginalia/service/module/DatabaseModule.java similarity index 70% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/DatabaseModule.java rename to common/service/src/main/java/nu/marginalia/service/module/DatabaseModule.java index a61a4f4b..d189a708 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/DatabaseModule.java +++ b/common/service/src/main/java/nu/marginalia/service/module/DatabaseModule.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.configuration.module; +package nu.marginalia.service.module; import com.google.inject.AbstractModule; import com.google.inject.Provides; @@ -6,17 +6,15 @@ import com.google.inject.Singleton; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.WmsaHome; -import org.h2.tools.RunScript; import org.mariadb.jdbc.Driver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import java.util.Properties; public class DatabaseModule extends AbstractModule { @@ -35,7 +33,7 @@ public class DatabaseModule extends AbstractModule { } private Properties loadDbProperties() { - Path propDir = WmsaHome.getHomePath().resolve("conf/db.properties"); + Path propDir = getHomePath().resolve("conf/db.properties"); if (!Files.isRegularFile(propDir)) { throw new IllegalStateException("Database properties file " + propDir + " does not exist"); } @@ -56,22 +54,27 @@ public class DatabaseModule extends AbstractModule { } + public static Path getHomePath() { + var retStr = Optional.ofNullable(System.getenv("WMSA_HOME")).orElse("/var/lib/wmsa"); + + var ret = Path.of(retStr); + if (!Files.isDirectory(ret)) { + throw new IllegalStateException("Could not find WMSA_HOME, either set environment variable or ensure /var/lib/wmsa exists"); + } + return ret; + } + + @SneakyThrows @Singleton @Provides public HikariDataSource provideConnection() { - if (Boolean.getBoolean("data-store-h2")) { - return getH2(); - } - else { - return getMariaDB(); - } - + return getMariaDB(); } @SneakyThrows private HikariDataSource getMariaDB() { - var connStr = dbProperties.getProperty(DB_CONN_KEY); + var connStr = System.getProperty("db.overrideJdbc", dbProperties.getProperty(DB_CONN_KEY)); try { HikariConfig config = new HikariConfig(); @@ -94,22 +97,4 @@ public class DatabaseModule extends AbstractModule { } } - - @SneakyThrows - private HikariDataSource getH2() { - HikariConfig config = new HikariConfig(); - config.setJdbcUrl("jdbc:h2:~/wmsa-db"); - config.setUsername("wmsa"); - config.setPassword(""); - - var ds = new HikariDataSource(config); - - try (var stream = ClassLoader.getSystemResourceAsStream("sql/data-store-init.sql")) { - RunScript.execute(ds.getConnection(), new InputStreamReader(stream)); - } - try (var stream = ClassLoader.getSystemResourceAsStream("sql/edge-crawler-cache.sql")) { - RunScript.execute(ds.getConnection(), new InputStreamReader(stream)); - } - return ds; - } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/LoggerConfiguration.java b/common/service/src/main/java/nu/marginalia/service/module/LoggerConfiguration.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/LoggerConfiguration.java rename to common/service/src/main/java/nu/marginalia/service/module/LoggerConfiguration.java index 9557b433..8f651da4 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/LoggerConfiguration.java +++ b/common/service/src/main/java/nu/marginalia/service/module/LoggerConfiguration.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.configuration.module; +package nu.marginalia.service.module; import com.google.inject.name.Named; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/MetricsPortProvider.java b/common/service/src/main/java/nu/marginalia/service/module/MetricsPortProvider.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/MetricsPortProvider.java rename to common/service/src/main/java/nu/marginalia/service/module/MetricsPortProvider.java index cca5bbfc..1f75fa9f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/MetricsPortProvider.java +++ b/common/service/src/main/java/nu/marginalia/service/module/MetricsPortProvider.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.configuration.module; +package nu.marginalia.service.module; import com.google.inject.name.Named; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Initialization.java b/common/service/src/main/java/nu/marginalia/service/server/Initialization.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Initialization.java rename to common/service/src/main/java/nu/marginalia/service/server/Initialization.java index 6b146672..c7a857ea 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Initialization.java +++ b/common/service/src/main/java/nu/marginalia/service/server/Initialization.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.configuration.server; +package nu.marginalia.service.server; import com.google.inject.Singleton; import lombok.SneakyThrows; @@ -22,11 +22,6 @@ public class Initialization { initialized = true; notifyAll(); } - - if (Boolean.getBoolean("go-no-go")) { - logger.info("Self-test OK"); - System.exit(0); - } } public boolean isReady() { @@ -41,7 +36,7 @@ public class Initialization { while (!initialized) { wait(); } - return initialized; + return true; } } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/MetricsServer.java b/common/service/src/main/java/nu/marginalia/service/server/MetricsServer.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/MetricsServer.java rename to common/service/src/main/java/nu/marginalia/service/server/MetricsServer.java index c8da5e97..1822b465 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/MetricsServer.java +++ b/common/service/src/main/java/nu/marginalia/service/server/MetricsServer.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.configuration.server; +package nu.marginalia.service.server; import com.google.inject.Inject; import com.google.inject.name.Named; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/RateLimiter.java b/common/service/src/main/java/nu/marginalia/service/server/RateLimiter.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/RateLimiter.java rename to common/service/src/main/java/nu/marginalia/service/server/RateLimiter.java index 06a6131a..f9de1cb5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/RateLimiter.java +++ b/common/service/src/main/java/nu/marginalia/service/server/RateLimiter.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.configuration.server; +package nu.marginalia.service.server; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket; -import io.github.bucket4j.Bucket4j; import io.github.bucket4j.Refill; import io.reactivex.rxjava3.schedulers.Schedulers; +import nu.marginalia.client.Context; import java.time.Duration; import java.util.Map; @@ -49,13 +49,11 @@ public class RateLimiter { } public boolean isAllowed(Context ctx) { - final Optional maybeIp = ctx.getIpHash(); - - if (maybeIp.isEmpty()) { // Internal server->server request + if (!ctx.isPublic()) { // Internal server->server request return true; } - return bucketMap.computeIfAbsent(maybeIp.get(), + return bucketMap.computeIfAbsent(ctx.getContextId(), (ip) -> createBucket()).tryConsume(1); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Service.java b/common/service/src/main/java/nu/marginalia/service/server/Service.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Service.java rename to common/service/src/main/java/nu/marginalia/service/server/Service.java index 9674611f..e5c04877 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Service.java +++ b/common/service/src/main/java/nu/marginalia/service/server/Service.java @@ -1,10 +1,9 @@ -package nu.marginalia.wmsa.configuration.server; +package nu.marginalia.service.server; import com.google.common.base.Strings; import io.prometheus.client.Counter; -import nu.marginalia.wmsa.client.exception.MessagingException; -import org.apache.http.HttpStatus; -import org.apache.logging.log4j.ThreadContext; +import nu.marginalia.client.Context; +import nu.marginalia.client.exception.MessagingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.Marker; @@ -17,6 +16,8 @@ import java.util.Optional; public class Service { private final Logger logger = LoggerFactory.getLogger(getClass()); + + // Marker for filtering out sensitive content from the persistent logs private final Marker httpMarker = MarkerFactory.getMarker("HTTP"); private final Initialization initialization; @@ -53,8 +54,8 @@ public class Service { configureStaticFiles.run(); - Spark.before(this::filterPublicRequests); Spark.before(this::auditRequestIn); + Spark.before(this::filterPublicRequests); Spark.after(this::auditRequestOut); Spark.exception(MessagingException.class, this::handleException); @@ -86,14 +87,14 @@ public class Service { if (!request.pathInfo().startsWith("/public/")) { logger.warn(httpMarker, "External connection to internal API: {} -> {} {}", context, request.requestMethod(), request.pathInfo()); - Spark.halt(HttpStatus.SC_FORBIDDEN); + Spark.halt(403); } String url = request.pathInfo(); if (request.queryString() != null) { url = url + "?" + request.queryString(); } - logger.info(httpMarker, "PUBLIC {}: {} {}", Context.fromRequest(request).getIpHash().orElse("?"), request.requestMethod(), url); + logger.info(httpMarker, "PUBLIC {}: {} {}", Context.fromRequest(request).getContextId(), request.requestMethod(), url); } private Object isInitialized(Request request, Response response) { @@ -101,7 +102,7 @@ public class Service { return "ok"; } else { - response.status(HttpStatus.SC_FAILED_DEPENDENCY); + response.status(424); return "bad"; } } @@ -115,21 +116,21 @@ public class Service { return "ok"; } else { - response.status(HttpStatus.SC_FAILED_DEPENDENCY); + response.status(424); return "bad"; } } private void auditRequestIn(Request request, Response response) { - request_counter.labels(serviceName).inc(); - // Paint context - if (!Strings.isNullOrEmpty(request.headers(Context.CONTEXT_HEADER))) { - Context.fromRequest(request); - } + paintThreadName(request, "req:"); + + request_counter.labels(serviceName).inc(); } + private void auditRequestOut(Request request, Response response) { - ThreadContext.clearMap(); + + paintThreadName(request, "rsp:"); if (response.status() < 400) { request_counter_good.labels(serviceName).inc(); @@ -143,6 +144,11 @@ public class Service { } } + private void paintThreadName(Request request, String prefix) { + var ctx = Context.fromRequest(request); + Thread.currentThread().setName(prefix + ctx.getContextId()); + } + private void handleException(Exception ex, Request request, Response response) { request_counter_err.labels(serviceName).inc(); if (ex instanceof MessagingException) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/StaticResources.java b/common/service/src/main/java/nu/marginalia/service/server/StaticResources.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/StaticResources.java rename to common/service/src/main/java/nu/marginalia/service/server/StaticResources.java index a3c2f756..332b9a55 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/StaticResources.java +++ b/common/service/src/main/java/nu/marginalia/service/server/StaticResources.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.resource_store; +package nu.marginalia.service.server; import lombok.SneakyThrows; import spark.Request; diff --git a/common/service/src/main/resources/log4j2.properties b/common/service/src/main/resources/log4j2.properties new file mode 100644 index 00000000..66d688b0 --- /dev/null +++ b/common/service/src/main/resources/log4j2.properties @@ -0,0 +1,28 @@ +log4j2.isThreadContextMapInheritable=true +status = info +appender.console.type = Console +appender.console.name = LogToConsole +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{HH:mm:ss,SSS} %style{%-8markerSimpleName}{FG_Cyan} %highlight{%-5level}{FATAL=red, ERROR=red, WARN=yellow} %-24t %-20c{1} -- %msg{nolookups}%n +appender.rolling.type = RollingFile +appender.rolling.name = RollingFile +appender.rolling.fileName = /var/log/wmsa/wmsa-${sys:service-name}.log +appender.rolling.filePattern = /var/log/wmsa/wmsa-${sys:service-name}-log-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz +appender.rolling.layout.pattern = %-5level %d{yyyy-MM-dd HH:mm:ss,SSS} %-20t %-20c{1}: %msg{nolookups}%n +appender.rolling.layout.type = PatternLayout +appender.rolling.policies.type = Policies +appender.rolling.policies.size.type = SizeBasedTriggeringPolicy +appender.rolling.policies.size.size=10MB +appender.rolling.strategy.type = DefaultRolloverStrategy +appender.rolling.strategy.max = 10 +appender.rolling.filter.query.type = MarkerFilter +appender.rolling.filter.query.onMismatch=ACCEPT +appender.rolling.filter.query.onMatch=DENY +appender.rolling.filter.query.marker=QUERY +appender.rolling.filter.http.type = MarkerFilter +appender.rolling.filter.http.onMismatch=ACCEPT +appender.rolling.filter.http.onMatch=DENY +appender.rolling.filter.http.marker=HTTP +rootLogger.level = info +rootLogger.appenderRef.console.ref = LogToConsole +rootLogger.appenderRef.rolling.ref = RollingFile \ No newline at end of file diff --git a/crawl/common/build.gradle b/crawl/common/build.gradle new file mode 100644 index 00000000..90b18eab --- /dev/null +++ b/crawl/common/build.gradle @@ -0,0 +1,55 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:config') + implementation project(':libraries:misc') + implementation project(':crawl:crawling-model') + + implementation libs.notnull + implementation libs.lombok + annotationProcessor libs.lombok + + implementation libs.bundles.gson + implementation libs.rxjava + implementation libs.bundles.slf4j + testImplementation libs.bundles.slf4j.test + + implementation libs.guava + implementation libs.guice + + implementation libs.snakeyaml + implementation libs.jsoup + implementation libs.zstd + + implementation libs.commons.net + + implementation libs.opencsv + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/AbortMonitor.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/AbortMonitor.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/AbortMonitor.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/AbortMonitor.java index e76ed65d..c23ab5db 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/AbortMonitor.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/AbortMonitor.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling.common; import lombok.SneakyThrows; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/TaskStats.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/TaskStats.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/TaskStats.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/TaskStats.java index 7c0384bb..4a75bcd4 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/TaskStats.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/TaskStats.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting; +package nu.marginalia.crawling.common; public class TaskStats { private final long[] taskTimes; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/WorkLog.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/WorkLog.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/WorkLog.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/WorkLog.java index fb5bf5b2..092ca66a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/WorkLog.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/WorkLog.java @@ -1,9 +1,10 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling.common; import com.google.errorprone.annotations.MustBeClosed; -import nu.marginalia.wmsa.edge.crawling.model.CrawlLogEntry; +import nu.marginalia.crawling.model.CrawlLogEntry; import org.apache.logging.log4j.util.Strings; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -27,9 +28,9 @@ public class WorkLog implements AutoCloseable { writeLogEntry("# Starting WorkLog @ " + LocalDateTime.now()); } - public static void readLog(Path logFile, Consumer entryConsumer) { + public static void readLog(Path logFile, Consumer entryConsumer) throws FileNotFoundException { if (!Files.exists(logFile)) { - return; + throw new FileNotFoundException("Log file not found " + logFile); } try (var entries = streamLog(logFile)) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/GeoIpBlocklist.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/GeoIpBlocklist.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/GeoIpBlocklist.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/GeoIpBlocklist.java index eca3b73e..7fe3e9cc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/GeoIpBlocklist.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/GeoIpBlocklist.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.crawling.blocklist; +package nu.marginalia.crawling.common.blocklist; import com.google.inject.Singleton; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvValidationException; import lombok.AllArgsConstructor; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.WmsaHome; +import nu.marginalia.model.EdgeDomain; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/InetAddressCache.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/InetAddressCache.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/InetAddressCache.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/InetAddressCache.java index 0bef701d..b1722c42 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/InetAddressCache.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/InetAddressCache.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.crawling.blocklist; +package nu.marginalia.crawling.common.blocklist; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.model.EdgeDomain; import java.net.InetAddress; import java.util.concurrent.ExecutionException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/IpBlockList.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/IpBlockList.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/IpBlockList.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/IpBlockList.java index 27c8f6dd..c5a42137 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/IpBlockList.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/IpBlockList.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.crawling.blocklist; +package nu.marginalia.crawling.common.blocklist; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.model.EdgeDomain; import org.apache.commons.net.util.SubnetUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/UrlBlocklist.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/UrlBlocklist.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/UrlBlocklist.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/UrlBlocklist.java index 1a00e161..b849b246 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/blocklist/UrlBlocklist.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/blocklist/UrlBlocklist.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.crawling.blocklist; +package nu.marginalia.crawling.common.blocklist; -import nu.marginalia.util.gregex.GuardedRegexFactory; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.gregex.GuardedRegexFactory; import java.util.ArrayList; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/LinkParser.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/link/LinkParser.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/LinkParser.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/link/LinkParser.java index 45611e08..024a47b3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/LinkParser.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/link/LinkParser.java @@ -1,9 +1,10 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.crawling.common.link; import com.google.common.base.CharMatcher; import com.google.common.base.Strings; import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.util.QueryParams; import org.jetbrains.annotations.Contract; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlPlanLoader.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/plan/CrawlPlanLoader.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlPlanLoader.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/plan/CrawlPlanLoader.java index f4060aff..239ed7ce 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlPlanLoader.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/plan/CrawlPlanLoader.java @@ -1,6 +1,5 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling.common.plan; -import nu.marginalia.wmsa.edge.model.EdgeCrawlPlan; import org.yaml.snakeyaml.Yaml; import java.io.FileReader; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerSpecificationLoader.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/plan/CrawlerSpecificationLoader.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerSpecificationLoader.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/plan/CrawlerSpecificationLoader.java index 59ad4155..619a52eb 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerSpecificationLoader.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/plan/CrawlerSpecificationLoader.java @@ -1,9 +1,12 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling.common.plan; import com.github.luben.zstd.ZstdInputStream; import com.google.gson.Gson; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.edge.crawling.model.CrawlingSpecification; +import com.google.gson.JsonStreamParser; +import com.google.gson.stream.JsonReader; +import nu.marginalia.crawling.common.AbortMonitor; +import nu.marginalia.crawling.model.CrawlingSpecification; +import nu.marginalia.model.gson.GsonFactory; import org.apache.logging.log4j.util.Strings; import java.io.BufferedReader; @@ -18,15 +21,9 @@ public class CrawlerSpecificationLoader { public static void readInputSpec(Path inputSpec, Consumer consumer) { try (var inputStream = new BufferedReader(new InputStreamReader(new ZstdInputStream(new FileInputStream(inputSpec.toFile()))))) { - - for (;;) { - var line = inputStream.readLine(); - if (line == null || !AbortMonitor.getInstance().isAlive()) - break; - - if (Strings.isNotBlank(line)) { - consumer.accept(gson.fromJson(line, CrawlingSpecification.class)); - } + var parser = new JsonStreamParser(inputStream); + while (parser.hasNext()) { + consumer.accept(gson.fromJson(parser.next(), CrawlingSpecification.class)); } } catch (IOException e) { e.printStackTrace(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/EdgeCrawlPlan.java b/crawl/common/src/main/java/nu/marginalia/crawling/common/plan/EdgeCrawlPlan.java similarity index 78% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/EdgeCrawlPlan.java rename to crawl/common/src/main/java/nu/marginalia/crawling/common/plan/EdgeCrawlPlan.java index 037c12f3..4b025a40 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/EdgeCrawlPlan.java +++ b/crawl/common/src/main/java/nu/marginalia/crawling/common/plan/EdgeCrawlPlan.java @@ -1,17 +1,19 @@ -package nu.marginalia.wmsa.edge.model; +package nu.marginalia.crawling.common.plan; import com.google.errorprone.annotations.MustBeClosed; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import lombok.ToString; -import nu.marginalia.wmsa.edge.crawling.CrawledDomainReader; -import nu.marginalia.wmsa.edge.crawling.CrawlerSpecificationLoader; -import nu.marginalia.wmsa.edge.crawling.WorkLog; -import nu.marginalia.wmsa.edge.crawling.model.CrawlLogEntry; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDomain; -import nu.marginalia.wmsa.edge.crawling.model.CrawlingSpecification; +import nu.marginalia.crawling.common.WorkLog; +import nu.marginalia.crawling.io.CrawledDomainReader; +import nu.marginalia.crawling.model.CrawlLogEntry; +import nu.marginalia.crawling.model.CrawledDomain; +import nu.marginalia.crawling.model.CrawlingSpecification; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; import java.util.Iterator; @@ -21,10 +23,13 @@ import java.util.stream.Stream; @AllArgsConstructor @NoArgsConstructor @ToString public class EdgeCrawlPlan { + private final Logger logger = LoggerFactory.getLogger(getClass()); public String jobSpec; public WorkDir crawl; public WorkDir process; + private static String rootDirRewrite = System.getProperty("crawl.rootDirRewrite"); + public Path getJobSpec() { return Path.of(jobSpec); } @@ -35,13 +40,22 @@ public class EdgeCrawlPlan { public String logName; public Path getDir() { - return Path.of(dir); + return Path.of(rewrite(dir)); } public Path getLogFile() { - return Path.of(dir).resolve(logName); + return Path.of(rewrite(dir)).resolve(logName); } } + private static String rewrite(String dir) { + if (rootDirRewrite == null) { + return dir; + } + String[] parts = rootDirRewrite.split(":"); + + return dir.replace(parts[0], parts[1]); + } + public Path getCrawledFilePath(String fileName) { String sp1 = fileName.substring(0, 2); String sp2 = fileName.substring(2, 4); @@ -66,10 +80,10 @@ public class EdgeCrawlPlan { CrawlerSpecificationLoader.readInputSpec(getJobSpec(), consumer); } - public void forEachCrawlingLogEntry(Consumer consumer) { + public void forEachCrawlingLogEntry(Consumer consumer) throws FileNotFoundException { WorkLog.readLog(this.crawl.getLogFile(), consumer); } - public void forEachProcessingLogEntry(Consumer consumer) { + public void forEachProcessingLogEntry(Consumer consumer) throws FileNotFoundException { WorkLog.readLog(this.process.getLogFile(), consumer); } @@ -84,6 +98,8 @@ public class EdgeCrawlPlan { .forEach(consumer); } catch (IOException ex) { + logger.warn("Failed to read domains", ex); + throw new RuntimeException(ex); } } @@ -99,6 +115,8 @@ public class EdgeCrawlPlan { .forEach(consumer); } catch (IOException ex) { + logger.warn("Failed to read domains", ex); + throw new RuntimeException(ex); } } diff --git a/marginalia_nu/src/e2e/resources/log4j2.properties b/crawl/common/src/main/resources/log4j2.properties similarity index 57% rename from marginalia_nu/src/e2e/resources/log4j2.properties rename to crawl/common/src/main/resources/log4j2.properties index 9c2dbefd..f6768d3e 100644 --- a/marginalia_nu/src/e2e/resources/log4j2.properties +++ b/crawl/common/src/main/resources/log4j2.properties @@ -1,15 +1,10 @@ - +log4j2.isThreadContextMapInheritable=true status = info - appender.console.type = Console appender.console.name = LogToConsole appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %highlight{%-5level}{FATAL=red, ERROR=red, WARN=yellow} %c{1}- %msg%n - -logger.console.name = nu.marginalia -logger.console.level = debug -logger.console.additivity = false -logger.console.appenderRef.rolling.ref = LogToConsole - +appender.console.layout.pattern = %highlight{%-5level}{FATAL=red, ERROR=red, WARN=yellow} %c{1}- %msg{nolookups}%n +appender.console.filter.http.type = MarkerFilter rootLogger.level = info rootLogger.appenderRef.console.ref = LogToConsole +#rootLogger.appenderRef.http.ref = LogHttpTraffic \ No newline at end of file diff --git a/crawl/converting-model/build.gradle b/crawl/converting-model/build.gradle new file mode 100644 index 00000000..4b323894 --- /dev/null +++ b/crawl/converting-model/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':api:index-api') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':libraries:language-processing') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.notnull + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/Instruction.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/Instruction.java similarity index 68% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/Instruction.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/Instruction.java index 7f40edf6..4964c9b1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/Instruction.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/Instruction.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.interpreter; +package nu.marginalia.converting.instruction; public interface Instruction { void apply(Interpreter interpreter); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/InstructionTag.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/InstructionTag.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/InstructionTag.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/InstructionTag.java index 398ad430..9b03794b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/InstructionTag.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/InstructionTag.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.converting.interpreter; +package nu.marginalia.converting.instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.*; +import nu.marginalia.converting.instruction.instructions.*; public enum InstructionTag { diff --git a/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/Interpreter.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/Interpreter.java new file mode 100644 index 00000000..e4efa9b9 --- /dev/null +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/Interpreter.java @@ -0,0 +1,25 @@ +package nu.marginalia.converting.instruction; + +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.model.crawl.DocumentKeywords; +import nu.marginalia.converting.instruction.instructions.DomainLink; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocument; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocumentWithError; + +public interface Interpreter { + void loadUrl(EdgeUrl[] url); + void loadDomain(EdgeDomain[] domain); + void loadRssFeed(EdgeUrl[] rssFeed); + void loadDomainLink(DomainLink[] links); + + void loadProcessedDomain(EdgeDomain domain, EdgeDomainIndexingState state, String ip); + void loadProcessedDocument(LoadProcessedDocument loadProcessedDocument); + void loadProcessedDocumentWithError(LoadProcessedDocumentWithError loadProcessedDocumentWithError); + + void loadKeywords(EdgeUrl url, EdgePageDocumentsMetadata metadata, DocumentKeywords words); + + void loadDomainRedirect(DomainLink link); +} diff --git a/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/DomainLink.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/DomainLink.java new file mode 100644 index 00000000..c33f9892 --- /dev/null +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/DomainLink.java @@ -0,0 +1,6 @@ +package nu.marginalia.converting.instruction.instructions; + +import nu.marginalia.model.EdgeDomain; + +public record DomainLink(EdgeDomain from, EdgeDomain to) { +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadDomain.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadDomain.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadDomain.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadDomain.java index 7cf88b06..f1f361a1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadDomain.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadDomain.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.converting.instruction.instructions; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.EdgeDomain; import java.util.Arrays; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadDomainLink.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadDomainLink.java similarity index 65% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadDomainLink.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadDomainLink.java index 2d302ddf..9a5b85f8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadDomainLink.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadDomainLink.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.converting.instruction.instructions; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; +import nu.marginalia.converting.instruction.Interpreter; import java.util.Arrays; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadDomainRedirect.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadDomainRedirect.java similarity index 63% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadDomainRedirect.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadDomainRedirect.java index 452d990e..5bd357ab 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadDomainRedirect.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadDomainRedirect.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.converting.instruction.instructions; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; +import nu.marginalia.converting.instruction.Interpreter; public record LoadDomainRedirect(DomainLink links) implements Instruction { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadKeywords.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadKeywords.java similarity index 57% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadKeywords.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadKeywords.java index 106f02b7..9ff24a5a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadKeywords.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadKeywords.java @@ -1,10 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.converting.instruction.instructions; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.model.crawl.DocumentKeywords; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.EdgeUrl; public record LoadKeywords(EdgeUrl url, EdgePageDocumentsMetadata metadata, DocumentKeywords words) implements Instruction { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadProcessedDocument.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadProcessedDocument.java similarity index 64% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadProcessedDocument.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadProcessedDocument.java index 3f65f7af..6c56a100 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadProcessedDocument.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadProcessedDocument.java @@ -1,13 +1,12 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.converting.instruction.instructions; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; -import nu.marginalia.wmsa.edge.model.crawl.EdgeUrlState; - -import javax.annotation.Nullable; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeUrlState; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.EdgeUrl; +import org.jetbrains.annotations.Nullable; public record LoadProcessedDocument(EdgeUrl url, diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadProcessedDocumentWithError.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadProcessedDocumentWithError.java similarity index 57% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadProcessedDocumentWithError.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadProcessedDocumentWithError.java index 8d37cb64..b798ac49 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadProcessedDocumentWithError.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadProcessedDocumentWithError.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.converting.instruction.instructions; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeUrlState; +import nu.marginalia.model.crawl.EdgeUrlState; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.EdgeUrl; public record LoadProcessedDocumentWithError(EdgeUrl url, diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadProcessedDomain.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadProcessedDomain.java similarity index 52% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadProcessedDomain.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadProcessedDomain.java index 2b1fd631..b7784a2b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadProcessedDomain.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadProcessedDomain.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.converting.instruction.instructions; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.EdgeDomain; public record LoadProcessedDomain(EdgeDomain domain, EdgeDomainIndexingState state, String ip) implements Instruction { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadRssFeed.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadRssFeed.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadRssFeed.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadRssFeed.java index d4dbe0eb..f6c8d7b5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadRssFeed.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadRssFeed.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.converting.instruction.instructions; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.EdgeUrl; import java.util.Arrays; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadUrl.java b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadUrl.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadUrl.java rename to crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadUrl.java index 50c2b34c..d126a515 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/LoadUrl.java +++ b/crawl/converting-model/src/main/java/nu/marginalia/converting/instruction/instructions/LoadUrl.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; +package nu.marginalia.converting.instruction.instructions; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.EdgeUrl; import java.util.Arrays; diff --git a/crawl/converting-process/build.gradle b/crawl/converting-process/build.gradle new file mode 100644 index 00000000..5c016842 --- /dev/null +++ b/crawl/converting-process/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'application' + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +application { + mainClass = 'nu.marginalia.converting.ConverterMain' + applicationName = 'converter-process' +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:service') + implementation project(':common:config') + implementation project(':libraries:misc') + implementation project(':api:index-api') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':libraries:language-processing') + implementation project(':crawl:common') + implementation project(':crawl:converting-model') + implementation project(':crawl:crawling-model') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.notnull + + implementation libs.jsoup + + implementation libs.guice + implementation libs.gson + + implementation libs.zstd + + implementation libs.bundles.mariadb + + implementation libs.trove + implementation libs.fastutil + + implementation libs.crawlercommons + + implementation libs.commons.lang3 + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + maxHeapSize = "8G" + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConversionLog.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/ConversionLog.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConversionLog.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/ConversionLog.java index b05ec3ad..564798f8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConversionLog.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/ConversionLog.java @@ -1,15 +1,15 @@ -package nu.marginalia.wmsa.edge.converting; +package nu.marginalia.converting; import com.github.luben.zstd.ZstdOutputStream; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocument; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocumentWithError; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.crawl.DocumentKeywords; +import nu.marginalia.converting.instruction.instructions.DomainLink; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocument; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocumentWithError; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; import java.io.BufferedOutputStream; import java.io.IOException; @@ -21,9 +21,6 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; public class ConversionLog implements AutoCloseable, Interpreter { - - - private final PrintWriter writer; public ConversionLog(Path rootDir) throws IOException { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConverterMain.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/ConverterMain.java similarity index 51% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConverterMain.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/ConverterMain.java index 8b941d92..1c3d9776 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConverterMain.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/ConverterMain.java @@ -1,17 +1,17 @@ -package nu.marginalia.wmsa.edge.converting; +package nu.marginalia.converting; import com.google.gson.Gson; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; +import nu.marginalia.crawling.common.WorkLog; +import nu.marginalia.crawling.common.plan.CrawlPlanLoader; +import nu.marginalia.crawling.common.plan.EdgeCrawlPlan; +import nu.marginalia.converting.compiler.InstructionsCompiler; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.processor.DomainProcessor; +import nu.marginalia.crawling.model.CrawledDomain; import nu.marginalia.util.ParallelPipe; -import nu.marginalia.wmsa.edge.converting.compiler.InstructionsCompiler; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.processor.DomainProcessor; -import nu.marginalia.wmsa.edge.crawling.CrawlPlanLoader; -import nu.marginalia.wmsa.edge.crawling.WorkLog; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDomain; -import nu.marginalia.wmsa.edge.model.EdgeCrawlPlan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,7 +22,7 @@ import java.util.List; public class ConverterMain { private final Logger logger = LoggerFactory.getLogger(getClass()); - private final LoadInstructionWriter instructionWriter; + private final InstructionWriter instructionWriter; public static void main(String... args) throws IOException { @@ -50,24 +50,36 @@ public class ConverterMain { try (WorkLog processLog = plan.createProcessWorkLog(); ConversionLog log = new ConversionLog(plan.process.getDir())) { - instructionWriter = new LoadInstructionWriter(log, plan.process.getDir(), gson); - var pipe = new ParallelPipe("Crawler", 16, 4, 2) { + instructionWriter = new InstructionWriter(log, plan.process.getDir(), gson); + var pipe = new ParallelPipe("Converter", 16, 4, 2) { @Override protected ProcessingInstructions onProcess(CrawledDomain domainData) { - var processed = processor.process(domainData); - var compiled = compiler.compile(processed); + Thread.currentThread().setName("Converter:Processor["+domainData.domain+"] - " + domainData.size()); + try { + var processed = processor.process(domainData); + var compiled = compiler.compile(processed); - return new ProcessingInstructions(domainData.id, compiled); + return new ProcessingInstructions(domainData.id, compiled); + } + finally { + Thread.currentThread().setName("Converter:Processor[IDLE]"); + } } @Override protected void onReceive(ProcessingInstructions processedInstructions) throws IOException { - var instructions = processedInstructions.instructions; - instructions.removeIf(Instruction::isNoOp); + Thread.currentThread().setName("Converter:Receiver["+processedInstructions.id+"]"); + try { + var instructions = processedInstructions.instructions; + instructions.removeIf(Instruction::isNoOp); - String where = instructionWriter.accept(processedInstructions.id, instructions); - processLog.setJobToFinished(processedInstructions.id, where, instructions.size()); + String where = instructionWriter.accept(processedInstructions.id, instructions); + processLog.setJobToFinished(processedInstructions.id, where, instructions.size()); + } + finally { + Thread.currentThread().setName("Converter:Receiver[IDLE]"); + } } }; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConverterModule.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/ConverterModule.java similarity index 52% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConverterModule.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/ConverterModule.java index 745452be..b7f95683 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConverterModule.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/ConverterModule.java @@ -1,17 +1,12 @@ -package nu.marginalia.wmsa.edge.converting; +package nu.marginalia.converting; import com.google.gson.Gson; import com.google.inject.AbstractModule; import com.google.inject.name.Names; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexClient; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexLocalService; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexWriterClient; -import nu.marginalia.wmsa.edge.model.EdgeCrawlPlan; - -import java.nio.file.Path; +import nu.marginalia.LanguageModels; +import nu.marginalia.WmsaHome; +import nu.marginalia.crawling.common.plan.EdgeCrawlPlan; +import nu.marginalia.model.gson.GsonFactory; public class ConverterModule extends AbstractModule { @@ -31,15 +26,6 @@ public class ConverterModule extends AbstractModule { bind(Integer.class).annotatedWith(Names.named("max-title-length")).toInstance(128); bind(Integer.class).annotatedWith(Names.named("max-summary-length")).toInstance(255); - if (null != System.getProperty("local-index-path")) { - bind(Path.class).annotatedWith(Names.named("local-index-path")).toInstance(Path.of(System.getProperty("local-index-path"))); - bind(EdgeIndexWriterClient.class).to(EdgeIndexLocalService.class); - } - else { - bind(EdgeIndexWriterClient.class).to(EdgeIndexClient.class); - } - - bind(LanguageModels.class).toInstance(WmsaHome.getLanguageModels()); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LoadInstructionWriter.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/InstructionWriter.java similarity index 79% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LoadInstructionWriter.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/InstructionWriter.java index b21fa138..b9a1e8cc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LoadInstructionWriter.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/InstructionWriter.java @@ -1,17 +1,17 @@ -package nu.marginalia.wmsa.edge.converting; +package nu.marginalia.converting; import com.github.luben.zstd.ZstdOutputStream; import com.google.gson.Gson; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocument; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocumentWithError; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.crawl.DocumentKeywords; +import nu.marginalia.converting.instruction.instructions.DomainLink; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocument; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocumentWithError; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,14 +23,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -public class LoadInstructionWriter { +public class InstructionWriter { private ConversionLog log; private final Path outputDir; private final Gson gson; - private static final Logger logger = LoggerFactory.getLogger(LoadInstructionWriter.class); + private static final Logger logger = LoggerFactory.getLogger(InstructionWriter.class); - public LoadInstructionWriter(ConversionLog log, Path outputDir, Gson gson) { + public InstructionWriter(ConversionLog log, Path outputDir, Gson gson) { this.log = log; this.outputDir = outputDir; this.gson = gson; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/UpdateDomainStatistics.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/UpdateDomainStatistics.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/UpdateDomainStatistics.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/UpdateDomainStatistics.java index 428bd902..a59c7426 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/UpdateDomainStatistics.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/UpdateDomainStatistics.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting; +package nu.marginalia.converting; import com.zaxxer.hikari.HikariDataSource; import gnu.trove.map.hash.TIntIntHashMap; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; +import nu.marginalia.service.module.DatabaseModule; import java.sql.SQLException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/DocumentsCompiler.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/DocumentsCompiler.java similarity index 64% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/DocumentsCompiler.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/DocumentsCompiler.java index 9f35a557..923e48f5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/DocumentsCompiler.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/DocumentsCompiler.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.edge.converting.compiler; +package nu.marginalia.converting.compiler; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadKeywords; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocument; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocumentWithError; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDocument; -import nu.marginalia.wmsa.edge.converting.processor.logic.HtmlFeature; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.model.crawl.DocumentKeywords; +import nu.marginalia.converting.instruction.instructions.LoadKeywords; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocument; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocumentWithError; +import nu.marginalia.converting.model.ProcessedDocument; +import nu.marginalia.model.crawl.HtmlFeature; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/FeedsCompiler.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/FeedsCompiler.java similarity index 59% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/FeedsCompiler.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/FeedsCompiler.java index e3774288..64779a0f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/FeedsCompiler.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/FeedsCompiler.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.converting.compiler; +package nu.marginalia.converting.compiler; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadRssFeed; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDocument; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.instructions.LoadRssFeed; +import nu.marginalia.converting.model.ProcessedDocument; +import nu.marginalia.model.EdgeUrl; import java.util.List; import java.util.Objects; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/InstructionsCompiler.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/InstructionsCompiler.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/InstructionsCompiler.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/InstructionsCompiler.java index 1b3614a1..a2242961 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/InstructionsCompiler.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/InstructionsCompiler.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.converting.compiler; +package nu.marginalia.converting.compiler; import com.google.inject.Inject; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDomain; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDomain; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDomain; +import nu.marginalia.converting.model.ProcessedDomain; import java.util.ArrayList; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/LinksCompiler.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/LinksCompiler.java similarity index 59% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/LinksCompiler.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/LinksCompiler.java index cb115821..a578602d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/LinksCompiler.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/LinksCompiler.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.converting.compiler; +package nu.marginalia.converting.compiler; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadDomainLink; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDocument; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.instructions.DomainLink; +import nu.marginalia.converting.instruction.instructions.LoadDomainLink; +import nu.marginalia.converting.model.ProcessedDocument; +import nu.marginalia.model.EdgeDomain; import java.util.List; import java.util.Objects; diff --git a/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/RedirectCompiler.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/RedirectCompiler.java new file mode 100644 index 00000000..b14dedca --- /dev/null +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/RedirectCompiler.java @@ -0,0 +1,19 @@ +package nu.marginalia.converting.compiler; + +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.instructions.DomainLink; +import nu.marginalia.converting.instruction.instructions.LoadDomain; +import nu.marginalia.converting.instruction.instructions.LoadDomainLink; +import nu.marginalia.converting.instruction.instructions.LoadDomainRedirect; +import nu.marginalia.model.EdgeDomain; + +import java.util.List; + +public class RedirectCompiler { + + public void compile(List ret, EdgeDomain from, EdgeDomain to) { + ret.add(new LoadDomain(to)); + ret.add(new LoadDomainLink(new DomainLink(from, to))); + ret.add(new LoadDomainRedirect(new DomainLink(from, to))); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/UrlsCompiler.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/UrlsCompiler.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/UrlsCompiler.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/UrlsCompiler.java index b847aa21..4d05a35d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/UrlsCompiler.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/compiler/UrlsCompiler.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.compiler; +package nu.marginalia.converting.compiler; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadDomain; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadUrl; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDocument; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.instructions.LoadDomain; +import nu.marginalia.converting.instruction.instructions.LoadUrl; +import nu.marginalia.converting.model.ProcessedDocument; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; import java.util.ArrayList; import java.util.HashSet; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/DisqualifiedException.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/model/DisqualifiedException.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/DisqualifiedException.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/model/DisqualifiedException.java index 3c97622a..e73a2d12 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/DisqualifiedException.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/model/DisqualifiedException.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.converting.model; +package nu.marginalia.converting.model; -import nu.marginalia.wmsa.edge.crawling.model.CrawlerDocumentStatus; +import nu.marginalia.crawling.model.CrawlerDocumentStatus; public class DisqualifiedException extends Exception { public final DisqualificationReason reason; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/ProcessedDocument.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/model/ProcessedDocument.java similarity index 52% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/ProcessedDocument.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/model/ProcessedDocument.java index df3367cc..da589c44 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/ProcessedDocument.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/model/ProcessedDocument.java @@ -1,9 +1,10 @@ -package nu.marginalia.wmsa.edge.converting.model; +package nu.marginalia.converting.model; import lombok.ToString; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgePageWords; -import nu.marginalia.wmsa.edge.model.crawl.EdgeUrlState; +import nu.marginalia.model.crawl.EdgePageDocumentFlags; +import nu.marginalia.model.crawl.EdgePageWords; +import nu.marginalia.model.crawl.EdgeUrlState; +import nu.marginalia.model.EdgeUrl; import java.util.OptionalDouble; @@ -17,10 +18,22 @@ public class ProcessedDocument { public EdgeUrlState state; public String stateReason; + public long lshHash; + public boolean isOk() { return EdgeUrlState.OK == state; } + public boolean isProcessedFully() { + if (!isOk()) + return false; + + if (details == null) + return false; + + return !details.metadata.hasFlag(EdgePageDocumentFlags.Simple); + } + public OptionalDouble quality() { if (details != null) { return OptionalDouble.of(details.quality); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/ProcessedDocumentDetails.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/model/ProcessedDocumentDetails.java similarity index 65% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/ProcessedDocumentDetails.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/model/ProcessedDocumentDetails.java index 29b2ecc3..bd120aac 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/ProcessedDocumentDetails.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/model/ProcessedDocumentDetails.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.converting.model; +package nu.marginalia.converting.model; import lombok.ToString; -import nu.marginalia.wmsa.edge.converting.processor.logic.HtmlFeature; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.HtmlFeature; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.model.EdgeUrl; import javax.annotation.Nullable; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/ProcessedDomain.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/model/ProcessedDomain.java similarity index 82% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/ProcessedDomain.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/model/ProcessedDomain.java index 101d1fb8..f3a08b98 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/model/ProcessedDomain.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/model/ProcessedDomain.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.model; +package nu.marginalia.converting.model; import lombok.ToString; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; import java.util.List; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/AcceptableAds.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/AcceptableAds.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/AcceptableAds.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/AcceptableAds.java index 2814eea7..d097c60a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/AcceptableAds.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/AcceptableAds.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.converting.processor; +package nu.marginalia.converting.processor; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawledDocument; import org.jsoup.nodes.Document; diff --git a/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/DocumentProcessor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/DocumentProcessor.java new file mode 100644 index 00000000..b7072236 --- /dev/null +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/DocumentProcessor.java @@ -0,0 +1,136 @@ +package nu.marginalia.converting.processor; + +import com.google.inject.Inject; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawledDomain; +import nu.marginalia.crawling.model.CrawlerDocumentStatus; +import nu.marginalia.model.crawl.EdgeUrlState; +import nu.marginalia.converting.model.DisqualifiedException; +import nu.marginalia.converting.model.ProcessedDocument; +import nu.marginalia.converting.processor.plugin.AbstractDocumentProcessorPlugin; +import nu.marginalia.converting.processor.plugin.HtmlDocumentProcessorPlugin; +import nu.marginalia.converting.processor.plugin.PlainTextDocumentProcessorPlugin; +import nu.marginalia.model.EdgeUrl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URISyntaxException; +import java.util.*; + +public class DocumentProcessor { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private static final Set acceptedContentTypes = Set.of("application/xhtml+xml", + "application/xhtml", + "text/html", + "text/plain"); + + + private final List processorPlugins = new ArrayList<>(); + + @Inject + public DocumentProcessor(HtmlDocumentProcessorPlugin htmlDocumentProcessorPlugin, + PlainTextDocumentProcessorPlugin plainTextDocumentProcessorPlugin) + { + + processorPlugins.add(htmlDocumentProcessorPlugin); + processorPlugins.add(plainTextDocumentProcessorPlugin); + } + + public ProcessedDocument process(CrawledDocument crawledDocument, CrawledDomain crawledDomain) { + ProcessedDocument ret = new ProcessedDocument(); + + try { + processDocument(crawledDocument, crawledDomain, ret); + } + catch (DisqualifiedException ex) { + ret.state = EdgeUrlState.DISQUALIFIED; + ret.stateReason = ex.reason.toString(); + logger.debug("Disqualified {}: {}", ret.url, ex.reason); + } + catch (Exception ex) { + ret.state = EdgeUrlState.DISQUALIFIED; + ret.stateReason = DisqualifiedException.DisqualificationReason.PROCESSING_EXCEPTION.toString(); + logger.info("Failed to convert " + crawledDocument.url, ex); + ex.printStackTrace(); + } + + return ret; + } + + private void processDocument(CrawledDocument crawledDocument, CrawledDomain crawledDomain, ProcessedDocument ret) throws URISyntaxException, DisqualifiedException { + + var crawlerStatus = CrawlerDocumentStatus.valueOf(crawledDocument.crawlerStatus); + if (crawlerStatus != CrawlerDocumentStatus.OK) { + throw new DisqualifiedException(crawlerStatus); + } + + if (AcceptableAds.hasAcceptableAdsHeader(crawledDocument)) { + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.ACCEPTABLE_ADS); + } + + if (!isAcceptedContentType(crawledDocument)) { + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.CONTENT_TYPE); + } + + + ret.url = getDocumentUrl(crawledDocument); + ret.state = crawlerStatusToUrlState(crawledDocument.crawlerStatus, crawledDocument.httpStatus); + + final var plugin = findPlugin(crawledDocument); + + AbstractDocumentProcessorPlugin.DetailsWithWords detailsWithWords = plugin.createDetails(crawledDomain, crawledDocument); + + ret.details = detailsWithWords.details(); + ret.words = detailsWithWords.words(); + } + + private AbstractDocumentProcessorPlugin findPlugin(CrawledDocument crawledDocument) throws DisqualifiedException { + for (var plugin : processorPlugins) { + if (plugin.isApplicable(crawledDocument)) + return plugin; + } + + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.CONTENT_TYPE); + } + + + private EdgeUrl getDocumentUrl(CrawledDocument crawledDocument) + throws URISyntaxException + { + if (crawledDocument.canonicalUrl != null) { + try { + return new EdgeUrl(crawledDocument.canonicalUrl); + } + catch (URISyntaxException ex) { /* fallthrough */ } + } + + return new EdgeUrl(crawledDocument.url); + } + + public static boolean isAcceptedContentType(CrawledDocument crawledDocument) { + if (crawledDocument.contentType == null) { + return false; + } + + var ct = crawledDocument.contentType; + + if (acceptedContentTypes.contains(ct)) + return true; + + if (ct.contains(";")) { + return acceptedContentTypes.contains(ct.substring(0, ct.indexOf(';'))); + } + return false; + } + + private EdgeUrlState crawlerStatusToUrlState(String crawlerStatus, int httpStatus) { + return switch (CrawlerDocumentStatus.valueOf(crawlerStatus)) { + case OK -> httpStatus < 300 ? EdgeUrlState.OK : EdgeUrlState.DEAD; + case REDIRECT -> EdgeUrlState.REDIRECT; + default -> EdgeUrlState.DEAD; + }; + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/DomainProcessor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/DomainProcessor.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/DomainProcessor.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/DomainProcessor.java index e5ed00e5..005a1efc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/DomainProcessor.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/DomainProcessor.java @@ -1,29 +1,33 @@ -package nu.marginalia.wmsa.edge.converting.processor; +package nu.marginalia.converting.processor; import com.google.common.base.Strings; import com.google.inject.Inject; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawledDomain; +import nu.marginalia.crawling.model.CrawlerDocumentStatus; +import nu.marginalia.crawling.model.CrawlerDomainStatus; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.converting.model.ProcessedDomain; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; import nu.marginalia.util.StringPool; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDomain; -import nu.marginalia.wmsa.edge.converting.processor.logic.InternalLinkGraph; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDocument; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDomain; -import nu.marginalia.wmsa.edge.crawling.model.CrawlerDomainStatus; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.converting.processor.logic.InternalLinkGraph; +import nu.marginalia.converting.processor.logic.LshDocumentDeduplicator; import java.util.*; -import static nu.marginalia.wmsa.edge.crawling.model.CrawlerDocumentStatus.BAD_CANONICAL; - public class DomainProcessor { private final DocumentProcessor documentProcessor; private final SiteWords siteWords; + private final LshDocumentDeduplicator documentDeduplicator; + @Inject public DomainProcessor(DocumentProcessor documentProcessor, - SiteWords siteWords) { + SiteWords siteWords, + LshDocumentDeduplicator documentDeduplicator) { this.documentProcessor = documentProcessor; this.siteWords = siteWords; + this.documentDeduplicator = documentDeduplicator; } public ProcessedDomain process(CrawledDomain crawledDomain) { @@ -62,6 +66,8 @@ public class DomainProcessor { stringPool.flush(); + documentDeduplicator.deduplicate(ret.documents); + InternalLinkGraph internalLinkGraph = new InternalLinkGraph(); ret.documents.forEach(internalLinkGraph::accept); @@ -84,6 +90,7 @@ public class DomainProcessor { return ret; } + private void fixBadCanonicalTags(List docs) { Map> seenCanonicals = new HashMap<>(); Set seenUrls = new HashSet<>(); @@ -108,7 +115,7 @@ public class DomainProcessor { document.canonicalUrl = document.url; } else { - document.crawlerStatus = BAD_CANONICAL.name(); + document.crawlerStatus = CrawlerDocumentStatus.BAD_CANONICAL.name(); } } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/SiteWords.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/SiteWords.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/SiteWords.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/SiteWords.java index 87e8c931..2fd75cba 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/SiteWords.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/SiteWords.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.edge.converting.processor; +package nu.marginalia.converting.processor; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDocument; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDomain; -import nu.marginalia.wmsa.edge.converting.processor.logic.CommonKeywordExtractor; -import nu.marginalia.wmsa.edge.converting.processor.logic.InternalLinkGraph; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgePageWordFlags; +import nu.marginalia.converting.model.ProcessedDocument; +import nu.marginalia.converting.model.ProcessedDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.CommonKeywordExtractor; +import nu.marginalia.converting.processor.logic.InternalLinkGraph; import javax.inject.Singleton; import java.util.HashMap; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/DocumentKeywordExtractor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/DocumentKeywordExtractor.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/DocumentKeywordExtractor.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/DocumentKeywordExtractor.java index 557e8d58..a3d225d8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/DocumentKeywordExtractor.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/DocumentKeywordExtractor.java @@ -1,12 +1,14 @@ -package nu.marginalia.util.language.processing; +package nu.marginalia.converting.processor.keywords; -import nu.marginalia.util.language.WordPatterns; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; -import nu.marginalia.util.language.processing.model.KeywordMetadata; -import nu.marginalia.util.language.processing.model.WordRep; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.model.crawl.EdgePageWords; +import nu.marginalia.language.WordPatterns; +import nu.marginalia.language.encoding.AsciiFlattener; +import nu.marginalia.language.keywords.KeywordExtractor; +import nu.marginalia.language.model.DocumentLanguageData; +import nu.marginalia.language.model.KeywordMetadata; +import nu.marginalia.language.model.WordRep; +import nu.marginalia.model.crawl.EdgePageWordFlags; +import nu.marginalia.model.crawl.EdgePageWords; +import nu.marginalia.language.statistics.TermFrequencyDict; import javax.inject.Inject; import java.util.*; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/KeywordCounter.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/KeywordCounter.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/KeywordCounter.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/KeywordCounter.java index 2ee90f6b..c153be0b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/KeywordCounter.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/KeywordCounter.java @@ -1,12 +1,14 @@ -package nu.marginalia.util.language.processing; +package nu.marginalia.converting.processor.keywords; -import com.github.jknack.handlebars.internal.lang3.StringUtils; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import nu.marginalia.util.language.WordPatterns; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; -import nu.marginalia.util.language.processing.model.KeywordMetadata; -import nu.marginalia.util.language.processing.model.WordRep; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; +import nu.marginalia.language.WordPatterns; +import nu.marginalia.language.model.DocumentLanguageData; +import nu.marginalia.language.model.KeywordMetadata; +import nu.marginalia.language.model.WordFrequencyData; +import nu.marginalia.language.model.WordRep; +import nu.marginalia.language.keywords.KeywordExtractor; +import nu.marginalia.language.statistics.TermFrequencyDict; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.HashMap; @@ -105,5 +107,4 @@ public class KeywordCounter { return (0.1 + 0.9*value/maxValue) * Math.log(freq/docCount); } - public record WordFrequencyData(int count, int tfIdfNormalized) { } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/NameCounter.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/NameCounter.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/NameCounter.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/NameCounter.java index 221790d6..22ce88ed 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/NameCounter.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/NameCounter.java @@ -1,8 +1,9 @@ -package nu.marginalia.util.language.processing; +package nu.marginalia.converting.processor.keywords; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; -import nu.marginalia.util.language.processing.model.DocumentSentence; -import nu.marginalia.util.language.processing.model.WordRep; +import nu.marginalia.language.model.DocumentLanguageData; +import nu.marginalia.language.model.DocumentSentence; +import nu.marginalia.language.model.WordRep; +import nu.marginalia.language.keywords.KeywordExtractor; import java.util.*; import java.util.stream.Collectors; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/SubjectCounter.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/SubjectCounter.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/SubjectCounter.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/SubjectCounter.java index b0f46f30..e99cbb5c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/SubjectCounter.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/keywords/SubjectCounter.java @@ -1,10 +1,11 @@ -package nu.marginalia.util.language.processing; +package nu.marginalia.converting.processor.keywords; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; -import nu.marginalia.util.language.processing.model.KeywordMetadata; -import nu.marginalia.util.language.processing.model.WordRep; -import nu.marginalia.util.language.processing.model.WordSpan; -import nu.marginalia.util.language.processing.model.tag.WordSeparator; +import nu.marginalia.language.model.DocumentLanguageData; +import nu.marginalia.language.model.KeywordMetadata; +import nu.marginalia.language.model.WordRep; +import nu.marginalia.language.model.WordSpan; +import nu.marginalia.language.model.WordSeparator; +import nu.marginalia.language.keywords.KeywordExtractor; import org.apache.commons.lang3.StringUtils; import java.util.*; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/CommonKeywordExtractor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/CommonKeywordExtractor.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/CommonKeywordExtractor.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/CommonKeywordExtractor.java index cabb6454..eb7d39e7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/CommonKeywordExtractor.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/CommonKeywordExtractor.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; import ca.rmen.porterstemmer.PorterStemmer; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDomain; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; +import nu.marginalia.model.crawl.EdgePageWordFlags; +import nu.marginalia.converting.model.ProcessedDomain; import java.util.*; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/DocumentValuator.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/DocumentValuator.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/DocumentValuator.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/DocumentValuator.java index 46fc7925..36d4f3b0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/DocumentValuator.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/DocumentValuator.java @@ -1,16 +1,14 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; import crawlercommons.utils.Strings; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; -import nu.marginalia.wmsa.edge.converting.model.DisqualifiedException; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDocument; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.language.model.DocumentLanguageData; +import nu.marginalia.converting.model.DisqualifiedException; import org.jsoup.nodes.Document; import java.util.Set; -import static nu.marginalia.wmsa.edge.converting.model.DisqualifiedException.DisqualificationReason.LENGTH; - public class DocumentValuator { private static final Set filthTable = Set.of( @@ -32,7 +30,7 @@ public class DocumentValuator { int rawLength = crawledDocument.documentBody.length(); if (textBodyLength == 0) { - throw new DisqualifiedException(LENGTH); + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.LENGTH); } return Math.log(textBodyLength / (double) (1+rawLength))*htmlStandard.scale diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/DomPruningFilter.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/DomPruningFilter.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/DomPruningFilter.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/DomPruningFilter.java index 1e68125f..f8c0b65d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/DomPruningFilter.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/DomPruningFilter.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/FeatureExtractor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/FeatureExtractor.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/FeatureExtractor.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/FeatureExtractor.java index 2681b1c6..c0e1bc91 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/FeatureExtractor.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/FeatureExtractor.java @@ -1,10 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; -import nu.marginalia.wmsa.edge.converting.processor.logic.topic.*; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDomain; +import nu.marginalia.crawling.model.CrawledDomain; +import nu.marginalia.language.model.DocumentLanguageData; +import nu.marginalia.model.crawl.HtmlFeature; +import nu.marginalia.converting.processor.logic.topic.*; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/FeedExtractor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/FeedExtractor.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/FeedExtractor.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/FeedExtractor.java index b82b77ac..c20a9878 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/FeedExtractor.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/FeedExtractor.java @@ -1,6 +1,7 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.crawling.common.link.LinkParser; +import nu.marginalia.model.EdgeUrl; import org.jsoup.nodes.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/HtmlStandardExtractor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/HtmlStandardExtractor.java similarity index 70% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/HtmlStandardExtractor.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/HtmlStandardExtractor.java index 3eeb4bce..5179274c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/HtmlStandardExtractor.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/HtmlStandardExtractor.java @@ -1,14 +1,12 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; import com.google.common.base.Strings; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; import org.jsoup.nodes.Document; import org.jsoup.nodes.DocumentType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard.*; - public class HtmlStandardExtractor { @@ -16,48 +14,48 @@ public class HtmlStandardExtractor { public static EdgeHtmlStandard parseDocType(DocumentType docType) { if (null == docType) { - return UNKNOWN; + return EdgeHtmlStandard.UNKNOWN; } String publicId = docType.publicId(); if (Strings.isNullOrEmpty(publicId)) - return HTML5; + return EdgeHtmlStandard.HTML5; publicId = publicId.toUpperCase(); if (publicId.startsWith("-//SOFTQUAD SOFTWARE//DTD") && publicId.contains("HTML 4")) { - return HTML4; + return EdgeHtmlStandard.HTML4; } if (publicId.startsWith("-//SOFTQUAD SOFTWARE//DTD") && publicId.contains("HTML 3")) { - return HTML123; + return EdgeHtmlStandard.HTML123; } if (publicId.startsWith("-//INTERNET/RFC XXXX//EN")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-//NETSCAPE COMM. CORP")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-//SQ//DTD HTML 2")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-//SOFTQUAD//DTD HTML 2")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-//W3O//DTD W3 HTML 2")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-//IETF//DTD HTML 2")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-//IETF//DTD HTML//EN")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-/W3C//DTD HTML 3")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-/W3C/DTD HTML 3")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-//IETF//DTD HTML 3")) - return HTML123; + return EdgeHtmlStandard.HTML123; if (publicId.startsWith("-//W3C//DTD XHTML")) - return XHTML; + return EdgeHtmlStandard.XHTML; if (publicId.startsWith("ISO/IEC 15445:2000//DTD")) - return XHTML; + return EdgeHtmlStandard.XHTML; if (publicId.startsWith("-//W3C//DTD HTML")) - return HTML4; + return EdgeHtmlStandard.HTML4; logger.debug("Unknown publicID standard {}", publicId); - return UNKNOWN; + return EdgeHtmlStandard.UNKNOWN; } public static EdgeHtmlStandard sniffHtmlStandard(Document parsed) { @@ -74,11 +72,11 @@ public class HtmlStandardExtractor { html4Attributes++; } if (html5Attributes > 0) { - return HTML5; + return EdgeHtmlStandard.HTML5; } if (html4Attributes > 0) { - return HTML4; + return EdgeHtmlStandard.HTML4; } - return HTML123; + return EdgeHtmlStandard.HTML123; } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/InternalLinkGraph.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/InternalLinkGraph.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/InternalLinkGraph.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/InternalLinkGraph.java index 6b0cd10f..1f69cf31 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/InternalLinkGraph.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/InternalLinkGraph.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDocument; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgePageWordFlags; +import nu.marginalia.converting.model.ProcessedDocument; +import nu.marginalia.model.EdgeUrl; import java.util.*; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/LinkProcessor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/LinkProcessor.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/LinkProcessor.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/LinkProcessor.java index b94f90d8..68b212de 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/LinkProcessor.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/LinkProcessor.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDocumentDetails; -import nu.marginalia.wmsa.edge.crawling.blocklist.UrlBlocklist; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.converting.model.ProcessedDocumentDetails; +import nu.marginalia.crawling.common.blocklist.UrlBlocklist; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; import java.util.ArrayList; import java.util.HashSet; diff --git a/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/LshDocumentDeduplicator.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/LshDocumentDeduplicator.java new file mode 100644 index 00000000..877c22d3 --- /dev/null +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/LshDocumentDeduplicator.java @@ -0,0 +1,60 @@ +package nu.marginalia.converting.processor.logic; + +import com.google.inject.Singleton; +import nu.marginalia.model.crawl.EdgeUrlState; +import nu.marginalia.converting.model.ProcessedDocument; +import nu.marginalia.lsh.EasyLSH; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Singleton +public class LshDocumentDeduplicator { + + private final int DISTANCE_THRESHOLD = 4; + private final Logger logger = LoggerFactory.getLogger(getClass()); + + public void deduplicate(List documents) { + Set goodDocuments = documents.stream() + .filter(ProcessedDocument::isProcessedFully) + .collect(Collectors.toSet()); + + for (var document : documents) { + if (!goodDocuments.contains(document)) { + continue; + } + + goodDocuments.removeIf(other -> removeIfDuplicate(document, other)); + } + } + + private boolean removeIfDuplicate(ProcessedDocument thisDoc, ProcessedDocument otherDoc) { + if (thisDoc == otherDoc) + return false; + + if (thisDoc.words.size() < 100 + || otherDoc.words.size() < 100) { + return false; + } + + if (EasyLSH.hammingDistance(thisDoc.lshHash, otherDoc.lshHash) < DISTANCE_THRESHOLD) + return false; + + if (thisDoc.url.path.length() + < otherDoc.url.path.length()) + { + logger.info("{} duplicates {}", otherDoc.url, thisDoc.url); + + otherDoc.state = EdgeUrlState.DISQUALIFIED; + otherDoc.stateReason = "Duplicate"; + + return true; + } + + return false; + + } +} diff --git a/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/PlainTextLogic.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/PlainTextLogic.java new file mode 100644 index 00000000..aefa5710 --- /dev/null +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/PlainTextLogic.java @@ -0,0 +1,115 @@ +package nu.marginalia.converting.processor.logic; + +import nu.marginalia.model.EdgeUrl; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class PlainTextLogic { + + public String getDescription(List firstFewLines) { + return StringUtils.truncate(firstFewLines.stream().filter(this::looksLikeText) + .collect(Collectors.joining(" ")).replaceAll("\\s+", " ") + + , 255); + } + + private boolean looksLikeText(String s) { + s = s.trim(); + if (s.length() < 16) + return false; + return 4 * s.chars().filter(Character::isAlphabetic).count() > 3L * s.length(); + + } + + public String getTitle(EdgeUrl url, List firstFewLines) { + List candidates = new ArrayList<>(firstFewLines); + + // Remove mailing list header type stuff + candidates.removeIf(line -> line.contains(":")); + + for (int line = 1; line < candidates.size(); line++) { + String maybeUnderline = candidates.get(line); + if (isUnderline(maybeUnderline)) { + String titleCandidate = candidates.get(line - 1).trim(); + if (titleCandidate.length() > 16) { + return StringUtils.truncate(titleCandidate, 128); + } + } + } + + for (var line : firstFewLines) { + if (isSideline(line)) { + return line.replaceAll("[^a-zA-Z0-9]+", " ").trim(); + } + } + + return url.path.substring(url.path.lastIndexOf('/')); + } + + public boolean isSideline(String s) { + + // detector for + // ==== HEADER ==== + // -style headings + + int start, end; + for (start = 0; start < s.length(); start++) { + if (!Character.isWhitespace(s.charAt(start))) break; + } + for (end = s.length() - 1; end > start; end--) { + if (!Character.isWhitespace(s.charAt(start))) break; + } + + if (end - start < 8) + return false; + + int c = s.charAt(start); + if ("=_*".indexOf(c) < 0) { + return false; + } + if (c != s.charAt(end)) { + return false; + } + + for (; start < end && s.charAt(start) == c; start++); + + if (end - start < 4) + return false; + + for (; end > start && s.charAt(end) == c; --end); + + if (end - start < 4) + return false; + + return true; + } + public boolean isUnderline(String s) { + int start, end; + for (start = 0; start < s.length(); start++) { + if (!Character.isWhitespace(s.charAt(start))) break; + } + for (end = s.length() - 1; end > start; end--) { + if (!Character.isWhitespace(s.charAt(start))) break; + } + if (end - start < 8) + return false; + + if ("=_*".indexOf(s.charAt(start)) < 0) { + return false; + } + + int c = s.charAt(start); + + for (int i = start; i < end; i++) { + if (c != s.charAt(i)) + return false; + } + + return true; + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/SalientImageDetector.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/SalientImageDetector.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/SalientImageDetector.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/SalientImageDetector.java index 271ad6f2..4a3baabd 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/SalientImageDetector.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/SalientImageDetector.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/SummaryExtractionFilter.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/SummaryExtractionFilter.java similarity index 99% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/SummaryExtractionFilter.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/SummaryExtractionFilter.java index adafa835..942aec70 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/SummaryExtractionFilter.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/SummaryExtractionFilter.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; import com.google.common.base.Strings; import org.apache.commons.lang3.StringUtils; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/SummaryExtractor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/SummaryExtractor.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/SummaryExtractor.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/SummaryExtractor.java index 4984125f..ce37df0a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/SummaryExtractor.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/SummaryExtractor.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; import com.google.inject.Inject; import com.google.inject.name.Named; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/TitleExtractor.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/TitleExtractor.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/TitleExtractor.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/TitleExtractor.java index 2ce4212e..4a3293d7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/TitleExtractor.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/TitleExtractor.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.processor.logic; import com.google.inject.Inject; import com.google.inject.name.Named; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; +import nu.marginalia.language.model.DocumentLanguageData; import org.apache.commons.lang3.StringUtils; import org.jsoup.nodes.Document; diff --git a/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateEffortLevel.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateEffortLevel.java new file mode 100644 index 00000000..a69373bb --- /dev/null +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateEffortLevel.java @@ -0,0 +1,6 @@ +package nu.marginalia.converting.processor.logic.pubdate; + +public enum PubDateEffortLevel { + LOW, + HIGH +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateHeuristic.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateHeuristic.java similarity index 56% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateHeuristic.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateHeuristic.java index 0bac7705..e2e67258 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateHeuristic.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateHeuristic.java @@ -1,7 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate; +package nu.marginalia.converting.processor.logic.pubdate; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateParser.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateParser.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateParser.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateParser.java index 8e49fda8..131a5f3d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateParser.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateParser.java @@ -1,6 +1,7 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate; +package nu.marginalia.converting.processor.logic.pubdate; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; import java.time.DateTimeException; import java.time.LocalDate; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateSniffer.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateSniffer.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateSniffer.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateSniffer.java index 25a5ece1..7eeca0d3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateSniffer.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/PubDateSniffer.java @@ -1,8 +1,9 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate; +package nu.marginalia.converting.processor.logic.pubdate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic.*; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.heuristic.*; +import nu.marginalia.model.EdgeUrl; import org.jsoup.nodes.Document; import java.util.ArrayList; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass1.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass1.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass1.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass1.java index cc85ab2a..8d32d965 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass1.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass1.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; import org.jetbrains.annotations.NotNull; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass2.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass2.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass2.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass2.java index 264f9eb1..4692153d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass2.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicDOMParsingPass2.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; import org.jetbrains.annotations.NotNull; import org.jsoup.nodes.Document; import org.jsoup.nodes.Node; diff --git a/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicGuessFromHtmlStandard.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicGuessFromHtmlStandard.java new file mode 100644 index 00000000..da44e3fa --- /dev/null +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicGuessFromHtmlStandard.java @@ -0,0 +1,23 @@ +package nu.marginalia.converting.processor.logic.pubdate.heuristic; + +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import org.jsoup.nodes.Document; + +import java.util.Optional; + +public class PubDateHeuristicGuessFromHtmlStandard implements PubDateHeuristic { + + @Override + public Optional apply(PubDateEffortLevel effortLevel, String headers, EdgeUrl url, Document document, EdgeHtmlStandard htmlStandard) { + if (htmlStandard == EdgeHtmlStandard.UNKNOWN) + return Optional.empty(); + + return Optional.of(new PubDate(null, PubDateParser.guessYear(htmlStandard))); + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5AnyTimeTag.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5AnyTimeTag.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5AnyTimeTag.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5AnyTimeTag.java index d20ed246..ca0220ae 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5AnyTimeTag.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5AnyTimeTag.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ArticleDateTag.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ArticleDateTag.java similarity index 54% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ArticleDateTag.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ArticleDateTag.java index 78c54b9a..c63e15b1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ArticleDateTag.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ArticleDateTag.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ItempropDateTag.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ItempropDateTag.java similarity index 54% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ItempropDateTag.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ItempropDateTag.java index 8dec0f6a..5b2b7034 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ItempropDateTag.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicHtml5ItempropDateTag.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicJSONLD.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicJSONLD.java similarity index 70% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicJSONLD.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicJSONLD.java index 2187a744..aedb0611 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicJSONLD.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicJSONLD.java @@ -1,14 +1,14 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonSyntaxException; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicLastModified.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicLastModified.java similarity index 57% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicLastModified.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicLastModified.java index 5a47c9df..f7ed3af9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicLastModified.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicLastModified.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicMicrodata.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicMicrodata.java similarity index 53% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicMicrodata.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicMicrodata.java index a257bba2..75de4a71 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicMicrodata.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicMicrodata.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicOpenGraph.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicOpenGraph.java similarity index 54% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicOpenGraph.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicOpenGraph.java index bd9b66a9..6ddd78d8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicOpenGraph.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicOpenGraph.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicRDFaTag.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicRDFaTag.java similarity index 53% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicRDFaTag.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicRDFaTag.java index 2618cdef..59f8e08d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicRDFaTag.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicRDFaTag.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass1.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass1.java similarity index 70% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass1.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass1.java index 70b19ad0..2756c089 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass1.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass1.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass2.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass2.java similarity index 67% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass2.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass2.java index 19aceecd..6432d9c3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass2.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/pubdate/heuristic/PubDateHeuristicUrlPatternPass2.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; +package nu.marginalia.converting.processor.logic.pubdate.heuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateHeuristic; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.converting.processor.logic.pubdate.PubDateEffortLevel; import org.jsoup.nodes.Document; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/AdblockSimulator.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/AdblockSimulator.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/AdblockSimulator.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/AdblockSimulator.java index 199e05bc..62c4b778 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/AdblockSimulator.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/AdblockSimulator.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.topic; +package nu.marginalia.converting.processor.logic.topic; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.wmsa.configuration.WmsaHome; +import nu.marginalia.WmsaHome; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/GoogleAnwersSpamDetector.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/GoogleAnwersSpamDetector.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/GoogleAnwersSpamDetector.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/GoogleAnwersSpamDetector.java index 75e7fea3..dc0c4eed 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/GoogleAnwersSpamDetector.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/GoogleAnwersSpamDetector.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.topic; +package nu.marginalia.converting.processor.logic.topic; import org.jsoup.nodes.Document; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/RecipeDetector.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/RecipeDetector.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/RecipeDetector.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/RecipeDetector.java index 74122799..29dea927 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/RecipeDetector.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/RecipeDetector.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.topic; +package nu.marginalia.converting.processor.logic.topic; import ca.rmen.porterstemmer.PorterStemmer; import com.google.inject.Inject; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; +import nu.marginalia.language.model.DocumentLanguageData; import java.util.HashMap; import java.util.Map; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/TextileCraftDetector.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/TextileCraftDetector.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/TextileCraftDetector.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/TextileCraftDetector.java index 1df3b8ee..771d1491 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/TextileCraftDetector.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/TextileCraftDetector.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.topic; +package nu.marginalia.converting.processor.logic.topic; import ca.rmen.porterstemmer.PorterStemmer; import com.google.inject.Inject; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; +import nu.marginalia.language.model.DocumentLanguageData; import java.util.HashMap; import java.util.Map; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/WoodworkingDetector.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/WoodworkingDetector.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/WoodworkingDetector.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/WoodworkingDetector.java index e58320f6..fd9be203 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/topic/WoodworkingDetector.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/logic/topic/WoodworkingDetector.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.topic; +package nu.marginalia.converting.processor.logic.topic; import ca.rmen.porterstemmer.PorterStemmer; import com.google.inject.Inject; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; +import nu.marginalia.language.model.DocumentLanguageData; import java.util.HashMap; import java.util.Map; diff --git a/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/AbstractDocumentProcessorPlugin.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/AbstractDocumentProcessorPlugin.java new file mode 100644 index 00000000..89c8afb6 --- /dev/null +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/AbstractDocumentProcessorPlugin.java @@ -0,0 +1,88 @@ +package nu.marginalia.converting.processor.plugin; + +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawledDomain; +import nu.marginalia.language.LanguageFilter; +import nu.marginalia.language.model.DocumentLanguageData; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgePageWords; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.model.DisqualifiedException; +import nu.marginalia.converting.model.ProcessedDocumentDetails; +import nu.marginalia.model.crawl.HtmlFeature; +import nu.marginalia.model.EdgeUrl; + +import java.net.URISyntaxException; +import java.util.*; + +public abstract class AbstractDocumentProcessorPlugin { + protected LanguageFilter languageFilter = new LanguageFilter(); + + public abstract DetailsWithWords createDetails(CrawledDomain crawledDomain, CrawledDocument crawledDocument) throws DisqualifiedException, URISyntaxException; + public abstract boolean isApplicable(CrawledDocument doc); + + protected void checkDocumentLanguage(DocumentLanguageData dld) throws DisqualifiedException { + double languageAgreement = languageFilter.dictionaryAgreement(dld); + if (languageAgreement < 0.1) { + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.LANGUAGE); + } + } + + protected static class MetaTagsBuilder { + private final Set tagWords = new HashSet<>(); + + public void build(EdgePageWords dest) { + dest.addAllSyntheticTerms(tagWords); + } + + public MetaTagsBuilder addDomainCrawlData(CrawledDomain domain) { + if (domain.ip != null) { + tagWords.add("ip:" + domain.ip.toLowerCase()); // lower case because IPv6 is hexadecimal + } + return this; + } + + public MetaTagsBuilder addUrl(EdgeUrl url) { + tagWords.add("proto:"+url.proto.toLowerCase()); + + var edgeDomain = url.domain; + + tagWords.add("site:" + edgeDomain.toString().toLowerCase()); + if (!Objects.equals(edgeDomain.toString(), edgeDomain.domain)) { + tagWords.add("site:" + edgeDomain.domain.toLowerCase()); + } + + tagWords.add("tld:" + edgeDomain.getTld()); + return this; + } + + public MetaTagsBuilder addFormat(EdgeHtmlStandard standard) { + tagWords.add("format:"+standard.toString().toLowerCase()); + return this; + } + + public MetaTagsBuilder addFeatures(Set features) { + features.stream().map(HtmlFeature::getKeyword).forEach(tagWords::add); + + tagWords.add("js:" + Boolean.toString(features.contains(HtmlFeature.JS)).toLowerCase()); + + return this; + } + public MetaTagsBuilder addPubDate(PubDate pubDate) { + + if (pubDate.year() > 1900) { + tagWords.add("year:" + pubDate.year()); + } + if (pubDate.dateIso8601() != null) { + tagWords.add("pub:" + pubDate.dateIso8601()); + } + + return this; + } + + } + + + public record DetailsWithWords(ProcessedDocumentDetails details, + EdgePageWords words) {} +} diff --git a/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/HtmlDocumentProcessorPlugin.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/HtmlDocumentProcessorPlugin.java new file mode 100644 index 00000000..5ea3c932 --- /dev/null +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/HtmlDocumentProcessorPlugin.java @@ -0,0 +1,270 @@ +package nu.marginalia.converting.processor.plugin; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import nu.marginalia.crawling.common.link.LinkParser; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawledDomain; +import nu.marginalia.language.model.KeywordMetadata; +import nu.marginalia.converting.processor.keywords.DocumentKeywordExtractor; +import nu.marginalia.language.sentence.SentenceExtractor; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgePageDocumentFlags; +import nu.marginalia.model.crawl.EdgePageWords; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.language.model.DocumentLanguageData; +import nu.marginalia.converting.processor.logic.*; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.processor.logic.pubdate.PubDateSniffer; +import nu.marginalia.gregex.GuardedRegex; +import nu.marginalia.gregex.GuardedRegexFactory; +import nu.marginalia.converting.model.DisqualifiedException; +import nu.marginalia.converting.model.ProcessedDocumentDetails; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; + + +public class HtmlDocumentProcessorPlugin extends AbstractDocumentProcessorPlugin { + + private final int minDocumentLength; + private final double minDocumentQuality; + + private final SentenceExtractor sentenceExtractor; + private final FeatureExtractor featureExtractor; + private final TitleExtractor titleExtractor; + private final DocumentKeywordExtractor keywordExtractor; + private final SummaryExtractor summaryExtractor; + private final PubDateSniffer pubDateSniffer; + + private static final DocumentValuator documentValuator = new DocumentValuator(); + + private static final LinkParser linkParser = new LinkParser(); + private static final FeedExtractor feedExtractor = new FeedExtractor(linkParser); + + @Inject + public HtmlDocumentProcessorPlugin(@Named("min-document-length") Integer minDocumentLength, + @Named("min-document-quality") Double minDocumentQuality, + SentenceExtractor sentenceExtractor, + FeatureExtractor featureExtractor, + TitleExtractor titleExtractor, + DocumentKeywordExtractor keywordExtractor, + SummaryExtractor summaryExtractor, + PubDateSniffer pubDateSniffer) { + this.minDocumentLength = minDocumentLength; + this.minDocumentQuality = minDocumentQuality; + this.sentenceExtractor = sentenceExtractor; + this.featureExtractor = featureExtractor; + + this.titleExtractor = titleExtractor; + this.keywordExtractor = keywordExtractor; + this.summaryExtractor = summaryExtractor; + this.pubDateSniffer = pubDateSniffer; + } + + @Override + public boolean isApplicable(CrawledDocument doc) { + return doc.contentType.toLowerCase().contains("html"); + } + + @Override + public DetailsWithWords createDetails(CrawledDomain crawledDomain, CrawledDocument crawledDocument) + throws DisqualifiedException, URISyntaxException { + + String documentBody = crawledDocument.documentBody.decode(); + + if (languageFilter.isBlockedUnicodeRange(documentBody)) { + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.LANGUAGE); + } + + Document doc = Jsoup.parse(documentBody); + + if (doc.select("meta[name=robots]").attr("content").contains("noindex")) { + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.FORBIDDEN); + } + + final EdgeUrl url = new EdgeUrl(crawledDocument.url); + + Document prunedDoc = doc.clone(); + + prunedDoc.getElementsByTag("svg").remove(); + prunedDoc.body().filter(new DomPruningFilter(0.5)); + + var dld = sentenceExtractor.extractSentences(prunedDoc); + + checkDocumentLanguage(dld); + + var ret = new ProcessedDocumentDetails(); + + ret.length = getLength(doc); + ret.standard = getHtmlStandard(doc); + ret.title = titleExtractor.getTitleAbbreviated(doc, dld, crawledDocument.url); + ret.quality = documentValuator.getQuality(crawledDocument, ret.standard, doc, dld); + + // don't move this up! it uses title and quality + // and is run before the heavy computations below + if (isDisqualified(url, dld, ret)) { + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.QUALITY); + } + + KeywordMetadata keywordMetadata = new KeywordMetadata(); + + ret.features = featureExtractor.getFeatures(crawledDomain, doc, dld); + ret.description = getDescription(doc); + ret.hashCode = dld.localitySensitiveHashCode(); + + PubDate pubDate = pubDateSniffer.getPubDate(crawledDocument.headers, url, doc, ret.standard, true); + ret.metadata = new EdgePageDocumentsMetadata(url.depth(), pubDate.yearByte(), 0, (int) -ret.quality, EnumSet.noneOf(EdgePageDocumentFlags.class)); + + EdgePageWords words = keywordExtractor.extractKeywords(dld, keywordMetadata); + + new MetaTagsBuilder() + .addDomainCrawlData(crawledDomain) + .addPubDate(pubDate) + .addUrl(url) + .addFeatures(ret.features) + .addFormat(ret.standard) + .build(words); + + getLinks(url, ret, doc, words); + + if (pubDate.hasYear()) { + ret.pubYear = pubDate.year(); + } + + return new DetailsWithWords(ret, words); + } + + + private static final GuardedRegex mastodonFeedRegex = GuardedRegexFactory.startsWith("/@", "^/@[^/]+/?$"); + + private boolean isDisqualified(EdgeUrl url, DocumentLanguageData dld, ProcessedDocumentDetails ret) { + if (ret.quality < minDocumentQuality) { + return true; + } + if (dld.totalNumWords() < minDocumentLength) { + return true; + } + // These pages shouldn't be publicly accessible + if ("phpinfo()".equals(ret.title)) { + return true; + } + + // Urls that look like /@foo are typically Mastodon or other twitter-like feeds, + // we don't want to index them because they change so rapidly; subdirectories are + // fine though + // + if (mastodonFeedRegex.test(url.path)) { + return true; + } + + // Annoying wordpress crap + if (url.path.startsWith("/tag/") && url.path.endsWith("/")) { + return true; + } + return false; + } + + + private void getLinks(EdgeUrl baseUrl, ProcessedDocumentDetails ret, Document doc, EdgePageWords words) { + + final LinkProcessor lp = new LinkProcessor(ret, baseUrl); + + baseUrl = linkParser.getBaseLink(doc, baseUrl); + + EdgeDomain domain = baseUrl.domain; + + for (var atag : doc.getElementsByTag("a")) { + var linkOpt = linkParser.parseLinkPermissive(baseUrl, atag); + if (linkParser.shouldIndexLink(atag)) { + linkOpt.ifPresent(lp::accept); + } + else { + linkOpt + .filter(url -> linkParser.hasBinarySuffix(url.path.toLowerCase())) + .ifPresent(lp::acceptNonIndexable); + } + } + for (var frame : doc.getElementsByTag("frame")) { + linkParser.parseFrame(baseUrl, frame).ifPresent(lp::accept); + } + for (var frame : doc.getElementsByTag("iframe")) { + linkParser.parseFrame(baseUrl, frame).ifPresent(lp::accept); + } + for (var link : doc.select("link[rel=alternate]")) { + feedExtractor + .getFeedFromAlternateTag(baseUrl, link) + .ifPresent(lp::acceptFeed); + } + + createLinkKeywords(words, lp); + createFileLinkKeywords(words, lp, domain); + } + + private void createFileLinkKeywords(EdgePageWords words, LinkProcessor lp, EdgeDomain domain) { + Set fileKeywords = new HashSet<>(100); + for (var link : lp.getNonIndexableUrls()) { + + if (!domain.hasSameTopDomain(link.domain)) { + continue; + } + + synthesizeFilenameKeyword(fileKeywords, link); + + } + + words.addAllSyntheticTerms(fileKeywords); + } + + private void synthesizeFilenameKeyword(Set fileKeywords, EdgeUrl link) { + + Path pFilename = Path.of(link.path.toLowerCase()).getFileName(); + + if (pFilename == null) return; + + String filename = pFilename.toString(); + if (filename.length() > 32 + || filename.endsWith(".xml") + || filename.endsWith(".jpg") + || filename.endsWith(".png") + || filename.endsWith(".pdf") + || filename.endsWith(".gif")) + return; + + fileKeywords.add(filename.replace(' ', '_')); + } + + private void createLinkKeywords(EdgePageWords words, LinkProcessor lp) { + final Set linkTerms = new HashSet<>(); + + for (var fd : lp.getForeignDomains()) { + linkTerms.add("links:"+fd.toString().toLowerCase()); + linkTerms.add("links:"+fd.getDomain().toLowerCase()); + } + words.addAllSyntheticTerms(linkTerms); + } + + private EdgeHtmlStandard getHtmlStandard(Document doc) { + EdgeHtmlStandard htmlStandard = HtmlStandardExtractor.parseDocType(doc.documentType()); + + if (EdgeHtmlStandard.UNKNOWN.equals(htmlStandard)) { + return HtmlStandardExtractor.sniffHtmlStandard(doc); + } + return htmlStandard; + } + + private String getDescription(Document doc) { + return summaryExtractor.extractSummary(doc); + } + + private int getLength(Document doc) { + return doc.text().length(); + } +} diff --git a/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/PlainTextDocumentProcessorPlugin.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/PlainTextDocumentProcessorPlugin.java new file mode 100644 index 00000000..b85092d6 --- /dev/null +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/processor/plugin/PlainTextDocumentProcessorPlugin.java @@ -0,0 +1,118 @@ +package nu.marginalia.converting.processor.plugin; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawledDomain; +import nu.marginalia.language.model.KeywordMetadata; +import nu.marginalia.converting.processor.keywords.DocumentKeywordExtractor; +import nu.marginalia.language.sentence.SentenceExtractor; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgePageDocumentFlags; +import nu.marginalia.model.crawl.EdgePageWords; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.model.crawl.PubDate; +import nu.marginalia.converting.model.DisqualifiedException; +import nu.marginalia.converting.model.ProcessedDocumentDetails; +import nu.marginalia.converting.processor.logic.PlainTextLogic; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.util.LineUtils; +import org.apache.commons.lang3.StringUtils; + +import java.net.URISyntaxException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; + + +public class PlainTextDocumentProcessorPlugin extends AbstractDocumentProcessorPlugin { + + private final int minDocumentLength; + private final int maxTitleLength; + private final SentenceExtractor sentenceExtractor; + private final DocumentKeywordExtractor keywordExtractor; + private final PlainTextLogic plainTextLogic = new PlainTextLogic(); + + + @Inject + public PlainTextDocumentProcessorPlugin(@Named("min-document-length") Integer minDocumentLength, + @Named("max-title-length") Integer maxTitleLength, + SentenceExtractor sentenceExtractor, + DocumentKeywordExtractor keywordExtractor) + { + this.minDocumentLength = minDocumentLength; + this.maxTitleLength = maxTitleLength; + this.sentenceExtractor = sentenceExtractor; + this.keywordExtractor = keywordExtractor; + } + + @Override + public boolean isApplicable(CrawledDocument doc) { + return doc.contentType.equalsIgnoreCase("text/plain"); + } + + @Override + public DetailsWithWords createDetails(CrawledDomain crawledDomain, CrawledDocument crawledDocument) + throws DisqualifiedException, URISyntaxException { + + String documentBody = crawledDocument.documentBody.decode(); + + if (languageFilter.isBlockedUnicodeRange(documentBody)) { + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.LANGUAGE); + } + + final EdgeUrl url = new EdgeUrl(crawledDocument.url); + + var dld = sentenceExtractor.extractSentences(documentBody, ""); + + checkDocumentLanguage(dld); + + if (dld.totalNumWords() < minDocumentLength) { + throw new DisqualifiedException(DisqualifiedException.DisqualificationReason.LENGTH); + } + + var ret = new ProcessedDocumentDetails(); + + List firstFewLines = LineUtils.firstNLines(documentBody, 40); + + ret.length = documentBody.length(); + ret.standard = EdgeHtmlStandard.PLAIN; + ret.title = StringUtils.truncate(plainTextLogic.getTitle(url, firstFewLines), maxTitleLength); + + ret.quality = -1; + + ret.features = new HashSet<>(); + ret.description = StringUtils.truncate(plainTextLogic.getDescription(firstFewLines), 255); + ret.hashCode = dld.localitySensitiveHashCode(); + + final PubDate pubDate = new PubDate(LocalDate.ofYearDay(1993, 1)); + + ret.metadata = new EdgePageDocumentsMetadata(url.depth(), pubDate.yearByte(), 0, (int) -ret.quality, EnumSet.of(EdgePageDocumentFlags.PlainText)); + + KeywordMetadata keywordMetadata = new KeywordMetadata(); + EdgePageWords words = keywordExtractor.extractKeywords(dld, keywordMetadata); + + new MetaTagsBuilder() + .addDomainCrawlData(crawledDomain) + .addPubDate(pubDate) + .addUrl(url) + .addFeatures(ret.features) + .addFormat(ret.standard) + .build(words); + + if (pubDate.hasYear()) { + ret.pubYear = pubDate.year(); + } + + /* These are assumed to be populated */ + ret.linksInternal = new ArrayList<>(); + ret.linksExternal = new ArrayList<>(); + ret.feedLinks = new ArrayList<>(); + + return new DetailsWithWords(ret, words); + } + + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/DocumentDebugger.java b/crawl/converting-process/src/main/java/nu/marginalia/converting/tool/DocumentDebugger.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/DocumentDebugger.java rename to crawl/converting-process/src/main/java/nu/marginalia/converting/tool/DocumentDebugger.java index a693dcdc..0d2fb906 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/DocumentDebugger.java +++ b/crawl/converting-process/src/main/java/nu/marginalia/converting/tool/DocumentDebugger.java @@ -1,14 +1,14 @@ -package nu.marginalia.util.language; +package nu.marginalia.converting.tool; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.util.language.processing.KeywordCounter; -import nu.marginalia.util.language.processing.KeywordExtractor; -import nu.marginalia.util.language.processing.NameCounter; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.util.language.processing.model.DocumentSentence; -import nu.marginalia.util.language.processing.model.WordRep; -import nu.marginalia.util.language.processing.model.tag.WordSeparator; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; +import nu.marginalia.LanguageModels; +import nu.marginalia.converting.processor.keywords.KeywordCounter; +import nu.marginalia.converting.processor.keywords.NameCounter; +import nu.marginalia.language.keywords.KeywordExtractor; +import nu.marginalia.language.sentence.SentenceExtractor; +import nu.marginalia.language.model.DocumentSentence; +import nu.marginalia.language.model.WordRep; +import nu.marginalia.language.model.WordSeparator; +import nu.marginalia.language.statistics.TermFrequencyDict; import org.jsoup.nodes.Document; import java.io.FileNotFoundException; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/DomPruningFilterTest.java b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/DomPruningFilterTest.java similarity index 72% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/DomPruningFilterTest.java rename to crawl/converting-process/src/test/java/nu/marginalia/converting/logic/DomPruningFilterTest.java index 957f6306..9e3b0684 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/DomPruningFilterTest.java +++ b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/DomPruningFilterTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.logic; import org.junit.jupiter.api.Test; diff --git a/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/PlainTextLogicTest.java b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/PlainTextLogicTest.java new file mode 100644 index 00000000..3e071291 --- /dev/null +++ b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/PlainTextLogicTest.java @@ -0,0 +1,267 @@ +package nu.marginalia.converting.logic; + +import nu.marginalia.converting.processor.logic.PlainTextLogic; +import nu.marginalia.util.LineUtils; +import nu.marginalia.model.EdgeUrl; +import org.junit.jupiter.api.Test; + +import java.net.URISyntaxException; + +class PlainTextLogicTest { + + PlainTextLogic ptl = new PlainTextLogic(); + + String uml = """ + User Mode Linux HOWTO + User Mode Linux Core Team + Fri Mar 7 11:53:53 EST 2008 + + This document describes the use and abuse of Jeff Dike's User Mode + Linux: a port of the Linux kernel as a normal Intel Linux process. + ______________________________________________________________________ + + Table of Contents + + + + 1. Introduction + 1.1 What is User Mode Linux? + 1.2 How is User Mode Linux Different? + 1.3 How does UML Work? + 1.4 Why Would I Want UML? + + 2. Compiling the kernel and modules + 2.1 Compiling the kernel + 2.2 Compiling and installing kernel modules + 2.3 Compiling and installing uml_utilities + + 3. Running UML and logging in + 3.1 Running UML + 3.2 Logging in + 3.3 Examples + + 4. UML on 2G/2G hosts + 4.1 Introduction + 4.2 The problem + 4.3 The solution + + 5. Setting up serial lines and consoles + 5.1 Specifying the device + 5.2 Specifying the channel + 5.3 Examples + + 6. Setting up the network + 6.1 General setup + 6.2 Userspace daemons + 6.3 Specifying ethernet addresses + 6.4 UML interface setup + 6.5 Multicast + 6.6 TUN/TAP with the uml_net helper + 6.7 TUN/TAP with a preconfigured tap device + 6.8 Ethertap + 6.9 The switch daemon + 6.10 Slip + 6.11 Slirp + 6.12 pcap + 6.13 Setting up the host yourself + + 7. Sharing Filesystems between Virtual Machines + 7.1 A warning + 7.2 Using layered block devices + 7.3 Note! + 7.4 Another warning + 7.5 Moving a backing file + 7.6 uml_moo : Merging a COW file with its backing file + 7.7 uml_mkcow : Create a new COW file + + 8. Creating filesystems + 8.1 Create the filesystem file + 8.2 Assign the file to a UML device + 8.3 Creating and mounting the filesystem + + 9. Host file access + 9.1 Using hostfs + 9.2 hostfs command line options + 9.3 hostfs as the root filesystem + 9.4 Building hostfs + + 10. The Management Console + 10.1 version + 10.2 halt and reboot + 10.3 config + 10.4 remove + 10.5 sysrq + 10.6 help + 10.7 cad + 10.8 stop + 10.9 go + 10.10 log + 10.11 proc + 10.12 Making online backups + 10.13 Event notification + + 11. Kernel debugging + 11.1 Starting the kernel under gdb + 11.2 Examining sleeping processes + 11.3 Running ddd on UML + 11.4 Debugging modules + 11.5 Attaching gdb to the kernel + 11.6 Using alternate debuggers + + 12. Kernel debugging examples + 12.1 The case of the hung fsck + 12.2 Episode 2: The case of the hung fsck + + 13. What to do when UML doesn't work + 13.1 Strange compilation errors when you build from source + 13.2 UML hangs on boot after mounting devfs + 13.3 A variety of panics and hangs with /tmp on a reiserfs filesystem + 13.4 The compile fails with errors about conflicting types for 'open', 'dup', and 'waitpid' + 13.5 UML doesn't work when /tmp is an NFS filesystem + 13.6 UML hangs on boot when compiled with gprof support + 13.7 syslogd dies with a SIGTERM on startup + 13.8 TUN/TAP networking doesn't work on a 2.4 host + 13.9 You can network to the host but not to other machines on the net + 13.10 I have no root and I want to scream + 13.11 UML build conflict between ptrace.h and ucontext.h + 13.12 The UML BogoMips is exactly half the host's BogoMips + 13.13 When you run UML, it immediately segfaults + 13.14 xterms appear, then immediately disappear + 13.15 cannot set up thread-local storage + 13.16 Process segfaults with a modern (NPTL-using) filesystem + 13.17 Any other panic, hang, or strange behavior + + 14. Diagnosing Problems + 14.1 Case 1 : Normal kernel panics + 14.2 Case 2 : Tracing thread panics + 14.3 Case 3 : Tracing thread panics caused by other threads + 14.4 Case 4 : Hangs + + 15. Thanks + 15.1 Code and Documentation + 15.2 Flushing out bugs + 15.3 Buglets and clean-ups + 15.4 Case Studies + 15.5 Other contributions + + + ______________________________________________________________________ + + 1. Introduction + + Welcome to User Mode Linux. It's going to be fun. + + + 1.1. What is User Mode Linux? + + User Mode Linux lets you run Linux inside itself! With that comes the + power to do all sorts of new things. It virtualises (or simulates, as + """; + + String cmucl = """ + ========================== C M U C L 20 a ============================= + + The CMUCL project is pleased to announce the release of CMUCL 20a. + This is a major release which contains numerous enhancements and + bug fixes from the 19f release. + + CMUCL is a free, high performance implementation of the Common Lisp + programming language which runs on most major Unix platforms. It + mainly conforms to the ANSI Common Lisp standard. CMUCL provides a + sophisticated native code compiler; a powerful foreign function + interface; an implementation of CLOS, the Common Lisp Object System, + which includes multi-methods and a meta-object protocol; a source-level + debugger and code profiler; and an Emacs-like editor implemented in + Common Lisp. CMUCL is maintained by a team of volunteers collaborating + over the Internet, and is mostly in the public domain. + + New in this release: + + * Known issues: + - On Linux and FreeBSD, it may not be possible call SAVE-LISP and + create executables. This seems to be broken on FreeBSD. On + Linux, it seems to depend on what version of Linux is used to + create the executable. Redhat Enterprise Linux appears to be + ok, but Open SuSE 10.x is not. + """; + + String xprint = """ + Archive-name: Xprint/FAQ_OLD + Version: 0.8 + Last-Modified: 2003/08/04 15:20:19 + Maintained-by: Roland Mainz + + NOTE: This version of the FAQ has been discontinued and was replaced by the + DocBook-based version available under xc/doc/hardcopy/XPRINT/Xprint_FAQ.xml + (available through http from + ) + + The following is a list of questions that are frequently asked about + Xprint. + + You can help make it an even better-quality FAQ by writing a short + contribution or update and sending it BY EMAIL ONLY to me. + A contribution should consist of a question and an answer, and increasing + number of people sends me contributions of the form "I don't know the + answer to this, but it must be a FAQ, please answer it for me". Please + read the FAQ first and then feel free to ask me if it is not in the FAQ. + + Thanks! + """; + + String vm = """ + + .. _vm: + + ============================================================= + Clawpack Virtual Machine\s + ============================================================= + + Using Clawpack requires a variety of other software packages, as summarized in + :ref:`installing`. An alternative to installing the prerequisites is to use the + virtual machine described in this section. + + Another alternative is to run Clawpack on the Cloud, see :ref:`aws`. + + To do so, you need only download and + """; + + String garfinkel = """ + The Net Effect: The DVD Rebellion\s + By Simson Garfinkel\s + MIT Technology Review + July/August 2001 + + Buy a copy of The Matrix on DVD and take it home. Play it on a Mac or + on a Windows PC and you're in for a pretty good time. But play it on + a PC running the Linux operating system, and the movie industry says + that you're breaking the law. + + Your transgression is that of "circumvention," a criminal act created + by the 1998 Digital Millennium Copyright Act. You see, the video on + DVDs is scrambled. Windows and Macintosh DVD players licensed by the + DVD Copy Control Association contain the algorithms to unscramble the + signal. The Linux DVD player contains these secrets as well. But + since the Linux-based program isn't licensed, using the software + constitutes an illegal circumvention of copyright management. + + """; + @Test + void getDescription() { + System.out.println(ptl.getDescription(LineUtils.firstNLines(uml, 25))); + System.out.println(ptl.getDescription(LineUtils.firstNLines(cmucl, 25))); + System.out.println(ptl.getDescription(LineUtils.firstNLines(xprint, 25))); + System.out.println(ptl.getDescription(LineUtils.firstNLines(vm, 25))); + System.out.println(ptl.getDescription(LineUtils.firstNLines(garfinkel, 25))); + } + + @Test + void getTitle() throws URISyntaxException { + System.out.println(ptl.getTitle(new EdgeUrl("http://user-mode-linux.sourceforge.net/old/UserModeLinux-HOWTO.txt"), LineUtils.firstNLines(uml, 25))); + System.out.println(ptl.getTitle(new EdgeUrl("https://www.cons.org/cmucl/news/release-20a.txt"), LineUtils.firstNLines(cmucl, 25))); + System.out.println(ptl.getTitle(new EdgeUrl("https://www.x.org/docs/XPRINT/Xprint_old_FAQ.txt"), LineUtils.firstNLines(xprint, 25))); + System.out.println(ptl.getTitle(new EdgeUrl("http://depts.washington.edu/clawpack/users-4.6/_sources/vm.txt"), LineUtils.firstNLines(vm, 25))); + System.out.println(ptl.getTitle(new EdgeUrl("http://www.cs.cmu.edu/afs/cs.cmu.edu/user/dst/www/DeCSS/Gallery/archive/garfinkel.txt"), LineUtils.firstNLines(garfinkel, 25))); + + } +} \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/PubDateSnifferTest.java b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/PubDateSnifferTest.java similarity index 96% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/PubDateSnifferTest.java rename to crawl/converting-process/src/test/java/nu/marginalia/converting/logic/PubDateSnifferTest.java index dd99d27e..53095944 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/PubDateSnifferTest.java +++ b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/PubDateSnifferTest.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.logic; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateSniffer; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic.PubDateHeuristicDOMParsingPass2; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; +import nu.marginalia.WmsaHome; +import nu.marginalia.converting.processor.logic.pubdate.PubDateParser; +import nu.marginalia.converting.processor.logic.pubdate.PubDateSniffer; +import nu.marginalia.converting.processor.logic.pubdate.heuristic.PubDateHeuristicDOMParsingPass2; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgeHtmlStandard; import org.jsoup.Jsoup; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/SummaryExtractorTest.java b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/SummaryExtractorTest.java similarity index 96% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/SummaryExtractorTest.java rename to crawl/converting-process/src/test/java/nu/marginalia/converting/logic/SummaryExtractorTest.java index 64942b5f..15a2d377 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/SummaryExtractorTest.java +++ b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/SummaryExtractorTest.java @@ -1,6 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic; +package nu.marginalia.converting.logic; -import nu.marginalia.wmsa.configuration.WmsaHome; +import nu.marginalia.WmsaHome; +import nu.marginalia.converting.processor.logic.SummaryExtractionFilter; +import nu.marginalia.converting.processor.logic.SummaryExtractor; import org.jsoup.Jsoup; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateTest.java b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/pubdate/PubDateTest.java similarity index 80% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateTest.java rename to crawl/converting-process/src/test/java/nu/marginalia/converting/logic/pubdate/PubDateTest.java index d55c3bfc..1ac342cf 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateTest.java +++ b/crawl/converting-process/src/test/java/nu/marginalia/converting/logic/pubdate/PubDateTest.java @@ -1,5 +1,6 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate; +package nu.marginalia.converting.logic.pubdate; +import nu.marginalia.model.crawl.PubDate; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/SentenceExtractorTest.java b/crawl/converting-process/src/test/java/nu/marginalia/converting/processor/keywords/SentenceExtractorTest.java similarity index 80% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/SentenceExtractorTest.java rename to crawl/converting-process/src/test/java/nu/marginalia/converting/processor/keywords/SentenceExtractorTest.java index 344973e4..3d942c19 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/SentenceExtractorTest.java +++ b/crawl/converting-process/src/test/java/nu/marginalia/converting/processor/keywords/SentenceExtractorTest.java @@ -1,22 +1,19 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.converting.processor.keywords; import lombok.SneakyThrows; -import nu.marginalia.util.TestLanguageModels; -import nu.marginalia.util.language.WordPatterns; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.util.language.processing.DocumentKeywordExtractor; -import nu.marginalia.util.language.processing.KeywordExtractor; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.util.language.processing.model.KeywordMetadata; -import nu.marginalia.util.language.processing.model.WordRep; -import nu.marginalia.util.language.processing.model.WordSpan; -import nu.marginalia.util.language.processing.model.tag.WordSeparator; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordMetadata; -import nu.marginalia.wmsa.edge.integration.wikipedia.WikipediaReader; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.LanguageModels; +import nu.marginalia.language.WordPatterns; +import nu.marginalia.language.model.KeywordMetadata; +import nu.marginalia.language.model.WordRep; +import nu.marginalia.language.model.WordSpan; +import nu.marginalia.language.sentence.SentenceExtractor; +import nu.marginalia.language.statistics.TermFrequencyDict; +import nu.marginalia.language.keywords.KeywordExtractor; +import nu.marginalia.language.model.WordSeparator; +import nu.marginalia.WmsaHome; +import nu.marginalia.model.crawl.EdgePageWordFlags; +import nu.marginalia.model.idx.EdgePageWordMetadata; +import nu.marginalia.test.util.TestLanguageModels; import org.apache.commons.lang3.tuple.Pair; import org.jsoup.Jsoup; import org.junit.jupiter.api.BeforeEach; @@ -56,6 +53,7 @@ class SentenceExtractorTest { var dict = new TermFrequencyDict(lm); DocumentKeywordExtractor documentKeywordExtractor = new DocumentKeywordExtractor(dict); + for (;;) { long total = 0; for (var file : Objects.requireNonNull(data.toFile().listFiles())) { @@ -111,30 +109,11 @@ class SentenceExtractorTest { } - @Test - public void testWikipedia() throws InterruptedException { - - System.out.println("Running"); - - var dict = new TermFrequencyDict(lm); - - DocumentKeywordExtractor documentKeywordExtractor = new DocumentKeywordExtractor(dict); - - var reader = new WikipediaReader("/home/vlofgren/Work/wikipedia_en_100_nopic_2021-06.zim", new EdgeDomain("encyclopedia.marginalia.nu"), - post -> { - - var newResult = newSe.extractSentences(Jsoup.parse(post.body)); - - var newRes = documentKeywordExtractor.extractKeywords(newResult, new KeywordMetadata()); - System.out.println(newRes); - }); - reader.join(); - } - @Test public void testPattern() { System.out.println(WordPatterns.singleWordAdditionalPattern.matcher("2.6.18164.el5pae").matches()); } + @Test void extractSentences() throws IOException { var data = WmsaHome.getHomePath().resolve("test-data/"); diff --git a/crawl/converting-process/src/test/java/nu/marginalia/test/util/TestLanguageModels.java b/crawl/converting-process/src/test/java/nu/marginalia/test/util/TestLanguageModels.java new file mode 100644 index 00000000..958604ca --- /dev/null +++ b/crawl/converting-process/src/test/java/nu/marginalia/test/util/TestLanguageModels.java @@ -0,0 +1,37 @@ +package nu.marginalia.test.util; + +import nu.marginalia.LanguageModels; +import nu.marginalia.WmsaHome; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +public class TestLanguageModels { + private static final Path LANGUAGE_MODELS_DEFAULT = WmsaHome.getHomePath().resolve("model"); + + public static Path getLanguageModelsPath() { + final Path languageModelsHome = Optional.ofNullable(System.getenv("LANGUAGE_MODELS_HOME")) + .map(Path::of) + .orElse(LANGUAGE_MODELS_DEFAULT); + + if (!Files.isDirectory(languageModelsHome)) { + throw new IllegalStateException("Could not find $LANGUAGE_MODELS_HOME, see doc/language-models.md"); + } + return languageModelsHome; + } + + public static LanguageModels getLanguageModels() { + + var languageModelsHome = getLanguageModelsPath(); + + return new LanguageModels( + languageModelsHome.resolve("ngrams.bin"), + languageModelsHome.resolve("tfreq-new-algo3.bin"), + languageModelsHome.resolve("opennlp-sentence.bin"), + languageModelsHome.resolve("English.RDR"), + languageModelsHome.resolve("English.DICT"), + languageModelsHome.resolve("opennlp-tokens.bin") + ); + } +} diff --git a/marginalia_nu/src/test/resources/html/monadnock.html b/crawl/converting-process/src/test/resources/html/monadnock.html similarity index 100% rename from marginalia_nu/src/test/resources/html/monadnock.html rename to crawl/converting-process/src/test/resources/html/monadnock.html diff --git a/marginalia_nu/src/test/resources/html/readme.md b/crawl/converting-process/src/test/resources/html/readme.md similarity index 100% rename from marginalia_nu/src/test/resources/html/readme.md rename to crawl/converting-process/src/test/resources/html/readme.md diff --git a/marginalia_nu/src/test/resources/html/summarization/187.shtml b/crawl/converting-process/src/test/resources/html/summarization/187.shtml similarity index 100% rename from marginalia_nu/src/test/resources/html/summarization/187.shtml rename to crawl/converting-process/src/test/resources/html/summarization/187.shtml diff --git a/marginalia_nu/src/test/resources/html/summarization/surrey.html b/crawl/converting-process/src/test/resources/html/summarization/surrey.html similarity index 100% rename from marginalia_nu/src/test/resources/html/summarization/surrey.html rename to crawl/converting-process/src/test/resources/html/summarization/surrey.html diff --git a/marginalia_nu/src/test/resources/html/summarization/surrey.html.1 b/crawl/converting-process/src/test/resources/html/summarization/surrey.html.1 similarity index 100% rename from marginalia_nu/src/test/resources/html/summarization/surrey.html.1 rename to crawl/converting-process/src/test/resources/html/summarization/surrey.html.1 diff --git a/marginalia_nu/src/test/resources/html/work-set/index b/crawl/converting-process/src/test/resources/html/work-set/index similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/index rename to crawl/converting-process/src/test/resources/html/work-set/index diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1021546012 b/crawl/converting-process/src/test/resources/html/work-set/url--1021546012 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1021546012 rename to crawl/converting-process/src/test/resources/html/work-set/url--1021546012 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1028592943 b/crawl/converting-process/src/test/resources/html/work-set/url--1028592943 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1028592943 rename to crawl/converting-process/src/test/resources/html/work-set/url--1028592943 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1081293162 b/crawl/converting-process/src/test/resources/html/work-set/url--1081293162 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1081293162 rename to crawl/converting-process/src/test/resources/html/work-set/url--1081293162 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1105046394 b/crawl/converting-process/src/test/resources/html/work-set/url--1105046394 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1105046394 rename to crawl/converting-process/src/test/resources/html/work-set/url--1105046394 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1146923296 b/crawl/converting-process/src/test/resources/html/work-set/url--1146923296 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1146923296 rename to crawl/converting-process/src/test/resources/html/work-set/url--1146923296 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1194694074 b/crawl/converting-process/src/test/resources/html/work-set/url--1194694074 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1194694074 rename to crawl/converting-process/src/test/resources/html/work-set/url--1194694074 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1207898281 b/crawl/converting-process/src/test/resources/html/work-set/url--1207898281 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1207898281 rename to crawl/converting-process/src/test/resources/html/work-set/url--1207898281 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1268145073 b/crawl/converting-process/src/test/resources/html/work-set/url--1268145073 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1268145073 rename to crawl/converting-process/src/test/resources/html/work-set/url--1268145073 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1294876331 b/crawl/converting-process/src/test/resources/html/work-set/url--1294876331 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1294876331 rename to crawl/converting-process/src/test/resources/html/work-set/url--1294876331 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1314767420 b/crawl/converting-process/src/test/resources/html/work-set/url--1314767420 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1314767420 rename to crawl/converting-process/src/test/resources/html/work-set/url--1314767420 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1316269786 b/crawl/converting-process/src/test/resources/html/work-set/url--1316269786 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1316269786 rename to crawl/converting-process/src/test/resources/html/work-set/url--1316269786 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1316766580 b/crawl/converting-process/src/test/resources/html/work-set/url--1316766580 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1316766580 rename to crawl/converting-process/src/test/resources/html/work-set/url--1316766580 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1319968043 b/crawl/converting-process/src/test/resources/html/work-set/url--1319968043 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1319968043 rename to crawl/converting-process/src/test/resources/html/work-set/url--1319968043 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1338576987 b/crawl/converting-process/src/test/resources/html/work-set/url--1338576987 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1338576987 rename to crawl/converting-process/src/test/resources/html/work-set/url--1338576987 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1341909571 b/crawl/converting-process/src/test/resources/html/work-set/url--1341909571 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1341909571 rename to crawl/converting-process/src/test/resources/html/work-set/url--1341909571 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1369578579 b/crawl/converting-process/src/test/resources/html/work-set/url--1369578579 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1369578579 rename to crawl/converting-process/src/test/resources/html/work-set/url--1369578579 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1437315645 b/crawl/converting-process/src/test/resources/html/work-set/url--1437315645 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1437315645 rename to crawl/converting-process/src/test/resources/html/work-set/url--1437315645 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1458954960 b/crawl/converting-process/src/test/resources/html/work-set/url--1458954960 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1458954960 rename to crawl/converting-process/src/test/resources/html/work-set/url--1458954960 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1475681345 b/crawl/converting-process/src/test/resources/html/work-set/url--1475681345 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1475681345 rename to crawl/converting-process/src/test/resources/html/work-set/url--1475681345 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1498328446 b/crawl/converting-process/src/test/resources/html/work-set/url--1498328446 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1498328446 rename to crawl/converting-process/src/test/resources/html/work-set/url--1498328446 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1507779664 b/crawl/converting-process/src/test/resources/html/work-set/url--1507779664 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1507779664 rename to crawl/converting-process/src/test/resources/html/work-set/url--1507779664 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1540303379 b/crawl/converting-process/src/test/resources/html/work-set/url--1540303379 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1540303379 rename to crawl/converting-process/src/test/resources/html/work-set/url--1540303379 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--154898476 b/crawl/converting-process/src/test/resources/html/work-set/url--154898476 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--154898476 rename to crawl/converting-process/src/test/resources/html/work-set/url--154898476 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1552059399 b/crawl/converting-process/src/test/resources/html/work-set/url--1552059399 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1552059399 rename to crawl/converting-process/src/test/resources/html/work-set/url--1552059399 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1557688340 b/crawl/converting-process/src/test/resources/html/work-set/url--1557688340 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1557688340 rename to crawl/converting-process/src/test/resources/html/work-set/url--1557688340 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1584145751 b/crawl/converting-process/src/test/resources/html/work-set/url--1584145751 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1584145751 rename to crawl/converting-process/src/test/resources/html/work-set/url--1584145751 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1605151204 b/crawl/converting-process/src/test/resources/html/work-set/url--1605151204 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1605151204 rename to crawl/converting-process/src/test/resources/html/work-set/url--1605151204 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--162269247 b/crawl/converting-process/src/test/resources/html/work-set/url--162269247 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--162269247 rename to crawl/converting-process/src/test/resources/html/work-set/url--162269247 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1624294488 b/crawl/converting-process/src/test/resources/html/work-set/url--1624294488 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1624294488 rename to crawl/converting-process/src/test/resources/html/work-set/url--1624294488 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--164108285 b/crawl/converting-process/src/test/resources/html/work-set/url--164108285 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--164108285 rename to crawl/converting-process/src/test/resources/html/work-set/url--164108285 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1645688243 b/crawl/converting-process/src/test/resources/html/work-set/url--1645688243 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1645688243 rename to crawl/converting-process/src/test/resources/html/work-set/url--1645688243 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1658004609 b/crawl/converting-process/src/test/resources/html/work-set/url--1658004609 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1658004609 rename to crawl/converting-process/src/test/resources/html/work-set/url--1658004609 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1658558834 b/crawl/converting-process/src/test/resources/html/work-set/url--1658558834 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1658558834 rename to crawl/converting-process/src/test/resources/html/work-set/url--1658558834 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1698664879 b/crawl/converting-process/src/test/resources/html/work-set/url--1698664879 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1698664879 rename to crawl/converting-process/src/test/resources/html/work-set/url--1698664879 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--169975195 b/crawl/converting-process/src/test/resources/html/work-set/url--169975195 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--169975195 rename to crawl/converting-process/src/test/resources/html/work-set/url--169975195 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1701203332 b/crawl/converting-process/src/test/resources/html/work-set/url--1701203332 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1701203332 rename to crawl/converting-process/src/test/resources/html/work-set/url--1701203332 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--17281998 b/crawl/converting-process/src/test/resources/html/work-set/url--17281998 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--17281998 rename to crawl/converting-process/src/test/resources/html/work-set/url--17281998 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1742070028 b/crawl/converting-process/src/test/resources/html/work-set/url--1742070028 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1742070028 rename to crawl/converting-process/src/test/resources/html/work-set/url--1742070028 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1745376814 b/crawl/converting-process/src/test/resources/html/work-set/url--1745376814 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1745376814 rename to crawl/converting-process/src/test/resources/html/work-set/url--1745376814 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1749889035 b/crawl/converting-process/src/test/resources/html/work-set/url--1749889035 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1749889035 rename to crawl/converting-process/src/test/resources/html/work-set/url--1749889035 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--176177364 b/crawl/converting-process/src/test/resources/html/work-set/url--176177364 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--176177364 rename to crawl/converting-process/src/test/resources/html/work-set/url--176177364 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--177014197 b/crawl/converting-process/src/test/resources/html/work-set/url--177014197 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--177014197 rename to crawl/converting-process/src/test/resources/html/work-set/url--177014197 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1794527707 b/crawl/converting-process/src/test/resources/html/work-set/url--1794527707 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1794527707 rename to crawl/converting-process/src/test/resources/html/work-set/url--1794527707 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1797740201 b/crawl/converting-process/src/test/resources/html/work-set/url--1797740201 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1797740201 rename to crawl/converting-process/src/test/resources/html/work-set/url--1797740201 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1799098579 b/crawl/converting-process/src/test/resources/html/work-set/url--1799098579 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1799098579 rename to crawl/converting-process/src/test/resources/html/work-set/url--1799098579 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1959637826 b/crawl/converting-process/src/test/resources/html/work-set/url--1959637826 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1959637826 rename to crawl/converting-process/src/test/resources/html/work-set/url--1959637826 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1971916964 b/crawl/converting-process/src/test/resources/html/work-set/url--1971916964 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1971916964 rename to crawl/converting-process/src/test/resources/html/work-set/url--1971916964 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--1985840368 b/crawl/converting-process/src/test/resources/html/work-set/url--1985840368 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--1985840368 rename to crawl/converting-process/src/test/resources/html/work-set/url--1985840368 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--2012610859 b/crawl/converting-process/src/test/resources/html/work-set/url--2012610859 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--2012610859 rename to crawl/converting-process/src/test/resources/html/work-set/url--2012610859 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--202178680 b/crawl/converting-process/src/test/resources/html/work-set/url--202178680 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--202178680 rename to crawl/converting-process/src/test/resources/html/work-set/url--202178680 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--2043528727 b/crawl/converting-process/src/test/resources/html/work-set/url--2043528727 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--2043528727 rename to crawl/converting-process/src/test/resources/html/work-set/url--2043528727 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--2081757477 b/crawl/converting-process/src/test/resources/html/work-set/url--2081757477 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--2081757477 rename to crawl/converting-process/src/test/resources/html/work-set/url--2081757477 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--2103982576 b/crawl/converting-process/src/test/resources/html/work-set/url--2103982576 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--2103982576 rename to crawl/converting-process/src/test/resources/html/work-set/url--2103982576 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--2111558769 b/crawl/converting-process/src/test/resources/html/work-set/url--2111558769 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--2111558769 rename to crawl/converting-process/src/test/resources/html/work-set/url--2111558769 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--213168798 b/crawl/converting-process/src/test/resources/html/work-set/url--213168798 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--213168798 rename to crawl/converting-process/src/test/resources/html/work-set/url--213168798 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--232544032 b/crawl/converting-process/src/test/resources/html/work-set/url--232544032 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--232544032 rename to crawl/converting-process/src/test/resources/html/work-set/url--232544032 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--253010011 b/crawl/converting-process/src/test/resources/html/work-set/url--253010011 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--253010011 rename to crawl/converting-process/src/test/resources/html/work-set/url--253010011 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--274250994 b/crawl/converting-process/src/test/resources/html/work-set/url--274250994 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--274250994 rename to crawl/converting-process/src/test/resources/html/work-set/url--274250994 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--332442790 b/crawl/converting-process/src/test/resources/html/work-set/url--332442790 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--332442790 rename to crawl/converting-process/src/test/resources/html/work-set/url--332442790 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--353437903 b/crawl/converting-process/src/test/resources/html/work-set/url--353437903 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--353437903 rename to crawl/converting-process/src/test/resources/html/work-set/url--353437903 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--364546777 b/crawl/converting-process/src/test/resources/html/work-set/url--364546777 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--364546777 rename to crawl/converting-process/src/test/resources/html/work-set/url--364546777 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--379129416 b/crawl/converting-process/src/test/resources/html/work-set/url--379129416 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--379129416 rename to crawl/converting-process/src/test/resources/html/work-set/url--379129416 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--399428149 b/crawl/converting-process/src/test/resources/html/work-set/url--399428149 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--399428149 rename to crawl/converting-process/src/test/resources/html/work-set/url--399428149 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--425233170 b/crawl/converting-process/src/test/resources/html/work-set/url--425233170 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--425233170 rename to crawl/converting-process/src/test/resources/html/work-set/url--425233170 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--434612307 b/crawl/converting-process/src/test/resources/html/work-set/url--434612307 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--434612307 rename to crawl/converting-process/src/test/resources/html/work-set/url--434612307 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--439772328 b/crawl/converting-process/src/test/resources/html/work-set/url--439772328 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--439772328 rename to crawl/converting-process/src/test/resources/html/work-set/url--439772328 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--458002611 b/crawl/converting-process/src/test/resources/html/work-set/url--458002611 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--458002611 rename to crawl/converting-process/src/test/resources/html/work-set/url--458002611 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--506010305 b/crawl/converting-process/src/test/resources/html/work-set/url--506010305 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--506010305 rename to crawl/converting-process/src/test/resources/html/work-set/url--506010305 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--546773534 b/crawl/converting-process/src/test/resources/html/work-set/url--546773534 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--546773534 rename to crawl/converting-process/src/test/resources/html/work-set/url--546773534 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--551288516 b/crawl/converting-process/src/test/resources/html/work-set/url--551288516 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--551288516 rename to crawl/converting-process/src/test/resources/html/work-set/url--551288516 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--602577763 b/crawl/converting-process/src/test/resources/html/work-set/url--602577763 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--602577763 rename to crawl/converting-process/src/test/resources/html/work-set/url--602577763 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--611668054 b/crawl/converting-process/src/test/resources/html/work-set/url--611668054 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--611668054 rename to crawl/converting-process/src/test/resources/html/work-set/url--611668054 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--634771245 b/crawl/converting-process/src/test/resources/html/work-set/url--634771245 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--634771245 rename to crawl/converting-process/src/test/resources/html/work-set/url--634771245 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--639320493 b/crawl/converting-process/src/test/resources/html/work-set/url--639320493 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--639320493 rename to crawl/converting-process/src/test/resources/html/work-set/url--639320493 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--643179018 b/crawl/converting-process/src/test/resources/html/work-set/url--643179018 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--643179018 rename to crawl/converting-process/src/test/resources/html/work-set/url--643179018 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--663772351 b/crawl/converting-process/src/test/resources/html/work-set/url--663772351 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--663772351 rename to crawl/converting-process/src/test/resources/html/work-set/url--663772351 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--670789152 b/crawl/converting-process/src/test/resources/html/work-set/url--670789152 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--670789152 rename to crawl/converting-process/src/test/resources/html/work-set/url--670789152 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--6797317 b/crawl/converting-process/src/test/resources/html/work-set/url--6797317 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--6797317 rename to crawl/converting-process/src/test/resources/html/work-set/url--6797317 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--700978490 b/crawl/converting-process/src/test/resources/html/work-set/url--700978490 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--700978490 rename to crawl/converting-process/src/test/resources/html/work-set/url--700978490 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--708035332 b/crawl/converting-process/src/test/resources/html/work-set/url--708035332 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--708035332 rename to crawl/converting-process/src/test/resources/html/work-set/url--708035332 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--804917062 b/crawl/converting-process/src/test/resources/html/work-set/url--804917062 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--804917062 rename to crawl/converting-process/src/test/resources/html/work-set/url--804917062 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--819771302 b/crawl/converting-process/src/test/resources/html/work-set/url--819771302 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--819771302 rename to crawl/converting-process/src/test/resources/html/work-set/url--819771302 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--840796372 b/crawl/converting-process/src/test/resources/html/work-set/url--840796372 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--840796372 rename to crawl/converting-process/src/test/resources/html/work-set/url--840796372 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--841445362 b/crawl/converting-process/src/test/resources/html/work-set/url--841445362 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--841445362 rename to crawl/converting-process/src/test/resources/html/work-set/url--841445362 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--862385354 b/crawl/converting-process/src/test/resources/html/work-set/url--862385354 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--862385354 rename to crawl/converting-process/src/test/resources/html/work-set/url--862385354 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--879796466 b/crawl/converting-process/src/test/resources/html/work-set/url--879796466 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--879796466 rename to crawl/converting-process/src/test/resources/html/work-set/url--879796466 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--89134993 b/crawl/converting-process/src/test/resources/html/work-set/url--89134993 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--89134993 rename to crawl/converting-process/src/test/resources/html/work-set/url--89134993 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--905197876 b/crawl/converting-process/src/test/resources/html/work-set/url--905197876 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--905197876 rename to crawl/converting-process/src/test/resources/html/work-set/url--905197876 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--920328354 b/crawl/converting-process/src/test/resources/html/work-set/url--920328354 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--920328354 rename to crawl/converting-process/src/test/resources/html/work-set/url--920328354 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--952827759 b/crawl/converting-process/src/test/resources/html/work-set/url--952827759 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--952827759 rename to crawl/converting-process/src/test/resources/html/work-set/url--952827759 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--964018507 b/crawl/converting-process/src/test/resources/html/work-set/url--964018507 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--964018507 rename to crawl/converting-process/src/test/resources/html/work-set/url--964018507 diff --git a/marginalia_nu/src/test/resources/html/work-set/url--972614909 b/crawl/converting-process/src/test/resources/html/work-set/url--972614909 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url--972614909 rename to crawl/converting-process/src/test/resources/html/work-set/url--972614909 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-10088520 b/crawl/converting-process/src/test/resources/html/work-set/url-10088520 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-10088520 rename to crawl/converting-process/src/test/resources/html/work-set/url-10088520 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1013281103 b/crawl/converting-process/src/test/resources/html/work-set/url-1013281103 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1013281103 rename to crawl/converting-process/src/test/resources/html/work-set/url-1013281103 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1019241851 b/crawl/converting-process/src/test/resources/html/work-set/url-1019241851 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1019241851 rename to crawl/converting-process/src/test/resources/html/work-set/url-1019241851 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1059944953 b/crawl/converting-process/src/test/resources/html/work-set/url-1059944953 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1059944953 rename to crawl/converting-process/src/test/resources/html/work-set/url-1059944953 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1118681302 b/crawl/converting-process/src/test/resources/html/work-set/url-1118681302 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1118681302 rename to crawl/converting-process/src/test/resources/html/work-set/url-1118681302 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1179298706 b/crawl/converting-process/src/test/resources/html/work-set/url-1179298706 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1179298706 rename to crawl/converting-process/src/test/resources/html/work-set/url-1179298706 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1191749784 b/crawl/converting-process/src/test/resources/html/work-set/url-1191749784 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1191749784 rename to crawl/converting-process/src/test/resources/html/work-set/url-1191749784 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1207094790 b/crawl/converting-process/src/test/resources/html/work-set/url-1207094790 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1207094790 rename to crawl/converting-process/src/test/resources/html/work-set/url-1207094790 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1213989666 b/crawl/converting-process/src/test/resources/html/work-set/url-1213989666 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1213989666 rename to crawl/converting-process/src/test/resources/html/work-set/url-1213989666 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1222442301 b/crawl/converting-process/src/test/resources/html/work-set/url-1222442301 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1222442301 rename to crawl/converting-process/src/test/resources/html/work-set/url-1222442301 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-130332455 b/crawl/converting-process/src/test/resources/html/work-set/url-130332455 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-130332455 rename to crawl/converting-process/src/test/resources/html/work-set/url-130332455 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1311055461 b/crawl/converting-process/src/test/resources/html/work-set/url-1311055461 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1311055461 rename to crawl/converting-process/src/test/resources/html/work-set/url-1311055461 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1391842722 b/crawl/converting-process/src/test/resources/html/work-set/url-1391842722 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1391842722 rename to crawl/converting-process/src/test/resources/html/work-set/url-1391842722 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1457388763 b/crawl/converting-process/src/test/resources/html/work-set/url-1457388763 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1457388763 rename to crawl/converting-process/src/test/resources/html/work-set/url-1457388763 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1506356272 b/crawl/converting-process/src/test/resources/html/work-set/url-1506356272 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1506356272 rename to crawl/converting-process/src/test/resources/html/work-set/url-1506356272 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1511762169 b/crawl/converting-process/src/test/resources/html/work-set/url-1511762169 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1511762169 rename to crawl/converting-process/src/test/resources/html/work-set/url-1511762169 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1534640058 b/crawl/converting-process/src/test/resources/html/work-set/url-1534640058 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1534640058 rename to crawl/converting-process/src/test/resources/html/work-set/url-1534640058 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1551513871 b/crawl/converting-process/src/test/resources/html/work-set/url-1551513871 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1551513871 rename to crawl/converting-process/src/test/resources/html/work-set/url-1551513871 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1567632447 b/crawl/converting-process/src/test/resources/html/work-set/url-1567632447 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1567632447 rename to crawl/converting-process/src/test/resources/html/work-set/url-1567632447 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1623049502 b/crawl/converting-process/src/test/resources/html/work-set/url-1623049502 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1623049502 rename to crawl/converting-process/src/test/resources/html/work-set/url-1623049502 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-163919330 b/crawl/converting-process/src/test/resources/html/work-set/url-163919330 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-163919330 rename to crawl/converting-process/src/test/resources/html/work-set/url-163919330 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1661398327 b/crawl/converting-process/src/test/resources/html/work-set/url-1661398327 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1661398327 rename to crawl/converting-process/src/test/resources/html/work-set/url-1661398327 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1724309925 b/crawl/converting-process/src/test/resources/html/work-set/url-1724309925 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1724309925 rename to crawl/converting-process/src/test/resources/html/work-set/url-1724309925 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1736807128 b/crawl/converting-process/src/test/resources/html/work-set/url-1736807128 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1736807128 rename to crawl/converting-process/src/test/resources/html/work-set/url-1736807128 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1739031345 b/crawl/converting-process/src/test/resources/html/work-set/url-1739031345 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1739031345 rename to crawl/converting-process/src/test/resources/html/work-set/url-1739031345 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1755745765 b/crawl/converting-process/src/test/resources/html/work-set/url-1755745765 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1755745765 rename to crawl/converting-process/src/test/resources/html/work-set/url-1755745765 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1802811100 b/crawl/converting-process/src/test/resources/html/work-set/url-1802811100 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1802811100 rename to crawl/converting-process/src/test/resources/html/work-set/url-1802811100 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1805364707 b/crawl/converting-process/src/test/resources/html/work-set/url-1805364707 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1805364707 rename to crawl/converting-process/src/test/resources/html/work-set/url-1805364707 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1832702370 b/crawl/converting-process/src/test/resources/html/work-set/url-1832702370 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1832702370 rename to crawl/converting-process/src/test/resources/html/work-set/url-1832702370 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1853114311 b/crawl/converting-process/src/test/resources/html/work-set/url-1853114311 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1853114311 rename to crawl/converting-process/src/test/resources/html/work-set/url-1853114311 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1924872844 b/crawl/converting-process/src/test/resources/html/work-set/url-1924872844 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1924872844 rename to crawl/converting-process/src/test/resources/html/work-set/url-1924872844 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-197772804 b/crawl/converting-process/src/test/resources/html/work-set/url-197772804 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-197772804 rename to crawl/converting-process/src/test/resources/html/work-set/url-197772804 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1984259912 b/crawl/converting-process/src/test/resources/html/work-set/url-1984259912 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1984259912 rename to crawl/converting-process/src/test/resources/html/work-set/url-1984259912 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-1990903988 b/crawl/converting-process/src/test/resources/html/work-set/url-1990903988 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-1990903988 rename to crawl/converting-process/src/test/resources/html/work-set/url-1990903988 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-2039310951 b/crawl/converting-process/src/test/resources/html/work-set/url-2039310951 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-2039310951 rename to crawl/converting-process/src/test/resources/html/work-set/url-2039310951 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-2040857056 b/crawl/converting-process/src/test/resources/html/work-set/url-2040857056 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-2040857056 rename to crawl/converting-process/src/test/resources/html/work-set/url-2040857056 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-2052613093 b/crawl/converting-process/src/test/resources/html/work-set/url-2052613093 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-2052613093 rename to crawl/converting-process/src/test/resources/html/work-set/url-2052613093 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-2063899866 b/crawl/converting-process/src/test/resources/html/work-set/url-2063899866 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-2063899866 rename to crawl/converting-process/src/test/resources/html/work-set/url-2063899866 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-2115548255 b/crawl/converting-process/src/test/resources/html/work-set/url-2115548255 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-2115548255 rename to crawl/converting-process/src/test/resources/html/work-set/url-2115548255 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-2127148436 b/crawl/converting-process/src/test/resources/html/work-set/url-2127148436 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-2127148436 rename to crawl/converting-process/src/test/resources/html/work-set/url-2127148436 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-2133781904 b/crawl/converting-process/src/test/resources/html/work-set/url-2133781904 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-2133781904 rename to crawl/converting-process/src/test/resources/html/work-set/url-2133781904 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-225690385 b/crawl/converting-process/src/test/resources/html/work-set/url-225690385 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-225690385 rename to crawl/converting-process/src/test/resources/html/work-set/url-225690385 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-226401955 b/crawl/converting-process/src/test/resources/html/work-set/url-226401955 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-226401955 rename to crawl/converting-process/src/test/resources/html/work-set/url-226401955 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-262970770 b/crawl/converting-process/src/test/resources/html/work-set/url-262970770 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-262970770 rename to crawl/converting-process/src/test/resources/html/work-set/url-262970770 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-30106798 b/crawl/converting-process/src/test/resources/html/work-set/url-30106798 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-30106798 rename to crawl/converting-process/src/test/resources/html/work-set/url-30106798 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-302167335 b/crawl/converting-process/src/test/resources/html/work-set/url-302167335 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-302167335 rename to crawl/converting-process/src/test/resources/html/work-set/url-302167335 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-327999153 b/crawl/converting-process/src/test/resources/html/work-set/url-327999153 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-327999153 rename to crawl/converting-process/src/test/resources/html/work-set/url-327999153 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-332568225 b/crawl/converting-process/src/test/resources/html/work-set/url-332568225 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-332568225 rename to crawl/converting-process/src/test/resources/html/work-set/url-332568225 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-343223418 b/crawl/converting-process/src/test/resources/html/work-set/url-343223418 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-343223418 rename to crawl/converting-process/src/test/resources/html/work-set/url-343223418 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-383103932 b/crawl/converting-process/src/test/resources/html/work-set/url-383103932 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-383103932 rename to crawl/converting-process/src/test/resources/html/work-set/url-383103932 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-412929678 b/crawl/converting-process/src/test/resources/html/work-set/url-412929678 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-412929678 rename to crawl/converting-process/src/test/resources/html/work-set/url-412929678 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-475213997 b/crawl/converting-process/src/test/resources/html/work-set/url-475213997 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-475213997 rename to crawl/converting-process/src/test/resources/html/work-set/url-475213997 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-483403121 b/crawl/converting-process/src/test/resources/html/work-set/url-483403121 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-483403121 rename to crawl/converting-process/src/test/resources/html/work-set/url-483403121 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-488667993 b/crawl/converting-process/src/test/resources/html/work-set/url-488667993 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-488667993 rename to crawl/converting-process/src/test/resources/html/work-set/url-488667993 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-50815201 b/crawl/converting-process/src/test/resources/html/work-set/url-50815201 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-50815201 rename to crawl/converting-process/src/test/resources/html/work-set/url-50815201 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-522685905 b/crawl/converting-process/src/test/resources/html/work-set/url-522685905 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-522685905 rename to crawl/converting-process/src/test/resources/html/work-set/url-522685905 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-570714305 b/crawl/converting-process/src/test/resources/html/work-set/url-570714305 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-570714305 rename to crawl/converting-process/src/test/resources/html/work-set/url-570714305 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-58733529 b/crawl/converting-process/src/test/resources/html/work-set/url-58733529 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-58733529 rename to crawl/converting-process/src/test/resources/html/work-set/url-58733529 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-616518304 b/crawl/converting-process/src/test/resources/html/work-set/url-616518304 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-616518304 rename to crawl/converting-process/src/test/resources/html/work-set/url-616518304 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-662169426 b/crawl/converting-process/src/test/resources/html/work-set/url-662169426 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-662169426 rename to crawl/converting-process/src/test/resources/html/work-set/url-662169426 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-677278788 b/crawl/converting-process/src/test/resources/html/work-set/url-677278788 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-677278788 rename to crawl/converting-process/src/test/resources/html/work-set/url-677278788 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-690486170 b/crawl/converting-process/src/test/resources/html/work-set/url-690486170 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-690486170 rename to crawl/converting-process/src/test/resources/html/work-set/url-690486170 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-709693331 b/crawl/converting-process/src/test/resources/html/work-set/url-709693331 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-709693331 rename to crawl/converting-process/src/test/resources/html/work-set/url-709693331 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-734531556 b/crawl/converting-process/src/test/resources/html/work-set/url-734531556 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-734531556 rename to crawl/converting-process/src/test/resources/html/work-set/url-734531556 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-767530276 b/crawl/converting-process/src/test/resources/html/work-set/url-767530276 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-767530276 rename to crawl/converting-process/src/test/resources/html/work-set/url-767530276 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-783154014 b/crawl/converting-process/src/test/resources/html/work-set/url-783154014 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-783154014 rename to crawl/converting-process/src/test/resources/html/work-set/url-783154014 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-796905237 b/crawl/converting-process/src/test/resources/html/work-set/url-796905237 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-796905237 rename to crawl/converting-process/src/test/resources/html/work-set/url-796905237 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-800099955 b/crawl/converting-process/src/test/resources/html/work-set/url-800099955 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-800099955 rename to crawl/converting-process/src/test/resources/html/work-set/url-800099955 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-804101946 b/crawl/converting-process/src/test/resources/html/work-set/url-804101946 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-804101946 rename to crawl/converting-process/src/test/resources/html/work-set/url-804101946 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-830664902 b/crawl/converting-process/src/test/resources/html/work-set/url-830664902 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-830664902 rename to crawl/converting-process/src/test/resources/html/work-set/url-830664902 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-876060686 b/crawl/converting-process/src/test/resources/html/work-set/url-876060686 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-876060686 rename to crawl/converting-process/src/test/resources/html/work-set/url-876060686 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-892584998 b/crawl/converting-process/src/test/resources/html/work-set/url-892584998 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-892584998 rename to crawl/converting-process/src/test/resources/html/work-set/url-892584998 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-942458463 b/crawl/converting-process/src/test/resources/html/work-set/url-942458463 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-942458463 rename to crawl/converting-process/src/test/resources/html/work-set/url-942458463 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-952036171 b/crawl/converting-process/src/test/resources/html/work-set/url-952036171 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-952036171 rename to crawl/converting-process/src/test/resources/html/work-set/url-952036171 diff --git a/marginalia_nu/src/test/resources/html/work-set/url-968207276 b/crawl/converting-process/src/test/resources/html/work-set/url-968207276 similarity index 100% rename from marginalia_nu/src/test/resources/html/work-set/url-968207276 rename to crawl/converting-process/src/test/resources/html/work-set/url-968207276 diff --git a/crawl/crawl-job-extractor-process/build.gradle b/crawl/crawl-job-extractor-process/build.gradle new file mode 100644 index 00000000..0b5d6057 --- /dev/null +++ b/crawl/crawl-job-extractor-process/build.gradle @@ -0,0 +1,47 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'application' + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +application { + mainClass = 'nu.marginalia.crawl.CrawlJobExtractorMain' + applicationName = 'crawl-job-extractor-process' +} + +dependencies { + implementation project(':common:model') + implementation project(':common:service') + implementation project(':crawl:crawling-model') + implementation project(':crawl:common') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.bundles.mariadb + implementation libs.guice + implementation libs.gson + implementation libs.zstd + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlJobExtractorMain.java b/crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobDomainExtractor.java similarity index 65% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlJobExtractorMain.java rename to crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobDomainExtractor.java index 2c3a373b..f0a2fda2 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlJobExtractorMain.java +++ b/crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobDomainExtractor.java @@ -1,29 +1,24 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawl; -import com.github.luben.zstd.ZstdOutputStream; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; -import com.google.gson.Gson; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.crawling.model.CrawlingSpecification; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklistImpl; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import org.mariadb.jdbc.Driver; +import nu.marginalia.crawling.model.CrawlingSpecification; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklistImpl; -import java.io.BufferedOutputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Path; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; import java.util.stream.Stream; -public class CrawlJobExtractorMain { +public class CrawlJobDomainExtractor { + private static final int MIN_VISIT_COUNT = 1000; + private static final int MAX_VISIT_COUNT = 100000; private static final String specificDomainSql = """ @@ -72,66 +67,22 @@ public class CrawlJobExtractorMain { AND VISITED ; """; - private static final int MIN_VISIT_COUNT = 1000; - private static final int MAX_VISIT_COUNT = 100000; + private final EdgeDomainBlacklistImpl blacklist; - - private final Connection conn; + private final HikariDataSource dataSource; private static final HashFunction hasher = Hashing.murmur3_128(0); - public static void main(String... args) throws SQLException, IOException { - Driver driver = new Driver(); - var outFile = Path.of(args[0]); - - Gson gson = GsonFactory.get(); - String[] targetDomains = Arrays.stream(args).skip(1).toArray(String[]::new); - - - try (var out = new PrintWriter(new ZstdOutputStream(new BufferedOutputStream(new FileOutputStream(outFile.toFile()))))) { - final var extractor = new CrawlJobExtractorMain(new DatabaseModule().provideConnection()); - final Stream jobs; - - if (targetDomains.length > 0) { - jobs = Arrays.stream(targetDomains).map(EdgeDomain::new).map(extractor::extractDomain); - } else { - jobs = extractor.extractDomains(); - } - - jobs.map(gson::toJson).forEach(out::println); - } + public CrawlJobDomainExtractor(EdgeDomainBlacklistImpl blacklist, HikariDataSource dataSource) { + this.blacklist = blacklist; + this.dataSource = dataSource; } - public static void writeSpec(Path outFile, String domain, List urls) throws IOException { - Gson gson = GsonFactory.get(); - - try (var out = new PrintWriter(new ZstdOutputStream(new BufferedOutputStream(new FileOutputStream(outFile.toFile()))))) { - var job = new CrawlingSpecification(); - job.crawlDepth = urls.size(); - job.domain = domain; - job.id = createId(new EdgeDomain(domain)); - job.urls = urls; - out.println(gson.toJson(job)); - } - } - - public static void writeSpec(Path outFile, CrawlingSpecification... specs) throws IOException { - Gson gson = GsonFactory.get(); - - try (var out = new PrintWriter(new ZstdOutputStream(new BufferedOutputStream(new FileOutputStream(outFile.toFile()))))) { - for (var spec : specs) { - out.println(gson.toJson(spec)); - } - } - } - - private record DomainWithId(String domainName, int id) { - } - - private Stream extractDomains() { + public Stream extractDomainsFromQueue() { Set ids = new HashSet<>(1_000_000); - try (var stmtDomains = conn.prepareStatement(domainsSql); + try (var conn = dataSource.getConnection(); + var stmtDomains = conn.prepareStatement(domainsSql); var stmtQueue = conn.prepareStatement(queuedDomainsSql); ) { ResultSet rsp; @@ -157,44 +108,15 @@ public class CrawlJobExtractorMain { .map(this::createCrawlJobForDomain); } - private CrawlingSpecification createCrawlJobForDomain(DomainWithId domainWithId) { - var spec = new CrawlingSpecification(); - spec.id = createId(domainWithId); - spec.domain = domainWithId.domainName; - spec.urls = new ArrayList<>(); - spec.crawlDepth = getCrawlDepth(domainWithId); - - try (var stmt = conn.prepareStatement(urlsSql)) { - stmt.setFetchSize(1000); - stmt.setInt(1, domainWithId.id); - var rsp = stmt.executeQuery(); - - while (rsp.next()) { - spec.urls.add(rsp.getString(1)); - } - } - catch (SQLException ex) { - ex.printStackTrace(); - } - - spec.urls.sort(Comparator.naturalOrder()); - - return spec; - } - - public CrawlJobExtractorMain(HikariDataSource ds) throws SQLException { - blacklist = new EdgeDomainBlacklistImpl(ds); - conn = ds.getConnection(); - } - public CrawlingSpecification extractDomain(EdgeDomain domain) { CrawlingSpecification spec = new CrawlingSpecification(); + spec.domain = domain.toString(); spec.id = createId(domain); spec.urls = new ArrayList<>(1000); - - try (var domainQuery = conn.prepareStatement(specificDomainSql); + try (var conn = dataSource.getConnection(); + var domainQuery = conn.prepareStatement(specificDomainSql); var urlQuery = conn.prepareStatement(urlsSql)) { domainQuery.setString(1, domain.toString()); @@ -222,6 +144,36 @@ public class CrawlJobExtractorMain { return spec; } + private record DomainWithId(String domainName, int id) { + + + } + + private CrawlingSpecification createCrawlJobForDomain(DomainWithId domainWithId) { + var spec = new CrawlingSpecification(); + spec.id = createId(domainWithId); + spec.domain = domainWithId.domainName; + spec.urls = new ArrayList<>(); + spec.crawlDepth = getCrawlDepth(domainWithId); + + try (var conn = dataSource.getConnection(); + var stmt = conn.prepareStatement(urlsSql)) { + stmt.setFetchSize(1000); + stmt.setInt(1, domainWithId.id); + var rsp = stmt.executeQuery(); + + while (rsp.next()) { + spec.urls.add(rsp.getString(1)); + } + } + catch (SQLException ex) { + ex.printStackTrace(); + } + + spec.urls.sort(Comparator.naturalOrder()); + + return spec; + } private static String createId(DomainWithId domainWithId) { return hasher.hashUnencodedChars(domainWithId.domainName).toString(); @@ -232,7 +184,8 @@ public class CrawlJobExtractorMain { } private int getCrawlDepth(DomainWithId domainWithId) { - try (var domainQuery = conn.prepareStatement(visitedUrlsSql)) { + try (var conn = dataSource.getConnection(); + var domainQuery = conn.prepareStatement(visitedUrlsSql)) { domainQuery.setInt(1, domainWithId.id); var rsp = domainQuery.executeQuery(); if (rsp.next()) { @@ -258,4 +211,5 @@ public class CrawlJobExtractorMain { return count; } + } diff --git a/crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobExtractorMain.java b/crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobExtractorMain.java new file mode 100644 index 00000000..0f53b2dc --- /dev/null +++ b/crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobExtractorMain.java @@ -0,0 +1,49 @@ +package nu.marginalia.crawl; + +import nu.marginalia.crawling.model.CrawlingSpecification; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklistImpl; +import nu.marginalia.service.module.DatabaseModule; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Stream; + +public class CrawlJobExtractorMain { + + public static void main(String... args) throws IOException { + if (args.length == 0) { + System.out.println("Parameters: outputfile.spec [domain1, domain2, ...]"); + System.out.println(); + System.out.println("If no domains are provided, a full crawl spec is created from the database"); + return; + } + + Path outFile = Path.of(args[0]); + if (Files.exists(outFile)) { + System.err.println("Out file " + outFile + " already exists, remove it first!"); + return; + } + + String[] targetDomains = Arrays.copyOfRange(args, 1, args.length); + + try (CrawlJobSpecWriter out = new CrawlJobSpecWriter(outFile)) + { + streamSpecs(targetDomains).forEach(out::accept); + } + } + + private static Stream streamSpecs(String[] targetDomains) { + var ds = new DatabaseModule().provideConnection(); + var domainExtractor = new CrawlJobDomainExtractor(new EdgeDomainBlacklistImpl(ds), ds); + + if (targetDomains.length > 0) { + return Arrays.stream(targetDomains).map(EdgeDomain::new).map(domainExtractor::extractDomain); + } else { + return domainExtractor.extractDomainsFromQueue(); + } + } + +} diff --git a/crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobSpecWriter.java b/crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobSpecWriter.java new file mode 100644 index 00000000..f853173f --- /dev/null +++ b/crawl/crawl-job-extractor-process/src/main/java/nu/marginalia/crawl/CrawlJobSpecWriter.java @@ -0,0 +1,27 @@ +package nu.marginalia.crawl; + +import com.github.luben.zstd.ZstdOutputStream; +import com.google.gson.Gson; +import nu.marginalia.crawling.model.CrawlingSpecification; +import nu.marginalia.model.gson.GsonFactory; + +import java.io.*; +import java.nio.file.Path; + +public class CrawlJobSpecWriter implements AutoCloseable { + + private final PrintWriter writer; + private final Gson gson = GsonFactory.get(); + + public CrawlJobSpecWriter(Path fileName) throws IOException { + writer = new PrintWriter(new ZstdOutputStream(new BufferedOutputStream(new FileOutputStream(fileName.toFile())))); + } + + public void accept(CrawlingSpecification crawlingSpecification) { + gson.toJson(crawlingSpecification, writer); + } + + public void close() { + writer.close(); + } +} diff --git a/crawl/crawl-job-extractor-process/src/test/java/nu/marginalia/crawl/CrawlJobSpecWriterTest.java b/crawl/crawl-job-extractor-process/src/test/java/nu/marginalia/crawl/CrawlJobSpecWriterTest.java new file mode 100644 index 00000000..dc082d0a --- /dev/null +++ b/crawl/crawl-job-extractor-process/src/test/java/nu/marginalia/crawl/CrawlJobSpecWriterTest.java @@ -0,0 +1,44 @@ +package nu.marginalia.crawl; + +import nu.marginalia.crawling.common.plan.CrawlerSpecificationLoader; +import nu.marginalia.crawling.model.CrawlingSpecification; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CrawlJobSpecWriterTest { + + Path tempFile; + + @BeforeEach + public void setUp() throws IOException { + tempFile = Files.createTempFile(getClass().getSimpleName(), "tmp"); + } + + @AfterEach + public void tearDown() throws IOException { + Files.delete(tempFile); + } + + @Test + public void testReadWrite() throws IOException { + try (CrawlJobSpecWriter writer = new CrawlJobSpecWriter(tempFile)) { + writer.accept(new CrawlingSpecification("first",1, "test1", List.of("a", "b", "c"))); + writer.accept(new CrawlingSpecification("second",1, "test2", List.of("a", "b", "c", "d"))); + writer.accept(new CrawlingSpecification("third",1, "test3", List.of("a", "b"))); + } + + List outputs = new ArrayList<>(); + CrawlerSpecificationLoader.readInputSpec(tempFile, outputs::add); + + assertEquals(outputs.size(), 3); + } +} diff --git a/crawl/crawling-model/build.gradle b/crawl/crawling-model/build.gradle new file mode 100644 index 00000000..eb72bbda --- /dev/null +++ b/crawl/crawling-model/build.gradle @@ -0,0 +1,47 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':libraries:misc') + implementation project(':api:index-api') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':libraries:language-processing') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.notnull + + implementation libs.gson + implementation libs.zstd + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawledDomainReader.java b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/io/CrawledDomainReader.java similarity index 87% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawledDomainReader.java rename to crawl/crawling-model/src/main/java/nu/marginalia/crawling/io/CrawledDomainReader.java index cb7aa8f9..49dee5b3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawledDomainReader.java +++ b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/io/CrawledDomainReader.java @@ -1,11 +1,13 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling.io; import com.github.luben.zstd.ZstdInputStream; import com.google.gson.Gson; import jdkoverride.LargeLineBufferedReader; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDocument; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDomain; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawledDomain; +import nu.marginalia.model.gson.GsonFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.FileInputStream; import java.io.IOException; @@ -18,7 +20,7 @@ import java.util.concurrent.TimeUnit; public class CrawledDomainReader { private final Gson gson = GsonFactory.get(); - + private final Logger logger = LoggerFactory.getLogger(getClass()); private final ForkJoinPool pool = new ForkJoinPool(6); public CrawledDomainReader() { @@ -61,6 +63,8 @@ public class CrawledDomainReader { return read(path); } catch (Exception ex) { + logger.warn("Failed to read domain", ex); + throw new RuntimeException(ex); } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawledDomainWriter.java b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/io/CrawledDomainWriter.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawledDomainWriter.java rename to crawl/crawling-model/src/main/java/nu/marginalia/crawling/io/CrawledDomainWriter.java index 40dc1f8c..51ffab18 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawledDomainWriter.java +++ b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/io/CrawledDomainWriter.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling.io; import com.github.luben.zstd.ZstdOutputStream; import com.google.gson.Gson; import lombok.SneakyThrows; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.edge.crawling.model.SerializableCrawlData; +import nu.marginalia.crawling.model.SerializableCrawlData; +import nu.marginalia.model.gson.GsonFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlLogEntry.java b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlLogEntry.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlLogEntry.java rename to crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlLogEntry.java index cdad9a70..213bef82 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlLogEntry.java +++ b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlLogEntry.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.model; +package nu.marginalia.crawling.model; public record CrawlLogEntry(String id, String ts, String path, int cnt) { } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawledDocument.java b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawledDocument.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawledDocument.java rename to crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawledDocument.java index 3250630f..a76dcb7f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawledDocument.java +++ b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawledDocument.java @@ -1,9 +1,8 @@ -package nu.marginalia.wmsa.edge.crawling.model; +package nu.marginalia.crawling.model; import lombok.Builder; import lombok.ToString; -import nu.marginalia.util.bigstring.BigString; -import nu.marginalia.util.bigstring.CompressedBigString; +import nu.marginalia.bigstring.BigString; @Builder @ToString diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawledDomain.java b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawledDomain.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawledDomain.java rename to crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawledDomain.java index a4c365d7..cfa39479 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawledDomain.java +++ b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawledDomain.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.model; +package nu.marginalia.crawling.model; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlerDocumentStatus.java b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlerDocumentStatus.java similarity index 76% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlerDocumentStatus.java rename to crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlerDocumentStatus.java index 0a9b0e0a..2369bcc6 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlerDocumentStatus.java +++ b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlerDocumentStatus.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.model; +package nu.marginalia.crawling.model; public enum CrawlerDocumentStatus { OK, diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlerDomainStatus.java b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlerDomainStatus.java similarity index 59% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlerDomainStatus.java rename to crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlerDomainStatus.java index 1c22067c..12a31c52 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlerDomainStatus.java +++ b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlerDomainStatus.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.model; +package nu.marginalia.crawling.model; public enum CrawlerDomainStatus { OK, ERROR, BLOCKED, REDIRECT diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlingSpecification.java b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlingSpecification.java similarity index 62% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlingSpecification.java rename to crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlingSpecification.java index 57298c84..696c5e43 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/CrawlingSpecification.java +++ b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/CrawlingSpecification.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.model; +package nu.marginalia.crawling.model; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; @@ -14,4 +14,9 @@ public class CrawlingSpecification { // Don't make this EdgeUrl, EdgeDomain etc. -- we want this plastic to change! public String domain; public List urls; + + @Override + public String toString() { + return String.format(getClass().getSimpleName() + "[" + id + "/" + domain + ": " + crawlDepth + "[ " + urls.size() + "]"); + } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/SerializableCrawlData.java b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/SerializableCrawlData.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/SerializableCrawlData.java rename to crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/SerializableCrawlData.java index 015ea743..c9804d54 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/model/SerializableCrawlData.java +++ b/crawl/crawling-model/src/main/java/nu/marginalia/crawling/model/SerializableCrawlData.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.model; +package nu.marginalia.crawling.model; public interface SerializableCrawlData { String getSerialIdentifier(); diff --git a/crawl/crawling-process/build.gradle b/crawl/crawling-process/build.gradle new file mode 100644 index 00000000..98f70752 --- /dev/null +++ b/crawl/crawling-process/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'application' + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +application { + mainClass = 'nu.marginalia.crawl.CrawlerMain' + applicationName = 'crawler-process' +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:config') + implementation project(':common:service') + implementation project(':libraries:misc') + implementation project(':api:index-api') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':libraries:language-processing') + implementation project(':crawl:common') + implementation project(':crawl:crawling-model') + implementation project(':crawl:converting-model') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.notnull + implementation libs.guice + implementation libs.gson + implementation libs.zstd + implementation libs.crawlercommons + implementation libs.okhttp3 + implementation libs.jsoup + implementation libs.opencsv + implementation libs.rxjava + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerMain.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/CrawlerMain.java similarity index 74% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerMain.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/CrawlerMain.java index 54283e98..c99f62c6 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerMain.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/CrawlerMain.java @@ -1,13 +1,17 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawl; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import nu.marginalia.wmsa.configuration.UserAgent; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.crawling.model.CrawlingSpecification; -import nu.marginalia.wmsa.edge.crawling.retreival.CrawlerRetreiver; -import nu.marginalia.wmsa.edge.crawling.retreival.HttpFetcher; -import nu.marginalia.wmsa.edge.model.EdgeCrawlPlan; +import nu.marginalia.UserAgent; +import nu.marginalia.WmsaHome; +import nu.marginalia.crawling.common.AbortMonitor; +import nu.marginalia.crawling.common.WorkLog; +import nu.marginalia.crawling.common.plan.CrawlPlanLoader; +import nu.marginalia.crawling.common.plan.EdgeCrawlPlan; +import nu.marginalia.crawling.io.CrawledDomainWriter; +import nu.marginalia.crawling.model.CrawlingSpecification; +import nu.marginalia.crawl.retreival.CrawlerRetreiver; +import nu.marginalia.crawl.retreival.HttpFetcher; import okhttp3.ConnectionPool; import okhttp3.Dispatcher; import okhttp3.internal.Util; @@ -18,7 +22,6 @@ import java.nio.file.Path; import java.util.concurrent.*; public class CrawlerMain implements AutoCloseable { - public static Gson gson = new GsonBuilder().create(); private final Logger logger = LoggerFactory.getLogger(getClass()); private final EdgeCrawlPlan plan; @@ -36,6 +39,9 @@ public class CrawlerMain implements AutoCloseable { final int poolSize = Integer.getInteger("crawler.pool-size", 512); final int poolQueueSize = 32; + AbortMonitor abortMonitor = AbortMonitor.getInstance(); + Semaphore taskSem = new Semaphore(poolSize); + public CrawlerMain(EdgeCrawlPlan plan) throws Exception { this.plan = plan; this.userAgent = WmsaHome.getUserAgent(); @@ -66,6 +72,35 @@ public class CrawlerMain implements AutoCloseable { System.exit(0); } + public void run() throws InterruptedException { + // First a validation run to ensure the file is all good to parse + logger.info("Validating JSON"); + plan.forEachCrawlingSpecification(unused -> {}); + + logger.info("Let's go"); + + plan.forEachCrawlingSpecification(this::startCrawlTask); + } + + private void startCrawlTask(CrawlingSpecification crawlingSpecification) { + if (abortMonitor.isAlive()) { + try { + taskSem.acquire(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + pool.execute(() -> { + try { + fetchDomain(crawlingSpecification); + } + finally { + taskSem.release(); + } + }); + } + } + private void fetchDomain(CrawlingSpecification specification) { if (workLog.isJobFinished(specification.id)) return; @@ -85,40 +120,6 @@ public class CrawlerMain implements AutoCloseable { } } - public void run() throws InterruptedException { - // First a validation run to ensure the file is all good to parse - - logger.info("Validating JSON"); - plan.forEachCrawlingSpecification(unused -> {}); - - logger.info("Let's go"); - - AbortMonitor abortMonitor = AbortMonitor.getInstance(); - - Semaphore taskSem = new Semaphore(poolSize); - - plan.forEachCrawlingSpecification(spec -> { - if (abortMonitor.isAlive()) { - try { - taskSem.acquire(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - pool.execute(() -> { - try { - fetchDomain(spec); - } - finally { - taskSem.release(); - } - }); - } - }); - - - } - public void close() throws Exception { logger.info("Awaiting termination"); pool.shutdown(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/Cookies.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/Cookies.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/Cookies.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/Cookies.java index b19478ea..7b43321e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/Cookies.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/Cookies.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.retreival; +package nu.marginalia.crawl.retreival; import okhttp3.Cookie; import okhttp3.CookieJar; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/CrawlerRetreiver.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/CrawlerRetreiver.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/CrawlerRetreiver.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/CrawlerRetreiver.java index 9e4242c5..b990ddc9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/CrawlerRetreiver.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/CrawlerRetreiver.java @@ -1,16 +1,15 @@ -package nu.marginalia.wmsa.edge.crawling.retreival; +package nu.marginalia.crawl.retreival; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.converting.processor.logic.LinkParser; -import nu.marginalia.wmsa.edge.crawling.CrawledDomainWriter; -import nu.marginalia.wmsa.edge.crawling.blocklist.GeoIpBlocklist; -import nu.marginalia.wmsa.edge.crawling.blocklist.IpBlockList; -import nu.marginalia.wmsa.edge.crawling.blocklist.UrlBlocklist; -import nu.marginalia.wmsa.edge.crawling.model.*; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.crawling.common.blocklist.GeoIpBlocklist; +import nu.marginalia.crawling.common.blocklist.IpBlockList; +import nu.marginalia.crawling.common.blocklist.UrlBlocklist; +import nu.marginalia.crawling.common.link.LinkParser; +import nu.marginalia.crawling.model.*; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.slf4j.Logger; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/FastTerminatingSocketFactory.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/FastTerminatingSocketFactory.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/FastTerminatingSocketFactory.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/FastTerminatingSocketFactory.java index f9a889b1..8679f09d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/FastTerminatingSocketFactory.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/FastTerminatingSocketFactory.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.retreival; +package nu.marginalia.crawl.retreival; import javax.net.SocketFactory; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/HttpFetcher.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/HttpFetcher.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/HttpFetcher.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/HttpFetcher.java index b7074825..141d7970 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/HttpFetcher.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/HttpFetcher.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.retreival; +package nu.marginalia.crawl.retreival; import com.google.inject.Inject; import com.google.inject.name.Named; @@ -7,14 +7,14 @@ import crawlercommons.robots.SimpleRobotRulesParser; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import lombok.ToString; -import nu.marginalia.util.bigstring.BigString; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDocument; -import nu.marginalia.wmsa.edge.crawling.model.CrawlerDocumentStatus; -import nu.marginalia.wmsa.edge.crawling.retreival.logic.ContentTypeLogic; -import nu.marginalia.wmsa.edge.crawling.retreival.logic.ContentTypeParser; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeContentType; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawlerDocumentStatus; +import nu.marginalia.model.crawl.EdgeContentType; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.bigstring.BigString; +import nu.marginalia.crawl.retreival.logic.ContentTypeLogic; +import nu.marginalia.crawl.retreival.logic.ContentTypeParser; import okhttp3.*; import org.apache.commons.io.input.BOMInputStream; import org.slf4j.Logger; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/HttpRedirectResolver.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/HttpRedirectResolver.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/HttpRedirectResolver.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/HttpRedirectResolver.java index 5857c19b..48a0de91 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/HttpRedirectResolver.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/HttpRedirectResolver.java @@ -1,13 +1,13 @@ -package nu.marginalia.wmsa.edge.crawling.retreival; +package nu.marginalia.crawl.retreival; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; import io.reactivex.rxjava3.core.Observable; import lombok.SneakyThrows; -import nu.marginalia.wmsa.client.exception.NetworkException; -import nu.marginalia.wmsa.edge.converting.processor.logic.LinkParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.crawling.common.link.LinkParser; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.client.exception.NetworkException; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/NoSecuritySSL.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/NoSecuritySSL.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/NoSecuritySSL.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/NoSecuritySSL.java index b276bd9b..225bea97 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/NoSecuritySSL.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/NoSecuritySSL.java @@ -1,9 +1,8 @@ -package nu.marginalia.wmsa.edge.crawling.retreival; +package nu.marginalia.crawl.retreival; import lombok.SneakyThrows; import javax.net.ssl.*; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; public class NoSecuritySSL { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/RateLimitException.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/RateLimitException.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/RateLimitException.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/RateLimitException.java index ac28dca9..5e0c57dd 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/RateLimitException.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/RateLimitException.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling.retreival; +package nu.marginalia.crawl.retreival; public class RateLimitException extends Exception { private final String retryAfter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/logic/ContentTypeLogic.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/logic/ContentTypeLogic.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/logic/ContentTypeLogic.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/logic/ContentTypeLogic.java index 9d05026c..c5860913 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/logic/ContentTypeLogic.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/logic/ContentTypeLogic.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.crawling.retreival.logic; +package nu.marginalia.crawl.retreival.logic; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.model.EdgeUrl; import java.util.List; import java.util.Set; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/logic/ContentTypeParser.java b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/logic/ContentTypeParser.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/logic/ContentTypeParser.java rename to crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/logic/ContentTypeParser.java index 2f3359f3..62d21ba9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/retreival/logic/ContentTypeParser.java +++ b/crawl/crawling-process/src/main/java/nu/marginalia/crawl/retreival/logic/ContentTypeParser.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.crawling.retreival.logic; +package nu.marginalia.crawl.retreival.logic; import crawlercommons.mimetypes.MimeTypeDetector; -import nu.marginalia.wmsa.edge.model.crawl.EdgeContentType; +import nu.marginalia.model.crawl.EdgeContentType; import org.jsoup.Jsoup; import java.util.Arrays; diff --git a/marginalia_nu/src/main/resources/ip-banned-cidr.txt b/crawl/crawling-process/src/main/resources/ip-banned-cidr.txt similarity index 100% rename from marginalia_nu/src/main/resources/ip-banned-cidr.txt rename to crawl/crawling-process/src/main/resources/ip-banned-cidr.txt diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/CrawlPlanLoaderTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/CrawlPlanLoaderTest.java similarity index 89% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/CrawlPlanLoaderTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/CrawlPlanLoaderTest.java index 40b77484..000fd5ee 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/CrawlPlanLoaderTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/CrawlPlanLoaderTest.java @@ -1,5 +1,6 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling; +import nu.marginalia.crawling.common.plan.CrawlPlanLoader; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -8,7 +9,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; class CrawlPlanLoaderTest { diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/DomainCrawlerRobotsTxtTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/DomainCrawlerRobotsTxtTest.java similarity index 96% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/DomainCrawlerRobotsTxtTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/DomainCrawlerRobotsTxtTest.java index ef77e373..0194fb01 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/DomainCrawlerRobotsTxtTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/DomainCrawlerRobotsTxtTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling; import crawlercommons.robots.SimpleRobotRules; import crawlercommons.robots.SimpleRobotRulesParser; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/HtmlTagCleanerTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/HtmlTagCleanerTest.java similarity index 88% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/HtmlTagCleanerTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/HtmlTagCleanerTest.java index 1b80c801..07f72179 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/HtmlTagCleanerTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/HtmlTagCleanerTest.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling; -import nu.marginalia.util.language.processing.HtmlTagCleaner; +import nu.marginalia.language.encoding.HtmlTagCleaner; import org.jsoup.Jsoup; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/HttpFetcherTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/HttpFetcherTest.java similarity index 88% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/HttpFetcherTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/HttpFetcherTest.java index 653294f8..c9cc8b0b 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/HttpFetcherTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/HttpFetcherTest.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling; import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.crawling.retreival.HttpFetcher; -import nu.marginalia.wmsa.edge.crawling.retreival.HttpRedirectResolver; -import nu.marginalia.wmsa.edge.crawling.retreival.RateLimitException; -import nu.marginalia.wmsa.edge.crawling.retreival.logic.ContentTypeLogic; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.crawl.retreival.HttpFetcher; +import nu.marginalia.crawl.retreival.HttpRedirectResolver; +import nu.marginalia.crawl.retreival.RateLimitException; +import nu.marginalia.crawl.retreival.logic.ContentTypeLogic; +import nu.marginalia.model.EdgeUrl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/LanguageFilterTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/LanguageFilterTest.java similarity index 92% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/LanguageFilterTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/LanguageFilterTest.java index 50730390..694810ba 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/LanguageFilterTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/LanguageFilterTest.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling; -import nu.marginalia.util.language.LanguageFilter; +import nu.marginalia.language.LanguageFilter; import org.jsoup.Jsoup; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/LinkParserTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/LinkParserTest.java similarity index 94% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/LinkParserTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/LinkParserTest.java index 065310f7..da3059f2 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/LinkParserTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/LinkParserTest.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling; -import nu.marginalia.wmsa.edge.converting.processor.logic.LinkParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.crawling.common.link.LinkParser; +import nu.marginalia.model.EdgeUrl; import org.jsoup.Jsoup; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/RssCrawlerTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/RssCrawlerTest.java similarity index 92% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/RssCrawlerTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/RssCrawlerTest.java index cf64ef6d..25a40ece 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/RssCrawlerTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/RssCrawlerTest.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling; -import nu.marginalia.wmsa.edge.converting.processor.logic.LinkParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.crawling.common.link.LinkParser; +import nu.marginalia.model.EdgeUrl; import org.jsoup.Jsoup; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/UrlBlocklistTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/UrlBlocklistTest.java similarity index 90% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/UrlBlocklistTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/UrlBlocklistTest.java index 2460987a..cd799540 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/UrlBlocklistTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/UrlBlocklistTest.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling; -import nu.marginalia.wmsa.edge.crawling.blocklist.UrlBlocklist; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.crawling.common.blocklist.UrlBlocklist; +import nu.marginalia.model.EdgeUrl; import org.junit.jupiter.api.Test; import java.net.URISyntaxException; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/WorkLogTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/WorkLogTest.java similarity index 88% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/WorkLogTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/WorkLogTest.java index bf5a6bdb..62d86e87 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/WorkLogTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/WorkLogTest.java @@ -1,5 +1,6 @@ -package nu.marginalia.wmsa.edge.crawling; +package nu.marginalia.crawling; +import nu.marginalia.crawling.common.WorkLog; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -8,7 +9,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; class WorkLogTest { Path outFile; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/retreival/CrawlerRetreiverTest.java b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/retreival/CrawlerRetreiverTest.java similarity index 75% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/retreival/CrawlerRetreiverTest.java rename to crawl/crawling-process/src/test/java/nu/marginalia/crawling/retreival/CrawlerRetreiverTest.java index c3f558e8..73dd832b 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/crawling/retreival/CrawlerRetreiverTest.java +++ b/crawl/crawling-process/src/test/java/nu/marginalia/crawling/retreival/CrawlerRetreiverTest.java @@ -1,8 +1,10 @@ -package nu.marginalia.wmsa.edge.crawling.retreival; +package nu.marginalia.crawling.retreival; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDocument; -import nu.marginalia.wmsa.edge.crawling.model.CrawlingSpecification; -import nu.marginalia.wmsa.edge.crawling.model.SerializableCrawlData; +import nu.marginalia.crawl.retreival.CrawlerRetreiver; +import nu.marginalia.crawl.retreival.HttpFetcher; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawlingSpecification; +import nu.marginalia.crawling.model.SerializableCrawlData; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; diff --git a/crawl/experimental/build.gradle b/crawl/experimental/build.gradle new file mode 100644 index 00000000..68e334ab --- /dev/null +++ b/crawl/experimental/build.gradle @@ -0,0 +1,50 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:config') + implementation project(':common:service') + implementation project(':libraries:misc') + implementation project(':api:index-api') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':libraries:language-processing') + implementation project(':crawl:common') + implementation project(':crawl:crawling-model') + implementation project(':crawl:converting-process') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.guice + implementation libs.jsoup + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/AdblockTesterTool.java b/crawl/experimental/src/main/java/nu/marginalia/experimental/AdblockTesterTool.java similarity index 67% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/AdblockTesterTool.java rename to crawl/experimental/src/main/java/nu/marginalia/experimental/AdblockTesterTool.java index b97fc27b..61f91e52 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/AdblockTesterTool.java +++ b/crawl/experimental/src/main/java/nu/marginalia/experimental/AdblockTesterTool.java @@ -1,17 +1,17 @@ -package nu.marginalia.wmsa.edge.tools; +package nu.marginalia.experimental; -import nu.marginalia.wmsa.edge.converting.processor.logic.topic.AdblockSimulator; -import nu.marginalia.wmsa.edge.crawling.CrawlPlanLoader; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDocument; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDomain; -import nu.marginalia.wmsa.edge.model.EdgeCrawlPlan; +import nu.marginalia.converting.processor.DocumentProcessor; +import nu.marginalia.converting.processor.logic.topic.AdblockSimulator; +import nu.marginalia.crawling.common.plan.CrawlPlanLoader; +import nu.marginalia.crawling.common.plan.EdgeCrawlPlan; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawledDomain; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import java.io.IOException; import java.nio.file.Path; -import static nu.marginalia.wmsa.edge.converting.processor.DocumentProcessor.isAcceptedContentType; public class AdblockTesterTool { @@ -40,7 +40,7 @@ public class AdblockTesterTool { private static void processDomain(CrawledDomain domain) { if (domain.doc == null) return; for (var doc : domain.doc) { - if (isAcceptedContentType(doc) && "OK".equals(doc.crawlerStatus)) { + if (DocumentProcessor.isAcceptedContentType(doc) && "OK".equals(doc.crawlerStatus)) { processDocument(doc); } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/ConverterLogicTestTool.java b/crawl/experimental/src/main/java/nu/marginalia/experimental/ConverterLogicTestTool.java similarity index 77% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/ConverterLogicTestTool.java rename to crawl/experimental/src/main/java/nu/marginalia/experimental/ConverterLogicTestTool.java index 78d90ccb..f9d15b81 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/ConverterLogicTestTool.java +++ b/crawl/experimental/src/main/java/nu/marginalia/experimental/ConverterLogicTestTool.java @@ -1,19 +1,19 @@ -package nu.marginalia.wmsa.edge.tools; +package nu.marginalia.experimental; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.converting.ConverterModule; -import nu.marginalia.wmsa.edge.converting.processor.DomainProcessor; -import nu.marginalia.wmsa.edge.converting.processor.logic.DomPruningFilter; -import nu.marginalia.wmsa.edge.converting.processor.logic.topic.GoogleAnwersSpamDetector; -import nu.marginalia.wmsa.edge.converting.processor.logic.topic.RecipeDetector; -import nu.marginalia.wmsa.edge.converting.processor.logic.topic.TextileCraftDetector; -import nu.marginalia.wmsa.edge.converting.processor.logic.topic.WoodworkingDetector; -import nu.marginalia.wmsa.edge.crawling.CrawlPlanLoader; -import nu.marginalia.wmsa.edge.model.EdgeCrawlPlan; +import nu.marginalia.converting.ConverterModule; +import nu.marginalia.crawling.common.plan.CrawlPlanLoader; +import nu.marginalia.crawling.common.plan.EdgeCrawlPlan; +import nu.marginalia.converting.processor.logic.DomPruningFilter; +import nu.marginalia.language.sentence.SentenceExtractor; +import nu.marginalia.WmsaHome; +import nu.marginalia.converting.processor.DomainProcessor; +import nu.marginalia.converting.processor.logic.topic.GoogleAnwersSpamDetector; +import nu.marginalia.converting.processor.logic.topic.RecipeDetector; +import nu.marginalia.converting.processor.logic.topic.TextileCraftDetector; +import nu.marginalia.converting.processor.logic.topic.WoodworkingDetector; import org.jsoup.Jsoup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/CrawlDataExtractorTool.java b/crawl/experimental/src/main/java/nu/marginalia/experimental/CrawlDataExtractorTool.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/CrawlDataExtractorTool.java rename to crawl/experimental/src/main/java/nu/marginalia/experimental/CrawlDataExtractorTool.java index cbe59e60..a4177562 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/CrawlDataExtractorTool.java +++ b/crawl/experimental/src/main/java/nu/marginalia/experimental/CrawlDataExtractorTool.java @@ -1,12 +1,13 @@ -package nu.marginalia.wmsa.edge.tools; +package nu.marginalia.experimental; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.converting.processor.logic.topic.AdblockSimulator; -import nu.marginalia.wmsa.edge.crawling.CrawlPlanLoader; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDocument; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDomain; -import nu.marginalia.wmsa.edge.model.EdgeCrawlPlan; +import nu.marginalia.converting.processor.DocumentProcessor; +import nu.marginalia.converting.processor.logic.topic.AdblockSimulator; +import nu.marginalia.crawling.common.plan.CrawlPlanLoader; +import nu.marginalia.crawling.common.plan.EdgeCrawlPlan; +import nu.marginalia.crawling.model.CrawledDocument; +import nu.marginalia.crawling.model.CrawledDomain; +import nu.marginalia.service.module.DatabaseModule; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -17,7 +18,6 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.*; -import static nu.marginalia.wmsa.edge.converting.processor.DocumentProcessor.isAcceptedContentType; public class CrawlDataExtractorTool { private static final AdblockSimulator abs; @@ -76,7 +76,7 @@ public class CrawlDataExtractorTool { if (!urls.contains(doc.url)) continue; - if (isAcceptedContentType(doc) && "OK".equals(doc.crawlerStatus)) { + if (DocumentProcessor.isAcceptedContentType(doc) && "OK".equals(doc.crawlerStatus)) { processDocument(doc); } } diff --git a/crawl/loading-process/build.gradle b/crawl/loading-process/build.gradle new file mode 100644 index 00000000..c57ec73d --- /dev/null +++ b/crawl/loading-process/build.gradle @@ -0,0 +1,68 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'application' + id 'jvm-test-suite' +} +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +application { + mainClass = 'nu.marginalia.loading.LoaderMain' + applicationName = 'loader-process' +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':api:index-api') + implementation project(':common:model') + implementation project(':common:config') + implementation project(':common:service') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':index:lexicon') + implementation project(':index:index-journal') + implementation project(':libraries:language-processing') + implementation project(':libraries:misc') + + testImplementation project(':services-core:search-service') + + implementation project(':crawl:common') + implementation project(':crawl:crawling-model') + implementation project(':crawl:converting-model') + + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.guice + implementation libs.gson + implementation libs.commons.lang3 + implementation libs.zstd + implementation libs.trove + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + + testImplementation libs.bundles.selenium + testImplementation platform('org.testcontainers:testcontainers-bom:1.17.4') + testImplementation 'org.testcontainers:mariadb:1.17.4' + testImplementation 'org.testcontainers:junit-jupiter:1.17.4' +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConvertedDomainReader.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/ConvertedDomainReader.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConvertedDomainReader.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/ConvertedDomainReader.java index 4efe95e3..6b9dfbbd 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ConvertedDomainReader.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/ConvertedDomainReader.java @@ -1,11 +1,10 @@ -package nu.marginalia.wmsa.edge.converting; +package nu.marginalia.loading; import com.github.luben.zstd.ZstdInputStream; import com.google.gson.Gson; import com.google.gson.JsonParseException; -import crawlercommons.utils.Strings; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.InstructionTag; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.converting.instruction.InstructionTag; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,7 +35,7 @@ public class ConvertedDomainReader { if (line == null) { break; } - if (Strings.isBlank(line)) { + if (line.isBlank()) { continue; } var parts= line.split(" ", 2); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LoaderMain.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/LoaderMain.java similarity index 68% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LoaderMain.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/LoaderMain.java index 97621467..7e702776 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LoaderMain.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/LoaderMain.java @@ -1,37 +1,38 @@ -package nu.marginalia.wmsa.edge.converting; +package nu.marginalia.loading; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; +import com.zaxxer.hikari.HikariDataSource; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.loader.Loader; -import nu.marginalia.wmsa.edge.converting.loader.LoaderFactory; -import nu.marginalia.wmsa.edge.crawling.CrawlPlanLoader; -import nu.marginalia.wmsa.edge.crawling.WorkLog; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexClient; -import nu.marginalia.wmsa.edge.model.EdgeCrawlPlan; +import nu.marginalia.crawling.common.TaskStats; +import nu.marginalia.crawling.common.WorkLog; +import nu.marginalia.crawling.common.plan.CrawlPlanLoader; +import nu.marginalia.crawling.common.plan.EdgeCrawlPlan; +import nu.marginalia.loading.loader.IndexLoadKeywords; +import nu.marginalia.loading.loader.Loader; +import nu.marginalia.loading.loader.LoaderFactory; +import nu.marginalia.converting.instruction.Instruction; +import nu.marginalia.service.module.DatabaseModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.Path; +import java.sql.SQLException; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class LoaderMain { - - private static final Logger logger = LoggerFactory.getLogger(LoaderMain.class); private final EdgeCrawlPlan plan; private final ConvertedDomainReader instructionsReader; private final LoaderFactory loaderFactory; - private final EdgeIndexClient indexClient; + private final IndexLoadKeywords indexLoadKeywords; private volatile boolean running = true; final Thread processorThread = new Thread(this::processor, "Processor Thread"); @@ -41,10 +42,13 @@ public class LoaderMain { System.err.println("Arguments: crawl-plan.yaml"); System.exit(0); } + + new org.mariadb.jdbc.Driver(); + var plan = new CrawlPlanLoader().load(Path.of(args[0])); Injector injector = Guice.createInjector( - new ConverterModule(plan), + new LoaderModule(plan), new DatabaseModule() ); @@ -55,17 +59,40 @@ public class LoaderMain { @Inject public LoaderMain(EdgeCrawlPlan plan, ConvertedDomainReader instructionsReader, - LoaderFactory loaderFactory, - EdgeIndexClient indexClient) { + HikariDataSource dataSource, + LoaderFactory loaderFactory, IndexLoadKeywords indexLoadKeywords) { this.plan = plan; this.instructionsReader = instructionsReader; this.loaderFactory = loaderFactory; - this.indexClient = indexClient; + this.indexLoadKeywords = indexLoadKeywords; + nukeTables(dataSource); + + Runtime.getRuntime().addShutdownHook(new Thread(this::shutDownIndex)); processorThread.start(); } + private void nukeTables(HikariDataSource dataSource) { + try (var conn = dataSource.getConnection(); + var stmt = conn.createStatement()) { + stmt.execute("SET FOREIGN_KEY_CHECKS = 0"); + stmt.execute("TRUNCATE TABLE EC_PAGE_DATA"); + stmt.execute("TRUNCATE TABLE EC_URL"); + stmt.execute("TRUNCATE TABLE EC_DOMAIN_LINK"); + stmt.execute("SET FOREIGN_KEY_CHECKS = 1"); + } + catch (SQLException ex) { + throw new RuntimeException(ex); + } + } + + @SneakyThrows + private void shutDownIndex() { + // This must run otherwise the journal doesn't get a proper header + indexLoadKeywords.close(); + } + @SneakyThrows public void run() { var logFile = plan.process.getLogFile(); @@ -80,7 +107,6 @@ public class LoaderMain { running = false; processorThread.join(); - indexClient.close(); System.exit(0); } @@ -135,6 +161,7 @@ public class LoaderMain { } catch (InterruptedException e) { throw new RuntimeException(e); } + } } diff --git a/crawl/loading-process/src/main/java/nu/marginalia/loading/LoaderModule.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/LoaderModule.java new file mode 100644 index 00000000..14a7389a --- /dev/null +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/LoaderModule.java @@ -0,0 +1,38 @@ +package nu.marginalia.loading; + +import com.google.gson.Gson; +import com.google.inject.AbstractModule; +import com.google.inject.name.Names; +import nu.marginalia.LanguageModels; +import nu.marginalia.WmsaHome; +import nu.marginalia.crawling.common.plan.EdgeCrawlPlan; +import nu.marginalia.model.gson.GsonFactory; +import nu.marginalia.service.SearchServiceDescriptors; +import nu.marginalia.service.descriptor.ServiceDescriptors; + +import java.nio.file.Path; + +public class LoaderModule extends AbstractModule { + + private final EdgeCrawlPlan plan; + + public LoaderModule(EdgeCrawlPlan plan) { + this.plan = plan; + } + + public void configure() { + bind(EdgeCrawlPlan.class).toInstance(plan); + + bind(ServiceDescriptors.class).toInstance(SearchServiceDescriptors.descriptors); + + bind(Gson.class).toInstance(createGson()); + + bind(Path.class).annotatedWith(Names.named("local-index-path")).toInstance(Path.of(System.getProperty("local-index-path", "/vol"))); + bind(LanguageModels.class).toInstance(WmsaHome.getLanguageModels()); + } + + private Gson createGson() { + return GsonFactory.get(); + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/IndexLoadKeywords.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/IndexLoadKeywords.java similarity index 63% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/IndexLoadKeywords.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/loader/IndexLoadKeywords.java index d12aa7ea..e3f1c485 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/IndexLoadKeywords.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/IndexLoadKeywords.java @@ -1,13 +1,12 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loading.loader; import com.google.inject.Inject; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexWriterClient; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeId; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.client.Context; +import nu.marginalia.model.crawl.DocumentKeywords; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.id.EdgeId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,17 +17,16 @@ public class IndexLoadKeywords implements Runnable { private static final Logger logger = LoggerFactory.getLogger(IndexLoadKeywords.class); private final LinkedBlockingQueue insertQueue = new LinkedBlockingQueue<>(32); - private final EdgeIndexWriterClient client; + private final LoaderIndexJournalWriter client; private record InsertTask(int urlId, int domainId, EdgePageDocumentsMetadata metadata, DocumentKeywords wordSet) {} private final Thread runThread; + private volatile boolean canceled = false; - private static final int index = Integer.getInteger("keyword-index", 1); - @Inject - public IndexLoadKeywords(EdgeIndexWriterClient client) { + public IndexLoadKeywords(LoaderIndexJournalWriter client) { this.client = client; runThread = new Thread(this, getClass().getSimpleName()); runThread.start(); @@ -39,14 +37,17 @@ public class IndexLoadKeywords implements Runnable { while (!canceled) { var data = insertQueue.poll(1, TimeUnit.SECONDS); if (data != null) { - client.putWords(Context.internal(), new EdgeId<>(data.domainId), new EdgeId<>(data.urlId), data.metadata(), data.wordSet, index); + client.putWords(new EdgeId<>(data.domainId), new EdgeId<>(data.urlId), data.metadata(), data.wordSet); } } } - public void close() throws InterruptedException { - canceled = true; - runThread.join(); + public void close() throws Exception { + if (!canceled) { + canceled = true; + runThread.join(); + client.close(); + } } public void load(LoaderData loaderData, EdgeUrl url, EdgePageDocumentsMetadata metadata, DocumentKeywords words) throws InterruptedException { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/Loader.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/Loader.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/Loader.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/loader/Loader.java index ba55ea10..e0a075f3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/Loader.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/Loader.java @@ -1,14 +1,15 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loading.loader; -import nu.marginalia.wmsa.edge.converting.interpreter.Interpreter; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocument; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocumentWithError; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import lombok.SneakyThrows; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.converting.instruction.Interpreter; +import nu.marginalia.model.crawl.DocumentKeywords; +import nu.marginalia.converting.instruction.instructions.DomainLink; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocument; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocumentWithError; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -141,4 +142,13 @@ public class Loader implements Interpreter { sqlLoadProcessedDocument.load(data, processedDocumentList); sqlLoadProcessedDocument.loadWithError(data, processedDocumentWithErrorList); } + + public void close() { + try { + indexLoadKeywords.close(); + } + catch (Exception ex) { + logger.error("Error when closing the index loader", ex); + } + } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/LoaderData.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderData.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/LoaderData.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderData.java index 5c9dc4a1..570cb579 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/LoaderData.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderData.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loading.loader; import gnu.trove.map.hash.TObjectIntHashMap; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; public class LoaderData { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/LoaderFactory.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderFactory.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/LoaderFactory.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderFactory.java index f92319aa..d4a24a9b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/LoaderFactory.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderFactory.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loading.loader; import com.google.inject.Inject; diff --git a/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderIndexJournalWriter.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderIndexJournalWriter.java new file mode 100644 index 00000000..68ef4f4e --- /dev/null +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/LoaderIndexJournalWriter.java @@ -0,0 +1,87 @@ +package nu.marginalia.loading.loader; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import nu.marginalia.dict.DictionaryMap; +import nu.marginalia.dict.OffHeapDictionaryHashMap; +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.index.journal.model.IndexJournalEntryHeader; +import nu.marginalia.index.journal.writer.IndexJournalWriterImpl; +import nu.marginalia.index.journal.writer.IndexJournalWriter; +import nu.marginalia.lexicon.KeywordLexicon; +import nu.marginalia.lexicon.journal.KeywordLexiconJournal; +import nu.marginalia.model.crawl.DocumentKeywords; +import nu.marginalia.util.KeywordListChunker; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.id.EdgeId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; + +@Singleton +public class LoaderIndexJournalWriter { + + private final KeywordLexicon lexicon; + private final IndexJournalWriter indexWriter; + private static final Logger logger = LoggerFactory.getLogger(LoaderIndexJournalWriter.class); + + @Inject + public LoaderIndexJournalWriter(@Named("local-index-path") Path path) throws IOException { + + var lexiconJournal = new KeywordLexiconJournal(path.resolve("dictionary.dat").toFile()); + lexicon = new KeywordLexicon(lexiconJournal); + indexWriter = new IndexJournalWriterImpl(lexicon, path.resolve("index.dat")); + } + + public void putWords(EdgeId domain, EdgeId url, + EdgePageDocumentsMetadata metadata, + DocumentKeywords wordSet) { + if (wordSet.keywords().length == 0) + return; + + if (domain.id() <= 0 || url.id() <= 0) { + logger.warn("Bad ID: {}:{}", domain, url); + return; + } + + for (var chunk : KeywordListChunker.chopList(wordSet, IndexJournalEntryData.MAX_LENGTH)) { + + var entry = new IndexJournalEntryData(getOrInsertWordIds(chunk.keywords(), chunk.metadata())); + var header = new IndexJournalEntryHeader(domain, url, metadata.encode()); + + indexWriter.put(header, entry); + } + + } + + private long[] getOrInsertWordIds(String[] words, long[] meta) { + long[] ids = new long[words.length*2]; + int putIdx = 0; + + for (int i = 0; i < words.length; i++) { + String word = words[i]; + + long id = lexicon.getOrInsert(word); + if (id != OffHeapDictionaryHashMap.NO_VALUE) { + ids[putIdx++] = id; + ids[putIdx++] = meta[i]; + } + } + + if (putIdx != words.length*2) { + ids = Arrays.copyOf(ids, putIdx); + } + return ids; + } + + public void close() throws Exception { + indexWriter.close(); + lexicon.close(); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomainLinks.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadDomainLinks.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomainLinks.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadDomainLinks.java index 1ce0035a..256b2712 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomainLinks.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadDomainLinks.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loading.loader; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; +import nu.marginalia.converting.instruction.instructions.DomainLink; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomains.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadDomains.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomains.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadDomains.java index b674b550..5c441c2f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomains.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadDomains.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loading.loader; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.model.EdgeDomain; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDocument.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadProcessedDocument.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDocument.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadProcessedDocument.java index fac60a74..3b910517 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDocument.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadProcessedDocument.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loading.loader; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocument; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocumentWithError; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocument; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocumentWithError; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +35,7 @@ public class SqlLoadProcessedDocument { IN FEATURES INT, IN STANDARD VARCHAR(32), IN QUALITY DOUBLE, - IN HASH INT, + IN HASH BIGINT, IN PUB_YEAR SMALLINT) BEGIN SET FOREIGN_KEY_CHECKS=0; @@ -83,7 +83,7 @@ public class SqlLoadProcessedDocument { stmt.setInt(6, doc.htmlFeatures()); stmt.setString(7, doc.standard().name()); stmt.setDouble(8, doc.quality()); - stmt.setInt(9, (int) doc.hash()); + stmt.setLong(9, doc.hash()); if (doc.pubYear() != null) { stmt.setShort(10, (short) doc.pubYear().intValue()); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDomain.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadProcessedDomain.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDomain.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadProcessedDomain.java index 09a0cc0a..3b2304a9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDomain.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadProcessedDomain.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loading.loader; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.converting.instruction.instructions.DomainLink; +import nu.marginalia.model.EdgeDomain; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadUrls.java b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadUrls.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadUrls.java rename to crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadUrls.java index 6f835863..1cd191f6 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadUrls.java +++ b/crawl/loading-process/src/main/java/nu/marginalia/loading/loader/SqlLoadUrls.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loading.loader; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/TestUtil.java b/crawl/loading-process/src/test/java/nu/marginalia/loader/DbTestUtil.java similarity index 93% rename from marginalia_nu/src/test/java/nu/marginalia/util/TestUtil.java rename to crawl/loading-process/src/test/java/nu/marginalia/loader/DbTestUtil.java index ae759dcb..1c3a71d2 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/TestUtil.java +++ b/crawl/loading-process/src/test/java/nu/marginalia/loader/DbTestUtil.java @@ -1,4 +1,4 @@ -package nu.marginalia.util; +package nu.marginalia.loader; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; @@ -6,11 +6,11 @@ import lombok.SneakyThrows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class TestUtil { +public class DbTestUtil { private static final int TEST_PORT_BASE = 6000; private static final int TEST_PORT_RANGE = 2000; - private final static Logger logger = LoggerFactory.getLogger(TestUtil.class); + private final static Logger logger = LoggerFactory.getLogger(DbTestUtil.class); public static int getPort() { return TEST_PORT_BASE + (int)(TEST_PORT_RANGE * Math.random()); diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomainLinksTest.java b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadDomainLinksTest.java similarity index 80% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomainLinksTest.java rename to crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadDomainLinksTest.java index 249bb160..8c2bbe95 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomainLinksTest.java +++ b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadDomainLinksTest.java @@ -1,9 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loader; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.util.TestUtil; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.loading.loader.LoaderData; +import nu.marginalia.loading.loader.SqlLoadDomainLinks; +import nu.marginalia.loading.loader.SqlLoadDomains; +import nu.marginalia.converting.instruction.instructions.DomainLink; +import nu.marginalia.model.EdgeDomain; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -11,6 +13,7 @@ import org.junit.jupiter.api.Test; import org.testcontainers.containers.MariaDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; + @Tag("slow") @Testcontainers class SqlLoadDomainLinksTest { @@ -26,7 +29,7 @@ class SqlLoadDomainLinksTest { LoaderData loaderData; @BeforeEach public void setUp() { - dataSource = TestUtil.getConnection(mariaDBContainer.getJdbcUrl()); + dataSource = DbTestUtil.getConnection(mariaDBContainer.getJdbcUrl()); var loadDomains = new SqlLoadDomains(dataSource); loaderData = new LoaderData(10); diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomainsTest.java b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadDomainsTest.java similarity index 82% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomainsTest.java rename to crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadDomainsTest.java index c57c5706..90c534ad 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadDomainsTest.java +++ b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadDomainsTest.java @@ -1,7 +1,8 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loader; -import nu.marginalia.util.TestUtil; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.loading.loader.LoaderData; +import nu.marginalia.loading.loader.SqlLoadDomains; +import nu.marginalia.model.EdgeDomain; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.testcontainers.containers.MariaDBContainer; @@ -24,7 +25,7 @@ class SqlLoadDomainsTest { @Test public void loadDomain() { - try (var dataSource = TestUtil.getConnection(mariaDBContainer.getJdbcUrl());) { + try (var dataSource = DbTestUtil.getConnection(mariaDBContainer.getJdbcUrl());) { var loadDomains = new SqlLoadDomains(dataSource); var loaderData = new LoaderData(10); @@ -39,7 +40,7 @@ class SqlLoadDomainsTest { @Test public void loadDomains() { - try (var dataSource = TestUtil.getConnection(mariaDBContainer.getJdbcUrl());) { + try (var dataSource = DbTestUtil.getConnection(mariaDBContainer.getJdbcUrl());) { var loadDomains = new SqlLoadDomains(dataSource); var loaderData = new LoaderData(10); diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDocumentTest.java b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadProcessedDocumentTest.java similarity index 67% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDocumentTest.java rename to crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadProcessedDocumentTest.java index 54e4eccb..51752127 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDocumentTest.java +++ b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadProcessedDocumentTest.java @@ -1,19 +1,19 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loader; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.util.TestUtil; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocument; -import nu.marginalia.wmsa.edge.converting.processor.logic.HtmlFeature; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDaoImpl; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; -import nu.marginalia.wmsa.edge.model.crawl.EdgeUrlState; -import nu.marginalia.wmsa.edge.model.id.EdgeIdArray; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; +import nu.marginalia.converting.instruction.instructions.LoadProcessedDocument; +import nu.marginalia.search.db.DbUrlDetailsQuery; +import nu.marginalia.loading.loader.LoaderData; +import nu.marginalia.loading.loader.SqlLoadDomains; +import nu.marginalia.loading.loader.SqlLoadProcessedDocument; +import nu.marginalia.loading.loader.SqlLoadUrls; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgeHtmlStandard; +import nu.marginalia.model.crawl.EdgeUrlState; +import nu.marginalia.model.crawl.HtmlFeature; +import nu.marginalia.model.id.EdgeIdArray; +import org.junit.jupiter.api.*; import org.testcontainers.containers.MariaDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; @@ -38,12 +38,12 @@ class SqlLoadProcessedDocumentTest { HikariDataSource dataSource; LoaderData loaderData; - EdgeDataStoreDaoImpl dataStoreDao; + DbUrlDetailsQuery dbUrlDetailsQuery; @BeforeEach public void setUp() throws URISyntaxException { - dataSource = TestUtil.getConnection(mariaDBContainer.getJdbcUrl()); - dataStoreDao = new EdgeDataStoreDaoImpl(dataSource); + dataSource = DbTestUtil.getConnection(mariaDBContainer.getJdbcUrl()); + dbUrlDetailsQuery = new DbUrlDetailsQuery(dataSource); var loadDomains = new SqlLoadDomains(dataSource); var loadUrls = new SqlLoadUrls(dataSource); @@ -59,7 +59,6 @@ class SqlLoadProcessedDocumentTest { @AfterEach public void tearDown() { - dataStoreDao.clearCaches(); dataSource.close(); } @@ -81,8 +80,8 @@ class SqlLoadProcessedDocumentTest { null ))); - var details = dataStoreDao.getUrlDetailsMulti(new EdgeIdArray<>(loaderData.getUrlId(new EdgeUrl("https://www.marginalia.nu/")))); - assertEquals(1, details.size()); + var details = dbUrlDetailsQuery.getUrlDetailsMulti(new EdgeIdArray<>(loaderData.getUrlId(new EdgeUrl("https://www.marginalia.nu/")))); + Assertions.assertEquals(1, details.size()); var urlDetails = details.get(0); diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDomainTest.java b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadProcessedDomainTest.java similarity index 80% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDomainTest.java rename to crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadProcessedDomainTest.java index 000b0923..82f38c23 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadProcessedDomainTest.java +++ b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadProcessedDomainTest.java @@ -1,10 +1,12 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loader; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.util.TestUtil; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.loading.loader.LoaderData; +import nu.marginalia.loading.loader.SqlLoadDomains; +import nu.marginalia.loading.loader.SqlLoadProcessedDomain; +import nu.marginalia.converting.instruction.instructions.DomainLink; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -29,7 +31,7 @@ class SqlLoadProcessedDomainTest { @BeforeEach public void setUp() { - dataSource = TestUtil.getConnection(mariaDBContainer.getJdbcUrl()); + dataSource = DbTestUtil.getConnection(mariaDBContainer.getJdbcUrl()); var loadDomains = new SqlLoadDomains(dataSource); loaderData = new LoaderData(10); diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadUrlsTest.java b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadUrlsTest.java similarity index 81% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadUrlsTest.java rename to crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadUrlsTest.java index 84d8d586..fe8c7847 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/converting/loader/SqlLoadUrlsTest.java +++ b/crawl/loading-process/src/test/java/nu/marginalia/loader/SqlLoadUrlsTest.java @@ -1,9 +1,11 @@ -package nu.marginalia.wmsa.edge.converting.loader; +package nu.marginalia.loader; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.util.TestUtil; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.loading.loader.LoaderData; +import nu.marginalia.loading.loader.SqlLoadDomains; +import nu.marginalia.loading.loader.SqlLoadUrls; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; @@ -29,7 +31,7 @@ class SqlLoadUrlsTest { LoaderData loaderData; @BeforeEach public void setUp() { - dataSource = TestUtil.getConnection(mariaDBContainer.getJdbcUrl()); + dataSource = DbTestUtil.getConnection(mariaDBContainer.getJdbcUrl()); var loadDomains = new SqlLoadDomains(dataSource); loaderData = new LoaderData(10); diff --git a/crawl/readme.md b/crawl/readme.md new file mode 100644 index 00000000..c5d3016e --- /dev/null +++ b/crawl/readme.md @@ -0,0 +1,74 @@ +# Crawl + +## 1. Crawl Job Extractor + +The [crawl-job-extractor-process](crawl-job-extractor-process/) creates a crawl job specification +based on the content in the database. + +## 2. Crawl Process + +The [crawling-process](crawling-process/) fetches website contents and saves them +as compressed JSON models described in [crawling-model](crawling-model/). + +## 3. Converting Process + +The [converting-process](converting-process/) reads crawl data from the crawling step and +processes them, extracting keywords and metadata and saves them as compressed JSON models +described in [converting-model](converting-model/). + +## 4. Loading Process + +The [loading-process](loading-process/) reads the processed data and creates an index journal +and lexicon, and loads domains and addresses into the MariaDB-database. + +## Overview + +Schematically the crawling and loading process looks like this: + +``` + //====================\\ + || Compressed JSON: || Specifications + || ID, Domain, Urls[] || File + || ID, Domain, Urls[] || + || ID, Domain, Urls[] || + || ... || + \\====================// + | + +-----------+ + | CRAWLING | Fetch each URL and + | STEP | output to file + +-----------+ + | + //========================\\ + || Compressed JSON: || Crawl + || Status, HTML[], ... || Files + || Status, HTML[], ... || + || Status, HTML[], ... || + || ... || + \\========================// + | + +------------+ + | CONVERTING | Analyze HTML and + | STEP | extract keywords + +------------+ features, links, URLs + | + //==================\\ + || Compressed JSON: || Processed + || URLs[] || Files + || Domains[] || + || Links[] || + || Keywords[] || + || ... || + || URLs[] || + || Domains[] || + || Links[] || + || Keywords[] || + || ... || + \\==================// + | + +------------+ + | LOADING | Insert URLs in DB + | STEP | Insert keywords in Index + +------------+ + +``` \ No newline at end of file diff --git a/doc/language-models.md b/doc/language-models.md deleted file mode 100644 index c5803bc5..00000000 --- a/doc/language-models.md +++ /dev/null @@ -1,15 +0,0 @@ -# Language Models - -## For Tests - -Many tests require language models to work, -download them from [https://downloads.marginalia.nu/](https://downloads.marginalia.nu/), -and put them somewhere. Then set the environment -variable ```LANGUAGE_MODELS_HOME``` to point to this directory. - -Alternatively, patch ```nu.marginalia.util.TestLanguageModels``` to -default to where you've put them. - -## For Production - -TBW \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..1e51ac22 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,128 @@ +x-svc: &service + env_file: + - "env/service.env" + volumes: + - vol:/vol + - conf:/wmsa/conf:ro + - model:/wmsa/model + - logs:/var/log/wmsa + networks: + - wmsa + depends_on: + - mariadb + +services: + index-service: + <<: *service + image: "marginalia.nu/index-service" + container_name: "index-service" + ports: + - "127.0.0.1:5021:5021/tcp" + - "127.0.0.1:4021:5000" + - "127.0.0.1:7021:4000" + search-service: + <<: *service + image: "marginalia.nu/search-service" + container_name: "search-service" + ports: + - "127.0.0.1:5023:5023" + - "127.0.0.1:4023:5000" + - "127.0.0.1:7023:4000" + depends_on: + - index-service + assistant-service: + <<: *service + image: "marginalia.nu/assistant-service" + container_name: "assistant-service" + ports: + - "127.0.0.1:5025:5025" + - "127.0.0.1:4025:5000" + - "127.0.0.1:7025:4000" + depends_on: + - mariadb + api-service: + <<: *service + image: "marginalia.nu/api-service" + container_name: "api-service" + ports: + - "127.0.0.1:5004:5025" + - "127.0.0.1:4004:5000" + - "127.0.0.1:7004:4000" + depends_on: + - mariadb + dating-service: + <<: *service + image: "marginalia.nu/dating-service" + container_name: "dating-service" + ports: + - "127.0.0.1:5070:5070" + - "127.0.0.1:4070:5000" + - "127.0.0.1:7070:4000" + depends_on: + - mariadb + explorer-service: + <<: *service + image: "marginalia.nu/explorer-service" + container_name: "explorer-service" + ports: + - "127.0.0.1:5071:5071" + - "127.0.0.1:4071:5000" + - "127.0.0.1:7071:4000" + depends_on: + - mariadb + mariadb: + image: "mariadb/server:10.3" + container_name: "mariadb" + env_file: "env/mariadb.env" + command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci'] + ports: + - "127.0.0.1:3306:3306/tcp" + volumes: + - db:/var/lib/mysql + - "./common/model/src/main/resources/sql/edge-crawler-cache.sql:/docker-entrypoint-initdb.d/init.sql" + networks: + - wmsa + nginx-gw: + image: "nginx" + container_name: "nginx-gw" + ports: + - "127.0.0.1:8080:80" + volumes: + - "./run/nginx-site.conf:/etc/nginx/conf.d/default.conf" + networks: + - wmsa + depends_on: + - search-service +networks: + wmsa: +volumes: + db: + driver: local + driver_opts: + type: none + o: bind + device: run/db + vol: + driver: local + driver_opts: + type: none + o: bind + device: run/vol + logs: + driver: local + driver_opts: + type: none + o: bind + device: run/logs + model: + driver: local + driver_opts: + type: none + o: bind + device: run/model + conf: + driver: local + driver_opts: + type: none + o: bind + device: run/conf \ No newline at end of file diff --git a/docker-service.gradle b/docker-service.gradle new file mode 100644 index 00000000..4f8605dc --- /dev/null +++ b/docker-service.gradle @@ -0,0 +1,41 @@ +import java.nio.file.Files + +ext { + dockerImage='openjdk:17-slim' + serviceJvmOpts='-Dservice-host=0.0.0.0 -ea -Dsmall-ram=true ${wmsa_jvm_param} -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=4000 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false' + serviceToolOpts='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5000' +} + +tasks.register('dockerFile') { + buildDir.mkdir() + + var df = new File(buildDir, "Dockerfile") + doLast { + df.text = """# +# I'm auto-generated, please don't make changes to me or commit me to git +# +# The template exists in docker-service.gradle +# +FROM ${dockerImage} + +ADD ${application.applicationName}.tar / +RUN mkdir /wmsa + +ENV JAVA_TOOL_OPTIONS="${serviceToolOpts}" +ENV JAVA_OPTS="${serviceJvmOpts} " + +ENTRYPOINT WMSA_HOME=/wmsa /${application.applicationName}/bin/${application.applicationName} \${arg0} \${arg1} +""" + } + it.outputs.file(df) +} + +docker { + dockerfile = tasks.dockerFile.outputs.files.singleFile + name = 'marginalia.nu/'+application.applicationName+':latest' + files tasks.distTar.outputs + tags 'latest' + + dependsOn tasks.distTar + dependsOn tasks.dockerFile +} diff --git a/env/mariadb.env b/env/mariadb.env new file mode 100644 index 00000000..b7fec6ea --- /dev/null +++ b/env/mariadb.env @@ -0,0 +1,4 @@ +MARIADB_RANDOM_ROOT_PASSWORD=1 +MARIADB_DATABASE=WMSA_prod +MARIADB_USER=wmsa +MARIADB_PASSWORD=wmsa \ No newline at end of file diff --git a/env/service.env b/env/service.env new file mode 100644 index 00000000..fef49dae --- /dev/null +++ b/env/service.env @@ -0,0 +1 @@ +WMSA_HOME=/home/vlofgren/Code/wmsa.local \ No newline at end of file diff --git a/features/domain-ranking/build.gradle b/features/domain-ranking/build.gradle new file mode 100644 index 00000000..ab6c8c40 --- /dev/null +++ b/features/domain-ranking/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id "de.undercouch.download" version "5.1.0" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':common:model') + implementation project(':common:service') + + implementation libs.lombok + annotationProcessor libs.lombok + + implementation libs.bundles.slf4j + implementation libs.bundles.mariadb + implementation libs.guice + implementation libs.notnull + implementation libs.roaringbitmap + implementation libs.trove + implementation libs.fastutil + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/DomainRankings.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/DomainRankings.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/DomainRankings.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/DomainRankings.java index d6ddcd62..b408f980 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/DomainRankings.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/DomainRankings.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.postings; +package nu.marginalia.ranking; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/RankingAlgorithm.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/RankingAlgorithm.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/RankingAlgorithm.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/RankingAlgorithm.java index 2e8589e4..606f3e60 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/RankingAlgorithm.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/RankingAlgorithm.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.edge.index.ranking; +package nu.marginalia.ranking; import gnu.trove.list.array.TIntArrayList; import gnu.trove.map.hash.TIntIntHashMap; import gnu.trove.map.hash.TIntObjectHashMap; import it.unimi.dsi.fastutil.ints.IntArrays; -import nu.marginalia.wmsa.edge.index.ranking.accumulator.RankingResultAccumulator; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainData; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcher; +import nu.marginalia.ranking.accumulator.RankingResultAccumulator; +import nu.marginalia.ranking.data.RankingDomainFetcher; +import nu.marginalia.ranking.data.RankingDomainData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,6 +82,7 @@ public abstract class RankingAlgorithm { } }); } + logger.info("Origin Domains: {}", originDomainIds.size()); } @@ -248,7 +249,7 @@ public abstract class RankingAlgorithm { public RankingResultAccumulator getRanking(int numResults, Supplier> accumulatorP) { - if (numResults < 0) { + if (numResults <= 0) { numResults = domainIdToIndex.size(); } numResults = min(numResults, min(domainIdToIndex.size(), rank.length)); @@ -265,6 +266,7 @@ public abstract class RankingAlgorithm { return accumulator; } + private static int[] sortOrder(double[] values) { int[] ret = new int[values.length]; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/ReversePageRank.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/ReversePageRank.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/ReversePageRank.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/ReversePageRank.java index 0c202958..76b138f9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/ReversePageRank.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/ReversePageRank.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.index.ranking; +package nu.marginalia.ranking; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcher; +import nu.marginalia.ranking.data.RankingDomainFetcher; public class ReversePageRank extends RankingAlgorithm { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/StandardPageRank.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/StandardPageRank.java similarity index 73% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/StandardPageRank.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/StandardPageRank.java index d9302fd6..0c629c96 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/StandardPageRank.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/StandardPageRank.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.index.ranking; +package nu.marginalia.ranking; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcher; +import nu.marginalia.ranking.data.RankingDomainFetcher; public class StandardPageRank extends RankingAlgorithm { @@ -22,10 +22,14 @@ public class StandardPageRank extends RankingAlgorithm { for (int j = 0; j < links.size(); j++) { int linkedDomain = links.getQuick(j); - int linkSize = 1; - var bl = linkDataSrc2Dest[linkedDomain]; - if (bl != null) { - linkSize = bl.size(); + final int linkSize; + var backLinks = linkDataSrc2Dest[linkedDomain]; + + if (backLinks == null) { + linkSize = 1; + } + else { + linkSize = backLinks.size(); } newRankValue += rank.get(linkedDomain) / linkSize; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultAccumulator.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultAccumulator.java similarity index 63% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultAccumulator.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultAccumulator.java index fea37b00..e9055f6e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultAccumulator.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultAccumulator.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.ranking.accumulator; +package nu.marginalia.ranking.accumulator; public interface RankingResultAccumulator { void add(int domainId, int rank); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultBitSetAccumulator.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultBitSetAccumulator.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultBitSetAccumulator.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultBitSetAccumulator.java index 26e72522..3a806d95 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultBitSetAccumulator.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultBitSetAccumulator.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.ranking.accumulator; +package nu.marginalia.ranking.accumulator; import org.roaringbitmap.RoaringBitmap; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultHashMapAccumulator.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultHashMapAccumulator.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultHashMapAccumulator.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultHashMapAccumulator.java index 653806ed..15365466 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultHashMapAccumulator.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultHashMapAccumulator.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.ranking.accumulator; +package nu.marginalia.ranking.accumulator; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultListAccumulator.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultListAccumulator.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultListAccumulator.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultListAccumulator.java index 663483e4..ecfab27c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/accumulator/RankingResultListAccumulator.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/accumulator/RankingResultListAccumulator.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.ranking.accumulator; +package nu.marginalia.ranking.accumulator; import gnu.trove.list.array.TIntArrayList; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/data/RankingDomainData.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/data/RankingDomainData.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/data/RankingDomainData.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/data/RankingDomainData.java index 4a59daf4..6d13fd09 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/data/RankingDomainData.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/data/RankingDomainData.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.index.ranking.data; +package nu.marginalia.ranking.data; import lombok.AllArgsConstructor; import lombok.Data; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; @Data @AllArgsConstructor diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/data/RankingDomainFetcher.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/data/RankingDomainFetcher.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/data/RankingDomainFetcher.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/data/RankingDomainFetcher.java index ff2b7e18..a330ede7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/data/RankingDomainFetcher.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/data/RankingDomainFetcher.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.index.ranking.data; +package nu.marginalia.ranking.data; import com.google.inject.Inject; import com.google.inject.Singleton; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklistImpl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklistImpl; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/data/RankingDomainFetcherForSimilarityData.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/data/RankingDomainFetcherForSimilarityData.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/data/RankingDomainFetcherForSimilarityData.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/data/RankingDomainFetcherForSimilarityData.java index dddaeebb..738ecb55 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/data/RankingDomainFetcherForSimilarityData.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/data/RankingDomainFetcherForSimilarityData.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.index.ranking.data; +package nu.marginalia.ranking.data; import com.google.inject.Inject; import com.google.inject.Singleton; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklistImpl; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklistImpl; import org.slf4j.LoggerFactory; import java.sql.SQLException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/CreateBrowseDomainRanksTool.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/CreateBrowseDomainRanksTool.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/CreateBrowseDomainRanksTool.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/CreateBrowseDomainRanksTool.java index f4cb6197..4ff472cc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/CreateBrowseDomainRanksTool.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/CreateBrowseDomainRanksTool.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.edge.index.ranking.tool; +package nu.marginalia.ranking.tool; import com.zaxxer.hikari.HikariDataSource; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklistImpl; -import nu.marginalia.wmsa.edge.index.ranking.StandardPageRank; -import nu.marginalia.wmsa.edge.index.ranking.accumulator.RankingResultListAccumulator; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcherForSimilarityData; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklistImpl; +import nu.marginalia.ranking.StandardPageRank; +import nu.marginalia.ranking.accumulator.RankingResultListAccumulator; +import nu.marginalia.ranking.data.RankingDomainFetcherForSimilarityData; +import nu.marginalia.service.module.DatabaseModule; import org.mariadb.jdbc.Driver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/PerusePageRankV2.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/PerusePageRankV2.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/PerusePageRankV2.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/PerusePageRankV2.java index 4fbdd08b..0e615552 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/PerusePageRankV2.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/PerusePageRankV2.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.ranking.tool; +package nu.marginalia.ranking.tool; import com.zaxxer.hikari.HikariDataSource; @@ -10,11 +10,11 @@ import it.unimi.dsi.fastutil.ints.IntArrays; import it.unimi.dsi.fastutil.ints.IntComparator; import lombok.AllArgsConstructor; import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.index.ranking.RankingAlgorithm; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainData; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcher; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklistImpl; +import nu.marginalia.ranking.RankingAlgorithm; +import nu.marginalia.ranking.data.RankingDomainData; +import nu.marginalia.ranking.data.RankingDomainFetcher; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklistImpl; +import nu.marginalia.service.module.DatabaseModule; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/PrintDomainRanksTool.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/PrintDomainRanksTool.java similarity index 78% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/PrintDomainRanksTool.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/PrintDomainRanksTool.java index 60f12008..d608abad 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/PrintDomainRanksTool.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/PrintDomainRanksTool.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.edge.index.ranking.tool; +package nu.marginalia.ranking.tool; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklistImpl; -import nu.marginalia.wmsa.edge.index.ranking.StandardPageRank; -import nu.marginalia.wmsa.edge.index.ranking.accumulator.RankingResultListAccumulator; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcher; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcherForSimilarityData; +import nu.marginalia.ranking.accumulator.RankingResultListAccumulator; +import nu.marginalia.ranking.data.RankingDomainFetcher; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklistImpl; +import nu.marginalia.ranking.StandardPageRank; +import nu.marginalia.ranking.data.RankingDomainFetcherForSimilarityData; +import nu.marginalia.service.module.DatabaseModule; import org.mariadb.jdbc.Driver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/UpdateDomainRanksTool.java b/features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/UpdateDomainRanksTool.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/UpdateDomainRanksTool.java rename to features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/UpdateDomainRanksTool.java index 714e3028..804df19e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/tool/UpdateDomainRanksTool.java +++ b/features/domain-ranking/src/main/java/nu/marginalia/ranking/tool/UpdateDomainRanksTool.java @@ -1,12 +1,13 @@ -package nu.marginalia.wmsa.edge.index.ranking.tool; +package nu.marginalia.ranking.tool; import com.zaxxer.hikari.HikariDataSource; import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.index.ranking.StandardPageRank; -import nu.marginalia.wmsa.edge.index.ranking.accumulator.RankingResultListAccumulator; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklistImpl; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcherForSimilarityData; +import nu.marginalia.ranking.StandardPageRank; +import nu.marginalia.ranking.accumulator.RankingResultListAccumulator; +import nu.marginalia.ranking.data.RankingDomainFetcherForSimilarityData; + +import nu.marginalia.model.dbcommon.EdgeDomainBlacklistImpl; +import nu.marginalia.service.module.DatabaseModule; import org.mariadb.jdbc.Driver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,8 +33,7 @@ public class UpdateDomainRanksTool { var uploader = new Thread(() -> uploadThread(conn), "Uploader"); logger.info("Ranking"); - var ds = new DatabaseModule().provideConnection(); - var domains = new RankingDomainFetcherForSimilarityData(ds, new EdgeDomainBlacklistImpl(ds)); + var domains = new RankingDomainFetcherForSimilarityData(conn, new EdgeDomainBlacklistImpl(conn)); var rpr = new StandardPageRank(domains, "memex.marginalia.nu", "bikobatanari.art", "sadgrl.online", "wiki.xxiivv.com"); rankMax = rpr.size(); diff --git a/features/query-parser/build.gradle b/features/query-parser/build.gradle new file mode 100644 index 00000000..422170ff --- /dev/null +++ b/features/query-parser/build.gradle @@ -0,0 +1,43 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} +dependencies { + implementation project(':libraries:language-processing') + implementation project(':libraries:misc') + implementation project(':common:config') + implementation project(':common:model') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + implementation libs.bundles.nlp + + implementation libs.bundles.handlebars + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + maxHeapSize = "8G" + useJUnitPlatform() +} + +task fastTests(type: Test) { + maxHeapSize = "8G" + useJUnitPlatform { + excludeTags "slow" + } +} + diff --git a/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryParser.java b/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryParser.java new file mode 100644 index 00000000..eebf2daa --- /dev/null +++ b/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryParser.java @@ -0,0 +1,105 @@ +package nu.marginalia.query_parser; + +import nu.marginalia.language.WordPatterns; +import nu.marginalia.query_parser.token.Token; +import nu.marginalia.query_parser.token.TokenType; +import nu.marginalia.util.TransformList; + +import java.util.List; + +public class QueryParser { + + private final QueryTokenizer tokenizer = new QueryTokenizer(); + + public List parse(String query) { + List basicTokens = tokenizer.tokenizeQuery(query); + + TransformList list = new TransformList<>(basicTokens); + + list.transformEach(QueryParser::handleQuoteTokens); + list.transformEach(QueryParser::trimLiterals); + list.transformEachPair(QueryParser::createNegatedTerms); + list.transformEachPair(QueryParser::createPriorityTerms); + list.transformEach(QueryParser::handleSpecialOperations); + list.scanAndTransform(TokenType.LPAREN, TokenType.RPAREN, QueryParser::handleAdvisoryTerms); + + return list.getBackingList(); + } + + private static void handleQuoteTokens(TransformList.Entity entity) { + var t = entity.value(); + if (t.type == TokenType.QUOT) { + entity.replace(new Token(TokenType.QUOT_TERM, + t.str.replaceAll("\\s+", WordPatterns.WORD_TOKEN_JOINER), + t.displayStr)); + } + } + + private static void trimLiterals(TransformList.Entity entity) { + var t = entity.value(); + + if (t.type == TokenType.LITERAL_TERM + && (t.str.endsWith(":") || t.str.endsWith(".")) + && t.str.length() > 1) { + entity.replace(new Token(TokenType.LITERAL_TERM, t.str.substring(0, t.str.length() - 1), t.displayStr)); + } + + } + + private static void createNegatedTerms(TransformList.Entity first, TransformList.Entity second) { + var t = first.value(); + var tn = second.value(); + + if (t.type == TokenType.MINUS && tn.type == TokenType.LITERAL_TERM) { + first.remove(); + second.replace(new Token(TokenType.EXCLUDE_TERM, tn.str, "-" + tn.str)); + } + } + + private static void createPriorityTerms(TransformList.Entity first, TransformList.Entity second) { + var t = first.value(); + var tn = second.value(); + + if (t.type == TokenType.QMARK && tn.type == TokenType.LITERAL_TERM) { + first.remove(); + second.replace(new Token(TokenType.PRIORTY_TERM, tn.str, "?" + tn.str)); + } + } + + private static void handleSpecialOperations(TransformList.Entity entity) { + var t = entity.value(); + if (t.type != TokenType.LITERAL_TERM) { + return; + } + + if (t.str.startsWith("q") && t.str.matches("q[=><]\\d+")) { + entity.replace(new Token(TokenType.QUALITY_TERM, t.str.substring(1), t.displayStr)); + } else if (t.str.startsWith("near:")) { + entity.replace(new Token(TokenType.NEAR_TERM, t.str.substring(5), t.displayStr)); + } else if (t.str.startsWith("year") && t.str.matches("year[=><]\\d{4}")) { + entity.replace(new Token(TokenType.YEAR_TERM, t.str.substring(4), t.displayStr)); + } else if (t.str.startsWith("size") && t.str.matches("size[=><]\\d+")) { + entity.replace(new Token(TokenType.SIZE_TERM, t.str.substring(4), t.displayStr)); + } else if (t.str.startsWith("rank") && t.str.matches("rank[=><]\\d+")) { + entity.replace(new Token(TokenType.RANK_TERM, t.str.substring(4), t.displayStr)); + } else if (t.str.startsWith("qs=")) { + entity.replace(new Token(TokenType.QS_TERM, t.str.substring(3), t.displayStr)); + } else if (t.str.contains(":")) { + entity.replace(new Token(TokenType.ADVICE_TERM, t.str, t.displayStr)); + } + } + + private static void handleAdvisoryTerms(TransformList.Entity entity) { + var t = entity.value(); + if (t.type == TokenType.LPAREN) { + entity.remove(); + } else if (t.type == TokenType.RPAREN) { + entity.remove(); + } else if (t.type == TokenType.LITERAL_TERM) { + entity.replace(new Token(TokenType.ADVICE_TERM, t.str, "(" + t.str + ")")); + } + } + + +} + diff --git a/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryPermutation.java b/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryPermutation.java new file mode 100644 index 00000000..1a51a5b8 --- /dev/null +++ b/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryPermutation.java @@ -0,0 +1,220 @@ +package nu.marginalia.query_parser; + +import nu.marginalia.language.WordPatterns; +import nu.marginalia.query_parser.token.Token; +import nu.marginalia.query_parser.token.TokenType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.stream.Stream.concat; + +public class QueryPermutation { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final QueryVariants queryVariants; + + + public QueryPermutation(QueryVariants queryVariants) { + this.queryVariants = queryVariants; + } + + public List> permuteQueries(List items) { + int start = -1; + int end = items.size(); + + for (int i = 0; i < items.size(); i++) { + var token = items.get(i); + + if (start < 0) { + if (token.type == TokenType.LITERAL_TERM && WordPatterns.wordQualitiesPredicate.test(token.str)) { + start = i; + } + } + else { + if (token.type != TokenType.LITERAL_TERM || !WordPatterns.wordPredicateEither.test(token.str)) { + end = i; + break; + } + } + } + + if (start >= 0 && end - start > 1) { + List> permuteParts = combineSearchTerms(items.subList(start, end)); + int s = start; + int e = end; + return permuteParts.stream().map(part -> + concat(items.subList(0, s).stream(), concat(part.stream(), items.subList(e, items.size()).stream())) + .collect(Collectors.toList())) + .peek(lst -> lst.removeIf(this::isJunkWord)) + .limit(24) + .collect(Collectors.toList()); + } + else { + return List.of(items); + } + } + + + public List> permuteQueriesNew(List items) { + int start = -1; + int end = items.size(); + + for (int i = 0; i < items.size(); i++) { + var token = items.get(i); + + if (start < 0) { + if (token.type == TokenType.LITERAL_TERM && WordPatterns.wordQualitiesPredicate.test(token.str)) { + start = i; + } + } + else { + if (token.type != TokenType.LITERAL_TERM || !WordPatterns.wordPredicateEither.test(token.str)) { + end = i; + break; + } + } + } + + if (start >= 0 && end - start >= 1) { + var result = queryVariants.getQueryVariants(items.subList(start, end)); + + logger.debug("{}", result); + + if (result.isEmpty()) { + logger.warn("Empty variants result, falling back on old code"); + return permuteQueries(items); + } + + List> queryVariants = new ArrayList<>(); + for (var query : result.faithful) { + var tokens = query.terms.stream().map(term -> new Token(TokenType.LITERAL_TERM, term)).collect(Collectors.toList()); + tokens.addAll(result.nonLiterals); + + queryVariants.add(tokens); + } + for (var query : result.alternative) { + if (queryVariants.size() >= 6) + break; + + var tokens = query.terms.stream().map(term -> new Token(TokenType.LITERAL_TERM, term)).collect(Collectors.toList()); + tokens.addAll(result.nonLiterals); + + queryVariants.add(tokens); + } + + List> returnValue = new ArrayList<>(queryVariants.size()); + for (var variant: queryVariants) { + List r = new ArrayList<>(start + variant.size() + (items.size() - end)); + r.addAll(items.subList(0, start)); + r.addAll(variant); + r.addAll(items.subList(end, items.size())); + returnValue.add(r); + } + + return returnValue; + + } + else { + return List.of(items); + } + } + + private boolean isJunkWord(Token token) { + if (WordPatterns.isStopWord(token.str) && + !token.str.matches("^(\\d+|([a-z]+:.*))$")) { + return true; + } + return switch (token.str) { + case "vs", "versus", "or", "and" -> true; + default -> false; + }; + } + + private List> combineSearchTerms(List subList) { + int size = subList.size(); + if (size < 1) { + return Collections.emptyList(); + } + else if (size == 1) { + if (WordPatterns.isStopWord(subList.get(0).str)) { + return Collections.emptyList(); + } + return List.of(subList); + } + + List> results = new ArrayList<>(size*(size+1)/2); + + if (subList.size() <= 4 && subList.get(0).str.length() >= 2 && !isPrefixWord(subList.get(subList.size()-1).str)) { + results.add(List.of(joinTokens(subList))); + } + outer: for (int i = size - 1; i >= 1; i--) { + + var left = combineSearchTerms(subList.subList(0, i)); + var right = combineSearchTerms(subList.subList(i, size)); + + for (var l : left) { + if (results.size() > 48) { + break outer; + } + + for (var r : right) { + if (results.size() > 48) { + break outer; + } + + List combined = new ArrayList<>(l.size() + r.size()); + combined.addAll(l); + combined.addAll(r); + if (!results.contains(combined)) { + results.add(combined); + } + } + } + } + if (!results.contains(subList)) { + results.add(subList); + } + Comparator> tc = (o1, o2) -> { + int dJoininess = o2.stream().mapToInt(s->(int)Math.pow(joininess(s.str), 2)).sum() - + o1.stream().mapToInt(s->(int)Math.pow(joininess(s.str), 2)).sum(); + if (dJoininess == 0) { + return (o2.stream().mapToInt(s->(int)Math.pow(rightiness(s.str), 2)).sum() - + o1.stream().mapToInt(s->(int)Math.pow(rightiness(s.str), 2)).sum()); + } + return (int) Math.signum(dJoininess); + }; + results.sort(tc); + return results; + } + + private boolean isPrefixWord(String str) { + return switch (str) { + case "the", "of", "when" -> true; + default -> false; + }; + } + + int joininess(String s) { + return (int) s.chars().filter(c -> c == '_').count(); + } + int rightiness(String s) { + int rightiness = 0; + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '_') { + rightiness+=i; + } + } + return rightiness; + } + + private Token joinTokens(List subList) { + return new Token(TokenType.LITERAL_TERM, + subList.stream().map(t -> t.str).collect(Collectors.joining("_")), + subList.stream().map(t -> t.str).collect(Collectors.joining(" "))); + } +} diff --git a/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryTokenizer.java b/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryTokenizer.java new file mode 100644 index 00000000..8ca580db --- /dev/null +++ b/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryTokenizer.java @@ -0,0 +1,65 @@ +package nu.marginalia.query_parser; + +import nu.marginalia.language.encoding.AsciiFlattener; +import nu.marginalia.query_parser.token.Token; +import nu.marginalia.query_parser.token.TokenType; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class QueryTokenizer { + private static final Pattern noisePattern = Pattern.compile("[,]"); + + public List tokenizeQuery(String rawQuery) { + List tokens = new ArrayList<>(); + + String query = AsciiFlattener.flattenUnicode(rawQuery); + query = noisePattern.matcher(query).replaceAll(" "); + + for (int i = 0; i < query.length(); i++) { + int chr = query.charAt(i); + + if ('(' == chr) { + tokens.add(new Token(TokenType.LPAREN, "(", "(")); + } + else if (')' == chr) { + tokens.add(new Token(TokenType.RPAREN, ")", ")")); + } + else if ('"' == chr) { + int end = query.indexOf('"', i+1); + if (end == -1) { + end = query.length(); + } + tokens.add(new Token(TokenType.QUOT, + query.substring(i+1, end).toLowerCase(), + query.substring(i, Math.min(query.length(), end+1)))); + i = end; + } + else if ('-' == chr) { + tokens.add(new Token(TokenType.MINUS, "-")); + } + else if ('?' == chr) { + tokens.add(new Token(TokenType.QMARK, "?")); + } + else if (Character.isSpaceChar(chr)) { + // + } + else { + + int end = i+1; + for (; end < query.length(); end++) { + if (query.charAt(end) == ' ' || query.charAt(end) == ')') + break; + } + tokens.add(new Token(TokenType.LITERAL_TERM, + query.substring(i, end).toLowerCase(), + query.substring(i, end))); + i = end-1; + } + } + return tokens; + } + + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryVariants.java b/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryVariants.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryVariants.java rename to features/query-parser/src/main/java/nu/marginalia/query_parser/QueryVariants.java index 6d42b599..cef1be5b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryVariants.java +++ b/features/query-parser/src/main/java/nu/marginalia/query_parser/QueryVariants.java @@ -1,29 +1,25 @@ -package nu.marginalia.wmsa.edge.search.query; +package nu.marginalia.query_parser; -import com.google.inject.Inject; -import com.google.inject.Singleton; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.util.language.processing.KeywordExtractor; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.util.language.processing.model.DocumentSentence; -import nu.marginalia.util.language.processing.model.WordSpan; -import nu.marginalia.wmsa.edge.assistant.dict.NGramBloomFilter; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; +import nu.marginalia.LanguageModels; +import nu.marginalia.language.statistics.EnglishDictionary; +import nu.marginalia.language.keywords.KeywordExtractor; +import nu.marginalia.language.sentence.SentenceExtractor; +import nu.marginalia.language.statistics.NGramBloomFilter; +import nu.marginalia.language.statistics.TermFrequencyDict; +import nu.marginalia.language.model.DocumentSentence; +import nu.marginalia.language.model.WordSpan; +import nu.marginalia.query_parser.token.Token; +import nu.marginalia.query_parser.token.TokenType; import opennlp.tools.stemmer.PorterStemmer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.*; import java.util.regex.Pattern; -@Singleton public class QueryVariants { - - private final Logger logger = LoggerFactory.getLogger(getClass()); private final KeywordExtractor keywordExtractor; private final TermFrequencyDict dict; private final PorterStemmer ps = new PorterStemmer(); @@ -32,7 +28,6 @@ public class QueryVariants { private final EnglishDictionary englishDictionary; private final ThreadLocal sentenceExtractor; - @Inject public QueryVariants(LanguageModels lm, TermFrequencyDict dict, NGramBloomFilter nGramBloomFilter, diff --git a/features/query-parser/src/main/java/nu/marginalia/query_parser/token/Token.java b/features/query-parser/src/main/java/nu/marginalia/query_parser/token/Token.java new file mode 100644 index 00000000..47290632 --- /dev/null +++ b/features/query-parser/src/main/java/nu/marginalia/query_parser/token/Token.java @@ -0,0 +1,32 @@ +package nu.marginalia.query_parser.token; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.With; + +@ToString +@EqualsAndHashCode +@With +public class Token { + public TokenType type; + public String str; + public final String displayStr; + + public Token(TokenType type, String str, String displayStr) { + this.type = type; + this.str = str; + this.displayStr = safeString(displayStr); + } + + + public Token(TokenType type, String str) { + this.type = type; + this.str = str; + this.displayStr = safeString(str); + } + + private static String safeString(String s) { + return s.replaceAll("<", "<") + .replaceAll(">", ">"); + } +} diff --git a/features/query-parser/src/main/java/nu/marginalia/query_parser/token/TokenType.java b/features/query-parser/src/main/java/nu/marginalia/query_parser/token/TokenType.java new file mode 100644 index 00000000..dc25b332 --- /dev/null +++ b/features/query-parser/src/main/java/nu/marginalia/query_parser/token/TokenType.java @@ -0,0 +1,34 @@ +package nu.marginalia.query_parser.token; + +import java.util.function.Predicate; + +public enum TokenType implements Predicate { + TERM, + + + LITERAL_TERM, + QUOT_TERM, + EXCLUDE_TERM, + ADVICE_TERM, + PRIORTY_TERM, + + QUALITY_TERM, + YEAR_TERM, + SIZE_TERM, + RANK_TERM, + NEAR_TERM, + + QS_TERM, + + QUOT, + MINUS, + QMARK, + LPAREN, + RPAREN, + + IGNORE; + + public boolean test(Token t) { + return t.type == this; + } +} diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/BodyQueryParserTest.java b/features/query-parser/src/test/java/nu/marginalia/query_parser/BodyQueryParserTest.java similarity index 78% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/BodyQueryParserTest.java rename to features/query-parser/src/test/java/nu/marginalia/query_parser/BodyQueryParserTest.java index 4b040983..27f282b4 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/BodyQueryParserTest.java +++ b/features/query-parser/src/test/java/nu/marginalia/query_parser/BodyQueryParserTest.java @@ -1,9 +1,11 @@ -package nu.marginalia.wmsa.edge.search.query; +package nu.marginalia.query_parser; +import nu.marginalia.LanguageModels; +import nu.marginalia.language.statistics.EnglishDictionary; +import nu.marginalia.language.statistics.NGramBloomFilter; +import nu.marginalia.language.statistics.TermFrequencyDict; +import nu.marginalia.query_parser.token.TokenType; import nu.marginalia.util.TestLanguageModels; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.wmsa.edge.assistant.dict.NGramBloomFilter; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,6 +21,7 @@ class BodyQueryParserTest { private static EnglishDictionary englishDictionary; private static NGramBloomFilter nGramBloomFilter; private static final LanguageModels lm = TestLanguageModels.getLanguageModels(); + private QueryPermutation permutation; @BeforeAll public static void init() throws IOException { @@ -29,7 +32,8 @@ class BodyQueryParserTest { @BeforeEach public void setUp() { - parser = new QueryParser(englishDictionary, new QueryVariants(lm, dict, nGramBloomFilter, englishDictionary)); + parser = new QueryParser(); + permutation = new QueryPermutation(new QueryVariants(lm, dict, nGramBloomFilter, englishDictionary)); } @Test @@ -52,7 +56,7 @@ class BodyQueryParserTest { results.forEach(System.out::println); assertEquals(TokenType.QUOT_TERM, results.get(0).type); assertEquals("hello_world", results.get(0).str); - assertEquals("\u201Chello world\u201D", results.get(0).displayStr); + assertEquals("\"hello world\"", results.get(0).displayStr); } @Test @@ -75,7 +79,7 @@ class BodyQueryParserTest { @Test void parseCombined() { - for (var list : parser.permuteQueries(parser.parse("dune 2 remake"))) { + for (var list : permutation.permuteQueries(parser.parse("dune 2 remake"))) { for (var t: list) { System.out.printf("%s ", t.str); } @@ -84,7 +88,7 @@ class BodyQueryParserTest { } @Test void parseCombinedDOS() { - for (var list : parser.permuteQueries(parser.parse("ab ba baa abba baba ab ba"))) { + for (var list : permutation.permuteQueries(parser.parse("ab ba baa abba baba ab ba"))) { for (var t: list) { System.out.printf("%s ", t.str); } @@ -94,7 +98,7 @@ class BodyQueryParserTest { @Test void parseCombinedSuperman() { - for (var list : parser.permuteQueries(parser.parse("wizardry proving grounds of the mad overlord"))) { + for (var list : permutation.permuteQueries(parser.parse("wizardry proving grounds of the mad overlord"))) { for (var t: list) { System.out.printf("%s ", t.str); } diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/QueryParserTest.java b/features/query-parser/src/test/java/nu/marginalia/query_parser/QueryParserTest.java similarity index 62% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/QueryParserTest.java rename to features/query-parser/src/test/java/nu/marginalia/query_parser/QueryParserTest.java index 734415e9..dbeaeb7b 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/QueryParserTest.java +++ b/features/query-parser/src/test/java/nu/marginalia/query_parser/QueryParserTest.java @@ -1,15 +1,20 @@ -package nu.marginalia.wmsa.edge.search.query; +package nu.marginalia.query_parser; +import nu.marginalia.LanguageModels; +import nu.marginalia.language.statistics.EnglishDictionary; +import nu.marginalia.language.statistics.NGramBloomFilter; +import nu.marginalia.language.statistics.TermFrequencyDict; +import nu.marginalia.query_parser.token.Token; +import nu.marginalia.query_parser.token.TokenType; import nu.marginalia.util.TestLanguageModels; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.wmsa.edge.assistant.dict.NGramBloomFilter; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.io.IOException; +import java.util.List; import java.util.stream.Collectors; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; class QueryParserTest { @@ -25,7 +30,7 @@ class QueryParserTest { nGramBloomFilter = new NGramBloomFilter(lm); englishDictionary = new EnglishDictionary(dict); - parser = new QueryParser(englishDictionary, new QueryVariants(lm, dict, nGramBloomFilter, englishDictionary)); + parser = new QueryParser(); } @Test @@ -62,10 +67,19 @@ class QueryParserTest { } @Test - void variantQueries() { - var r = parser.parse("car stemming"); - parser.variantQueries(r).forEach(query -> { - System.out.println(query.stream().map(t -> t.str).collect(Collectors.joining(", "))); - }); + public void testNonAsciiNames() { + verifyParseResult("André the Giant", "andre", "the", "giant"); + verifyParseResult("Stanisław Lem", "stanislaw", "lem"); + verifyParseResult("Nicolae Ceaușescu", "nicolae", "ceausescu"); + verifyParseResult("Þorrablót", "thorrablot"); + verifyParseResult("Karolis Koncevičius", "karolis", "koncevicius"); } + + private void verifyParseResult(String query, String... expectedTokens) { + assertArrayEquals(expectedTokens, getTokenStrings(parser.parse(query))); + } + private String[] getTokenStrings(List tokens) { + return tokens.stream().map(t -> t.str).toArray(String[]::new); + } + } \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/QueryVariantsTest.java b/features/query-parser/src/test/java/nu/marginalia/query_parser/QueryVariantsTest.java similarity index 83% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/QueryVariantsTest.java rename to features/query-parser/src/test/java/nu/marginalia/query_parser/QueryVariantsTest.java index b2477ca8..0abd0cc1 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/QueryVariantsTest.java +++ b/features/query-parser/src/test/java/nu/marginalia/query_parser/QueryVariantsTest.java @@ -1,10 +1,11 @@ -package nu.marginalia.wmsa.edge.search.query; +package nu.marginalia.query_parser; +import nu.marginalia.LanguageModels; +import nu.marginalia.language.statistics.EnglishDictionary; +import nu.marginalia.language.statistics.NGramBloomFilter; +import nu.marginalia.language.statistics.TermFrequencyDict; import nu.marginalia.util.TestLanguageModels; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.wmsa.edge.assistant.dict.NGramBloomFilter; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; +import nu.marginalia.language.sentence.SentenceExtractor; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -24,7 +25,7 @@ class QueryVariantsTest { var dict = new TermFrequencyDict(lm); var ngrams = new NGramBloomFilter(lm); variants = new QueryVariants(lm, dict, ngrams, new EnglishDictionary(dict)); - parser = new QueryParser(new EnglishDictionary(dict), variants); + parser = new QueryParser(); } @Test @@ -64,6 +65,7 @@ class QueryVariantsTest { testCase("Knitting"); testCase("capcom"); testCase("the man of tomorrow"); + } private void testCase(String input) { diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/TestLanguageModels.java b/features/query-parser/src/test/java/nu/marginalia/util/TestLanguageModels.java similarity index 92% rename from marginalia_nu/src/test/java/nu/marginalia/util/TestLanguageModels.java rename to features/query-parser/src/test/java/nu/marginalia/util/TestLanguageModels.java index cdd23c4f..81df1ed9 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/TestLanguageModels.java +++ b/features/query-parser/src/test/java/nu/marginalia/util/TestLanguageModels.java @@ -1,7 +1,7 @@ package nu.marginalia.util; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.wmsa.configuration.WmsaHome; +import nu.marginalia.LanguageModels; +import nu.marginalia.WmsaHome; import java.nio.file.Files; import java.nio.file.Path; diff --git a/features/random-websites/build.gradle b/features/random-websites/build.gradle new file mode 100644 index 00000000..b3fb2692 --- /dev/null +++ b/features/random-websites/build.gradle @@ -0,0 +1,55 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id "me.champeau.jmh" version "0.6.6" + id "de.undercouch.download" version "5.1.0" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:service') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.guice + implementation libs.roaringbitmap + implementation libs.trove + implementation libs.fastutil + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +configurations { + e2eTestImplementation.extendsFrom(testImplementation) + +} + +test { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + maxHeapSize = "8G" + useJUnitPlatform() +} + +task fastTests(type: Test) { + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + maxHeapSize = "8G" + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsFromUrlId.java b/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsFromUrlId.java new file mode 100644 index 00000000..27be5967 --- /dev/null +++ b/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsFromUrlId.java @@ -0,0 +1,71 @@ +package nu.marginalia.browse; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.zaxxer.hikari.HikariDataSource; +import nu.marginalia.browse.model.BrowseResult; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.id.EdgeIdCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.*; + +@Singleton +public class DbBrowseDomainsFromUrlId { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final HikariDataSource dataSource; + + @Inject + public DbBrowseDomainsFromUrlId(HikariDataSource dataSource) { + this.dataSource = dataSource; + } + + private String idList(EdgeIdCollection ids) { + StringJoiner j = new StringJoiner(",", "(", ")"); + for (var id : ids.values()) { + j.add(Integer.toString(id)); + } + return j.toString(); + } + + public List getBrowseResultFromUrlIds(EdgeIdCollection urlIds) { + if (urlIds.isEmpty()) + return Collections.emptyList(); + + List ret = new ArrayList<>(urlIds.size()); + + try (var conn = dataSource.getConnection()) { + try (var stmt = conn.createStatement()) { + + String inStmt = idList(urlIds); + + var rsp = stmt.executeQuery(""" + SELECT DOMAIN_ID, DOMAIN_NAME + FROM EC_URL_VIEW + INNER JOIN DOMAIN_METADATA ON EC_URL_VIEW.DOMAIN_ID=DOMAIN_METADATA.ID + WHERE + KNOWN_URLS<5000 + AND QUALITY>-10 + AND EC_URL_VIEW.ID IN + """ + inStmt); // this injection is safe, inStmt is derived from concatenating a list of integers + while (rsp.next()) { + int id = rsp.getInt(1); + String domain = rsp.getString(2); + + ret.add(new BrowseResult(new EdgeDomain(domain).toRootUrl(), id, 0)); + } + } + } + catch (SQLException ex) { + logger.error("SQL error", ex); + } + + return ret; + } + + +} diff --git a/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsRandom.java b/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsRandom.java new file mode 100644 index 00000000..2f0b4cc0 --- /dev/null +++ b/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsRandom.java @@ -0,0 +1,60 @@ +package nu.marginalia.browse; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.zaxxer.hikari.HikariDataSource; +import nu.marginalia.browse.model.BrowseResult; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklist; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.*; + +@Singleton +public class DbBrowseDomainsRandom { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final HikariDataSource dataSource; + + @Inject + public DbBrowseDomainsRandom(HikariDataSource dataSource) { + this.dataSource = dataSource; + } + + public List getRandomDomains(int count, EdgeDomainBlacklist blacklist, int set) { + + final String q = """ + SELECT DOMAIN_ID, DOMAIN_NAME + FROM EC_RANDOM_DOMAINS + INNER JOIN EC_DOMAIN ON EC_DOMAIN.ID=DOMAIN_ID + WHERE STATE<2 + AND DOMAIN_SET=? + AND DOMAIN_ALIAS IS NULL + ORDER BY RAND() + LIMIT ? + """; + List domains = new ArrayList<>(count); + try (var conn = dataSource.getConnection()) { + try (var stmt = conn.prepareStatement(q)) { + stmt.setInt(1, set);; + stmt.setInt(2, count); + var rsp = stmt.executeQuery(); + while (rsp.next()) { + int id = rsp.getInt(1); + String domain = rsp.getString(2); + + if (!blacklist.isBlacklisted(id)) { + domains.add(new BrowseResult(new EdgeDomain(domain).toRootUrl(), id, 0)); + } + } + } + } + catch (SQLException ex) { + logger.error("SQL error", ex); + } + return domains; + } + +} diff --git a/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsSimilarCosine.java b/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsSimilarCosine.java new file mode 100644 index 00000000..6f3d9bd8 --- /dev/null +++ b/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsSimilarCosine.java @@ -0,0 +1,66 @@ +package nu.marginalia.browse; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import com.zaxxer.hikari.HikariDataSource; +import nu.marginalia.browse.model.BrowseResult; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklist; +import nu.marginalia.model.id.EdgeId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.*; + +@Singleton +public class DbBrowseDomainsSimilarCosine { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final HikariDataSource dataSource; + + @Inject + public DbBrowseDomainsSimilarCosine(HikariDataSource dataSource) { + this.dataSource = dataSource; + } + + public List getDomainNeighborsAdjacentCosine(EdgeId domainId, EdgeDomainBlacklist blacklist, int count) { + List domains = new ArrayList<>(count); + + String q = """ + SELECT + EC_DOMAIN.ID, + NV.NEIGHBOR_NAME, + NV.RELATEDNESS + FROM EC_NEIGHBORS_VIEW NV + INNER JOIN DATA_DOMAIN_SCREENSHOT ON DATA_DOMAIN_SCREENSHOT.DOMAIN_NAME=NV.NEIGHBOR_NAME + INNER JOIN EC_DOMAIN ON EC_DOMAIN.ID=NV.NEIGHBOR_ID + WHERE NV.DOMAIN_ID=? + GROUP BY NV.NEIGHBOR_ID + ORDER BY NV.RELATEDNESS DESC + """; + + try (var connection = dataSource.getConnection()) { + try (var stmt = connection.prepareStatement(q)) { + stmt.setFetchSize(count); + stmt.setInt(1, domainId.id()); + stmt.setInt(2, count); + var rsp = stmt.executeQuery(); + while (rsp.next() && domains.size() < count) { + int id = rsp.getInt(1); + String domain = rsp.getString(2); + double relatedness = rsp.getDouble(3); + + if (!blacklist.isBlacklisted(id)) { + domains.add(new BrowseResult(new EdgeDomain(domain).toRootUrl(), id, relatedness)); + } + } + } + } catch (SQLException throwables) { + throwables.printStackTrace(); + } + + return domains; + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDataStoreDaoImpl.java b/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsSimilarOldAlgo.java similarity index 53% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDataStoreDaoImpl.java rename to features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsSimilarOldAlgo.java index 1fdf93ef..01f43060 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDataStoreDaoImpl.java +++ b/features/random-websites/src/main/java/nu/marginalia/browse/DbBrowseDomainsSimilarOldAlgo.java @@ -1,179 +1,31 @@ -package nu.marginalia.wmsa.edge.dbcommon; +package nu.marginalia.browse; -import com.google.common.base.Strings; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.inject.Inject; +import com.google.inject.Singleton; import com.zaxxer.hikari.HikariDataSource; -import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; -import nu.marginalia.wmsa.edge.model.id.EdgeId; -import nu.marginalia.wmsa.edge.model.id.EdgeIdCollection; -import nu.marginalia.wmsa.edge.model.search.EdgePageScoreAdjustment; -import nu.marginalia.wmsa.edge.model.search.EdgeUrlDetails; -import nu.marginalia.wmsa.edge.search.model.BrowseResult; +import nu.marginalia.browse.model.BrowseResult; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklist; +import nu.marginalia.model.id.EdgeId; +import nu.marginalia.model.id.EdgeIdCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.SQLException; import java.util.*; +@Singleton +public class DbBrowseDomainsSimilarOldAlgo { -public class EdgeDataStoreDaoImpl implements EdgeDataStoreDao { - private final HikariDataSource dataSource; private final Logger logger = LoggerFactory.getLogger(getClass()); + private final HikariDataSource dataSource; - private final Cache> urlIdCache = CacheBuilder.newBuilder().maximumSize(100_000).build(); - private final Cache> domainIdCache = CacheBuilder.newBuilder().maximumSize(10_000).build(); - - public static double QUALITY_LOWER_BOUND_CUTOFF = -15.; @Inject - public EdgeDataStoreDaoImpl(HikariDataSource dataSource) - { + public DbBrowseDomainsSimilarOldAlgo(HikariDataSource dataSource) { this.dataSource = dataSource; } - - public synchronized void clearCaches() - { - urlIdCache.invalidateAll(); - domainIdCache.invalidateAll(); - } - - @SneakyThrows - @Override - public EdgeId getDomainId(EdgeDomain domain) { - try (var connection = dataSource.getConnection()) { - - return domainIdCache.get(domain, () -> { - try (var stmt = connection.prepareStatement("SELECT ID FROM EC_DOMAIN WHERE DOMAIN_NAME=?")) { - stmt.setString(1, domain.toString()); - var rsp = stmt.executeQuery(); - if (rsp.next()) { - return new EdgeId<>(rsp.getInt(1)); - } - } - throw new NoSuchElementException(); - }); - } - catch (UncheckedExecutionException ex) { - throw ex.getCause(); - } - } - - private String idList(EdgeIdCollection ids) { - StringJoiner j = new StringJoiner(",", "(", ")"); - for (var id : ids.values()) { - j.add(Integer.toString(id)); - } - return j.toString(); - } - - @SneakyThrows - @Override - public List getUrlDetailsMulti(EdgeIdCollection ids) { - if (ids.isEmpty()) { - return Collections.emptyList(); - } - List result = new ArrayList<>(ids.size()); - - try (var connection = dataSource.getConnection()) { - - String idString = idList(ids); - - try (var stmt = connection.prepareStatement( - """ - SELECT ID, URL, - TITLE, DESCRIPTION, - QUALITY, - WORDS_TOTAL, FORMAT, FEATURES, - IP, DOMAIN_STATE, - DATA_HASH - FROM EC_URL_VIEW - WHERE TITLE IS NOT NULL - AND ID IN - """ + idString)) { - stmt.setFetchSize(ids.size()); - - var rsp = stmt.executeQuery(); - while (rsp.next()) { - EdgeUrl url = new EdgeUrl(rsp.getString(2)); - var val = new EdgeUrlDetails(rsp.getInt(1), url, - rsp.getString(3), // title - rsp.getString(4), // description - rsp.getDouble(5), // quality - rsp.getInt(6), // wordsTotal - rsp.getString(7), // format - rsp.getInt(8), // features - rsp.getString(9), // ip - EdgeDomainIndexingState.valueOf(rsp.getString(10)), // domainState - rsp.getInt(11), // dataHash - EdgePageScoreAdjustment.zero(), // urlQualityAdjustment - Integer.MAX_VALUE, // rankingId - Double.MAX_VALUE, // termScore - 1, // resultsFromSameDomain - "", // positions - null // result item - ); - if (val.urlQuality <= QUALITY_LOWER_BOUND_CUTOFF - && Strings.isNullOrEmpty(val.description) - && val.url.path.length() > 1) { - continue; - } - result.add(val); - - } - } - } - - return result; - } - - - - public List getDomainNeighborsAdjacentCosine(EdgeId domainId, EdgeDomainBlacklist blacklist, int count) { - List domains = new ArrayList<>(count); - - String q = """ - SELECT - EC_DOMAIN.ID, - NV.NEIGHBOR_NAME, - NV.RELATEDNESS - FROM EC_NEIGHBORS_VIEW NV - INNER JOIN DATA_DOMAIN_SCREENSHOT ON DATA_DOMAIN_SCREENSHOT.DOMAIN_NAME=NV.NEIGHBOR_NAME - INNER JOIN EC_DOMAIN ON EC_DOMAIN.ID=NV.NEIGHBOR_ID - WHERE NV.DOMAIN_ID=? - GROUP BY NV.NEIGHBOR_ID - ORDER BY NV.RELATEDNESS DESC - """; - - try (var connection = dataSource.getConnection()) { - try (var stmt = connection.prepareStatement(q)) { - stmt.setFetchSize(count); - stmt.setInt(1, domainId.id()); - stmt.setInt(2, count); - var rsp = stmt.executeQuery(); - while (rsp.next() && domains.size() < count) { - int id = rsp.getInt(1); - String domain = rsp.getString(2); - double relatedness = rsp.getDouble(3); - - if (!blacklist.isBlacklisted(id)) { - domains.add(new BrowseResult(new EdgeDomain(domain).toRootUrl(), id, relatedness)); - } - } - } - } catch (SQLException throwables) { - throwables.printStackTrace(); - } - - return domains; - } - - @Override public List getDomainNeighborsAdjacent(EdgeId domainId, EdgeDomainBlacklist blacklist, int count) { final Set domains = new HashSet<>(count*3); @@ -279,7 +131,6 @@ public class EdgeDataStoreDaoImpl implements EdgeDataStoreDao { return new ArrayList<>(domains); } - @Override public List getRandomDomains(int count, EdgeDomainBlacklist blacklist, int set) { final String q = """ @@ -305,7 +156,7 @@ public class EdgeDataStoreDaoImpl implements EdgeDataStoreDao { if (!blacklist.isBlacklisted(id)) { domains.add(new BrowseResult(new EdgeDomain(domain).toRootUrl(), id, 0)); } - } + } } } catch (SQLException ex) { @@ -314,7 +165,15 @@ public class EdgeDataStoreDaoImpl implements EdgeDataStoreDao { return domains; } - @Override + + private String idList(EdgeIdCollection ids) { + StringJoiner j = new StringJoiner(",", "(", ")"); + for (var id : ids.values()) { + j.add(Integer.toString(id)); + } + return j.toString(); + } + public List getBrowseResultFromUrlIds(EdgeIdCollection urlIds) { if (urlIds.isEmpty()) return Collections.emptyList(); @@ -350,19 +209,5 @@ public class EdgeDataStoreDaoImpl implements EdgeDataStoreDao { return ret; } - @Override - @SneakyThrows - public Optional getDomain(EdgeId id) { - try (var connection = dataSource.getConnection()) { - try (var stmt = connection.prepareStatement("SELECT DOMAIN_NAME FROM EC_DOMAIN WHERE ID=?")) { - stmt.setInt(1, id.id()); - var rsp = stmt.executeQuery(); - if (rsp.next()) { - return Optional.of(new EdgeDomain(rsp.getString(1))); - } - return Optional.empty(); - } - } - } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/AndCardIntSet.java b/features/random-websites/src/main/java/nu/marginalia/browse/experimental/AndCardIntSet.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/util/AndCardIntSet.java rename to features/random-websites/src/main/java/nu/marginalia/browse/experimental/AndCardIntSet.java index 08caa671..645618aa 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/AndCardIntSet.java +++ b/features/random-websites/src/main/java/nu/marginalia/browse/experimental/AndCardIntSet.java @@ -1,4 +1,4 @@ -package nu.marginalia.util; +package nu.marginalia.browse.experimental; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; @@ -82,10 +82,6 @@ public class AndCardIntSet { if (!testHash(a,b)) { return 0; } -// -// if (a.getCardinality() + b.getCardinality() < 10) { -// return andLinearSmall(a, b); -// } return andLinear(a,b); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/tool/EdgeDomainLinkConsineSimilarityMain.java b/features/random-websites/src/main/java/nu/marginalia/browse/experimental/EdgeDomainLinkConsineSimilarityMain.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/util/tool/EdgeDomainLinkConsineSimilarityMain.java rename to features/random-websites/src/main/java/nu/marginalia/browse/experimental/EdgeDomainLinkConsineSimilarityMain.java index 56c9c65b..c4d4e0b2 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/tool/EdgeDomainLinkConsineSimilarityMain.java +++ b/features/random-websites/src/main/java/nu/marginalia/browse/experimental/EdgeDomainLinkConsineSimilarityMain.java @@ -1,15 +1,14 @@ -package nu.marginalia.util.tool; +package nu.marginalia.browse.experimental; import com.zaxxer.hikari.HikariDataSource; import gnu.trove.map.hash.TIntIntHashMap; import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.set.hash.TIntHashSet; import lombok.SneakyThrows; -import nu.marginalia.util.AndCardIntSet; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDaoImpl; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.id.EdgeId; +import nu.marginalia.model.dbcommon.DbDomainQueries; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.id.EdgeId; +import nu.marginalia.service.module.DatabaseModule; import org.roaringbitmap.RoaringBitmap; import java.sql.ResultSet; @@ -19,7 +18,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static nu.marginalia.util.AndCardIntSet.*; +import static nu.marginalia.browse.experimental.AndCardIntSet.*; public class EdgeDomainLinkConsineSimilarityMain { ArrayList idsList = new ArrayList<>(100_000); @@ -98,7 +97,7 @@ public class EdgeDomainLinkConsineSimilarityMain { @SneakyThrows public void tryDomains(String... domainName) { - var dataStoreDao = new EdgeDataStoreDaoImpl(dataSource); + var dataStoreDao = new DbDomainQueries(dataSource); System.out.println(Arrays.toString(domainName)); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/tool/EdgeWordWordConsineSimilarityMain.java b/features/random-websites/src/main/java/nu/marginalia/browse/experimental/EdgeWordWordConsineSimilarityMain.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/util/tool/EdgeWordWordConsineSimilarityMain.java rename to features/random-websites/src/main/java/nu/marginalia/browse/experimental/EdgeWordWordConsineSimilarityMain.java index 8e71b26d..d05be9b5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/tool/EdgeWordWordConsineSimilarityMain.java +++ b/features/random-websites/src/main/java/nu/marginalia/browse/experimental/EdgeWordWordConsineSimilarityMain.java @@ -1,10 +1,9 @@ -package nu.marginalia.util.tool; +package nu.marginalia.browse.experimental; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.SneakyThrows; -import nu.marginalia.util.AndCardIntSet; import org.roaringbitmap.RoaringBitmap; import java.io.IOException; @@ -16,8 +15,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.IntStream; -import static nu.marginalia.util.AndCardIntSet.andCardinality; -import static nu.marginalia.util.AndCardIntSet.weightedProduct; +import static nu.marginalia.browse.experimental.AndCardIntSet.andCardinality; +import static nu.marginalia.browse.experimental.AndCardIntSet.weightedProduct; public class EdgeWordWordConsineSimilarityMain { final Object2IntOpenHashMap stringIds; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/BrowseResult.java b/features/random-websites/src/main/java/nu/marginalia/browse/model/BrowseResult.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/BrowseResult.java rename to features/random-websites/src/main/java/nu/marginalia/browse/model/BrowseResult.java index 3a65ac47..41ab01d2 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/BrowseResult.java +++ b/features/random-websites/src/main/java/nu/marginalia/browse/model/BrowseResult.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.search.model; +package nu.marginalia.browse.model; -import nu.marginalia.wmsa.edge.model.EdgeUrl; +import nu.marginalia.model.EdgeUrl; public record BrowseResult (EdgeUrl url, int domainId, double relatedness) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/BrowseResultSet.java b/features/random-websites/src/main/java/nu/marginalia/browse/model/BrowseResultSet.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/BrowseResultSet.java rename to features/random-websites/src/main/java/nu/marginalia/browse/model/BrowseResultSet.java index da4af52c..75758593 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/BrowseResultSet.java +++ b/features/random-websites/src/main/java/nu/marginalia/browse/model/BrowseResultSet.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.search.model; +package nu.marginalia.browse.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/AndCardIntSetTest.java b/features/random-websites/src/test/java/nu/marginalia/experimental/AndCardIntSetTest.java similarity index 87% rename from marginalia_nu/src/test/java/nu/marginalia/util/AndCardIntSetTest.java rename to features/random-websites/src/test/java/nu/marginalia/experimental/AndCardIntSetTest.java index 8f0e7d11..65f83952 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/AndCardIntSetTest.java +++ b/features/random-websites/src/test/java/nu/marginalia/experimental/AndCardIntSetTest.java @@ -1,5 +1,7 @@ -package nu.marginalia.util; +package nu.marginalia.experimental; +import nu.marginalia.browse.experimental.AndCardIntSet; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/features/renderer/build.gradle b/features/renderer/build.gradle new file mode 100644 index 00000000..1f1790da --- /dev/null +++ b/features/renderer/build.gradle @@ -0,0 +1,35 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} +dependencies { + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.bundles.handlebars + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} + diff --git a/features/renderer/src/main/java/nu/marginalia/renderer/MustacheRenderer.java b/features/renderer/src/main/java/nu/marginalia/renderer/MustacheRenderer.java new file mode 100644 index 00000000..4060a15b --- /dev/null +++ b/features/renderer/src/main/java/nu/marginalia/renderer/MustacheRenderer.java @@ -0,0 +1,60 @@ +package nu.marginalia.renderer; + +import com.github.jknack.handlebars.*; +import com.github.jknack.handlebars.helper.ConditionalHelpers; +import com.github.jknack.handlebars.io.ClassPathTemplateLoader; +import com.github.jknack.handlebars.io.TemplateLoader; +import lombok.SneakyThrows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class MustacheRenderer { + private final Template template; + private final Logger logger = LoggerFactory.getLogger(getClass()); + + MustacheRenderer(String templateFile) throws IOException { + + TemplateLoader loader = new ClassPathTemplateLoader(); + loader.setPrefix("/templates"); + loader.setSuffix(".hdb"); + + var handlebars = new Handlebars(loader); + handlebars.registerHelpers(ConditionalHelpers.class); + handlebars.registerHelper("md", new MarkdownHelper()); + + try { + template = handlebars.compile(templateFile); + logger.info("Loaded template " + templateFile); + } + catch (FileNotFoundException ex) { + throw new RenderingException("Could not find template " + templateFile); + } + catch (HandlebarsException ex) { + throw new RenderingException("Failed to load template " + templateFile, ex); + } + } + + @SneakyThrows + public String render(T model) { + return template.apply(model); + } + + @SneakyThrows + public String render(T model, String name, List children) { + Context ctx = Context.newBuilder(model).combine(name, children).build(); + + return template.apply(ctx); + } + + @SneakyThrows + public String render(T model, Map children) { + Context ctx = Context.newBuilder(model).combine(children).build(); + return template.apply(ctx); + } + +} diff --git a/features/renderer/src/main/java/nu/marginalia/renderer/RendererFactory.java b/features/renderer/src/main/java/nu/marginalia/renderer/RendererFactory.java new file mode 100644 index 00000000..8d191881 --- /dev/null +++ b/features/renderer/src/main/java/nu/marginalia/renderer/RendererFactory.java @@ -0,0 +1,13 @@ +package nu.marginalia.renderer; + +import java.io.IOException; + +public class RendererFactory { + + public RendererFactory() { + } + + public MustacheRenderer renderer(String template) throws IOException { + return new MustacheRenderer<>(template); + } +} diff --git a/features/renderer/src/main/java/nu/marginalia/renderer/RenderingException.java b/features/renderer/src/main/java/nu/marginalia/renderer/RenderingException.java new file mode 100644 index 00000000..30c01ec7 --- /dev/null +++ b/features/renderer/src/main/java/nu/marginalia/renderer/RenderingException.java @@ -0,0 +1,12 @@ +package nu.marginalia.renderer; + +import java.io.IOException; + +public class RenderingException extends IOException { + public RenderingException(String message) { + super(message); + } + public RenderingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/features/screenshots/build.gradle b/features/screenshots/build.gradle new file mode 100644 index 00000000..880c4ccd --- /dev/null +++ b/features/screenshots/build.gradle @@ -0,0 +1,44 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:service') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.notnull + implementation libs.guice + implementation libs.spark + implementation libs.bundles.mariadb + implementation libs.commons.io + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + +} + + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/screenshot/ScreenshotService.java b/features/screenshots/src/main/java/nu/marginalia/screenshot/ScreenshotService.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/screenshot/ScreenshotService.java rename to features/screenshots/src/main/java/nu/marginalia/screenshot/ScreenshotService.java index 46324c1c..50769aa6 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/screenshot/ScreenshotService.java +++ b/features/screenshots/src/main/java/nu/marginalia/screenshot/ScreenshotService.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.edge.assistant.screenshot; +package nu.marginalia.screenshot; import com.google.common.base.Strings; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.id.EdgeId; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.dbcommon.DbDomainQueries; +import nu.marginalia.model.id.EdgeId; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,14 +20,14 @@ import static java.lang.Integer.parseInt; public class ScreenshotService { - private final EdgeDataStoreDao edgeDataStoreDao; + private final DbDomainQueries domainQueries; private final HikariDataSource dataSource; private final Logger logger = LoggerFactory.getLogger(getClass()); @Inject - public ScreenshotService(EdgeDataStoreDao edgeDataStoreDao, HikariDataSource dataSource) { - this.edgeDataStoreDao = edgeDataStoreDao; + public ScreenshotService(DbDomainQueries dbDomainQueries, HikariDataSource dataSource) { + this.domainQueries = dbDomainQueries; this.dataSource = dataSource; } @@ -87,7 +87,7 @@ public class ScreenshotService { private Object serveSvgPlaceholder(Response response, int id) { - var domainName = edgeDataStoreDao.getDomain(new EdgeId<>(id)).map(Object::toString); + var domainName = domainQueries.getDomain(new EdgeId<>(id)).map(Object::toString); if (domainName.isEmpty()) { Spark.halt(404); } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..24759993 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.parallel=true +org.gradle.caching=false \ No newline at end of file diff --git a/index/index-forward/build.gradle b/index/index-forward/build.gradle new file mode 100644 index 00000000..371df73a --- /dev/null +++ b/index/index-forward/build.gradle @@ -0,0 +1,46 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':libraries:array') + implementation project(':libraries:btree') + implementation project(':libraries:misc') + implementation project(':features:domain-ranking') + implementation project(':index:index-query') + implementation project(':index:index-journal') + implementation project(':index:lexicon') + implementation project(':common:model') + implementation project(':third-party') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.roaringbitmap + implementation libs.fastutil + implementation libs.trove + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexConverter.java b/index/index-forward/src/main/java/nu/marginalia/index/forward/ForwardIndexConverter.java similarity index 70% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexConverter.java rename to index/index-forward/src/main/java/nu/marginalia/index/forward/ForwardIndexConverter.java index 8d821c88..c3f2b0b4 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexConverter.java +++ b/index/index-forward/src/main/java/nu/marginalia/index/forward/ForwardIndexConverter.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.edge.index.postings.forward; +package nu.marginalia.index.forward; import com.upserve.uppend.blobs.NativeIO; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.index.postings.DomainRankings; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReader; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReaderSingleFile; +import nu.marginalia.index.journal.reader.IndexJournalReader; +import nu.marginalia.array.LongArray; +import nu.marginalia.index.journal.reader.IndexJournalReaderSingleCompressedFile; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.ranking.DomainRankings; import org.roaringbitmap.IntConsumer; import org.roaringbitmap.RoaringBitmap; import org.slf4j.Logger; @@ -17,8 +17,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import static nu.marginalia.wmsa.edge.index.postings.forward.ForwardIndexParameters.*; - public class ForwardIndexConverter { private final File inputFile; @@ -45,12 +43,13 @@ public class ForwardIndexConverter { public void convert() throws IOException { deleteOldFiles(); - SearchIndexJournalReaderSingleFile journalReader = new SearchIndexJournalReaderSingleFile(LongArray.mmapRead(inputFile.toPath())); - if (journalReader.fileHeader.fileSize() <= SearchIndexJournalReader.FILE_HEADER_SIZE_BYTES) { + IndexJournalReaderSingleCompressedFile journalReader = new IndexJournalReaderSingleCompressedFile(inputFile.toPath()); + if (journalReader.fileHeader().fileSize() <= IndexJournalReader.FILE_HEADER_SIZE_BYTES) { + logger.warn("Bailing: Journal is empty!"); return; } - logger.info("Converting {} {}",inputFile, journalReader.fileHeader); + logger.info("Converting {} {}", inputFile, journalReader.fileHeader); logger.info("Domain Rankings size = {}", domainRankings.size()); @@ -67,16 +66,16 @@ public class ForwardIndexConverter { logger.info("Creating Supplementary Indexes"); - LongArray docFileData = LongArray.mmapForWriting(outputFileDocsData, ENTRY_SIZE * docsFileId.size()); + LongArray docFileData = LongArray.mmapForWriting(outputFileDocsData, ForwardIndexParameters.ENTRY_SIZE * docsFileId.size()); journalReader.forEach(entry -> { - long entryOffset = (long) ENTRY_SIZE * docIdToIdx.get(entry.urlId()); + long entryOffset = (long) ForwardIndexParameters.ENTRY_SIZE * docIdToIdx.get(entry.urlId()); int ranking = domainRankings.getRanking(entry.domainId()); long meta = EdgePageDocumentsMetadata.encodeRank(entry.docMeta(), ranking); - docFileData.set(entryOffset + METADATA_OFFSET, meta); - docFileData.set(entryOffset + DOMAIN_OFFSET, entry.domainId()); + docFileData.set(entryOffset + ForwardIndexParameters.METADATA_OFFSET, meta); + docFileData.set(entryOffset + ForwardIndexParameters.DOMAIN_OFFSET, entry.domainId()); }); docFileData.force(); @@ -90,7 +89,7 @@ public class ForwardIndexConverter { } } - private LongArray getDocIds(Path outputFileDocs, SearchIndexJournalReader journalReader) throws IOException { + private LongArray getDocIds(Path outputFileDocs, IndexJournalReader journalReader) throws IOException { RoaringBitmap rbm = new RoaringBitmap(); journalReader.forEachUrlId(rbm::add); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexParameters.java b/index/index-forward/src/main/java/nu/marginalia/index/forward/ForwardIndexParameters.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexParameters.java rename to index/index-forward/src/main/java/nu/marginalia/index/forward/ForwardIndexParameters.java index f019a40b..ca09c440 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexParameters.java +++ b/index/index-forward/src/main/java/nu/marginalia/index/forward/ForwardIndexParameters.java @@ -1,8 +1,7 @@ -package nu.marginalia.wmsa.edge.index.postings.forward; +package nu.marginalia.index.forward; class ForwardIndexParameters { public static final int ENTRY_SIZE = 2; - public static final int DOMAIN_OFFSET = 0; public static final int METADATA_OFFSET = 1; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexReader.java b/index/index-forward/src/main/java/nu/marginalia/index/forward/ForwardIndexReader.java similarity index 70% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexReader.java rename to index/index-forward/src/main/java/nu/marginalia/index/forward/ForwardIndexReader.java index c4080574..e7f7f045 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexReader.java +++ b/index/index-forward/src/main/java/nu/marginalia/index/forward/ForwardIndexReader.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.index.postings.forward; +package nu.marginalia.index.forward; import com.upserve.uppend.blobs.NativeIO; import gnu.trove.map.hash.TLongIntHashMap; -import nu.marginalia.util.array.LongArray; +import nu.marginalia.array.LongArray; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,8 +10,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import static nu.marginalia.wmsa.edge.index.postings.forward.ForwardIndexParameters.*; - public class ForwardIndexReader { private final TLongIntHashMap ids; private final LongArray data; @@ -19,9 +17,14 @@ public class ForwardIndexReader { private final Logger logger = LoggerFactory.getLogger(getClass()); public ForwardIndexReader(Path idsFile, Path dataFile) throws IOException { - if (!Files.exists(dataFile) || - !Files.exists(idsFile) - ) { + if (!Files.exists(dataFile)) { + logger.warn("Failed to create ForwardIndexReader, {} is absent", dataFile); + ids = null; + data = null; + return; + } + else if (!Files.exists(idsFile)) { + logger.warn("Failed to create ForwardIndexReader, {} is absent", idsFile); ids = null; data = null; return; @@ -63,14 +66,14 @@ public class ForwardIndexReader { long offset = idxForDoc(docId); if (offset < 0) return 0; - return data.get(ENTRY_SIZE * offset + METADATA_OFFSET); + return data.get(ForwardIndexParameters.ENTRY_SIZE * offset + ForwardIndexParameters.METADATA_OFFSET); } public int getDomainId(long docId) { long offset = idxForDoc(docId); if (offset < 0) return 0; - return Math.max(0, (int) data.get(ENTRY_SIZE * offset + DOMAIN_OFFSET)); + return Math.max(0, (int) data.get(ForwardIndexParameters.ENTRY_SIZE * offset + ForwardIndexParameters.DOMAIN_OFFSET)); } public DocPost docPost(long docId) { @@ -90,14 +93,14 @@ public class ForwardIndexReader { if (idx < 0) return 0; - return data.get(ENTRY_SIZE * idx + METADATA_OFFSET); + return data.get(ForwardIndexParameters.ENTRY_SIZE * idx + ForwardIndexParameters.METADATA_OFFSET); } public int domainId() { if (idx < 0) return 0; - return Math.max(0, (int) data.get(ENTRY_SIZE * idx + DOMAIN_OFFSET)); + return Math.max(0, (int) data.get(ForwardIndexParameters.ENTRY_SIZE * idx + ForwardIndexParameters.DOMAIN_OFFSET)); } } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ParamMatchingQueryFilter.java b/index/index-forward/src/main/java/nu/marginalia/index/forward/ParamMatchingQueryFilter.java similarity index 87% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ParamMatchingQueryFilter.java rename to index/index-forward/src/main/java/nu/marginalia/index/forward/ParamMatchingQueryFilter.java index 81f671c5..e1cf7eef 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/forward/ParamMatchingQueryFilter.java +++ b/index/index-forward/src/main/java/nu/marginalia/index/forward/ParamMatchingQueryFilter.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.index.postings.forward; +package nu.marginalia.index.forward; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.index.query.IndexQueryParams; -import nu.marginalia.wmsa.edge.index.query.filter.QueryFilterStepIf; -import nu.marginalia.wmsa.edge.model.search.domain.SpecificationLimitType; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.index.query.limit.SpecificationLimitType; +import nu.marginalia.index.query.IndexQueryParams; +import nu.marginalia.index.query.filter.QueryFilterStepIf; public class ParamMatchingQueryFilter implements QueryFilterStepIf { private final IndexQueryParams params; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexConverterTest.java b/index/index-forward/src/test/java/nu/marginalia/index/forward/ForwardIndexConverterTest.java similarity index 60% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexConverterTest.java rename to index/index-forward/src/test/java/nu/marginalia/index/forward/ForwardIndexConverterTest.java index e5652faa..8e8bc252 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings/forward/ForwardIndexConverterTest.java +++ b/index/index-forward/src/test/java/nu/marginalia/index/forward/ForwardIndexConverterTest.java @@ -1,16 +1,14 @@ -package nu.marginalia.wmsa.edge.index.postings.forward; +package nu.marginalia.index.forward; import lombok.SneakyThrows; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.dict.OffHeapDictionaryHashMap; -import nu.marginalia.util.test.TestUtil; -import nu.marginalia.wmsa.edge.index.lexicon.KeywordLexicon; -import nu.marginalia.wmsa.edge.index.lexicon.journal.KeywordLexiconJournal; -import nu.marginalia.wmsa.edge.index.postings.DomainRankings; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntryHeader; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReaderSingleFile; -import nu.marginalia.wmsa.edge.index.postings.journal.writer.SearchIndexJournalWriterImpl; +import nu.marginalia.dict.OffHeapDictionaryHashMap; +import nu.marginalia.index.journal.model.IndexJournalEntry; +import nu.marginalia.index.journal.writer.IndexJournalWriterImpl; +import nu.marginalia.index.journal.writer.IndexJournalWriter; +import nu.marginalia.ranking.DomainRankings; +import nu.marginalia.lexicon.KeywordLexicon; +import nu.marginalia.lexicon.journal.KeywordLexiconJournal; +import nu.marginalia.test.TestUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -27,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; class ForwardIndexConverterTest { KeywordLexicon keywordLexicon; - SearchIndexJournalWriterImpl writer; + IndexJournalWriter writer; Path indexFile; Path wordsFile1; @@ -47,12 +45,12 @@ class ForwardIndexConverterTest { dictionaryFile = Files.createTempFile("tmp", ".dict"); dictionaryFile.toFile().deleteOnExit(); - keywordLexicon = new KeywordLexicon(new KeywordLexiconJournal(dictionaryFile.toFile()), new OffHeapDictionaryHashMap(1L<<18)); + keywordLexicon = new KeywordLexicon(new KeywordLexiconJournal(dictionaryFile.toFile())); keywordLexicon.getOrInsert("0"); indexFile = Files.createTempFile("tmp", ".idx"); indexFile.toFile().deleteOnExit(); - writer = new SearchIndexJournalWriterImpl(keywordLexicon, indexFile.toFile()); + writer = new IndexJournalWriterImpl(keywordLexicon, indexFile); wordsFile1 = Files.createTempFile("words1", ".idx"); urlsFile1 = Files.createTempFile("urls1", ".idx"); @@ -65,12 +63,10 @@ class ForwardIndexConverterTest { keywordLexicon.commitToDisk(); - Thread.sleep(1000); writer.forceWrite(); + writer.close(); - var reader = new SearchIndexJournalReaderSingleFile(LongArray.mmapRead(indexFile)); - docsFileId = dataDir.resolve("docs-i.dat"); docsFileData = dataDir.resolve("docs-d.dat"); } @@ -87,17 +83,16 @@ class ForwardIndexConverterTest { long createId(long url, long domain) { return (domain << 32) | url; } - public void createEntry(SearchIndexJournalWriterImpl writer, KeywordLexicon keywordLexicon, int id) { + public void createEntry(IndexJournalWriter writer, KeywordLexicon keywordLexicon, int id) { int[] factors = getFactorsI(id); - var header = new SearchIndexJournalEntryHeader(factors.length, createId(id, id/20), id % 5); - long[] data = new long[factors.length*2]; - for (int i = 0; i < factors.length; i++) { - data[2*i] = keywordLexicon.getOrInsert(Integer.toString(factors[i])); - data[2*i + 1] = -factors[i]; + var entryBuilder = IndexJournalEntry.builder(createId(id, id/20), id%5); + + for (int i = 0; i+1 < factors.length; i+=2) { + entryBuilder.add(keywordLexicon.getOrInsert(Integer.toString(factors[i])), -factors[i+1]); } - writer.put(header, new SearchIndexJournalEntry(data)); + writer.put(entryBuilder.build()); } @Test diff --git a/index/index-forward/src/test/java/nu/marginalia/test/TestUtil.java b/index/index-forward/src/test/java/nu/marginalia/test/TestUtil.java new file mode 100644 index 00000000..44a489bb --- /dev/null +++ b/index/index-forward/src/test/java/nu/marginalia/test/TestUtil.java @@ -0,0 +1,50 @@ +package nu.marginalia.test; + + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +public class TestUtil { + private static boolean isTempDir(Path dir) { + return dir.startsWith("/tmp") || dir.toString().contains("tmp"); + } + + public static void clearTempDir(Path dir) { + if (!isTempDir(dir)) { + throw new IllegalArgumentException("Refusing to recursively delete directory with that name"); + } + if (Files.isDirectory(dir)) { + for (File f : dir.toFile().listFiles()) { + File[] files = f.listFiles(); + if (files != null) { + Arrays.stream(files).map(File::toPath).forEach(TestUtil::clearTempDir); + } + System.out.println("Deleting " + f + " (" + fileSize(f.toPath()) + ")"); + f.delete(); + } + } + System.out.println("Deleting " + dir); + dir.toFile().delete(); + } + + private static String fileSize(Path path) { + try { + long sizeBytes = Files.size(path); + + if (sizeBytes > 1024 * 1024 * 1024) return round(sizeBytes / 1073741824.) + "Gb"; + if (sizeBytes > 1024 * 1024) return round(sizeBytes / 1048576.) + "Mb"; + if (sizeBytes > 1024) return round(sizeBytes / 1024.) + "Kb"; + return sizeBytes + "b"; + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static String round(double d) { + return String.format("%.2f", d); + } +} diff --git a/index/index-journal/build.gradle b/index/index-journal/build.gradle new file mode 100644 index 00000000..6ec38550 --- /dev/null +++ b/index/index-journal/build.gradle @@ -0,0 +1,44 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':libraries:array') + implementation project(':common:model') + implementation project(':third-party') + implementation project(':index:lexicon') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.rxjava + implementation libs.trove + implementation libs.zstd + implementation libs.commons.lang3 + implementation libs.roaringbitmap + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntry.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntry.java new file mode 100644 index 00000000..dd0b8e1b --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntry.java @@ -0,0 +1,28 @@ +package nu.marginalia.index.journal.model; + +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.id.EdgeId; + +public record IndexJournalEntry(IndexJournalEntryHeader header, IndexJournalEntryData data) { + + public static IndexJournalEntryBuilder builder(long documentId, long documentMeta) { + return new IndexJournalEntryBuilder(documentId, documentMeta); + } + + public static IndexJournalEntryBuilder builder(int domainId, + int urlId, + long documentMeta) { + + + return builder(new EdgeId<>(domainId), new EdgeId<>(urlId), documentMeta); + } + + public static IndexJournalEntryBuilder builder(EdgeId domainId, + EdgeId urlId, + long documentMeta) { + + + return new IndexJournalEntryBuilder(IndexJournalEntryHeader.combineIds(domainId, urlId), documentMeta); + } +} diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryBuilder.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryBuilder.java new file mode 100644 index 00000000..f5f1fc22 --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryBuilder.java @@ -0,0 +1,34 @@ +package nu.marginalia.index.journal.model; + +import gnu.trove.list.array.TLongArrayList; + +public class IndexJournalEntryBuilder { + private final long documentId; + private final long documentMeta; + private final TLongArrayList items = new TLongArrayList(); + + public IndexJournalEntryBuilder(long documentId, long documentMeta) { + this.documentId = documentId; + this.documentMeta = documentMeta; + } + + public IndexJournalEntryBuilder capacity(int size) { + items.ensureCapacity(size); + return this; + } + + public IndexJournalEntryBuilder add(long wordId, long metadata) { + + items.add(wordId); + items.add(metadata); + + return this; + } + + public IndexJournalEntry build() { + return new IndexJournalEntry( + new IndexJournalEntryHeader(items.size(), documentId, documentMeta), + new IndexJournalEntryData(items.toArray()) + ); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalEntry.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryData.java similarity index 77% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalEntry.java rename to index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryData.java index 6fe28af3..423626ce 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalEntry.java +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryData.java @@ -1,32 +1,33 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.model; +package nu.marginalia.index.journal.model; +import java.io.DataOutputStream; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Iterator; -public class SearchIndexJournalEntry implements Iterable { +public class IndexJournalEntryData implements Iterable { private final int size; private final long[] underlyingArray; public static final int MAX_LENGTH = 1000; public static final int ENTRY_SIZE = 2; - public SearchIndexJournalEntry(long[] underlyingArray) { + public IndexJournalEntryData(long[] underlyingArray) { this.size = underlyingArray.length; this.underlyingArray = underlyingArray; } - public SearchIndexJournalEntry(int size, long[] underlyingArray) { + public IndexJournalEntryData(int size, long[] underlyingArray) { this.size = size; this.underlyingArray = underlyingArray; } - public void write(ByteBuffer buffer) { + public void write(DataOutputStream dos) throws IOException { for (int i = 0; i < size; i++) { - buffer.putLong(underlyingArray[i]); + dos.writeLong(underlyingArray[i]); } } - public long get(int idx) { if (idx >= size) throw new ArrayIndexOutOfBoundsException(); diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryHeader.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryHeader.java new file mode 100644 index 00000000..bbc81a17 --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalEntryHeader.java @@ -0,0 +1,20 @@ +package nu.marginalia.index.journal.model; + +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.id.EdgeId; + +public record IndexJournalEntryHeader(int entrySize, long combinedId, long documentMeta) { + + public IndexJournalEntryHeader(EdgeId domainId, EdgeId urlId, long documentMeta) { + this(-1, combineIds(domainId, urlId), documentMeta); + } + + static long combineIds(EdgeId domainId, EdgeId urlId) { + long did = domainId.id(); + long uid = urlId.id(); + + return (did << 32L) | uid; + } + +} diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalFileHeader.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalFileHeader.java new file mode 100644 index 00000000..42ae60b4 --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalFileHeader.java @@ -0,0 +1,4 @@ +package nu.marginalia.index.journal.model; + +public record IndexJournalFileHeader(long fileSize, long wordCount) { +} diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalStatistics.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalStatistics.java new file mode 100644 index 00000000..9eb28473 --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/model/IndexJournalStatistics.java @@ -0,0 +1,3 @@ +package nu.marginalia.index.journal.model; + +public record IndexJournalStatistics(int highestWord, int documentCardinality) { } diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReadEntry.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReadEntry.java new file mode 100644 index 00000000..9610dea4 --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReadEntry.java @@ -0,0 +1,60 @@ +package nu.marginalia.index.journal.reader; + +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.index.journal.model.IndexJournalEntryHeader; + +import java.io.DataInputStream; +import java.io.IOException; + +public class IndexJournalReadEntry { + public final IndexJournalEntryHeader header; + + private final long[] buffer; + + public IndexJournalReadEntry(IndexJournalEntryHeader header, long[] buffer) { + this.header = header; + this.buffer = buffer; + } + + + public static IndexJournalReadEntry read(DataInputStream inputStream) throws IOException { + + final long sizeBlock = inputStream.readLong(); + final long docId = inputStream.readLong(); + final long meta = inputStream.readLong(); + + var header = new IndexJournalEntryHeader( + (int) (sizeBlock >>> 32L), + docId, + meta); + + long[] buffer = new long[header.entrySize()]; + + for (int i = 0; i < header.entrySize(); i++) { + buffer[i] = inputStream.readLong(); + } + + return new IndexJournalReadEntry(header, buffer); + } + + public long docId() { + return header.combinedId(); + } + + public long docMeta() { + return header.documentMeta(); + } + + public int domainId() { + return (int) (docId() >>> 32L); + } + + public int urlId() { + return (int) (docId() & 0xFFFF_FFFFL); + } + + public IndexJournalEntryData readEntry() { + return new IndexJournalEntryData(header.entrySize(), buffer); + } + +} diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReader.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReader.java new file mode 100644 index 00000000..1467c500 --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReader.java @@ -0,0 +1,48 @@ +package nu.marginalia.index.journal.reader; + +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.index.journal.model.IndexJournalFileHeader; +import nu.marginalia.index.journal.model.IndexJournalStatistics; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.Iterator; +import java.util.function.IntConsumer; + +public interface IndexJournalReader extends Iterable { + int FILE_HEADER_SIZE_LONGS = 2; + int FILE_HEADER_SIZE_BYTES = 8 * FILE_HEADER_SIZE_LONGS; + + IndexJournalFileHeader fileHeader(); + + IndexJournalStatistics getStatistics(); + + void forEachWordId(IntConsumer consumer); + + void forEachUrlIdWordId(BiIntConsumer consumer); + + void forEachDocIdWordId(LongIntConsumer consumer); + + void forEachDocIdRecord(LongObjectConsumer consumer); + + void forEachUrlId(IntConsumer consumer); + + @NotNull + @Override + Iterator iterator(); + + void close() throws IOException; + + interface BiIntConsumer { + void accept(int left, int right); + } + + interface LongIntConsumer { + void accept(long left, int right); + } + + interface LongObjectConsumer { + void accept(long left, T right); + } + +} diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReaderSingleCompressedFile.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReaderSingleCompressedFile.java new file mode 100644 index 00000000..6c6bf58b --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/reader/IndexJournalReaderSingleCompressedFile.java @@ -0,0 +1,197 @@ +package nu.marginalia.index.journal.reader; + +import com.github.luben.zstd.ZstdInputStream; +import lombok.SneakyThrows; +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.index.journal.model.IndexJournalFileHeader; +import nu.marginalia.index.journal.model.IndexJournalStatistics; +import org.jetbrains.annotations.NotNull; +import org.roaringbitmap.longlong.Roaring64Bitmap; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Iterator; +import java.util.function.IntConsumer; +import java.util.function.Predicate; + +public class IndexJournalReaderSingleCompressedFile implements IndexJournalReader { + + private static Path journalFile; + public final IndexJournalFileHeader fileHeader; + + private DataInputStream dataInputStream = null; + + final Predicate entryPredicate; + final Predicate recordPredicate; + + public IndexJournalReaderSingleCompressedFile(Path file) throws IOException { + fileHeader = readHeader(file); + + this.recordPredicate = null; + this.entryPredicate = null; + } + + public IndexJournalReaderSingleCompressedFile(Path file, Predicate entryPredicate, Predicate recordPredicate) throws IOException { + journalFile = file; + fileHeader = readHeader(file); + + var fileInputStream = Files.newInputStream(file, StandardOpenOption.READ); + fileInputStream.skipNBytes(FILE_HEADER_SIZE_BYTES); + + this.recordPredicate = recordPredicate; + this.entryPredicate = entryPredicate; + } + + private static IndexJournalFileHeader readHeader(Path file) throws IOException { + journalFile = file; + + try (var raf = new RandomAccessFile(file.toFile(), "r")) { + long unused = raf.readLong(); + long wordCount = raf.readLong(); + + return new IndexJournalFileHeader(unused, wordCount); + } + } + + private static DataInputStream createInputStream(Path file) throws IOException { + var fileInputStream = Files.newInputStream(file, StandardOpenOption.READ); + + // skip the header + fileInputStream.skipNBytes(16); + + return new DataInputStream(new ZstdInputStream(fileInputStream)); + } + + public IndexJournalFileHeader fileHeader() { + return fileHeader; + } + + public boolean filter(IndexJournalReadEntry entry) { + return entryPredicate == null || entryPredicate.test(entry); + } + + public boolean filter(IndexJournalReadEntry entry, IndexJournalEntryData.Record record) { + return (entryPredicate == null || entryPredicate.test(entry)) + && (recordPredicate == null || recordPredicate.test(record)); + } + + public void close() throws IOException { + dataInputStream.close(); + } + + + @Override + public IndexJournalStatistics getStatistics() { + int highestWord = 0; + + // Docs cardinality is a candidate for a HyperLogLog + Roaring64Bitmap docsBitmap = new Roaring64Bitmap(); + + for (var entry : this) { + var entryData = entry.readEntry(); + + if (filter(entry)) { + docsBitmap.addLong(entry.docId() & 0x0000_0000_FFFF_FFFFL); + + for (var item : entryData) { + if (filter(entry, item)) { + highestWord = Integer.max(item.wordId(), highestWord); + } + } + } + } + + return new IndexJournalStatistics(highestWord, docsBitmap.getIntCardinality()); + } + + @Override + public void forEachWordId(IntConsumer consumer) { + for (var entry : this) { + var data = entry.readEntry(); + for (var post : data) { + if (filter(entry, post)) { + consumer.accept(post.wordId()); + } + } + } + } + + @Override + public void forEachUrlIdWordId(BiIntConsumer consumer) { + for (var entry : this) { + var data = entry.readEntry(); + + for (var post : data) { + if (filter(entry, post)) { + consumer.accept(entry.urlId(), post.wordId()); + } + } + } + } + + @Override + public void forEachDocIdWordId(LongIntConsumer consumer) { + for (var entry : this) { + var data = entry.readEntry(); + + for (var post : data) { + if (filter(entry, post)) { + consumer.accept(entry.docId(), post.wordId()); + } + } + } + } + + @Override + public void forEachDocIdRecord(LongObjectConsumer consumer) { + for (var entry : this) { + var data = entry.readEntry(); + + for (var post : data) { + if (filter(entry, post)) { + consumer.accept(entry.docId(), post); + } + } + } + } + @Override + public void forEachUrlId(IntConsumer consumer) { + for (var entry : this) { + if (filter(entry)) { + consumer.accept(entry.urlId()); + } + } + } + + @SneakyThrows + @NotNull + @Override + public Iterator iterator() { + if (dataInputStream != null) { + dataInputStream.close(); + } + dataInputStream = createInputStream(journalFile); + + return new JournalEntryIterator(); + } + + private class JournalEntryIterator implements Iterator { + private int i = 0; + + @Override + @SneakyThrows + public boolean hasNext() { + return i < fileHeader.fileSize(); + } + + @SneakyThrows + @Override + public IndexJournalReadEntry next() { + i++; + return IndexJournalReadEntry.read(dataInputStream); + } + } + +} diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/writer/IndexJournalWriter.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/writer/IndexJournalWriter.java new file mode 100644 index 00000000..03b98d52 --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/writer/IndexJournalWriter.java @@ -0,0 +1,20 @@ +package nu.marginalia.index.journal.writer; + +import nu.marginalia.index.journal.model.IndexJournalEntry; +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.index.journal.model.IndexJournalEntryHeader; + +import java.io.IOException; + +public interface IndexJournalWriter { + void put(IndexJournalEntryHeader header, IndexJournalEntryData entry); + default void put(IndexJournalEntry entry) { + put(entry.header(), entry.data()); + } + + void forceWrite() throws IOException; + + void flushWords(); + void close() throws IOException; + +} diff --git a/index/index-journal/src/main/java/nu.marginalia.index/journal/writer/IndexJournalWriterImpl.java b/index/index-journal/src/main/java/nu.marginalia.index/journal/writer/IndexJournalWriterImpl.java new file mode 100644 index 00000000..c9bf44cd --- /dev/null +++ b/index/index-journal/src/main/java/nu.marginalia.index/journal/writer/IndexJournalWriterImpl.java @@ -0,0 +1,73 @@ +package nu.marginalia.index.journal.writer; + +import com.github.luben.zstd.ZstdOutputStream; +import lombok.SneakyThrows; +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.index.journal.model.IndexJournalEntryHeader; +import nu.marginalia.index.journal.reader.IndexJournalReader; +import nu.marginalia.lexicon.KeywordLexicon; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +public class IndexJournalWriterImpl implements IndexJournalWriter{ + private final KeywordLexicon lexicon; + private final Path outputFile; + private final DataOutputStream outputStream; + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private int numEntries = 0; + + public IndexJournalWriterImpl(KeywordLexicon lexicon, Path outputFile) throws IOException { + this.lexicon = lexicon; + this.outputFile = outputFile; + + var fileStream = Files.newOutputStream(outputFile, StandardOpenOption.CREATE); + + writeHeaderPlaceholder(fileStream); + + outputStream = new DataOutputStream(new ZstdOutputStream(fileStream)); + } + + private static void writeHeaderPlaceholder(OutputStream fileStream) throws IOException { + fileStream.write(new byte[IndexJournalReader.FILE_HEADER_SIZE_BYTES]); + } + + @Override + @SneakyThrows + public void put(IndexJournalEntryHeader header, IndexJournalEntryData entry) { + outputStream.writeInt(entry.size()); + outputStream.writeInt(0); + outputStream.writeLong(header.combinedId()); + outputStream.writeLong(header.documentMeta()); + entry.write(outputStream); + + numEntries++; + } + + @Override + public void forceWrite() throws IOException { + outputStream.flush(); + + try (var raf = new RandomAccessFile(outputFile.toFile(), "rws")) { + raf.writeLong(numEntries); + raf.writeLong(lexicon.size()); + } + } + + @Override + public void flushWords() { + lexicon.commitToDisk(); + } + + public void close() throws IOException { + forceWrite(); + + outputStream.close(); + } +} diff --git a/index/index-journal/src/test/java/nu/marginalia/index/journal/IndexJournalTest.java b/index/index-journal/src/test/java/nu/marginalia/index/journal/IndexJournalTest.java new file mode 100644 index 00000000..67b23dee --- /dev/null +++ b/index/index-journal/src/test/java/nu/marginalia/index/journal/IndexJournalTest.java @@ -0,0 +1,133 @@ +package nu.marginalia.index.journal; + +import nu.marginalia.index.journal.model.IndexJournalEntry; +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.index.journal.reader.IndexJournalReader; +import nu.marginalia.index.journal.reader.IndexJournalReaderSingleCompressedFile; +import nu.marginalia.index.journal.writer.IndexJournalWriterImpl; +import nu.marginalia.lexicon.KeywordLexicon; +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class IndexJournalTest { + Path tempFile; + KeywordLexicon lexicon; + IndexJournalReader reader; + + @BeforeEach + public void setUp() throws IOException { + tempFile = Files.createTempFile(getClass().getSimpleName(), ".dat"); + lexicon = Mockito.mock(KeywordLexicon.class); + + var journalWriter = new IndexJournalWriterImpl(lexicon, tempFile); + journalWriter.put(IndexJournalEntry.builder(44, 10, 55) + .add(1, 2) + .add(2, 3) + .add(3, 4) + .add(5, 6).build()); + + journalWriter.put(IndexJournalEntry.builder(43, 15, 10) + .add(5, 5) + .add(6, 6) + .build()); + journalWriter.forceWrite(); + journalWriter.close(); + + reader = new IndexJournalReaderSingleCompressedFile(tempFile); + } + @AfterEach + public void tearDown() throws IOException { + reader.close(); + Files.delete(tempFile); + } + + @Test + public void reiterable() { + // Verifies that the reader can be run twice to the same effect + + int cnt = 0; + int cnt2 = 0; + + for (var item : reader) cnt++; + for (var item : reader) cnt2++; + + assertEquals(cnt2, cnt); + } + + @Test + public void forEachUrlId() { + List expected = List.of(10, 15); + List actual = new ArrayList<>(); + + reader.forEachUrlId(actual::add); + assertEquals(expected, actual); + } + + @Test + public void forEachWordId() { + List expected = List.of(1, 2, 3, 5, 5 ,6); + List actual = new ArrayList<>(); + + reader.forEachWordId(actual::add); + assertEquals(expected, actual); + } + + + @Test + public void forEachUrlIdWordId() { + List> expected = List.of( + Pair.of(10, 1), + Pair.of(10, 2), + Pair.of(10, 3), + Pair.of(10, 5), + Pair.of(15, 5), + Pair.of(15, 6)); + List> actual = new ArrayList<>(); + + reader.forEachUrlIdWordId((url, word) -> actual.add(Pair.of(url, word))); + assertEquals(expected, actual); + } + + @Test + public void forEachDocIdWordId() { + List> expected = List.of( + Pair.of(10L | (44L << 32), 1), + Pair.of(10L | (44L << 32), 2), + Pair.of(10L | (44L << 32), 3), + Pair.of(10L | (44L << 32), 5), + Pair.of(15L | (43L << 32), 5), + Pair.of(15L | (43L << 32), 6)); + List> actual = new ArrayList<>(); + + reader.forEachDocIdWordId((url, word) -> actual.add(Pair.of(url, word))); + assertEquals(expected, actual); + } + + @Test + public void forEachDocIdRecord() { + List> expected = List.of( + Pair.of(10L | (44L << 32), new IndexJournalEntryData.Record(1, 2)), + Pair.of(10L | (44L << 32), new IndexJournalEntryData.Record(2, 3)), + Pair.of(10L | (44L << 32), new IndexJournalEntryData.Record(3, 4)), + Pair.of(10L | (44L << 32), new IndexJournalEntryData.Record(5, 6)), + Pair.of(15L | (43L << 32), new IndexJournalEntryData.Record(5, 5)), + Pair.of(15L | (43L << 32), new IndexJournalEntryData.Record(6, 6)) + ); + List> actual = new ArrayList<>(); + + reader.forEachDocIdRecord((url, word) -> actual.add(Pair.of(url, word))); + assertEquals(expected, actual); + } + +} diff --git a/index/index-query/build.gradle b/index/index-query/build.gradle new file mode 100644 index 00000000..0558edbe --- /dev/null +++ b/index/index-query/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':libraries:array') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/EmptyEntrySource.java b/index/index-query/src/main/java/nu/marginalia/index/query/EmptyEntrySource.java similarity index 73% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/EmptyEntrySource.java rename to index/index-query/src/main/java/nu/marginalia/index/query/EmptyEntrySource.java index f38b4c0d..a1e39069 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/EmptyEntrySource.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/EmptyEntrySource.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.index.query; +package nu.marginalia.index.query; -import nu.marginalia.util.array.buffer.LongQueryBuffer; +import nu.marginalia.array.buffer.LongQueryBuffer; public class EmptyEntrySource implements EntrySource { @Override diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/EntrySource.java b/index/index-query/src/main/java/nu/marginalia/index/query/EntrySource.java similarity index 54% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/EntrySource.java rename to index/index-query/src/main/java/nu/marginalia/index/query/EntrySource.java index 5ec62c05..68c09187 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/EntrySource.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/EntrySource.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.index.query; +package nu.marginalia.index.query; -import nu.marginalia.util.array.buffer.LongQueryBuffer; +import nu.marginalia.array.buffer.LongQueryBuffer; public interface EntrySource { void skip(int n); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/EntrySourceFromArrayRange.java b/index/index-query/src/main/java/nu/marginalia/index/query/EntrySourceFromArrayRange.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/EntrySourceFromArrayRange.java rename to index/index-query/src/main/java/nu/marginalia/index/query/EntrySourceFromArrayRange.java index a0d9ee32..270df865 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/EntrySourceFromArrayRange.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/EntrySourceFromArrayRange.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.index.query; +package nu.marginalia.index.query; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.buffer.LongQueryBuffer; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.buffer.LongQueryBuffer; import static java.lang.Math.min; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQuery.java b/index/index-query/src/main/java/nu/marginalia/index/query/IndexQuery.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQuery.java rename to index/index-query/src/main/java/nu/marginalia/index/query/IndexQuery.java index b2994d36..b8df5fc7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQuery.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/IndexQuery.java @@ -1,8 +1,7 @@ -package nu.marginalia.wmsa.edge.index.query; +package nu.marginalia.index.query; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.wmsa.edge.index.query.EntrySource; -import nu.marginalia.wmsa.edge.index.query.filter.QueryFilterStepIf; +import nu.marginalia.index.query.filter.QueryFilterStepIf; +import nu.marginalia.array.buffer.LongQueryBuffer; import java.util.ArrayList; import java.util.List; @@ -64,6 +63,7 @@ public class IndexQuery { public long dataCost() { return dataCost; } + public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Sources:\n"); diff --git a/index/index-query/src/main/java/nu/marginalia/index/query/IndexQueryBuilder.java b/index/index-query/src/main/java/nu/marginalia/index/query/IndexQueryBuilder.java new file mode 100644 index 00000000..dddc1e3b --- /dev/null +++ b/index/index-query/src/main/java/nu/marginalia/index/query/IndexQueryBuilder.java @@ -0,0 +1,12 @@ +package nu.marginalia.index.query; + +import nu.marginalia.index.query.filter.QueryFilterStepIf; + +public interface IndexQueryBuilder { + IndexQueryBuilder also(int termId); + + IndexQueryBuilder not(int termId); + IndexQueryBuilder addInclusionFilter(QueryFilterStepIf filterStep); + + IndexQuery build(); +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQueryParams.java b/index/index-query/src/main/java/nu/marginalia/index/query/IndexQueryParams.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQueryParams.java rename to index/index-query/src/main/java/nu/marginalia/index/query/IndexQueryParams.java index 031410fc..1b840815 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQueryParams.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/IndexQueryParams.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.index.query; +package nu.marginalia.index.query; -import nu.marginalia.wmsa.edge.index.model.QueryStrategy; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSet; -import nu.marginalia.wmsa.edge.model.search.domain.SpecificationLimit; +import nu.marginalia.index.query.limit.QueryStrategy; +import nu.marginalia.index.searchset.SearchSet; +import nu.marginalia.index.query.limit.SpecificationLimit; public record IndexQueryParams(SpecificationLimit qualityLimit, SpecificationLimit year, diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexSearchBudget.java b/index/index-query/src/main/java/nu/marginalia/index/query/IndexSearchBudget.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexSearchBudget.java rename to index/index-query/src/main/java/nu/marginalia/index/query/IndexSearchBudget.java index dfcbf06f..5551839e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexSearchBudget.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/IndexSearchBudget.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.query; +package nu.marginalia.index.query; public class IndexSearchBudget { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterAnyOf.java b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterAnyOf.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterAnyOf.java rename to index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterAnyOf.java index 293fe7d0..2569ec42 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterAnyOf.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterAnyOf.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.index.query.filter; +package nu.marginalia.index.query.filter; -import nu.marginalia.util.array.buffer.LongQueryBuffer; +import nu.marginalia.array.buffer.LongQueryBuffer; import java.util.Arrays; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterLetThrough.java b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterLetThrough.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterLetThrough.java rename to index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterLetThrough.java index 3f471cd8..688ef938 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterLetThrough.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterLetThrough.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.query.filter; +package nu.marginalia.index.query.filter; public class QueryFilterLetThrough implements QueryFilterStepIf { static final QueryFilterStepIf instance = new QueryFilterLetThrough(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterNoPass.java b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterNoPass.java similarity index 79% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterNoPass.java rename to index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterNoPass.java index 4ad69531..1bcd04ae 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterNoPass.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterNoPass.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.index.query.filter; +package nu.marginalia.index.query.filter; -import nu.marginalia.util.array.buffer.LongQueryBuffer; +import nu.marginalia.array.buffer.LongQueryBuffer; public class QueryFilterNoPass implements QueryFilterStepIf { static final QueryFilterStepIf instance = new QueryFilterNoPass(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterStepExcludeFromPredicate.java b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterStepExcludeFromPredicate.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterStepExcludeFromPredicate.java rename to index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterStepExcludeFromPredicate.java index 8cb4561f..92c8c972 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterStepExcludeFromPredicate.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterStepExcludeFromPredicate.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.query.filter; +package nu.marginalia.index.query.filter; import java.util.function.LongPredicate; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterStepFromPredicate.java b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterStepFromPredicate.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterStepFromPredicate.java rename to index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterStepFromPredicate.java index 26207152..56f08b71 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterStepFromPredicate.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterStepFromPredicate.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.query.filter; +package nu.marginalia.index.query.filter; import java.util.function.LongPredicate; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterStepIf.java b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterStepIf.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterStepIf.java rename to index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterStepIf.java index 9af75a7f..e3692538 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/filter/QueryFilterStepIf.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/filter/QueryFilterStepIf.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.index.query.filter; +package nu.marginalia.index.query.filter; -import nu.marginalia.util.array.buffer.LongQueryBuffer; +import nu.marginalia.array.buffer.LongQueryBuffer; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/QueryLimits.java b/index/index-query/src/main/java/nu/marginalia/index/query/limit/QueryLimits.java similarity index 68% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/QueryLimits.java rename to index/index-query/src/main/java/nu/marginalia/index/query/limit/QueryLimits.java index dce78343..b403e5ea 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/QueryLimits.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/limit/QueryLimits.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.model; +package nu.marginalia.index.query.limit; public record QueryLimits(int resultsByDomain, int resultsTotal, int timeoutMs, int fetchSize) { } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/QueryStrategy.java b/index/index-query/src/main/java/nu/marginalia/index/query/limit/QueryStrategy.java similarity index 76% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/QueryStrategy.java rename to index/index-query/src/main/java/nu/marginalia/index/query/limit/QueryStrategy.java index d8682a61..e7f69252 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/model/QueryStrategy.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/limit/QueryStrategy.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.model; +package nu.marginalia.index.query.limit; public enum QueryStrategy { SENTENCE, diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/SpecificationLimit.java b/index/index-query/src/main/java/nu/marginalia/index/query/limit/SpecificationLimit.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/SpecificationLimit.java rename to index/index-query/src/main/java/nu/marginalia/index/query/limit/SpecificationLimit.java index 5a9a587b..1af0b10a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/SpecificationLimit.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/limit/SpecificationLimit.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.search.domain; +package nu.marginalia.index.query.limit; public record SpecificationLimit(SpecificationLimitType type, int value) { public static SpecificationLimit none() { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/SpecificationLimitType.java b/index/index-query/src/main/java/nu/marginalia/index/query/limit/SpecificationLimitType.java similarity index 63% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/SpecificationLimitType.java rename to index/index-query/src/main/java/nu/marginalia/index/query/limit/SpecificationLimitType.java index 24c2fd12..a47675c7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/domain/SpecificationLimitType.java +++ b/index/index-query/src/main/java/nu/marginalia/index/query/limit/SpecificationLimitType.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.model.search.domain; +package nu.marginalia.index.query.limit; public enum SpecificationLimitType { NONE, diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SearchSet.java b/index/index-query/src/main/java/nu/marginalia/index/searchset/SearchSet.java similarity index 55% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SearchSet.java rename to index/index-query/src/main/java/nu/marginalia/index/searchset/SearchSet.java index 8f412374..0cc40e4d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SearchSet.java +++ b/index/index-query/src/main/java/nu/marginalia/index/searchset/SearchSet.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.svc.searchset; +package nu.marginalia.index.searchset; public interface SearchSet { boolean contains(int urlId); diff --git a/index/index-reverse/build.gradle b/index/index-reverse/build.gradle new file mode 100644 index 00000000..a719b5f4 --- /dev/null +++ b/index/index-reverse/build.gradle @@ -0,0 +1,43 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':libraries:array') + implementation project(':libraries:btree') + implementation project(':libraries:misc') + implementation project(':features:domain-ranking') + implementation project(':index:index-query') + implementation project(':index:index-journal') + implementation project(':index:lexicon') + implementation project(':common:model') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexConverter.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexConverter.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexConverter.java rename to index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexConverter.java index 2cf79112..0a9005fe 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexConverter.java +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexConverter.java @@ -1,18 +1,18 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse; +package nu.marginalia.index.reverse; +import lombok.SneakyThrows; +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.index.journal.model.IndexJournalStatistics; +import nu.marginalia.index.journal.reader.IndexJournalReader; +import nu.marginalia.ranking.DomainRankings; import nu.marginalia.util.RandomWriteFunnel; -import nu.marginalia.util.array.IntArray; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.algo.SortingContext; -import nu.marginalia.util.array.functional.LongBinaryIOOperation; -import nu.marginalia.util.array.functional.LongIOTransformer; -import nu.marginalia.util.array.functional.LongTransformer; -import nu.marginalia.util.btree.BTreeWriter; -import nu.marginalia.wmsa.edge.index.postings.DomainRankings; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalStatistics; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReader; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReaderSingleFile; +import nu.marginalia.array.IntArray; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.algo.SortingContext; +import nu.marginalia.array.functional.LongBinaryIOOperation; +import nu.marginalia.array.functional.LongIOTransformer; +import nu.marginalia.array.functional.LongTransformer; +import nu.marginalia.btree.BTreeWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,9 +22,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import static nu.marginalia.wmsa.edge.index.postings.reverse.ReverseIndexParameters.ENTRY_SIZE; -import static nu.marginalia.wmsa.edge.index.postings.reverse.ReverseIndexParameters.bTreeContext; - public class ReverseIndexConverter { private static final int RWF_BIN_SIZE = 10_000_000; @@ -32,14 +29,14 @@ public class ReverseIndexConverter { private final Logger logger = LoggerFactory.getLogger(getClass()); - private final SearchIndexJournalReaderSingleFile journalReader; + private final IndexJournalReader journalReader; private final DomainRankings domainRankings; private final Path outputFileWords; private final Path outputFileDocs; private final SortingContext sortingContext; public ReverseIndexConverter(Path tmpFileDir, - SearchIndexJournalReaderSingleFile journalReader, + IndexJournalReader journalReader, DomainRankings domainRankings, Path outputFileWords, Path outputFileDocs) { @@ -54,11 +51,12 @@ public class ReverseIndexConverter { public void convert() throws IOException { deleteOldFiles(); - if (journalReader.fileHeader.fileSize() <= SearchIndexJournalReader.FILE_HEADER_SIZE_BYTES) { + if (journalReader.fileHeader().fileSize() <= IndexJournalReader.FILE_HEADER_SIZE_BYTES) { + logger.warn("Bailing: Journal is empty!"); return; } - final SearchIndexJournalStatistics statistics = journalReader.getStatistics(); + final IndexJournalStatistics statistics = journalReader.getStatistics(); final Path intermediateUrlsFile = Files.createTempFile(tmpFileDir, "urls-sorted", ".dat"); @@ -97,7 +95,7 @@ public class ReverseIndexConverter { { LongArray intermediateDocs = LongArray.mmapForWriting(intermediateUrlsFile); wordsOffsets.foldIO(0, 0, wordsFileSize, (s, e) -> { - intermediateDocs.sortLargeSpanN(sortingContext, ENTRY_SIZE, s, e); + intermediateDocs.sortLargeSpanN(sortingContext, ReverseIndexParameters.ENTRY_SIZE, s, e); return e; }); intermediateDocs.force(); @@ -138,7 +136,7 @@ public class ReverseIndexConverter { public long apply(long start, long end) throws IOException { if (end == start) return end; - size += bTreeContext.calculateSize((int) (end - start) / ENTRY_SIZE); + size += ReverseIndexParameters.bTreeContext.calculateSize((int) (end - start) / ReverseIndexParameters.ENTRY_SIZE); return end; } @@ -154,7 +152,7 @@ public class ReverseIndexConverter { @Override public long transform(long pos, long count) { - return (offset += ENTRY_SIZE * count); + return (offset += ReverseIndexParameters.ENTRY_SIZE * count); } } @@ -192,7 +190,7 @@ public class ReverseIndexConverter { } } - private class IntermediateIndexConstructor implements SearchIndexJournalReaderSingleFile.LongObjectConsumer, AutoCloseable { + private class IntermediateIndexConstructor implements IndexJournalReader.LongObjectConsumer, AutoCloseable { private final LongArray wordRangeEnds; private final IntArray wordRangeOffset; @@ -208,8 +206,9 @@ public class ReverseIndexConverter { this.documentsFile = documentsFile; } + @SneakyThrows @Override - public void accept(long docId, SearchIndexJournalEntry.Record record) { + public void accept(long docId, IndexJournalEntryData.Record record) { /* Encode the ID as * diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexParameters.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexParameters.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexParameters.java rename to index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexParameters.java index e38fa3b5..1ee6578c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexParameters.java +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexParameters.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse; +package nu.marginalia.index.reverse; -import nu.marginalia.util.btree.model.BTreeContext; +import nu.marginalia.btree.model.BTreeContext; class ReverseIndexParameters { public static final int ENTRY_SIZE = 2; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexPrefixEntrySource.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexPrefixEntrySource.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexPrefixEntrySource.java rename to index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexPrefixEntrySource.java index 0cae45ab..359e91cc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexPrefixEntrySource.java +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexPrefixEntrySource.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse; +package nu.marginalia.index.reverse; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.btree.BTreeReader; -import nu.marginalia.wmsa.edge.index.query.EntrySource; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.btree.BTreeReader; +import nu.marginalia.index.query.EntrySource; import static java.lang.Math.min; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexPrioReader.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexPrioReader.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexPrioReader.java rename to index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexPrioReader.java index b2ce74dc..2a38a9dd 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexPrioReader.java +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexPrioReader.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse; +package nu.marginalia.index.reverse; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.btree.BTreeReader; -import nu.marginalia.wmsa.edge.index.postings.reverse.query.ReverseIndexEntrySource; -import nu.marginalia.wmsa.edge.index.postings.reverse.query.ReverseIndexEntrySourceBehavior; -import nu.marginalia.wmsa.edge.index.query.EmptyEntrySource; -import nu.marginalia.wmsa.edge.index.query.EntrySource; +import nu.marginalia.index.reverse.query.ReverseIndexEntrySourceBehavior; +import nu.marginalia.index.reverse.query.ReverseIndexEntrySource; +import nu.marginalia.index.query.EntrySource; +import nu.marginalia.array.LongArray; +import nu.marginalia.btree.BTreeReader; +import nu.marginalia.index.query.EmptyEntrySource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexPriorityParameters.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexPriorityParameters.java similarity index 62% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexPriorityParameters.java rename to index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexPriorityParameters.java index b6d6fb38..60b5cf80 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexPriorityParameters.java +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexPriorityParameters.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse; +package nu.marginalia.index.reverse; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.model.crawl.EdgePageWordFlags; public class ReverseIndexPriorityParameters { private static final long highPriorityFlags = EdgePageWordFlags.Title.asBit() @@ -11,7 +11,7 @@ public class ReverseIndexPriorityParameters { | EdgePageWordFlags.Site.asBit() | EdgePageWordFlags.SiteAdjacent.asBit(); - public static boolean filterPriorityRecord(SearchIndexJournalEntry.Record record) { + public static boolean filterPriorityRecord(IndexJournalEntryData.Record record) { long meta = record.metadata(); return (meta & highPriorityFlags) != 0; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexReader.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexReader.java similarity index 77% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexReader.java rename to index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexReader.java index 6f4475e7..7c078e9c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexReader.java +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/ReverseIndexReader.java @@ -1,16 +1,16 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse; +package nu.marginalia.index.reverse; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.btree.BTreeReader; -import nu.marginalia.wmsa.edge.index.postings.reverse.query.ReverseIndexEntrySource; -import nu.marginalia.wmsa.edge.index.postings.reverse.query.ReverseIndexEntrySourceBehavior; -import nu.marginalia.wmsa.edge.index.postings.reverse.query.ReverseIndexRejectFilter; -import nu.marginalia.wmsa.edge.index.postings.reverse.query.ReverseIndexRetainFilter; -import nu.marginalia.wmsa.edge.index.query.EmptyEntrySource; -import nu.marginalia.wmsa.edge.index.query.EntrySource; -import nu.marginalia.wmsa.edge.index.query.filter.QueryFilterLetThrough; -import nu.marginalia.wmsa.edge.index.query.filter.QueryFilterNoPass; -import nu.marginalia.wmsa.edge.index.query.filter.QueryFilterStepIf; +import nu.marginalia.index.reverse.query.ReverseIndexEntrySourceBehavior; +import nu.marginalia.index.reverse.query.ReverseIndexEntrySource; +import nu.marginalia.index.reverse.query.ReverseIndexRejectFilter; +import nu.marginalia.index.reverse.query.ReverseIndexRetainFilter; +import nu.marginalia.array.LongArray; +import nu.marginalia.btree.BTreeReader; +import nu.marginalia.index.query.EmptyEntrySource; +import nu.marginalia.index.query.EntrySource; +import nu.marginalia.index.query.filter.QueryFilterLetThrough; +import nu.marginalia.index.query.filter.QueryFilterNoPass; +import nu.marginalia.index.query.filter.QueryFilterStepIf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexEntrySource.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexEntrySource.java similarity index 87% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexEntrySource.java rename to index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexEntrySource.java index a47b134a..13cdc3af 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexEntrySource.java +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexEntrySource.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse.query; +package nu.marginalia.index.reverse.query; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.btree.BTreeReader; -import nu.marginalia.wmsa.edge.index.query.EntrySource; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.btree.BTreeReader; +import nu.marginalia.index.query.EntrySource; import static java.lang.Math.min; diff --git a/index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexEntrySourceBehavior.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexEntrySourceBehavior.java new file mode 100644 index 00000000..67058fed --- /dev/null +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexEntrySourceBehavior.java @@ -0,0 +1,11 @@ +package nu.marginalia.index.reverse.query; + +public enum ReverseIndexEntrySourceBehavior { + /** Eagerly read from this entry source */ + DO_PREFER, + + /** Do not use this entry source if entries have been fetched + * from another entry source + */ + DO_NOT_PREFER +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexRejectFilter.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexRejectFilter.java similarity index 68% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexRejectFilter.java rename to index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexRejectFilter.java index ca317349..0ad4112f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexRejectFilter.java +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexRejectFilter.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse.query; +package nu.marginalia.index.reverse.query; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.btree.BTreeReader; -import nu.marginalia.wmsa.edge.index.query.filter.QueryFilterStepIf; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.btree.BTreeReader; +import nu.marginalia.index.query.filter.QueryFilterStepIf; public record ReverseIndexRejectFilter(BTreeReader range) implements QueryFilterStepIf { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexRetainFilter.java b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexRetainFilter.java similarity index 68% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexRetainFilter.java rename to index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexRetainFilter.java index 9c408a34..a9a14dad 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexRetainFilter.java +++ b/index/index-reverse/src/main/java/nu/marginalia/index/reverse/query/ReverseIndexRetainFilter.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse.query; +package nu.marginalia.index.reverse.query; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.btree.BTreeReader; -import nu.marginalia.wmsa.edge.index.query.filter.QueryFilterStepIf; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.btree.BTreeReader; +import nu.marginalia.index.query.filter.QueryFilterStepIf; public record ReverseIndexRetainFilter(BTreeReader range) implements QueryFilterStepIf { diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexConverterTest.java b/index/index-reverse/src/test/java/nu/marginalia/index/reverse/ReverseIndexConverterTest.java similarity index 67% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexConverterTest.java rename to index/index-reverse/src/test/java/nu/marginalia/index/reverse/ReverseIndexConverterTest.java index 32fcb58b..a50bae9c 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexConverterTest.java +++ b/index/index-reverse/src/test/java/nu/marginalia/index/reverse/ReverseIndexConverterTest.java @@ -1,19 +1,18 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse; +package nu.marginalia.index.reverse; import lombok.SneakyThrows; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.dict.OffHeapDictionaryHashMap; -import nu.marginalia.util.test.TestUtil; -import nu.marginalia.wmsa.edge.index.lexicon.KeywordLexicon; -import nu.marginalia.wmsa.edge.index.lexicon.journal.KeywordLexiconJournal; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.index.postings.DomainRankings; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntryHeader; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReaderSingleFile; -import nu.marginalia.wmsa.edge.index.postings.journal.writer.SearchIndexJournalWriterImpl; -import nu.marginalia.wmsa.edge.index.postings.reverse.query.ReverseIndexEntrySourceBehavior; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.dict.OffHeapDictionaryHashMap; +import nu.marginalia.index.journal.model.IndexJournalEntry; +import nu.marginalia.index.journal.reader.IndexJournalReaderSingleCompressedFile; +import nu.marginalia.index.journal.writer.IndexJournalWriterImpl; +import nu.marginalia.index.journal.writer.IndexJournalWriter; +import nu.marginalia.index.reverse.query.ReverseIndexEntrySourceBehavior; +import nu.marginalia.ranking.DomainRankings; +import nu.marginalia.lexicon.KeywordLexicon; +import nu.marginalia.lexicon.journal.KeywordLexiconJournal; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; +import nu.marginalia.test.TestUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -29,7 +28,6 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; class ReverseIndexConverterTest { KeywordLexicon keywordLexicon; - SearchIndexJournalWriterImpl writer; Path indexFile; Path wordsFile1; @@ -44,40 +42,38 @@ class ReverseIndexConverterTest { dictionaryFile = Files.createTempFile("tmp", ".dict"); dictionaryFile.toFile().deleteOnExit(); - keywordLexicon = new KeywordLexicon(new KeywordLexiconJournal(dictionaryFile.toFile()), new OffHeapDictionaryHashMap(1L<<16)); + keywordLexicon = new KeywordLexicon(new KeywordLexiconJournal(dictionaryFile.toFile())); keywordLexicon.getOrInsert("0"); indexFile = Files.createTempFile("tmp", ".idx"); indexFile.toFile().deleteOnExit(); - writer = new SearchIndexJournalWriterImpl(keywordLexicon, indexFile.toFile()); + wordsFile1 = Files.createTempFile("words1", ".idx"); urlsFile1 = Files.createTempFile("urls1", ".idx"); } - public void createEntry(SearchIndexJournalWriterImpl writer, KeywordLexicon keywordLexicon, int id) { + public void createEntry(IndexJournalWriter writer, KeywordLexicon keywordLexicon, int id) { int[] factors = IntStream.rangeClosed(1, id).filter(v -> (id % v) == 0).toArray(); - var header = new SearchIndexJournalEntryHeader(factors.length, id, EdgePageDocumentsMetadata.defaultValue()); - long[] data = new long[factors.length*2]; + var entryBuilder = IndexJournalEntry.builder(id, EdgePageDocumentsMetadata.defaultValue()); + for (int i = 0; i < factors.length; i++) { - data[2*i] = keywordLexicon.getOrInsert(Integer.toString(factors[i])); - data[2*i + 1] = factors[i]; + entryBuilder.add(keywordLexicon.getOrInsert(Integer.toString(factors[i])), -factors[i]); } - writer.put(header, new SearchIndexJournalEntry(data)); + writer.put(entryBuilder.build()); } @Test - void testReverseIndex() throws IOException, InterruptedException { + void testReverseIndex() throws IOException { + var writer = new IndexJournalWriterImpl(keywordLexicon, indexFile); + for (int i = 1; i < 512; i++) { createEntry(writer, keywordLexicon, i); } - - keywordLexicon.commitToDisk(); - Thread.sleep(1000); - writer.forceWrite(); + writer.close(); Path tmpDir = Path.of("/tmp"); @@ -85,7 +81,7 @@ class ReverseIndexConverterTest { var wordsFile = dataDir.resolve("urls.dat"); var docsFile = dataDir.resolve("docs.dat"); - var journalReader = new SearchIndexJournalReaderSingleFile(LongArray.mmapRead(indexFile)); + var journalReader = new IndexJournalReaderSingleCompressedFile(indexFile); new ReverseIndexConverter(tmpDir, journalReader, new DomainRankings(), wordsFile, docsFile) .convert(); diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexConverterTest2.java b/index/index-reverse/src/test/java/nu/marginalia/index/reverse/ReverseIndexConverterTest2.java similarity index 67% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexConverterTest2.java rename to index/index-reverse/src/test/java/nu/marginalia/index/reverse/ReverseIndexConverterTest2.java index 2525d39b..bdd08524 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/postings/reverse/ReverseIndexConverterTest2.java +++ b/index/index-reverse/src/test/java/nu/marginalia/index/reverse/ReverseIndexConverterTest2.java @@ -1,18 +1,18 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse; +package nu.marginalia.index.reverse; import lombok.SneakyThrows; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.dict.OffHeapDictionaryHashMap; -import nu.marginalia.util.test.TestUtil; -import nu.marginalia.wmsa.edge.index.lexicon.KeywordLexicon; -import nu.marginalia.wmsa.edge.index.lexicon.journal.KeywordLexiconJournal; -import nu.marginalia.wmsa.edge.index.postings.DomainRankings; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntryHeader; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReaderSingleFile; -import nu.marginalia.wmsa.edge.index.postings.journal.writer.SearchIndexJournalWriterImpl; -import nu.marginalia.wmsa.edge.index.postings.reverse.query.ReverseIndexEntrySourceBehavior; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.dict.OffHeapDictionaryHashMap; +import nu.marginalia.index.journal.model.IndexJournalEntryData; +import nu.marginalia.index.journal.model.IndexJournalEntryHeader; +import nu.marginalia.index.journal.reader.IndexJournalReaderSingleCompressedFile; +import nu.marginalia.index.journal.writer.IndexJournalWriterImpl; +import nu.marginalia.index.journal.writer.IndexJournalWriter; +import nu.marginalia.index.reverse.query.ReverseIndexEntrySourceBehavior; +import nu.marginalia.ranking.DomainRankings; +import nu.marginalia.lexicon.KeywordLexicon; +import nu.marginalia.lexicon.journal.KeywordLexiconJournal; +import nu.marginalia.test.TestUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,7 +29,7 @@ import java.util.stream.LongStream; class ReverseIndexConverterTest2 { KeywordLexicon keywordLexicon; - SearchIndexJournalWriterImpl writer; + IndexJournalWriter writer; Path indexFile; Path wordsFile1; @@ -51,12 +51,12 @@ class ReverseIndexConverterTest2 { dictionaryFile = Files.createTempFile("tmp", ".dict"); dictionaryFile.toFile().deleteOnExit(); - keywordLexicon = new KeywordLexicon(new KeywordLexiconJournal(dictionaryFile.toFile()), new OffHeapDictionaryHashMap(1L<<18)); + keywordLexicon = new KeywordLexicon(new KeywordLexiconJournal(dictionaryFile.toFile())); keywordLexicon.getOrInsert("0"); indexFile = Files.createTempFile("tmp", ".idx"); indexFile.toFile().deleteOnExit(); - writer = new SearchIndexJournalWriterImpl(keywordLexicon, indexFile.toFile()); + writer = new IndexJournalWriterImpl(keywordLexicon, indexFile); wordsFile1 = Files.createTempFile("words1", ".idx"); urlsFile1 = Files.createTempFile("urls1", ".idx"); @@ -76,7 +76,7 @@ class ReverseIndexConverterTest2 { Thread.sleep(1000); writer.forceWrite(); - var reader = new SearchIndexJournalReaderSingleFile(LongArray.mmapRead(indexFile)); + var reader = new IndexJournalReaderSingleCompressedFile(indexFile); wordsFile = dataDir.resolve("words.dat"); docsFile = dataDir.resolve("docs.dat"); @@ -97,9 +97,9 @@ class ReverseIndexConverterTest2 { long createId(long url, long domain) { return (domain << 32) | url; } - public void createEntry(SearchIndexJournalWriterImpl writer, KeywordLexicon keywordLexicon, int id) { + public void createEntry(IndexJournalWriter writer, KeywordLexicon keywordLexicon, int id) { int[] factors = getFactorsI(id); - var header = new SearchIndexJournalEntryHeader(factors.length, createId(id, id/20), id % 5); + var header = new IndexJournalEntryHeader(factors.length, createId(id, id/20), id % 5); long[] data = new long[factors.length*2]; for (int i = 0; i < factors.length; i++) { @@ -107,7 +107,7 @@ class ReverseIndexConverterTest2 { data[2*i + 1] = (i % 21 != 0) ? 0 : -factors[i]; } - writer.put(header, new SearchIndexJournalEntry(data)); + writer.put(header, new IndexJournalEntryData(data)); } @Test @@ -115,7 +115,7 @@ class ReverseIndexConverterTest2 { Path tmpDir = Path.of("/tmp"); - new ReverseIndexConverter(tmpDir, new SearchIndexJournalReaderSingleFile(LongArray.mmapRead(indexFile)), new DomainRankings(), wordsFile, docsFile).convert(); + new ReverseIndexConverter(tmpDir, new IndexJournalReaderSingleCompressedFile(indexFile), new DomainRankings(), wordsFile, docsFile).convert(); var reverseReader = new ReverseIndexReader(wordsFile, docsFile); @@ -140,7 +140,7 @@ class ReverseIndexConverterTest2 { Path tmpDir = Path.of("/tmp"); - new ReverseIndexConverter(tmpDir, new SearchIndexJournalReaderSingleFile(LongArray.mmapRead(indexFile), null, ReverseIndexPriorityParameters::filterPriorityRecord), new DomainRankings(), wordsFile, docsFile).convert(); + new ReverseIndexConverter(tmpDir, new IndexJournalReaderSingleCompressedFile(indexFile, null, ReverseIndexPriorityParameters::filterPriorityRecord), new DomainRankings(), wordsFile, docsFile).convert(); var reverseReader = new ReverseIndexReader(wordsFile, docsFile); diff --git a/index/index-reverse/src/test/java/nu/marginalia/test/TestUtil.java b/index/index-reverse/src/test/java/nu/marginalia/test/TestUtil.java new file mode 100644 index 00000000..44a489bb --- /dev/null +++ b/index/index-reverse/src/test/java/nu/marginalia/test/TestUtil.java @@ -0,0 +1,50 @@ +package nu.marginalia.test; + + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +public class TestUtil { + private static boolean isTempDir(Path dir) { + return dir.startsWith("/tmp") || dir.toString().contains("tmp"); + } + + public static void clearTempDir(Path dir) { + if (!isTempDir(dir)) { + throw new IllegalArgumentException("Refusing to recursively delete directory with that name"); + } + if (Files.isDirectory(dir)) { + for (File f : dir.toFile().listFiles()) { + File[] files = f.listFiles(); + if (files != null) { + Arrays.stream(files).map(File::toPath).forEach(TestUtil::clearTempDir); + } + System.out.println("Deleting " + f + " (" + fileSize(f.toPath()) + ")"); + f.delete(); + } + } + System.out.println("Deleting " + dir); + dir.toFile().delete(); + } + + private static String fileSize(Path path) { + try { + long sizeBytes = Files.size(path); + + if (sizeBytes > 1024 * 1024 * 1024) return round(sizeBytes / 1073741824.) + "Gb"; + if (sizeBytes > 1024 * 1024) return round(sizeBytes / 1048576.) + "Mb"; + if (sizeBytes > 1024) return round(sizeBytes / 1024.) + "Kb"; + return sizeBytes + "b"; + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static String round(double d) { + return String.format("%.2f", d); + } +} diff --git a/index/lexicon/build.gradle b/index/lexicon/build.gradle new file mode 100644 index 00000000..68646f95 --- /dev/null +++ b/index/lexicon/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':libraries:misc') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.guava + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/KeywordLexicon.java b/index/lexicon/src/main/java/nu/marginalia/lexicon/KeywordLexicon.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/KeywordLexicon.java rename to index/lexicon/src/main/java/nu/marginalia/lexicon/KeywordLexicon.java index 5f02cb98..40f9d73b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/KeywordLexicon.java +++ b/index/lexicon/src/main/java/nu/marginalia/lexicon/KeywordLexicon.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.index.lexicon; +package nu.marginalia.lexicon; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import io.prometheus.client.Gauge; import lombok.SneakyThrows; -import nu.marginalia.util.dict.DictionaryMap; -import nu.marginalia.wmsa.edge.index.lexicon.journal.KeywordLexiconJournal; +import nu.marginalia.dict.DictionaryMap; +import nu.marginalia.lexicon.journal.KeywordLexiconJournal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,15 +30,15 @@ public class KeywordLexicon implements AutoCloseable { private final KeywordLexiconJournal journal; @SneakyThrows - public KeywordLexicon(KeywordLexiconJournal keywordLexiconJournal, DictionaryMap reverseIndexHashMap) { + public KeywordLexicon(KeywordLexiconJournal keywordLexiconJournal) { journal = keywordLexiconJournal; - reverseIndex = reverseIndexHashMap; + reverseIndex = DictionaryMap.create(); logger.info("Creating dictionary writer"); if (!instances.compareAndSet(0, 1)) { - logger.error("MULTIPLE WRITER INSTANCES!"); + logger.error("MULTIPLE LEXICON INSTANCES!"); } journal.loadFile(bytes -> reverseIndex.put(hashFunction.hashBytes(bytes).padToLong())); @@ -60,10 +60,17 @@ public class KeywordLexicon implements AutoCloseable { final long key = hashFunction.hashBytes(bytes).padToLong(); int idx = getReadOnly(key); - if (idx >= 0) - return idx; + if (idx < 0) { + idx = insertNew(key, bytes); + } + + return idx; + } + + private int insertNew(long key, byte[] bytes) throws InterruptedException { Lock lock = memoryLock.writeLock(); + int idx; try { lock.lock(); @@ -111,7 +118,7 @@ public class KeywordLexicon implements AutoCloseable { @Override public void close() throws Exception { - logger.warn("Closing DictionaryWriter"); + logger.warn("Closing Lexicon"); journal.close(); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/KeywordLexiconReadOnlyView.java b/index/lexicon/src/main/java/nu/marginalia/lexicon/KeywordLexiconReadOnlyView.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/KeywordLexiconReadOnlyView.java rename to index/lexicon/src/main/java/nu/marginalia/lexicon/KeywordLexiconReadOnlyView.java index 485bb423..9cdef151 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/KeywordLexiconReadOnlyView.java +++ b/index/lexicon/src/main/java/nu/marginalia/lexicon/KeywordLexiconReadOnlyView.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.lexicon; +package nu.marginalia.lexicon; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/journal/KeywordLexiconJournal.java b/index/lexicon/src/main/java/nu/marginalia/lexicon/journal/KeywordLexiconJournal.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/journal/KeywordLexiconJournal.java rename to index/lexicon/src/main/java/nu/marginalia/lexicon/journal/KeywordLexiconJournal.java index c226c1e6..84a23247 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/journal/KeywordLexiconJournal.java +++ b/index/lexicon/src/main/java/nu/marginalia/lexicon/journal/KeywordLexiconJournal.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.lexicon.journal; +package nu.marginalia.lexicon.journal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/journal/KeywordLexiconJournalCommitQueue.java b/index/lexicon/src/main/java/nu/marginalia/lexicon/journal/KeywordLexiconJournalCommitQueue.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/journal/KeywordLexiconJournalCommitQueue.java rename to index/lexicon/src/main/java/nu/marginalia/lexicon/journal/KeywordLexiconJournalCommitQueue.java index 67d4043a..7c6a460f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/journal/KeywordLexiconJournalCommitQueue.java +++ b/index/lexicon/src/main/java/nu/marginalia/lexicon/journal/KeywordLexiconJournalCommitQueue.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.lexicon.journal; +package nu.marginalia.lexicon.journal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -7,7 +7,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -public class KeywordLexiconJournalCommitQueue { +class KeywordLexiconJournalCommitQueue { private final ArrayList commitQueue = new ArrayList<>(10_000); private final Logger logger = LoggerFactory.getLogger(getClass()); private static final long BACK_PRESSURE_LIMIT = 25_000; @@ -25,15 +25,19 @@ public class KeywordLexiconJournalCommitQueue { public synchronized List getQueuedEntries() { - if (commitQueue.isEmpty()) + List data; + if (commitQueue.isEmpty()) { return Collections.emptyList(); - var data = new ArrayList<>(commitQueue); - commitQueue.clear(); + } + else { + data = new ArrayList<>(commitQueue); + commitQueue.clear(); + } notifyAll(); if (data.size() > BACK_PRESSURE_LIMIT) { - logger.warn("Dictionary Journal Backpressure: {}", data.size()); + logger.warn("Lexicon Journal Backpressure: {}", data.size()); } return data; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/journal/KeywordLexiconJournalFile.java b/index/lexicon/src/main/java/nu/marginalia/lexicon/journal/KeywordLexiconJournalFile.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/journal/KeywordLexiconJournalFile.java rename to index/lexicon/src/main/java/nu/marginalia/lexicon/journal/KeywordLexiconJournalFile.java index 24ca03b1..7473e4df 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/lexicon/journal/KeywordLexiconJournalFile.java +++ b/index/lexicon/src/main/java/nu/marginalia/lexicon/journal/KeywordLexiconJournalFile.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.lexicon.journal; +package nu.marginalia.lexicon.journal; import lombok.SneakyThrows; import org.slf4j.Logger; @@ -22,20 +22,6 @@ public class KeywordLexiconJournalFile implements AutoCloseable { private final ReadWriteLock diskLock = new ReentrantReadWriteLock(); - @SneakyThrows - public static void main(String... args) { - if (args.length != 1) { - System.err.println("Dumps lexicon content to stdout"); - System.err.println("Arguments: filename"); - return; - } - - KeywordLexiconJournalFile lf = new KeywordLexiconJournalFile(new File(args[0])); - lf.loadFile(bytes -> { - System.out.println(new String(bytes)); - }); - } - public KeywordLexiconJournalFile(File journalFile) throws IOException { this.journalFileRAF = new RandomAccessFile(journalFile, "rw"); this.journalFile = journalFile; diff --git a/index/lexicon/src/test/java/nu/marginalia/lexicon/KeywordLexiconTest.java b/index/lexicon/src/test/java/nu/marginalia/lexicon/KeywordLexiconTest.java new file mode 100644 index 00000000..ca044e5e --- /dev/null +++ b/index/lexicon/src/test/java/nu/marginalia/lexicon/KeywordLexiconTest.java @@ -0,0 +1,77 @@ +package nu.marginalia.lexicon; + +import nu.marginalia.dict.OnHeapDictionaryMap; +import nu.marginalia.lexicon.journal.KeywordLexiconJournal; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class KeywordLexiconTest { + + private Path journalFile; + private KeywordLexicon lexicon; + + @BeforeEach + public void setUp() throws IOException { + journalFile = Files.createTempFile(getClass().getSimpleName(), ".dat"); + + var lexiconJournal = new KeywordLexiconJournal(journalFile.toFile()); + lexicon = new KeywordLexicon(lexiconJournal); + } + + @AfterEach + public void tearDown() throws Exception { + lexicon.close(); + Files.delete(journalFile); + } + + @Test + public void testConsistentInserts() { + int a = lexicon.getOrInsert("aaa"); + int b = lexicon.getOrInsert("bbb"); + int a2 = lexicon.getOrInsert("aaa"); + int c = lexicon.getOrInsert("ccc"); + + assertEquals(a, a2); + assertNotEquals(a, b); + assertNotEquals(a, c); + assertNotEquals(b, c); + } + + @Test + public void testInsertReplay() { + int a = lexicon.getOrInsert("aaa"); + int b = lexicon.getOrInsert("bbb"); + int c = lexicon.getOrInsert("ccc"); + + assertEquals(a, lexicon.getReadOnly("aaa")); + assertEquals(b, lexicon.getReadOnly("bbb")); + assertEquals(c, lexicon.getReadOnly("ccc")); + } + + @Test + public void testReload() throws IOException { + int a = lexicon.getOrInsert("aaa"); + int b = lexicon.getOrInsert("bbb"); + int c = lexicon.getOrInsert("ccc"); + lexicon.commitToDisk(); + + var lexiconJournal = new KeywordLexiconJournal(journalFile.toFile()); + try (var anotherLexicon = new KeywordLexicon(lexiconJournal)) { + assertEquals(a, anotherLexicon.getReadOnly("aaa")); + assertEquals(b, anotherLexicon.getReadOnly("bbb")); + assertEquals(c, anotherLexicon.getReadOnly("ccc")); + } + catch (Exception ex) { + Assertions.fail("???", ex); + } + } +} diff --git a/index/readme.md b/index/readme.md new file mode 100644 index 00000000..dce903d2 --- /dev/null +++ b/index/readme.md @@ -0,0 +1,17 @@ +# Index + +These are components that offer functionality for the [index-service](../services-core/index-service). + +## Indexes + +There are two indexes with accompanying tools for constructing them. + +* [index-forward](index-forward/) is the `document->word` index containing metadata +about each word, such as its position. +* [index-reverse](index-reverse/) is the `word->document` index. + +These indices rely heavily on the [libraries/btree](../libraries/btree) and [libraries/btree](../libraries/array) components. +# Libraries +* [index-query](index-query/) contains structures for evaluating search queries. +* [index-journal](index-journal/) contains tools for writing and reading index data. +* [lexicon](lexicon/) contains a mapping between words' string representation and an unique integer identifier. \ No newline at end of file diff --git a/libraries/array/build.gradle b/libraries/array/build.gradle new file mode 100644 index 00000000..65574dfd --- /dev/null +++ b/libraries/array/build.gradle @@ -0,0 +1,30 @@ +plugins { + id 'java' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.notnull + implementation libs.commons.lang3 + implementation libs.fastutil + implementation libs.lz4 + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/IntArray.java b/libraries/array/src/main/java/nu/marginalia/array/IntArray.java similarity index 78% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/IntArray.java rename to libraries/array/src/main/java/nu/marginalia/array/IntArray.java index f86d536f..7f882164 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/IntArray.java +++ b/libraries/array/src/main/java/nu/marginalia/array/IntArray.java @@ -1,14 +1,14 @@ -package nu.marginalia.util.array; +package nu.marginalia.array; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.algo.IntArrayBase; -import nu.marginalia.util.array.algo.IntArraySearch; -import nu.marginalia.util.array.algo.IntArraySort; -import nu.marginalia.util.array.algo.IntArrayTransformations; -import nu.marginalia.util.array.delegate.ShiftedIntArray; -import nu.marginalia.util.array.page.IntArrayPage; -import nu.marginalia.util.array.page.PagingIntArray; -import nu.marginalia.util.array.scheme.ArrayPartitioningScheme; +import nu.marginalia.array.algo.IntArrayBase; +import nu.marginalia.array.algo.IntArraySearch; +import nu.marginalia.array.algo.IntArraySort; +import nu.marginalia.array.algo.IntArrayTransformations; +import nu.marginalia.array.delegate.ShiftedIntArray; +import nu.marginalia.array.page.IntArrayPage; +import nu.marginalia.array.page.PagingIntArray; +import nu.marginalia.array.scheme.ArrayPartitioningScheme; import java.io.IOException; import java.nio.file.Files; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/LongArray.java b/libraries/array/src/main/java/nu/marginalia/array/LongArray.java similarity index 78% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/LongArray.java rename to libraries/array/src/main/java/nu/marginalia/array/LongArray.java index 82543f4a..82a43bb1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/LongArray.java +++ b/libraries/array/src/main/java/nu/marginalia/array/LongArray.java @@ -1,14 +1,14 @@ -package nu.marginalia.util.array; +package nu.marginalia.array; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.algo.LongArrayBase; -import nu.marginalia.util.array.algo.LongArraySearch; -import nu.marginalia.util.array.algo.LongArraySort; -import nu.marginalia.util.array.algo.LongArrayTransformations; -import nu.marginalia.util.array.delegate.ShiftedLongArray; -import nu.marginalia.util.array.page.LongArrayPage; -import nu.marginalia.util.array.page.PagingLongArray; -import nu.marginalia.util.array.scheme.ArrayPartitioningScheme; +import nu.marginalia.array.algo.LongArrayBase; +import nu.marginalia.array.algo.LongArraySearch; +import nu.marginalia.array.algo.LongArraySort; +import nu.marginalia.array.algo.LongArrayTransformations; +import nu.marginalia.array.delegate.ShiftedLongArray; +import nu.marginalia.array.page.LongArrayPage; +import nu.marginalia.array.page.PagingLongArray; +import nu.marginalia.array.scheme.ArrayPartitioningScheme; import java.io.IOException; import java.nio.file.Files; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/BulkTransferArray.java b/libraries/array/src/main/java/nu/marginalia/array/algo/BulkTransferArray.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/BulkTransferArray.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/BulkTransferArray.java index bf0df57d..00e8f44f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/BulkTransferArray.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/BulkTransferArray.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; public interface BulkTransferArray { diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArrayBase.java b/libraries/array/src/main/java/nu/marginalia/array/algo/IntArrayBase.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArrayBase.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/IntArrayBase.java index 94e462b7..e487cbbb 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArrayBase.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/IntArrayBase.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; import java.io.IOException; import java.nio.IntBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArraySearch.java b/libraries/array/src/main/java/nu/marginalia/array/algo/IntArraySearch.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArraySearch.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/IntArraySearch.java index 104c5800..e4f925af 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArraySearch.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/IntArraySearch.java @@ -1,8 +1,6 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.buffer.IntQueryBuffer; - -import static nu.marginalia.util.array.algo.LongArraySearch.encodeSearchMiss; +import nu.marginalia.array.buffer.IntQueryBuffer; public interface IntArraySearch extends IntArrayBase { @@ -18,7 +16,7 @@ public interface IntArraySearch extends IntArrayBase { if (val > key) break; } - return encodeSearchMiss(pos - 1); + return LongArraySearch.encodeSearchMiss(pos - 1); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArraySort.java b/libraries/array/src/main/java/nu/marginalia/array/algo/IntArraySort.java similarity index 99% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArraySort.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/IntArraySort.java index e6ba6c87..c965b229 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArraySort.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/IntArraySort.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; import java.io.IOException; import java.nio.IntBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArrayTransformations.java b/libraries/array/src/main/java/nu/marginalia/array/algo/IntArrayTransformations.java similarity index 77% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArrayTransformations.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/IntArrayTransformations.java index c087f60e..85125d4f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/IntArrayTransformations.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/IntArrayTransformations.java @@ -1,9 +1,9 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.functional.IntBinaryIOOperation; -import nu.marginalia.util.array.functional.IntIOTransformer; -import nu.marginalia.util.array.functional.IntTransformer; -import nu.marginalia.util.array.functional.LongIntConsumer; +import nu.marginalia.array.functional.IntBinaryIOOperation; +import nu.marginalia.array.functional.IntIOTransformer; +import nu.marginalia.array.functional.IntTransformer; +import nu.marginalia.array.functional.LongIntConsumer; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArrayBase.java b/libraries/array/src/main/java/nu/marginalia/array/algo/LongArrayBase.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArrayBase.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/LongArrayBase.java index 216e089b..d51bf107 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArrayBase.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/LongArrayBase.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; import java.io.IOException; import java.nio.LongBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArraySearch.java b/libraries/array/src/main/java/nu/marginalia/array/algo/LongArraySearch.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArraySearch.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/LongArraySearch.java index 2f2579b2..d9020a6e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArraySearch.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/LongArraySearch.java @@ -1,6 +1,6 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.buffer.LongQueryBuffer; +import nu.marginalia.array.buffer.LongQueryBuffer; public interface LongArraySearch extends LongArrayBase { diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArraySort.java b/libraries/array/src/main/java/nu/marginalia/array/algo/LongArraySort.java similarity index 99% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArraySort.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/LongArraySort.java index 5c1fb10f..3ca5987f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArraySort.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/LongArraySort.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; import java.io.IOException; import java.nio.LongBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArrayTransformations.java b/libraries/array/src/main/java/nu/marginalia/array/algo/LongArrayTransformations.java similarity index 77% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArrayTransformations.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/LongArrayTransformations.java index 3ff4b82f..7d59466a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/LongArrayTransformations.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/LongArrayTransformations.java @@ -1,9 +1,9 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.functional.LongBinaryIOOperation; -import nu.marginalia.util.array.functional.LongIOTransformer; -import nu.marginalia.util.array.functional.LongLongConsumer; -import nu.marginalia.util.array.functional.LongTransformer; +import nu.marginalia.array.functional.LongBinaryIOOperation; +import nu.marginalia.array.functional.LongIOTransformer; +import nu.marginalia.array.functional.LongLongConsumer; +import nu.marginalia.array.functional.LongTransformer; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/SortingContext.java b/libraries/array/src/main/java/nu/marginalia/array/algo/SortingContext.java similarity index 71% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/algo/SortingContext.java rename to libraries/array/src/main/java/nu/marginalia/array/algo/SortingContext.java index 0bd436fb..aae1ed04 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/algo/SortingContext.java +++ b/libraries/array/src/main/java/nu/marginalia/array/algo/SortingContext.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; import java.nio.file.Path; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/buffer/IntQueryBuffer.java b/libraries/array/src/main/java/nu/marginalia/array/buffer/IntQueryBuffer.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/buffer/IntQueryBuffer.java rename to libraries/array/src/main/java/nu/marginalia/array/buffer/IntQueryBuffer.java index 75d829cd..bd88ccc0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/buffer/IntQueryBuffer.java +++ b/libraries/array/src/main/java/nu/marginalia/array/buffer/IntQueryBuffer.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.buffer; +package nu.marginalia.array.buffer; import java.util.Arrays; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/buffer/LongQueryBuffer.java b/libraries/array/src/main/java/nu/marginalia/array/buffer/LongQueryBuffer.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/buffer/LongQueryBuffer.java rename to libraries/array/src/main/java/nu/marginalia/array/buffer/LongQueryBuffer.java index 6bea19ff..ed0f36fb 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/buffer/LongQueryBuffer.java +++ b/libraries/array/src/main/java/nu/marginalia/array/buffer/LongQueryBuffer.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.buffer; +package nu.marginalia.array.buffer; import java.util.Arrays; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ReferenceImplIntArrayDelegate.java b/libraries/array/src/main/java/nu/marginalia/array/delegate/ReferenceImplIntArrayDelegate.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ReferenceImplIntArrayDelegate.java rename to libraries/array/src/main/java/nu/marginalia/array/delegate/ReferenceImplIntArrayDelegate.java index bdb4eccb..ed2767e5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ReferenceImplIntArrayDelegate.java +++ b/libraries/array/src/main/java/nu/marginalia/array/delegate/ReferenceImplIntArrayDelegate.java @@ -1,7 +1,7 @@ -package nu.marginalia.util.array.delegate; +package nu.marginalia.array.delegate; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.IntArray; +import nu.marginalia.array.IntArray; import java.io.IOException; import java.nio.channels.FileChannel; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ReferenceImplLongArrayDelegate.java b/libraries/array/src/main/java/nu/marginalia/array/delegate/ReferenceImplLongArrayDelegate.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ReferenceImplLongArrayDelegate.java rename to libraries/array/src/main/java/nu/marginalia/array/delegate/ReferenceImplLongArrayDelegate.java index f33f565c..15d8761f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ReferenceImplLongArrayDelegate.java +++ b/libraries/array/src/main/java/nu/marginalia/array/delegate/ReferenceImplLongArrayDelegate.java @@ -1,7 +1,7 @@ -package nu.marginalia.util.array.delegate; +package nu.marginalia.array.delegate; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.LongArray; +import nu.marginalia.array.LongArray; import java.io.IOException; import java.nio.channels.FileChannel; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ShiftedIntArray.java b/libraries/array/src/main/java/nu/marginalia/array/delegate/ShiftedIntArray.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ShiftedIntArray.java rename to libraries/array/src/main/java/nu/marginalia/array/delegate/ShiftedIntArray.java index a920c99a..7e1974c1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ShiftedIntArray.java +++ b/libraries/array/src/main/java/nu/marginalia/array/delegate/ShiftedIntArray.java @@ -1,13 +1,13 @@ -package nu.marginalia.util.array.delegate; +package nu.marginalia.array.delegate; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.IntArray; -import nu.marginalia.util.array.algo.SortingContext; -import nu.marginalia.util.array.buffer.IntQueryBuffer; -import nu.marginalia.util.array.functional.IntBinaryIOOperation; -import nu.marginalia.util.array.functional.IntIOTransformer; -import nu.marginalia.util.array.functional.IntTransformer; -import nu.marginalia.util.array.functional.LongIntConsumer; +import nu.marginalia.array.IntArray; +import nu.marginalia.array.algo.SortingContext; +import nu.marginalia.array.buffer.IntQueryBuffer; +import nu.marginalia.array.functional.IntBinaryIOOperation; +import nu.marginalia.array.functional.IntIOTransformer; +import nu.marginalia.array.functional.IntTransformer; +import nu.marginalia.array.functional.LongIntConsumer; import java.io.IOException; import java.nio.IntBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ShiftedLongArray.java b/libraries/array/src/main/java/nu/marginalia/array/delegate/ShiftedLongArray.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ShiftedLongArray.java rename to libraries/array/src/main/java/nu/marginalia/array/delegate/ShiftedLongArray.java index 53a4f89b..eb681f26 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/delegate/ShiftedLongArray.java +++ b/libraries/array/src/main/java/nu/marginalia/array/delegate/ShiftedLongArray.java @@ -1,14 +1,14 @@ -package nu.marginalia.util.array.delegate; +package nu.marginalia.array.delegate; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.algo.LongArraySearch; -import nu.marginalia.util.array.algo.SortingContext; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.array.functional.LongBinaryIOOperation; -import nu.marginalia.util.array.functional.LongIOTransformer; -import nu.marginalia.util.array.functional.LongLongConsumer; -import nu.marginalia.util.array.functional.LongTransformer; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.algo.LongArraySearch; +import nu.marginalia.array.algo.SortingContext; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.array.functional.LongBinaryIOOperation; +import nu.marginalia.array.functional.LongIOTransformer; +import nu.marginalia.array.functional.LongLongConsumer; +import nu.marginalia.array.functional.LongTransformer; import java.io.IOException; import java.nio.LongBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeCall.java b/libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeCall.java similarity index 65% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeCall.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeCall.java index 5f96462d..90ed5ad8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeCall.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeCall.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; public interface AddressRangeCall { void apply(T array, int start, int end); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeCallIO.java b/libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeCallIO.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeCallIO.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeCallIO.java index a7fa2867..a52c1941 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeCallIO.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeCallIO.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeIntFunction.java b/libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeIntFunction.java similarity index 67% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeIntFunction.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeIntFunction.java index 93b3b58f..30113292 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeIntFunction.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeIntFunction.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; public interface AddressRangeIntFunction { int apply(T array, int start, int end); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeLongFunction.java b/libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeLongFunction.java similarity index 67% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeLongFunction.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeLongFunction.java index ef214419..d97564be 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/AddressRangeLongFunction.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/AddressRangeLongFunction.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; public interface AddressRangeLongFunction { long apply(T array, int start, int end); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/IntBinaryIOOperation.java b/libraries/array/src/main/java/nu/marginalia/array/functional/IntBinaryIOOperation.java similarity index 73% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/IntBinaryIOOperation.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/IntBinaryIOOperation.java index 6761f633..5e3082ed 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/IntBinaryIOOperation.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/IntBinaryIOOperation.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/IntIOTransformer.java b/libraries/array/src/main/java/nu/marginalia/array/functional/IntIOTransformer.java similarity index 73% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/IntIOTransformer.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/IntIOTransformer.java index 96f84477..02675e27 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/IntIOTransformer.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/IntIOTransformer.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/IntTransformer.java b/libraries/array/src/main/java/nu/marginalia/array/functional/IntTransformer.java similarity index 62% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/IntTransformer.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/IntTransformer.java index c1ba44e6..8001299c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/IntTransformer.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/IntTransformer.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; public interface IntTransformer { int transform(long pos, int old); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongBinaryIOOperation.java b/libraries/array/src/main/java/nu/marginalia/array/functional/LongBinaryIOOperation.java similarity index 74% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongBinaryIOOperation.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/LongBinaryIOOperation.java index c097c016..8141d624 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongBinaryIOOperation.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/LongBinaryIOOperation.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongIOTransformer.java b/libraries/array/src/main/java/nu/marginalia/array/functional/LongIOTransformer.java similarity index 73% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongIOTransformer.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/LongIOTransformer.java index 997bcfd8..62fe490a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongIOTransformer.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/LongIOTransformer.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongIntConsumer.java b/libraries/array/src/main/java/nu/marginalia/array/functional/LongIntConsumer.java similarity index 62% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongIntConsumer.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/LongIntConsumer.java index 781ebe9e..a8a4ab2b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongIntConsumer.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/LongIntConsumer.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; public interface LongIntConsumer { void accept(long pos, int val); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongLongConsumer.java b/libraries/array/src/main/java/nu/marginalia/array/functional/LongLongConsumer.java similarity index 62% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongLongConsumer.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/LongLongConsumer.java index 6390d59e..4707673d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongLongConsumer.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/LongLongConsumer.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; public interface LongLongConsumer { void accept(long pos, long val); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongTransformer.java b/libraries/array/src/main/java/nu/marginalia/array/functional/LongTransformer.java similarity index 63% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongTransformer.java rename to libraries/array/src/main/java/nu/marginalia/array/functional/LongTransformer.java index 4f998646..8ab0ae7e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functional/LongTransformer.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functional/LongTransformer.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.functional; +package nu.marginalia.array.functional; public interface LongTransformer { long transform(long pos, long old); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functor/IntIOFolder.java b/libraries/array/src/main/java/nu/marginalia/array/functor/IntIOFolder.java similarity index 67% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functor/IntIOFolder.java rename to libraries/array/src/main/java/nu/marginalia/array/functor/IntIOFolder.java index b3ee83ce..ee749020 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functor/IntIOFolder.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functor/IntIOFolder.java @@ -1,8 +1,8 @@ -package nu.marginalia.util.array.functor; +package nu.marginalia.array.functor; -import nu.marginalia.util.array.functional.AddressRangeCallIO; -import nu.marginalia.util.array.functional.IntBinaryIOOperation; -import nu.marginalia.util.array.page.IntArrayPage; +import nu.marginalia.array.functional.AddressRangeCallIO; +import nu.marginalia.array.functional.IntBinaryIOOperation; +import nu.marginalia.array.page.IntArrayPage; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/functor/LongIOFolder.java b/libraries/array/src/main/java/nu/marginalia/array/functor/LongIOFolder.java similarity index 67% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/functor/LongIOFolder.java rename to libraries/array/src/main/java/nu/marginalia/array/functor/LongIOFolder.java index ce9e796f..2f65d54e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/functor/LongIOFolder.java +++ b/libraries/array/src/main/java/nu/marginalia/array/functor/LongIOFolder.java @@ -1,8 +1,8 @@ -package nu.marginalia.util.array.functor; +package nu.marginalia.array.functor; -import nu.marginalia.util.array.functional.AddressRangeCallIO; -import nu.marginalia.util.array.functional.LongBinaryIOOperation; -import nu.marginalia.util.array.page.LongArrayPage; +import nu.marginalia.array.functional.AddressRangeCallIO; +import nu.marginalia.array.functional.LongBinaryIOOperation; +import nu.marginalia.array.page.LongArrayPage; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/AbstractPagingArray.java b/libraries/array/src/main/java/nu/marginalia/array/page/AbstractPagingArray.java similarity index 87% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/page/AbstractPagingArray.java rename to libraries/array/src/main/java/nu/marginalia/array/page/AbstractPagingArray.java index c772d43e..27b35dc5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/AbstractPagingArray.java +++ b/libraries/array/src/main/java/nu/marginalia/array/page/AbstractPagingArray.java @@ -1,14 +1,14 @@ -package nu.marginalia.util.array.page; +package nu.marginalia.array.page; -import nu.marginalia.util.array.algo.BulkTransferArray; -import nu.marginalia.util.array.functional.AddressRangeCall; -import nu.marginalia.util.array.functional.AddressRangeCallIO; -import nu.marginalia.util.array.scheme.ArrayPartitioningScheme; +import nu.marginalia.array.algo.BulkTransferArray; +import nu.marginalia.array.functional.AddressRangeCall; +import nu.marginalia.array.functional.AddressRangeCallIO; +import nu.marginalia.array.scheme.ArrayPartitioningScheme; import java.io.IOException; -import static nu.marginalia.util.array.algo.LongArraySearch.decodeSearchMiss; -import static nu.marginalia.util.array.algo.LongArraySearch.encodeSearchMiss; +import static nu.marginalia.array.algo.LongArraySearch.decodeSearchMiss; +import static nu.marginalia.array.algo.LongArraySearch.encodeSearchMiss; public class AbstractPagingArray, B> { final T[] pages; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/IntArrayPage.java b/libraries/array/src/main/java/nu/marginalia/array/page/IntArrayPage.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/page/IntArrayPage.java rename to libraries/array/src/main/java/nu/marginalia/array/page/IntArrayPage.java index b2270c8c..acb29259 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/IntArrayPage.java +++ b/libraries/array/src/main/java/nu/marginalia/array/page/IntArrayPage.java @@ -1,7 +1,7 @@ -package nu.marginalia.util.array.page; +package nu.marginalia.array.page; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.IntArray; +import nu.marginalia.array.IntArray; import java.io.IOException; import java.nio.ByteBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/LongArrayPage.java b/libraries/array/src/main/java/nu/marginalia/array/page/LongArrayPage.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/page/LongArrayPage.java rename to libraries/array/src/main/java/nu/marginalia/array/page/LongArrayPage.java index ed9e3c96..b27df533 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/LongArrayPage.java +++ b/libraries/array/src/main/java/nu/marginalia/array/page/LongArrayPage.java @@ -1,8 +1,8 @@ -package nu.marginalia.util.array.page; +package nu.marginalia.array.page; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.trace.ArrayTrace; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.trace.ArrayTrace; import java.io.IOException; import java.nio.ByteBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/PagingIntArray.java b/libraries/array/src/main/java/nu/marginalia/array/page/PagingIntArray.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/page/PagingIntArray.java rename to libraries/array/src/main/java/nu/marginalia/array/page/PagingIntArray.java index 6b44fecb..fff17c46 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/PagingIntArray.java +++ b/libraries/array/src/main/java/nu/marginalia/array/page/PagingIntArray.java @@ -1,16 +1,16 @@ -package nu.marginalia.util.array.page; +package nu.marginalia.array.page; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.IntArray; -import nu.marginalia.util.array.algo.SortingContext; -import nu.marginalia.util.array.buffer.IntQueryBuffer; -import nu.marginalia.util.array.delegate.ReferenceImplIntArrayDelegate; -import nu.marginalia.util.array.functional.IntBinaryIOOperation; -import nu.marginalia.util.array.functional.IntIOTransformer; -import nu.marginalia.util.array.functional.IntTransformer; -import nu.marginalia.util.array.functional.LongIntConsumer; -import nu.marginalia.util.array.functor.IntIOFolder; -import nu.marginalia.util.array.scheme.ArrayPartitioningScheme; +import nu.marginalia.array.IntArray; +import nu.marginalia.array.algo.SortingContext; +import nu.marginalia.array.buffer.IntQueryBuffer; +import nu.marginalia.array.delegate.ReferenceImplIntArrayDelegate; +import nu.marginalia.array.functional.IntBinaryIOOperation; +import nu.marginalia.array.functional.IntIOTransformer; +import nu.marginalia.array.functional.IntTransformer; +import nu.marginalia.array.functional.LongIntConsumer; +import nu.marginalia.array.functor.IntIOFolder; +import nu.marginalia.array.scheme.ArrayPartitioningScheme; import java.io.IOException; import java.nio.IntBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/PagingLongArray.java b/libraries/array/src/main/java/nu/marginalia/array/page/PagingLongArray.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/page/PagingLongArray.java rename to libraries/array/src/main/java/nu/marginalia/array/page/PagingLongArray.java index 597979ef..7aa3b1d3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/PagingLongArray.java +++ b/libraries/array/src/main/java/nu/marginalia/array/page/PagingLongArray.java @@ -1,16 +1,16 @@ -package nu.marginalia.util.array.page; +package nu.marginalia.array.page; import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.algo.SortingContext; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.array.delegate.ReferenceImplLongArrayDelegate; -import nu.marginalia.util.array.functional.LongBinaryIOOperation; -import nu.marginalia.util.array.functional.LongIOTransformer; -import nu.marginalia.util.array.functional.LongLongConsumer; -import nu.marginalia.util.array.functional.LongTransformer; -import nu.marginalia.util.array.functor.LongIOFolder; -import nu.marginalia.util.array.scheme.ArrayPartitioningScheme; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.algo.SortingContext; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.array.delegate.ReferenceImplLongArrayDelegate; +import nu.marginalia.array.functional.LongBinaryIOOperation; +import nu.marginalia.array.functional.LongIOTransformer; +import nu.marginalia.array.functional.LongLongConsumer; +import nu.marginalia.array.functional.LongTransformer; +import nu.marginalia.array.functor.LongIOFolder; +import nu.marginalia.array.scheme.ArrayPartitioningScheme; import java.io.IOException; import java.nio.LongBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/PartitionPage.java b/libraries/array/src/main/java/nu/marginalia/array/page/PartitionPage.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/page/PartitionPage.java rename to libraries/array/src/main/java/nu/marginalia/array/page/PartitionPage.java index c324157c..56afd660 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/page/PartitionPage.java +++ b/libraries/array/src/main/java/nu/marginalia/array/page/PartitionPage.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.page; +package nu.marginalia.array.page; import java.io.IOException; import java.nio.ByteBuffer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/scheme/ArrayPartitioningScheme.java b/libraries/array/src/main/java/nu/marginalia/array/scheme/ArrayPartitioningScheme.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/scheme/ArrayPartitioningScheme.java rename to libraries/array/src/main/java/nu/marginalia/array/scheme/ArrayPartitioningScheme.java index a8063a17..e705d0a3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/scheme/ArrayPartitioningScheme.java +++ b/libraries/array/src/main/java/nu/marginalia/array/scheme/ArrayPartitioningScheme.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.scheme; +package nu.marginalia.array.scheme; public interface ArrayPartitioningScheme { diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/scheme/PowerOf2PartitioningScheme.java b/libraries/array/src/main/java/nu/marginalia/array/scheme/PowerOf2PartitioningScheme.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/scheme/PowerOf2PartitioningScheme.java rename to libraries/array/src/main/java/nu/marginalia/array/scheme/PowerOf2PartitioningScheme.java index 20bb453e..98dfac8f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/scheme/PowerOf2PartitioningScheme.java +++ b/libraries/array/src/main/java/nu/marginalia/array/scheme/PowerOf2PartitioningScheme.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.scheme; +package nu.marginalia.array.scheme; public class PowerOf2PartitioningScheme implements ArrayPartitioningScheme { final int partitionSize; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/scheme/SequentialPartitioningScheme.java b/libraries/array/src/main/java/nu/marginalia/array/scheme/SequentialPartitioningScheme.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/scheme/SequentialPartitioningScheme.java rename to libraries/array/src/main/java/nu/marginalia/array/scheme/SequentialPartitioningScheme.java index 19af52d1..7bd3f0a7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/scheme/SequentialPartitioningScheme.java +++ b/libraries/array/src/main/java/nu/marginalia/array/scheme/SequentialPartitioningScheme.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.scheme; +package nu.marginalia.array.scheme; public class SequentialPartitioningScheme implements ArrayPartitioningScheme { diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/trace/ArrayTrace.java b/libraries/array/src/main/java/nu/marginalia/array/trace/ArrayTrace.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/trace/ArrayTrace.java rename to libraries/array/src/main/java/nu/marginalia/array/trace/ArrayTrace.java index 38e08ede..bd3f47b7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/trace/ArrayTrace.java +++ b/libraries/array/src/main/java/nu/marginalia/array/trace/ArrayTrace.java @@ -1,6 +1,6 @@ -package nu.marginalia.util.array.trace; +package nu.marginalia.array.trace; -import nu.marginalia.util.array.LongArray; +import nu.marginalia.array.LongArray; import java.nio.file.Path; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/trace/ArrayTraceViz.java b/libraries/array/src/main/java/nu/marginalia/array/trace/ArrayTraceViz.java similarity index 99% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/trace/ArrayTraceViz.java rename to libraries/array/src/main/java/nu/marginalia/array/trace/ArrayTraceViz.java index babf727f..87527ce1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/trace/ArrayTraceViz.java +++ b/libraries/array/src/main/java/nu/marginalia/array/trace/ArrayTraceViz.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.trace; +package nu.marginalia.array.trace; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/trace/FileTrace.java b/libraries/array/src/main/java/nu/marginalia/array/trace/FileTrace.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/trace/FileTrace.java rename to libraries/array/src/main/java/nu/marginalia/array/trace/FileTrace.java index b1fe9c57..3779ea1a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/trace/FileTrace.java +++ b/libraries/array/src/main/java/nu/marginalia/array/trace/FileTrace.java @@ -1,6 +1,6 @@ -package nu.marginalia.util.array.trace; +package nu.marginalia.array.trace; -import nu.marginalia.util.array.LongArray; +import nu.marginalia.array.LongArray; import java.io.IOException; import java.io.PrintStream; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/array/trace/NullTrace.java b/libraries/array/src/main/java/nu/marginalia/array/trace/NullTrace.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/util/array/trace/NullTrace.java rename to libraries/array/src/main/java/nu/marginalia/array/trace/NullTrace.java index 20e2125f..52bca8c9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/array/trace/NullTrace.java +++ b/libraries/array/src/main/java/nu/marginalia/array/trace/NullTrace.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.trace; +package nu.marginalia.array.trace; public class NullTrace implements ArrayTrace { diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/array/IntLowBitPartitioningSchemeTest.java b/libraries/array/src/test/java/nu/marginalia/array/IntLowBitPartitioningSchemeTest.java similarity index 83% rename from marginalia_nu/src/test/java/nu/marginalia/util/array/IntLowBitPartitioningSchemeTest.java rename to libraries/array/src/test/java/nu/marginalia/array/IntLowBitPartitioningSchemeTest.java index 25c42338..cc5b8cd3 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/array/IntLowBitPartitioningSchemeTest.java +++ b/libraries/array/src/test/java/nu/marginalia/array/IntLowBitPartitioningSchemeTest.java @@ -1,6 +1,6 @@ -package nu.marginalia.util.array; +package nu.marginalia.array; -import nu.marginalia.util.array.scheme.SequentialPartitioningScheme; +import nu.marginalia.array.scheme.SequentialPartitioningScheme; import org.junit.jupiter.api.Test; class IntLowBitPartitioningSchemeTest { diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/array/PagingIntArrayTest.java b/libraries/array/src/test/java/nu/marginalia/array/PagingIntArrayTest.java similarity index 93% rename from marginalia_nu/src/test/java/nu/marginalia/util/array/PagingIntArrayTest.java rename to libraries/array/src/test/java/nu/marginalia/array/PagingIntArrayTest.java index e6acfac8..6b86d8e5 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/array/PagingIntArrayTest.java +++ b/libraries/array/src/test/java/nu/marginalia/array/PagingIntArrayTest.java @@ -1,8 +1,8 @@ -package nu.marginalia.util.array; +package nu.marginalia.array; -import nu.marginalia.util.array.page.PagingIntArray; -import nu.marginalia.util.array.page.PagingLongArray; -import nu.marginalia.util.array.scheme.SequentialPartitioningScheme; +import nu.marginalia.array.page.PagingIntArray; +import nu.marginalia.array.page.PagingLongArray; +import nu.marginalia.array.scheme.SequentialPartitioningScheme; import nu.marginalia.util.test.TestUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/IntArraySearchTest.java b/libraries/array/src/test/java/nu/marginalia/array/algo/IntArraySearchTest.java similarity index 93% rename from marginalia_nu/src/test/java/nu/marginalia/util/array/algo/IntArraySearchTest.java rename to libraries/array/src/test/java/nu/marginalia/array/algo/IntArraySearchTest.java index c000c16f..48eea698 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/IntArraySearchTest.java +++ b/libraries/array/src/test/java/nu/marginalia/array/algo/IntArraySearchTest.java @@ -1,9 +1,9 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.IntArray; -import nu.marginalia.util.array.buffer.IntQueryBuffer; -import nu.marginalia.util.array.page.PagingIntArray; -import nu.marginalia.util.array.scheme.PowerOf2PartitioningScheme; +import nu.marginalia.array.IntArray; +import nu.marginalia.array.buffer.IntQueryBuffer; +import nu.marginalia.array.page.PagingIntArray; +import nu.marginalia.array.scheme.PowerOf2PartitioningScheme; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/IntArraySortTest.java b/libraries/array/src/test/java/nu/marginalia/array/algo/IntArraySortTest.java similarity index 84% rename from marginalia_nu/src/test/java/nu/marginalia/util/array/algo/IntArraySortTest.java rename to libraries/array/src/test/java/nu/marginalia/array/algo/IntArraySortTest.java index 6bcda5b2..67a1ab6b 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/IntArraySortTest.java +++ b/libraries/array/src/test/java/nu/marginalia/array/algo/IntArraySortTest.java @@ -1,9 +1,9 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.IntArray; -import nu.marginalia.util.array.page.IntArrayPage; -import nu.marginalia.util.array.page.PagingIntArray; -import nu.marginalia.util.array.scheme.PowerOf2PartitioningScheme; +import nu.marginalia.array.IntArray; +import nu.marginalia.array.page.IntArrayPage; +import nu.marginalia.array.page.PagingIntArray; +import nu.marginalia.array.scheme.PowerOf2PartitioningScheme; import nu.marginalia.util.test.TestUtil; import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.Assertions; @@ -76,6 +76,7 @@ class IntArraySortTest { for (int i = 0; i < values.length; i++) { values[i] = i; } + ArrayUtils.shuffle(values); int sentinelA = 0xFEEDBEEF; @@ -117,36 +118,36 @@ class IntArraySortTest { @Test void insertionSort() { basic.insertionSort(0, size); - Assertions.assertTrue(basic.isSorted(0, 128)); + assertTrue(basic.isSorted(0, 128)); paged.insertionSort(0, size); - Assertions.assertTrue(paged.isSorted(0, 128)); + assertTrue(paged.isSorted(0, 128)); shifted.insertionSort(0, size); - Assertions.assertTrue(shifted.isSorted(0, 128)); + assertTrue(shifted.isSorted(0, 128)); } @Test void quickSort() { basic.quickSort(0, size); - Assertions.assertTrue(basic.isSorted(0, size)); + assertTrue(basic.isSorted(0, size)); paged.quickSort(0, size); - Assertions.assertTrue(paged.isSorted(0, size)); + assertTrue(paged.isSorted(0, size)); shifted.quickSort(0, size); - Assertions.assertTrue(shifted.isSorted(0, 128)); + assertTrue(shifted.isSorted(0, 128)); } @Test void mergeSort() throws IOException { basic.mergeSort(0, size, Path.of("/tmp")); - Assertions.assertTrue(basic.isSorted(0, size)); + assertTrue(basic.isSorted(0, size)); paged.mergeSort(0, size, Path.of("/tmp")); - Assertions.assertTrue(paged.isSorted(0, size)); + assertTrue(paged.isSorted(0, size)); shifted.mergeSort(0, size, Path.of("/tmp")); - Assertions.assertTrue(shifted.isSorted(0, 128)); + assertTrue(shifted.isSorted(0, 128)); } } \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/IntArrayTransformationsTest.java b/libraries/array/src/test/java/nu/marginalia/array/algo/IntArrayTransformationsTest.java similarity index 89% rename from marginalia_nu/src/test/java/nu/marginalia/util/array/algo/IntArrayTransformationsTest.java rename to libraries/array/src/test/java/nu/marginalia/array/algo/IntArrayTransformationsTest.java index 8b9b9773..bd821d69 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/IntArrayTransformationsTest.java +++ b/libraries/array/src/test/java/nu/marginalia/array/algo/IntArrayTransformationsTest.java @@ -1,9 +1,9 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.IntArray; -import nu.marginalia.util.array.page.IntArrayPage; -import nu.marginalia.util.array.page.PagingIntArray; -import nu.marginalia.util.array.scheme.PowerOf2PartitioningScheme; +import nu.marginalia.array.IntArray; +import nu.marginalia.array.page.IntArrayPage; +import nu.marginalia.array.page.PagingIntArray; +import nu.marginalia.array.scheme.PowerOf2PartitioningScheme; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/LongArraySearchTest.java b/libraries/array/src/test/java/nu/marginalia/array/algo/LongArraySearchTest.java similarity index 94% rename from marginalia_nu/src/test/java/nu/marginalia/util/array/algo/LongArraySearchTest.java rename to libraries/array/src/test/java/nu/marginalia/array/algo/LongArraySearchTest.java index f3cdae16..512355a3 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/LongArraySearchTest.java +++ b/libraries/array/src/test/java/nu/marginalia/array/algo/LongArraySearchTest.java @@ -1,9 +1,9 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.array.page.PagingLongArray; -import nu.marginalia.util.array.scheme.PowerOf2PartitioningScheme; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.array.page.PagingLongArray; +import nu.marginalia.array.scheme.PowerOf2PartitioningScheme; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/LongArraySortTest.java b/libraries/array/src/test/java/nu/marginalia/array/algo/LongArraySortTest.java similarity index 78% rename from marginalia_nu/src/test/java/nu/marginalia/util/array/algo/LongArraySortTest.java rename to libraries/array/src/test/java/nu/marginalia/array/algo/LongArraySortTest.java index e417020f..c81c1f4c 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/LongArraySortTest.java +++ b/libraries/array/src/test/java/nu/marginalia/array/algo/LongArraySortTest.java @@ -1,9 +1,9 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.page.LongArrayPage; -import nu.marginalia.util.array.page.PagingLongArray; -import nu.marginalia.util.array.scheme.PowerOf2PartitioningScheme; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.page.LongArrayPage; +import nu.marginalia.array.page.PagingLongArray; +import nu.marginalia.array.scheme.PowerOf2PartitioningScheme; import nu.marginalia.util.test.TestUtil; import org.apache.commons.lang3.ArrayUtils; import org.junit.jupiter.api.Assertions; @@ -116,72 +116,72 @@ class LongArraySortTest { @Test void insertionSort() { basic.insertionSort(0, size); - Assertions.assertTrue(basic.isSorted(0, 128)); + assertTrue(basic.isSorted(0, 128)); paged.insertionSort(0, size); - Assertions.assertTrue(paged.isSorted(0, 128)); + assertTrue(paged.isSorted(0, 128)); shifted.insertionSort(0, size); - Assertions.assertTrue(shifted.isSorted(0, 128)); + assertTrue(shifted.isSorted(0, 128)); } @Test void insertionSortN() { basic.insertionSortN(2, 0, size); - Assertions.assertTrue(basic.isSortedN(2, 0, size)); + assertTrue(basic.isSortedN(2, 0, size)); paged.insertionSortN(2, 0, size); - Assertions.assertTrue(paged.isSortedN(2, 0, size)); + assertTrue(paged.isSortedN(2, 0, size)); shifted.insertionSortN(2, 0, size); - Assertions.assertTrue(shifted.isSortedN(2, 0, size)); + assertTrue(shifted.isSortedN(2, 0, size)); } @Test void quickSort() { basic.quickSort(0, size); - Assertions.assertTrue(basic.isSorted(0, size)); + assertTrue(basic.isSorted(0, size)); paged.quickSort(0, size); - Assertions.assertTrue(paged.isSorted(0, size)); + assertTrue(paged.isSorted(0, size)); shifted.quickSort(0, size); - Assertions.assertTrue(shifted.isSorted(0, size)); + assertTrue(shifted.isSorted(0, size)); } @Test void quickSortN() { basic.quickSortN(2, 0, size); - Assertions.assertTrue(basic.isSortedN(2, 0, size)); + assertTrue(basic.isSortedN(2, 0, size)); paged.quickSortN(2, 0, size); - Assertions.assertTrue(paged.isSortedN(2, 0, size)); + assertTrue(paged.isSortedN(2, 0, size)); shifted.quickSortN(2, 0, size); - Assertions.assertTrue(shifted.isSortedN(2, 0, size)); + assertTrue(shifted.isSortedN(2, 0, size)); } @Test void mergeSortN() throws IOException { basic.mergeSortN(2, 0, size, Path.of("/tmp")); - Assertions.assertTrue(basic.isSortedN(2, 0, size)); + assertTrue(basic.isSortedN(2, 0, size)); paged.mergeSortN(2, 0, size, Path.of("/tmp")); - Assertions.assertTrue(paged.isSortedN(2, 0, size)); + assertTrue(paged.isSortedN(2, 0, size)); shifted.mergeSortN(2, 0, size, Path.of("/tmp")); - Assertions.assertTrue(shifted.isSortedN(2, 0, size)); + assertTrue(shifted.isSortedN(2, 0, size)); } @Test void mergeSort() throws IOException { basic.mergeSort(0, size, Path.of("/tmp")); - Assertions.assertTrue(basic.isSorted(0, size)); + assertTrue(basic.isSorted(0, size)); paged.mergeSort(0, size, Path.of("/tmp")); - Assertions.assertTrue(paged.isSorted(0, size)); + assertTrue(paged.isSorted(0, size)); shifted.mergeSort(0, size, Path.of("/tmp")); - Assertions.assertTrue(shifted.isSorted(0, size)); + assertTrue(shifted.isSorted(0, size)); } } \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/LongArrayTransformationsTest.java b/libraries/array/src/test/java/nu/marginalia/array/algo/LongArrayTransformationsTest.java similarity index 89% rename from marginalia_nu/src/test/java/nu/marginalia/util/array/algo/LongArrayTransformationsTest.java rename to libraries/array/src/test/java/nu/marginalia/array/algo/LongArrayTransformationsTest.java index 47f33bb3..b75d17a3 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/array/algo/LongArrayTransformationsTest.java +++ b/libraries/array/src/test/java/nu/marginalia/array/algo/LongArrayTransformationsTest.java @@ -1,9 +1,9 @@ -package nu.marginalia.util.array.algo; +package nu.marginalia.array.algo; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.page.LongArrayPage; -import nu.marginalia.util.array.page.PagingLongArray; -import nu.marginalia.util.array.scheme.PowerOf2PartitioningScheme; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.page.LongArrayPage; +import nu.marginalia.array.page.PagingLongArray; +import nu.marginalia.array.scheme.PowerOf2PartitioningScheme; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/array/scheme/ArrayPartitioningSchemeTest.java b/libraries/array/src/test/java/nu/marginalia/array/scheme/ArrayPartitioningSchemeTest.java similarity index 93% rename from marginalia_nu/src/test/java/nu/marginalia/util/array/scheme/ArrayPartitioningSchemeTest.java rename to libraries/array/src/test/java/nu/marginalia/array/scheme/ArrayPartitioningSchemeTest.java index 6c7cdd40..c0506df0 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/array/scheme/ArrayPartitioningSchemeTest.java +++ b/libraries/array/src/test/java/nu/marginalia/array/scheme/ArrayPartitioningSchemeTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.array.scheme; +package nu.marginalia.array.scheme; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/test/TestUtil.java b/libraries/array/src/test/java/nu/marginalia/util/test/TestUtil.java similarity index 69% rename from marginalia_nu/src/test/java/nu/marginalia/util/test/TestUtil.java rename to libraries/array/src/test/java/nu/marginalia/util/test/TestUtil.java index f3a40e4c..c8f3735a 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/test/TestUtil.java +++ b/libraries/array/src/test/java/nu/marginalia/util/test/TestUtil.java @@ -1,6 +1,5 @@ package nu.marginalia.util.test; -import lombok.SneakyThrows; import java.io.File; import java.io.IOException; @@ -12,7 +11,7 @@ public class TestUtil { private static boolean isTempDir(Path dir) { return dir.startsWith("/tmp") || dir.toString().contains("tmp"); } - @SneakyThrows + public static void clearTempDir(Path dir) { if (!isTempDir(dir)) { throw new IllegalArgumentException("Refusing to recursively delete directory with that name"); @@ -31,13 +30,18 @@ public class TestUtil { dir.toFile().delete(); } - private static String fileSize(Path path) throws IOException { - long sizeBytes = Files.size(path); + private static String fileSize(Path path) { + try { + long sizeBytes = Files.size(path); - if (sizeBytes > 1024*1024*1024) return round(sizeBytes / 1073741824.) + "Gb"; - if (sizeBytes > 1024*1024) return round(sizeBytes / 1048576.) + "Mb"; - if (sizeBytes > 1024) return round(sizeBytes / 1024.) + "Kb"; - return sizeBytes + "b"; + if (sizeBytes > 1024 * 1024 * 1024) return round(sizeBytes / 1073741824.) + "Gb"; + if (sizeBytes > 1024 * 1024) return round(sizeBytes / 1048576.) + "Mb"; + if (sizeBytes > 1024) return round(sizeBytes / 1024.) + "Kb"; + return sizeBytes + "b"; + } + catch (IOException ex) { + throw new RuntimeException(ex); + } } private static String round(double d) { diff --git a/libraries/btree/build.gradle b/libraries/btree/build.gradle new file mode 100644 index 00000000..270ef2f9 --- /dev/null +++ b/libraries/btree/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'java' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':libraries:array') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} diff --git a/libraries/btree/src/main/java/nu/marginalia/btree/BTreeDogEar.java b/libraries/btree/src/main/java/nu/marginalia/btree/BTreeDogEar.java new file mode 100644 index 00000000..c3e1e345 --- /dev/null +++ b/libraries/btree/src/main/java/nu/marginalia/btree/BTreeDogEar.java @@ -0,0 +1,46 @@ +package nu.marginalia.btree; + +import nu.marginalia.array.LongArray; +import nu.marginalia.btree.model.BTreeContext; +import nu.marginalia.btree.model.BTreeHeader; + +/* + * End-of-block mark that's used as a sentinel to verify that + * the BTreeWriter's caller actually writes as much as they say + * they want to. (Failing to do so will corrupt the tree) + * + */ +class BTreeDogEar { + + private final LongArray sentinelSlice; + + public static BTreeDogEar empty() { + return new BTreeDogEar(null); + } + + public static BTreeDogEar create(BTreeContext ctx, BTreeHeader header, LongArray base) { + + if (header.numEntries() > 3) { + var sentinelSlice = base.range( + (long) header.numEntries() * ctx.entrySize() - 3, + (long) header.numEntries() * ctx.entrySize()); + sentinelSlice.set(0, 4L); + sentinelSlice.set(1, 5L); + sentinelSlice.set(2, 1L); + return new BTreeDogEar(sentinelSlice); + } + + return BTreeDogEar.empty(); + } + private BTreeDogEar(LongArray sentinelSlice) { + this.sentinelSlice = sentinelSlice; + } + + public boolean verify() { + if (sentinelSlice == null) + return true; + + return 4 != sentinelSlice.get(0) || 5 != sentinelSlice.get(1) || 1 != sentinelSlice.get(2); + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeReader.java b/libraries/btree/src/main/java/nu/marginalia/btree/BTreeReader.java similarity index 69% rename from marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeReader.java rename to libraries/btree/src/main/java/nu/marginalia/btree/BTreeReader.java index f8bdd1f6..47e6e69b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeReader.java +++ b/libraries/btree/src/main/java/nu/marginalia/btree/BTreeReader.java @@ -1,16 +1,15 @@ -package nu.marginalia.util.btree; +package nu.marginalia.btree; -import lombok.SneakyThrows; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.array.algo.LongArraySearch; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.array.delegate.ShiftedLongArray; -import nu.marginalia.util.btree.model.BTreeContext; -import nu.marginalia.util.btree.model.BTreeHeader; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.algo.LongArraySearch; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.array.delegate.ShiftedLongArray; +import nu.marginalia.btree.model.BTreeContext; +import nu.marginalia.btree.model.BTreeHeader; import static java.lang.Math.min; -public class BTreeReader implements BTreeReaderIf { +public class BTreeReader { private final LongArray index; private final ShiftedLongArray data; @@ -22,7 +21,7 @@ public class BTreeReader implements BTreeReaderIf { public BTreeReader(LongArray file, BTreeContext ctx, long offset) { this.ctx = ctx; - this.header = createHeader(file, offset); + this.header = readHeader(file, offset); dataBlockEnd = (long) ctx.entrySize() * header.numEntries(); index = file.range(header.indexOffsetLongs(), header.dataOffsetLongs()); @@ -30,10 +29,8 @@ public class BTreeReader implements BTreeReaderIf { } - public static BTreeHeader createHeader(LongArray file, long fileOffset) { - long[] parts = new long[3]; - file.get(fileOffset, fileOffset+3, parts); - return new BTreeHeader(parts[0], parts[1], parts[2]); + public static BTreeHeader readHeader(LongArray file, long fileOffset) { + return new BTreeHeader(file, fileOffset); } public BTreeHeader getHeader() { @@ -44,7 +41,7 @@ public class BTreeReader implements BTreeReaderIf { return header.numEntries(); } - @SneakyThrows + /** Keeps all items in buffer that exist in the btree */ public void retainEntries(LongQueryBuffer buffer) { if (header.layers() == 0) { BTreePointer pointer = new BTreePointer(header); @@ -55,7 +52,7 @@ public class BTreeReader implements BTreeReaderIf { retainSingle(buffer); } - @SneakyThrows + /** Removes all items in buffer that exist in the btree */ public void rejectEntries(LongQueryBuffer buffer) { if (header.layers() == 0) { BTreePointer pointer = new BTreePointer(header); @@ -100,7 +97,7 @@ public class BTreeReader implements BTreeReaderIf { } - /** + /** Locate entry in btree * * @return file offset of entry matching keyRaw, negative if absent */ @@ -118,43 +115,72 @@ public class BTreeReader implements BTreeReaderIf { data.get(pos, pos + n, buf); } + /** Used for querying interlaced data in the btree. + *

+ * If entry size is e.g. 2, the data is positioned like [key1, data1, key2, data2, key3, data3] + * then given keys=[key1, key3], and offset=1 (i.e. look 1 step to the right), the return value will be + * [data1, data3]. + *

+ * For each item in the keys array where the key is not found in the btree, the value will be zero. + *

+ * Caveat: The keys are assumed to be sorted. + */ public long[] queryData(long[] keys, int offset) { - BTreePointer pointer = new BTreePointer(header); - long[] ret = new long[keys.length]; - - // this function could be re-written like retain() and would be - // much faster + if (getClass().desiredAssertionStatus()) { + assert(isSorted(keys)); + } if (header.layers() == 0) { - long searchStart = 0; - for (int i = 0; i < keys.length; i++) { - long key = keys[i]; - searchStart = data.binarySearchN(ctx.entrySize(), key, searchStart, data.size); - if (searchStart < 0) { - searchStart = LongArraySearch.decodeSearchMiss(searchStart); - } - else { - ret[i] = data.get(searchStart + offset); - } - } - + return queryJustIndex(keys, offset); } else { - for (int i = 0; i < keys.length; i++) { - if (i > 0) { - pointer.resetToRoot(); - } + return queryBtree(keys, offset); + } + } - if (pointer.walkToData(keys[i])) { - long dataAddress = pointer.findData(keys[i]); - if (dataAddress >= 0) { - ret[i] = data.get(dataAddress + offset); - } + private boolean isSorted(long[] keys) { + for (int i = 1; i < keys.length; i++) { + if (keys[i] < keys[i-1]) + return false; + } + return true; + } + + private long[] queryJustIndex(long[] keys, int offset) { + long[] ret = new long[keys.length]; + + long searchStart = 0; + for (int i = 0; i < keys.length; i++) { + long key = keys[i]; + searchStart = data.binarySearchN(ctx.entrySize(), key, searchStart, data.size); + if (searchStart < 0) { + searchStart = LongArraySearch.decodeSearchMiss(searchStart); + } + else { + ret[i] = data.get(searchStart + offset); + } + } + return ret; + } + + private long[] queryBtree(long[] keys, int offset) { + BTreePointer pointer = new BTreePointer(header); + long[] ret = new long[keys.length]; + + // FIXME: this function could be re-written like retain() and would be much faster + for (int i = 0; i < keys.length; i++) { + if (i > 0) { + pointer.resetToRoot(); + } + + if (pointer.walkToData(keys[i])) { + long dataAddress = pointer.findData(keys[i]); + if (dataAddress >= 0) { + ret[i] = data.get(dataAddress + offset); } } } - return ret; } @@ -192,11 +218,11 @@ public class BTreeReader implements BTreeReaderIf { final long searchStart = layerOffsets[layer] + offset; - final long nextLayerOffset = (int) index.binarySearchUpperBound(key, searchStart, searchStart + ctx.BLOCK_SIZE_WORDS()) - searchStart; + final long nextLayerOffset = (int) index.binarySearchUpperBound(key, searchStart, searchStart + ctx.blockSizeWords()) - searchStart; layer --; boundary = index.get(searchStart + nextLayerOffset); - offset = ctx.BLOCK_SIZE_WORDS() * (offset + nextLayerOffset); + offset = ctx.blockSizeWords() * (offset + nextLayerOffset); return true; } @@ -229,7 +255,7 @@ public class BTreeReader implements BTreeReaderIf { remainingBlock = (layerOffsets.length == 0) ? remainingTotal - : (long) ctx.BLOCK_SIZE_WORDS() * ctx.entrySize(); + : (long) ctx.blockSizeWords() * ctx.entrySize(); long searchEnd = searchStart + (int) min(remainingTotal, remainingBlock); @@ -247,7 +273,7 @@ public class BTreeReader implements BTreeReaderIf { long relOffset = dataOffset - blockBase; long remainingTotal = dataBlockEnd - dataOffset; - long remainingBlock = ctx.BLOCK_SIZE_WORDS() - relOffset; + long remainingBlock = ctx.blockSizeWords() - relOffset; long searchEnd = dataOffset + (int) min(remainingTotal, remainingBlock); @@ -271,7 +297,7 @@ public class BTreeReader implements BTreeReaderIf { long relOffset = dataOffset - blockBase; long remainingTotal = dataBlockEnd - dataOffset; - long remainingBlock = ctx.BLOCK_SIZE_WORDS() - relOffset; + long remainingBlock = ctx.blockSizeWords() - relOffset; long searchEnd = dataOffset + (int) min(remainingTotal, remainingBlock); diff --git a/libraries/btree/src/main/java/nu/marginalia/btree/BTreeWriteCallback.java b/libraries/btree/src/main/java/nu/marginalia/btree/BTreeWriteCallback.java new file mode 100644 index 00000000..2e63bd86 --- /dev/null +++ b/libraries/btree/src/main/java/nu/marginalia/btree/BTreeWriteCallback.java @@ -0,0 +1,9 @@ +package nu.marginalia.btree; + +import nu.marginalia.array.LongArray; + +import java.io.IOException; + +public interface BTreeWriteCallback { + void write(LongArray slice) throws IOException; +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeWriter.java b/libraries/btree/src/main/java/nu/marginalia/btree/BTreeWriter.java similarity index 64% rename from marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeWriter.java rename to libraries/btree/src/main/java/nu/marginalia/btree/BTreeWriter.java index a6ad6f91..a5512e03 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeWriter.java +++ b/libraries/btree/src/main/java/nu/marginalia/btree/BTreeWriter.java @@ -1,10 +1,9 @@ -package nu.marginalia.util.btree; +package nu.marginalia.btree; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.btree.model.BTreeContext; -import nu.marginalia.util.btree.model.BTreeHeader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import nu.marginalia.array.LongArray; +import nu.marginalia.array.delegate.ShiftedLongArray; +import nu.marginalia.btree.model.BTreeContext; +import nu.marginalia.btree.model.BTreeHeader; import java.io.IOException; @@ -12,7 +11,6 @@ import java.io.IOException; public class BTreeWriter { private final BTreeContext ctx; private final LongArray map; - private final Logger logger = LoggerFactory.getLogger(getClass()); public BTreeWriter(LongArray map, BTreeContext ctx) { this.map = map; @@ -35,24 +33,24 @@ public class BTreeWriter { * * @return The size of the written data */ - public long write(long offset, int numEntries, WriteCallback writeIndexCallback) + public long write(long offset, int numEntries, BTreeWriteCallback writeIndexCallback) throws IOException { BTreeHeader header = makeHeader(offset, numEntries); - header.write(map, offset); + writeHeader(header, map, offset); final long startRange = header.dataOffsetLongs(); final long endRange = startRange + (long) numEntries * ctx.entrySize(); var slice = map.range(startRange, endRange); - BTreeDogEar dogEar = new BTreeDogEar(ctx, header, slice); + final BTreeDogEar dogEar = createDogEar(ctx, header, slice); writeIndexCallback.write(slice); if (!dogEar.verify()) { - logger.error("Dog ear was not overwritten: {}", header); + throw new IllegalStateException("Dog ear was not overwritten: " + header); } if (header.layers() >= 1) { // Omit layer if data fits within a single block @@ -62,10 +60,25 @@ public class BTreeWriter { return ctx.calculateSize(numEntries); } + private void writeHeader(BTreeHeader header, LongArray map, long offset) { + map.set(offset, ((long) header.layers() << 32L) | ((long)header.numEntries() & 0xFFFF_FFFFL)); + map.set(offset+1, header.indexOffsetLongs()); + map.set(offset+2, header.dataOffsetLongs()); + } + + private BTreeDogEar createDogEar(BTreeContext ctx, BTreeHeader header, ShiftedLongArray slice) { + if (BTreeWriter.class.desiredAssertionStatus()) { + return BTreeDogEar.create(ctx, header, slice); + } + else { + return BTreeDogEar.empty(); + } + } + public static BTreeHeader makeHeader(BTreeContext ctx, long offset, int numEntries) { final int numLayers = ctx.numIndexLayers(numEntries); - final int padding = BTreeHeader.getPadding(ctx, offset, numLayers); + final int padding = getHeaderPadding(ctx, offset, numLayers); final long indexOffset = offset + BTreeHeader.BTreeHeaderSizeLongs + padding; final long dataOffset = indexOffset + indexSize(ctx, numEntries, numLayers); @@ -73,6 +86,22 @@ public class BTreeWriter { return new BTreeHeader(numLayers, numEntries, indexOffset, dataOffset); } + + private static int getHeaderPadding(BTreeContext ctx, long offset, int numLayers) { + final int padding; + if (numLayers == 0) { + padding = 0; + } + else { + /* If this the amount of data is big enough to be a b-tree and not just + * a sorted list, there needs to be padding between the header and the index + * in order to get aligned blocks + */ + padding = (int) (ctx.blockSizeWords() - ((offset + BTreeHeader.BTreeHeaderSizeLongs) % ctx.blockSizeWords())); + } + return padding; + } + public BTreeHeader makeHeader(long offset, int numEntries) { return makeHeader(ctx, offset, numEntries); } @@ -81,7 +110,7 @@ public class BTreeWriter { private void writeIndex(BTreeHeader header) { var layerOffsets = header.getRelativeLayerOffsets(ctx); - long indexedDataStepSize = ctx.BLOCK_SIZE_WORDS(); + long indexedDataStepSize = ctx.blockSizeWords(); /* Index layer 0 indexes the data itself Index layer 1 indexes layer 0 @@ -89,7 +118,7 @@ public class BTreeWriter { And so on */ for (int layer = 0; layer < header.layers(); layer++, - indexedDataStepSize*=ctx.BLOCK_SIZE_WORDS()) { + indexedDataStepSize*=ctx.blockSizeWords()) { writeIndexLayer(header, layerOffsets, indexedDataStepSize, layer); } @@ -124,8 +153,8 @@ public class BTreeWriter { final long trailerStart = indexOffsetBase + indexWord; final long trailerEnd = trailerStart - + ctx.BLOCK_SIZE_WORDS() - - (int) (indexWord % ctx.BLOCK_SIZE_WORDS()); + + ctx.blockSizeWords() + - (int) (indexWord % ctx.blockSizeWords()); if (trailerStart < trailerEnd) { map.fill(trailerStart, trailerEnd, Long.MAX_VALUE); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/btree/model/BTreeContext.java b/libraries/btree/src/main/java/nu/marginalia/btree/model/BTreeContext.java similarity index 63% rename from marginalia_nu/src/main/java/nu/marginalia/util/btree/model/BTreeContext.java rename to libraries/btree/src/main/java/nu/marginalia/btree/model/BTreeContext.java index d335d320..350c1ff9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/btree/model/BTreeContext.java +++ b/libraries/btree/src/main/java/nu/marginalia/btree/model/BTreeContext.java @@ -1,11 +1,11 @@ -package nu.marginalia.util.btree.model; +package nu.marginalia.btree.model; -import nu.marginalia.util.btree.BTreeWriter; +import nu.marginalia.btree.BTreeWriter; -public record BTreeContext(int MAX_LAYERS, +public record BTreeContext(int maxLayers, int entrySize, - int BLOCK_SIZE_BITS, - int BLOCK_SIZE_WORDS) { + int blockSizeBits, + int blockSizeWords) { // 8 pages is the breaking point where using a B-tree is actually advantageous // over just binary searching in a sorted list. Above 8 pages, binary search will @@ -25,26 +25,26 @@ public record BTreeContext(int MAX_LAYERS, } public int numIndexLayers(int numEntries) { - if (numEntries <= BLOCK_SIZE_WORDS*MIN_PAGES_FOR_BTREE/entrySize) { + if (numEntries <= blockSizeWords *MIN_PAGES_FOR_BTREE/entrySize) { return 0; } - for (int i = 1; i < MAX_LAYERS; i++) { - long div = (1L << (BLOCK_SIZE_BITS*i)); + for (int i = 1; i < maxLayers; i++) { + long div = (1L << (blockSizeBits *i)); long frq = numEntries / div; - if (frq < (1L << BLOCK_SIZE_BITS)) { + if (frq < (1L << blockSizeBits)) { if (numEntries == (numEntries & div)) { return i; } return i+1; } } - return MAX_LAYERS; + return maxLayers; } public long indexLayerSize(int numWords, int level) { - final long layerSize = 1L<<(BLOCK_SIZE_BITS*(level+1)); + final long layerSize = 1L<<(blockSizeBits *(level+1)); - return BLOCK_SIZE_WORDS * (numWords / layerSize + Long.signum(numWords % layerSize)); + return blockSizeWords * (numWords / layerSize + Long.signum(numWords % layerSize)); } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/btree/model/BTreeHeader.java b/libraries/btree/src/main/java/nu/marginalia/btree/model/BTreeHeader.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/util/btree/model/BTreeHeader.java rename to libraries/btree/src/main/java/nu/marginalia/btree/model/BTreeHeader.java index a0dc3be3..e400c5b0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/btree/model/BTreeHeader.java +++ b/libraries/btree/src/main/java/nu/marginalia/btree/model/BTreeHeader.java @@ -1,6 +1,6 @@ -package nu.marginalia.util.btree.model; +package nu.marginalia.btree.model; -import nu.marginalia.util.array.LongArray; +import nu.marginalia.array.LongArray; public record BTreeHeader(int layers, int numEntries, long indexOffsetLongs, long dataOffsetLongs) { public BTreeHeader { @@ -16,30 +16,17 @@ public record BTreeHeader(int layers, int numEntries, long indexOffsetLongs, lon public BTreeHeader(long a, long b, long c) { this((int)(a >>> 32), (int)(a & 0xFFFF_FFFFL), b, c); } - - public static int getPadding(BTreeContext ctx, long offset, int numLayers) { - final int padding; - if (numLayers == 0) { - padding = 0; - } - else { - padding = (int) (ctx.BLOCK_SIZE_WORDS() - ((offset + BTreeHeader.BTreeHeaderSizeLongs) % ctx.BLOCK_SIZE_WORDS())); - } - return padding; + public BTreeHeader(LongArray array, long offset) { + this(array.get(offset), array.get(offset+1), array.get(offset+2)); } - public void write(LongArray dest, long offset) { - dest.set(offset, ((long) layers << 32L) | ((long)numEntries & 0xFFFF_FFFFL)); - dest.set(offset+1, indexOffsetLongs); - dest.set(offset+2, dataOffsetLongs); - } - - public long relativeIndexLayerOffset(BTreeContext ctx, int n) { long offset = 0; + for (int i = n+1; i < layers; i++) { offset += ctx.indexLayerSize( numEntries, i); } + return offset; } diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/btree/BTreeWriterTest.java b/libraries/btree/src/test/java/nu/marginalia/btree/BTreeWriterTest.java similarity index 56% rename from marginalia_nu/src/test/java/nu/marginalia/util/btree/BTreeWriterTest.java rename to libraries/btree/src/test/java/nu/marginalia/btree/BTreeWriterTest.java index 611d3ddd..af4798f9 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/btree/BTreeWriterTest.java +++ b/libraries/btree/src/test/java/nu/marginalia/btree/BTreeWriterTest.java @@ -1,39 +1,43 @@ -package nu.marginalia.util.btree; +package nu.marginalia.btree; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.btree.model.BTreeContext; -import nu.marginalia.util.btree.model.BTreeHeader; +import nu.marginalia.array.LongArray; +import nu.marginalia.btree.model.BTreeContext; +import nu.marginalia.btree.model.BTreeHeader; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.HashSet; import java.util.Set; import java.util.StringJoiner; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.LongStream; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class BTreeWriterTest { - final BTreeContext ctx = new BTreeContext(4, 2, 3); - final BTreeWriter writer = new BTreeWriter(null, ctx); - Logger logger = LoggerFactory.getLogger(getClass()); + @Test void testSmallDataBlock() { - var header = writer.makeHeader(1024, ctx.BLOCK_SIZE_WORDS()/2); + BTreeContext ctx = new BTreeContext(4, 2, 3); + BTreeWriter writer = new BTreeWriter(null, ctx); + + var header = writer.makeHeader(1024, ctx.blockSizeWords()/2); assertEquals(1024 + BTreeHeader.BTreeHeaderSizeLongs, header.dataOffsetLongs()); assertEquals(header.dataOffsetLongs(), header.indexOffsetLongs()); } @Test void testLayerCount() { - int wsq = ctx.BLOCK_SIZE_WORDS()*ctx.BLOCK_SIZE_WORDS(); - int wcub = ctx.BLOCK_SIZE_WORDS()*ctx.BLOCK_SIZE_WORDS()*ctx.BLOCK_SIZE_WORDS(); + BTreeContext ctx = new BTreeContext(4, 2, 3); + BTreeWriter writer = new BTreeWriter(null, ctx); + + int wsq = ctx.blockSizeWords()*ctx.blockSizeWords(); + int wcub = ctx.blockSizeWords()*ctx.blockSizeWords()*ctx.blockSizeWords(); assertEquals(2, writer.makeHeader(1024, wsq-1).layers()); assertEquals(2, writer.makeHeader(1024, wsq).layers()); @@ -46,7 +50,10 @@ class BTreeWriterTest { @Test void testLayerOffset() { - int wcub = ctx.BLOCK_SIZE_WORDS()*ctx.BLOCK_SIZE_WORDS()*ctx.BLOCK_SIZE_WORDS(); + BTreeContext ctx = new BTreeContext(4, 2, 3); + BTreeWriter writer = new BTreeWriter(null, ctx); + + int wcub = ctx.blockSizeWords()*ctx.blockSizeWords()*ctx.blockSizeWords(); System.out.println(writer.makeHeader(1025, wcub).relativeIndexLayerOffset(ctx, 0)); System.out.println(writer.makeHeader(1025, wcub).relativeIndexLayerOffset(ctx, 1)); System.out.println(writer.makeHeader(1025, wcub).relativeIndexLayerOffset(ctx, 2)); @@ -58,7 +65,7 @@ class BTreeWriterTest { printTreeLayout(i, header, ctx); if (header.layers() >= 1) { - assertEquals(1, ctx.indexLayerSize(i, header.layers() - 1) / ctx.BLOCK_SIZE_WORDS()); + assertEquals(1, ctx.indexLayerSize(i, header.layers() - 1) / ctx.blockSizeWords()); } } } @@ -66,44 +73,30 @@ class BTreeWriterTest { private void printTreeLayout(int numEntries, BTreeHeader header, BTreeContext ctx) { StringJoiner sj = new StringJoiner(","); for (int l = 0; l < header.layers(); l++) { - sj.add(""+ctx.indexLayerSize(numEntries, l)/ctx.BLOCK_SIZE_WORDS()); + sj.add(""+ctx.indexLayerSize(numEntries, l)/ctx.blockSizeWords()); } System.out.println(numEntries + ":" + sj); } @Test public void testWriteEntrySize2() throws IOException { + BTreeContext ctx = new BTreeContext(4, 2, 3); var tempFile = Files.createTempFile(Path.of("/tmp"), "tst", "dat"); - Set toPut = new HashSet<>(); - for (int i = 0; i < 64; i++) { - while (!toPut.add((int)(Integer.MAX_VALUE * Math.random()))); - } - - int[] data = toPut.stream().mapToInt(Integer::valueOf).sorted().toArray(); + int[] data = generateItems32(64); try { LongArray longArray = LongArray.allocate(10000); - { - var writer = new BTreeWriter(longArray, ctx); - writer.write(0, toPut.size(), (slice) -> { - for (int i = 0; i < data.length; i++) { - slice.set(2L*i, data[i]); - slice.set( 2L*i + 1, i); - } - }); - } + writeIntEntrySize2(data, ctx, longArray); - { - var reader = new BTreeReader(longArray, ctx, 0); - for (int i = 0; i < data.length; i++) { - long offset = reader.findEntry(data[i]); - assertTrue(offset >= 0, "Negative offset for " + i + " -> " + offset); - offset += reader.getHeader().dataOffsetLongs(); - assertEquals(i, longArray.get(offset+1)); - } + var reader = new BTreeReader(longArray, ctx, 0); + for (int i = 0; i < data.length; i++) { + long offset = reader.findEntry(data[i]); + assertTrue(offset >= 0, "Negative offset for " + i + " -> " + offset); + offset += reader.getHeader().dataOffsetLongs(); + assertEquals(i, longArray.get(offset+1)); } } catch (Exception e) { e.printStackTrace(); @@ -114,23 +107,15 @@ class BTreeWriterTest { @Test public void testWriteEntrySize2Small() throws IOException { + BTreeContext ctx = new BTreeContext(4, 2, 3); - var tempFile = Files.createTempFile(Path.of("/tmp"), "tst", "dat"); - Set toPut = new HashSet<>(); + int[] data = generateItems32(5); + Set items = IntStream.of(data).boxed().collect(Collectors.toSet()); - for (int i = 0; i < 5; i++) { - while (!toPut.add((int)(Integer.MAX_VALUE * Math.random()))); - } - - int[] data = toPut.stream().mapToInt(Integer::valueOf).sorted().toArray(); LongArray array = LongArray.allocate(22000); - var writer = new BTreeWriter(array, ctx); - writer.write( 0, toPut.size(), (slice) -> { - for (int i = 0; i < data.length; i++) { - slice.set(2L*i, data[i]); - slice.set(2L*i + 1, i); - } - }); + + writeIntEntrySize2(data, ctx, array); + var reader = new BTreeReader(array, ctx, 0); for (int i = 0; i < data.length; i++) { long offset = reader.findEntry(data[i]); @@ -141,7 +126,7 @@ class BTreeWriterTest { for (int i = 0; i < 500; i++) { long val = (long)(Long.MAX_VALUE * Math.random()); - while (toPut.contains((int)val)) val = (long)(Long.MAX_VALUE * Math.random()); + while (items.contains((int)val)) val = (long)(Long.MAX_VALUE * Math.random()); assertTrue(reader.findEntry( val) < 0); } } @@ -150,28 +135,16 @@ class BTreeWriterTest { @Test public void testWriteEqualityNotMasked() throws IOException { for (int bs = 2; bs <= 4; bs++) { - var tempFile = Files.createTempFile(Path.of("/tmp"), "tst", "dat"); - Set toPut = new HashSet<>(); - var ctx = new BTreeContext(5, 1, bs); - - for (int i = 0; i < 500; i++) { - while (!toPut.add((long) (Long.MAX_VALUE * Math.random()))) ; - } - - long[] data = toPut.stream().mapToLong(Long::valueOf).sorted().toArray(); + long[] data = generateItems64(500); + Set items = LongStream.of(data).boxed().collect(Collectors.toSet()); LongArray array = LongArray.allocate(22000); - var writer = new BTreeWriter(array, ctx); - writer.write(0, toPut.size(), (slice) -> { - for (int i = 0; i < data.length; i++) { - slice.set(i, data[i]); - } - }); + writeLongEntrySize1(data, ctx, array); var reader = new BTreeReader(array, ctx, 0); - printTreeLayout(toPut.size(), reader.getHeader(), ctx); + printTreeLayout(data.length, reader.getHeader(), ctx); for (int i = 0; i < data.length; i++) { long offset = reader.findEntry(data[i]); @@ -182,10 +155,38 @@ class BTreeWriterTest { for (int i = 0; i < 500; i++) { long val = (long) (Long.MAX_VALUE * Math.random()); - while (toPut.contains(val)) val = (long) (Long.MAX_VALUE * Math.random()); + while (items.contains(val)) val = (long) (Long.MAX_VALUE * Math.random()); assertTrue(reader.findEntry( val) < 0); } } } + public int[] generateItems32(int n) { + return IntStream.generate(() -> (int) (Integer.MAX_VALUE * Math.random())).distinct().limit(n).sorted().toArray(); + } + + public long[] generateItems64(int n) { + return LongStream.generate(() -> (long) (Long.MAX_VALUE * Math.random())).distinct().limit(n).sorted().toArray(); + } + + private void writeIntEntrySize2(int[] data, BTreeContext ctx, LongArray array) throws IOException { + var writer = new BTreeWriter(array, ctx); + writer.write(0, data.length, (slice) -> { + for (int i = 0; i < data.length; i++) { + slice.set(2L*i, data[i]); + slice.set(2L*i + 1, i); + } + }); + } + + private void writeLongEntrySize1(long[] data, BTreeContext ctx, LongArray array) throws IOException { + var writer = new BTreeWriter(array, ctx); + writer.write(0, data.length, (slice) -> { + for (int i = 0; i < data.length; i++) { + slice.set(i, data[i]); + } + }); + } + + } \ No newline at end of file diff --git a/libraries/language-processing/build.gradle b/libraries/language-processing/build.gradle new file mode 100644 index 00000000..ae614d46 --- /dev/null +++ b/libraries/language-processing/build.gradle @@ -0,0 +1,50 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id "me.champeau.jmh" version "0.6.6" + id "de.undercouch.download" version "5.1.0" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:config') + implementation project(':libraries:misc') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + implementation libs.notnull + + implementation libs.guice + implementation libs.jsoup + implementation libs.trove + + implementation libs.bundles.nlp + implementation libs.commons.lang3 + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/LanguageFilter.java b/libraries/language-processing/src/main/java/nu/marginalia/language/LanguageFilter.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/LanguageFilter.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/LanguageFilter.java index 7649201b..b4ba2793 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/LanguageFilter.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/LanguageFilter.java @@ -1,6 +1,7 @@ -package nu.marginalia.util.language; +package nu.marginalia.language; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; +import nu.marginalia.language.encoding.UnicodeRanges; +import nu.marginalia.language.model.DocumentLanguageData; import org.jsoup.nodes.Document; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/WordPatterns.java b/libraries/language-processing/src/main/java/nu/marginalia/language/WordPatterns.java similarity index 99% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/WordPatterns.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/WordPatterns.java index c2fc0045..270d6810 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/WordPatterns.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/WordPatterns.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.language; +package nu.marginalia.language; import org.apache.commons.lang3.StringUtils; diff --git a/libraries/language-processing/src/main/java/nu/marginalia/language/encoding/AsciiFlattener.java b/libraries/language-processing/src/main/java/nu/marginalia/language/encoding/AsciiFlattener.java new file mode 100644 index 00000000..77cc1196 --- /dev/null +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/encoding/AsciiFlattener.java @@ -0,0 +1,130 @@ +package nu.marginalia.language.encoding; + +public class AsciiFlattener { + + public static String flattenUnicode(String s) { + + if (isPlainAscii(s)) { + return s; + } + + StringBuilder sb = new StringBuilder(s.length()); + + int numCp = s.codePointCount(0, s.length()); + + // Falsehoods programmers believe about the latin alphabet ;-) + + for (int i = 0; i < numCp; i++) { + int c = s.codePointAt(i); + + if ("\u201C\u201D".indexOf(c) >= 0) { + sb.append('"'); + } + else if ("áâàȁăåäāǟãąą̊ḁẚⱥ".indexOf(c) >= 0) { + sb.append('a'); + } + else if ("ḃḅḇƀɓ".indexOf(c) >= 0) { + sb.append('b'); + } + else if ("ćĉčçḉċƈȼ".indexOf(c) >= 0) { + sb.append('c'); + } + else if ("ɗḓďḋḍḏḑđðɖḏ".indexOf(c) >= 0) { + sb.append('d'); + } + else if ("éêèȅěëēẽĕęėẹȇḕḗḙḛḝɇ".indexOf(c) >= 0) { + sb.append('e'); + } + else if ("ḟƒ".indexOf(c) >= 0) { + sb.append('f'); + } + else if ("ǵĝǧğġģɠḡǥ".indexOf(c) >= 0) { + sb.append('g'); + } + else if ("ĥȟḧḣḥẖḩḫħⱨ".indexOf(c) >= 0) { + sb.append('g'); + } + else if ("iıíîìȉïḯīĩįịḭ".indexOf(c) >= 0) { + sb.append('i'); + } + else if ("ĵǰɉ".indexOf(c) >= 0) { + sb.append('j'); + } + else if ("ḱǩķḳḵƙⱪ".indexOf(c) >= 0) { + sb.append('k'); + } + else if ("ĺłḽľļḷḹḻƚɫⱡ".indexOf(c) >= 0) { + sb.append('l'); + } + else if ("ḿṁṃ".indexOf(c) >= 0) { + sb.append('m'); + } + else if ("ŋńǹñṋňṅṇṉʼnn̈ņ".indexOf(c) >= 0) { + sb.append('n'); + } + else if ("óőôòȍŏȯȱöȫōṓṑõṍṏȭøǿǫǭọȏơ".indexOf(c) >= 0) { + sb.append('o'); + } + else if ("ṕṗƥᵽ".indexOf(c) >= 0) { + sb.append('p'); + } + else if ("ꝗ".indexOf(c) >= 0) { + sb.append('q'); + } + else if ("ŕȑřŗṙṛṝṟɍɽ".indexOf(c) >= 0) { + sb.append('r'); + } + else if ("śṥŝšṧşșṡṣṩ".indexOf(c) >= 0) { + sb.append('s'); + } + else if ("ťṱẗţțŧṫṭṯⱦ".indexOf(c) >= 0) { + sb.append('t'); + } + else if ("úùûŭưűüūṻųůũṹụṳṵṷʉ".indexOf(c) >= 0) { + sb.append('u'); + } + else if ("ṽṿʋỽ".indexOf(c) >= 0) { + sb.append('v'); + } + else if ("ẃŵẁẅẘẇẉⱳ".indexOf(c) >= 0) { + sb.append('w'); + } + else if ("x̂ẍẋ".indexOf(c) >= 0) { + sb.append('x'); + } + else if ("ƴýŷỳÿȳỹẙẏy̨ɏỿ".indexOf(c) >= 0) { + sb.append('y'); + } + else if ("źẑžżẓẕƶȥ".indexOf(c) >= 0) { + sb.append('z'); + } + else if ("Þþ".indexOf(c) >= 0) { + sb.append("th"); + } + else if ('ß' == c) { + sb.append("ss"); + } + else if (isAscii(c)) { + sb.append((char) c); + } + } + + return sb.toString(); + } + + private static boolean isPlainAscii(String s) { + int i; + + int numCp = s.codePointCount(0, s.length()); + + for (i = 0; i < numCp && isAscii(s.codePointAt(i)); i++); + + return i == s.length(); + } + + private static boolean isAscii(int c) { + return (c & ~0x7f) == 0; + } + + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/HtmlTagCleaner.java b/libraries/language-processing/src/main/java/nu/marginalia/language/encoding/HtmlTagCleaner.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/HtmlTagCleaner.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/encoding/HtmlTagCleaner.java index 17156c3b..c2865f14 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/HtmlTagCleaner.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/encoding/HtmlTagCleaner.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.language.processing; +package nu.marginalia.language.encoding; import org.jsoup.nodes.Document; import org.jsoup.nodes.TextNode; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/UnicodeRanges.java b/libraries/language-processing/src/main/java/nu/marginalia/language/encoding/UnicodeRanges.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/UnicodeRanges.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/encoding/UnicodeRanges.java index bd1d3043..d690269b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/UnicodeRanges.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/encoding/UnicodeRanges.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.language; +package nu.marginalia.language.encoding; public enum UnicodeRanges { GREEK(false, 0x0370,0x03FF), diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/KeywordExtractor.java b/libraries/language-processing/src/main/java/nu/marginalia/language/keywords/KeywordExtractor.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/KeywordExtractor.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/keywords/KeywordExtractor.java index 8673ac4c..a00df4e7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/KeywordExtractor.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/keywords/KeywordExtractor.java @@ -1,9 +1,9 @@ -package nu.marginalia.util.language.processing; +package nu.marginalia.language.keywords; -import nu.marginalia.util.language.WordPatterns; -import nu.marginalia.util.language.processing.model.DocumentSentence; -import nu.marginalia.util.language.processing.model.WordSpan; -import nu.marginalia.util.language.processing.model.tag.WordSeparator; +import nu.marginalia.language.WordPatterns; +import nu.marginalia.language.model.DocumentSentence; +import nu.marginalia.language.model.WordSpan; +import nu.marginalia.language.model.WordSeparator; import java.lang.ref.SoftReference; import java.util.ArrayList; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/DocumentLanguageData.java b/libraries/language-processing/src/main/java/nu/marginalia/language/model/DocumentLanguageData.java similarity index 68% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/DocumentLanguageData.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/model/DocumentLanguageData.java index 89b95fd0..a40fd637 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/DocumentLanguageData.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/model/DocumentLanguageData.java @@ -1,8 +1,9 @@ -package nu.marginalia.util.language.processing.model; +package nu.marginalia.language.model; import gnu.trove.map.hash.TObjectIntHashMap; import lombok.AllArgsConstructor; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; +import nu.marginalia.language.sentence.SentenceExtractor; +import nu.marginalia.lsh.EasyLSH; import java.util.Arrays; import java.util.stream.Stream; @@ -31,4 +32,15 @@ public class DocumentLanguageData { public Stream stream() { return Arrays.stream(sentences).map(sent -> sent.words).flatMap(Arrays::stream); } + + public long localitySensitiveHashCode() { + var hash = new EasyLSH(); + + for (var sent : sentences) { + for (var word : sent) { + hash.addUnordered(word.word()); + } + } + return hash.get(); + } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/DocumentSentence.java b/libraries/language-processing/src/main/java/nu/marginalia/language/model/DocumentSentence.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/DocumentSentence.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/model/DocumentSentence.java index 0f0ae0aa..cf5d7488 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/DocumentSentence.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/model/DocumentSentence.java @@ -1,7 +1,7 @@ -package nu.marginalia.util.language.processing.model; +package nu.marginalia.language.model; -import nu.marginalia.util.language.WordPatterns; +import nu.marginalia.language.WordPatterns; import org.jetbrains.annotations.NotNull; import java.lang.ref.SoftReference; @@ -52,7 +52,37 @@ public class DocumentSentence implements Iterable{ return words.length; } - private String removeJunk(String s) { + public String constructWordFromSpan(WordSpan span) { + if (span.size() == 1) { + return trimJunkCharacters(wordsLowerCase[span.start]); + } + else { + StringJoiner sj = new StringJoiner("_"); + for (int i = span.start; i < span.end; i++) { + sj.add(wordsLowerCase[i]); + } + return trimJunkCharacters(sj.toString()); + } + } + + public String constructStemmedWordFromSpan(WordSpan span) { + if (span.size() > 1) { + + StringJoiner sj = new StringJoiner("_"); + for (int i = span.start; i < span.end; i++) { + if (includeInStemming(i)) + sj.add(normalizeJoiner(stemmedWords[i])); + + } + return sj.toString(); + } + else if (includeInStemming(span.start)) { + return normalizeJoiner(stemmedWords[span.start]); + } + else return ""; + } + + private String trimJunkCharacters(String s) { int start = 0; int end = s.length(); @@ -73,21 +103,6 @@ public class DocumentSentence implements Iterable{ return s; } } - - public String constructWordFromSpan(WordSpan span) { - if (span.size() == 1) { - return removeJunk(wordsLowerCase[span.start]); - } - else { - StringJoiner sj = new StringJoiner("_"); - for (int i = span.start; i < span.end; i++) { - sj.add(wordsLowerCase[i]); - } - return removeJunk(sj.toString()); - } - } - - private String normalizeJoiner(String s) { if (s.indexOf('+') >= 0) { @@ -101,22 +116,6 @@ public class DocumentSentence implements Iterable{ } return s; } - public String constructStemmedWordFromSpan(WordSpan span) { - if (span.size() > 1) { - - StringJoiner sj = new StringJoiner("_"); - for (int i = span.start; i < span.end; i++) { - if (includeInStemming(i)) - sj.add(normalizeJoiner(stemmedWords[i])); - - } - return sj.toString(); - } - else if (includeInStemming(span.start)) { - return normalizeJoiner(stemmedWords[span.start]); - } - else return ""; - } private boolean includeInStemming(int i) { if (posTags[i].equals("IN") || posTags[i].equals("TO") || posTags[i].equals("CC") || posTags[i].equals("DT")) { @@ -125,7 +124,6 @@ public class DocumentSentence implements Iterable{ return true; } - @Override public String toString() { return IntStream.range(0, length()).mapToObj(i -> String.format("%s[%s]", words[i], posTags[i])).collect(Collectors.joining(" ")); diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/KeywordMetadata.java b/libraries/language-processing/src/main/java/nu/marginalia/language/model/KeywordMetadata.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/KeywordMetadata.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/model/KeywordMetadata.java index 58e53551..b44b510d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/KeywordMetadata.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/model/KeywordMetadata.java @@ -1,8 +1,7 @@ -package nu.marginalia.util.language.processing.model; +package nu.marginalia.language.model; -import nu.marginalia.util.language.processing.KeywordCounter; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordMetadata; +import nu.marginalia.model.idx.EdgePageWordMetadata; +import nu.marginalia.model.crawl.EdgePageWordFlags; import java.util.EnumSet; import java.util.HashMap; @@ -11,7 +10,7 @@ import java.util.HashSet; public record KeywordMetadata(HashSet titleKeywords, HashSet subjectKeywords, HashSet namesKeywords, - HashMap wordsTfIdf, + HashMap wordsTfIdf, HashMap positionMask, EnumSet wordFlagsTemplate ) @@ -28,10 +27,10 @@ public record KeywordMetadata(HashSet titleKeywords, this(EnumSet.noneOf(EdgePageWordFlags.class)); } - private static final KeywordCounter.WordFrequencyData empty = new KeywordCounter.WordFrequencyData(0, 0); + private static final WordFrequencyData empty = new WordFrequencyData(0, 0); public long getMetadataForWord(EnumSet flagsTemplate, String stemmed) { - KeywordCounter.WordFrequencyData tfidf = wordsTfIdf.getOrDefault(stemmed, empty); + WordFrequencyData tfidf = wordsTfIdf.getOrDefault(stemmed, empty); EnumSet flags = flagsTemplate.clone(); if (subjectKeywords.contains(stemmed)) diff --git a/libraries/language-processing/src/main/java/nu/marginalia/language/model/WordFrequencyData.java b/libraries/language-processing/src/main/java/nu/marginalia/language/model/WordFrequencyData.java new file mode 100644 index 00000000..3435a702 --- /dev/null +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/model/WordFrequencyData.java @@ -0,0 +1,4 @@ +package nu.marginalia.language.model; + + +public record WordFrequencyData(int count, int tfIdfNormalized) { } \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordRep.java b/libraries/language-processing/src/main/java/nu/marginalia/language/model/WordRep.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordRep.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/model/WordRep.java index 5f87894f..541539f7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordRep.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/model/WordRep.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.language.processing.model; +package nu.marginalia.language.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/tag/WordSeparator.java b/libraries/language-processing/src/main/java/nu/marginalia/language/model/WordSeparator.java similarity index 66% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/tag/WordSeparator.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/model/WordSeparator.java index 231ea7cd..3476073f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/tag/WordSeparator.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/model/WordSeparator.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.language.processing.model.tag; +package nu.marginalia.language.model; public final class WordSeparator { public static final int COMMA = 0; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordSpan.java b/libraries/language-processing/src/main/java/nu/marginalia/language/model/WordSpan.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordSpan.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/model/WordSpan.java index 44c20a7f..82ea4e48 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordSpan.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/model/WordSpan.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.language.processing.model; +package nu.marginalia.language.model; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/sentence/SentenceExtractor.java b/libraries/language-processing/src/main/java/nu/marginalia/language/sentence/SentenceExtractor.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/sentence/SentenceExtractor.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/sentence/SentenceExtractor.java index 2957eaa9..abb4ec8a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/sentence/SentenceExtractor.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/sentence/SentenceExtractor.java @@ -1,17 +1,17 @@ -package nu.marginalia.util.language.processing.sentence; +package nu.marginalia.language.sentence; import com.github.datquocnguyen.RDRPOSTagger; -import com.github.jknack.handlebars.internal.lang3.StringUtils; import gnu.trove.map.hash.TObjectIntHashMap; import lombok.SneakyThrows; +import nu.marginalia.LanguageModels; +import nu.marginalia.language.encoding.HtmlTagCleaner; import nu.marginalia.util.StringPool; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.util.language.processing.HtmlTagCleaner; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; -import nu.marginalia.util.language.processing.model.DocumentSentence; +import nu.marginalia.language.model.DocumentLanguageData; +import nu.marginalia.language.model.DocumentSentence; import opennlp.tools.sentdetect.SentenceDetectorME; import opennlp.tools.sentdetect.SentenceModel; import opennlp.tools.stemmer.PorterStemmer; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; @@ -81,7 +81,7 @@ public class SentenceExtractor { Optional.ofNullable(doc.getElementsByTag("h1").first()).map(Element::text).orElse(""); if (title.trim().length() < 3) { - title = Optional.ofNullable(doc.getElementsByTag("h2").first()).map(Element::text).orElse(""); + title = doc.getElementsByTag("h2").text(); } if (title.trim().length() < 3 && textSentences.length > 0) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/sentence/SentenceExtractorStringUtils.java b/libraries/language-processing/src/main/java/nu/marginalia/language/sentence/SentenceExtractorStringUtils.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/sentence/SentenceExtractorStringUtils.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/sentence/SentenceExtractorStringUtils.java index 08a1605c..41f27c24 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/sentence/SentenceExtractorStringUtils.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/sentence/SentenceExtractorStringUtils.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.language.processing.sentence; +package nu.marginalia.language.sentence; import java.util.Arrays; import java.util.Objects; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/sentence/SentenceSegmentSplitter.java b/libraries/language-processing/src/main/java/nu/marginalia/language/sentence/SentenceSegmentSplitter.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/util/language/processing/sentence/SentenceSegmentSplitter.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/sentence/SentenceSegmentSplitter.java index 6a4516cf..4eb0dccf 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/sentence/SentenceSegmentSplitter.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/sentence/SentenceSegmentSplitter.java @@ -1,14 +1,14 @@ -package nu.marginalia.util.language.processing.sentence; +package nu.marginalia.language.sentence; import gnu.trove.list.array.TIntArrayList; import lombok.AllArgsConstructor; import lombok.Getter; -import nu.marginalia.util.language.processing.model.tag.WordSeparator; +import nu.marginalia.language.model.WordSeparator; import java.util.ArrayList; import java.util.List; -import static nu.marginalia.util.language.WordPatterns.*; +import static nu.marginalia.language.WordPatterns.*; public class SentenceSegmentSplitter { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/EnglishDictionary.java b/libraries/language-processing/src/main/java/nu/marginalia/language/statistics/EnglishDictionary.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/EnglishDictionary.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/statistics/EnglishDictionary.java index 9a9fefd0..f861fdda 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/EnglishDictionary.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/statistics/EnglishDictionary.java @@ -1,7 +1,6 @@ -package nu.marginalia.wmsa.edge.search.query; +package nu.marginalia.language.statistics; import com.google.inject.Inject; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/NGramBloomFilter.java b/libraries/language-processing/src/main/java/nu/marginalia/language/statistics/NGramBloomFilter.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/NGramBloomFilter.java rename to libraries/language-processing/src/main/java/nu/marginalia/language/statistics/NGramBloomFilter.java index cb463c61..c842ee5b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/NGramBloomFilter.java +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/statistics/NGramBloomFilter.java @@ -1,22 +1,18 @@ -package nu.marginalia.wmsa.edge.assistant.dict; +package nu.marginalia.language.statistics; import ca.rmen.porterstemmer.PorterStemmer; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import com.google.inject.Inject; +import nu.marginalia.LanguageModels; import nu.marginalia.util.DenseBitMap; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.index.lexicon.journal.KeywordLexiconJournalFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; public class NGramBloomFilter { @@ -27,10 +23,6 @@ public class NGramBloomFilter { private static final Logger logger = LoggerFactory.getLogger(NGramBloomFilter.class); @Inject - public NGramBloomFilter() throws IOException { - this(WmsaHome.getLanguageModels()); - } - public NGramBloomFilter(LanguageModels lm) throws IOException { this(loadSafely(lm.ngramBloomFilter)); } @@ -55,29 +47,29 @@ public class NGramBloomFilter { return bitMap.get(bit); } - public static void main(String... args) throws IOException { - var filter = convertFromDictionaryFile(new File(args[0])); - filter.bitMap.writeToFile(Path.of(args[1])); - } +// public static void main(String... args) throws IOException { +// var filter = convertFromDictionaryFile(new File(args[0])); +// filter.bitMap.writeToFile(Path.of(args[1])); +// } public static NGramBloomFilter load(Path file) throws IOException { return new NGramBloomFilter(DenseBitMap.loadFromFile(file)); } - public static NGramBloomFilter convertFromDictionaryFile(File file) throws IOException { - DenseBitMap bitMap = new DenseBitMap(1024*1024*1024L); - AtomicInteger popCount = new AtomicInteger(); - try (var f = new KeywordLexiconJournalFile(file)) { - f.loadFile(data -> { - long bit = bitForWord(new String(data), bitMap.cardinality); - if (!bitMap.set(bit)) - popCount.incrementAndGet(); - }); - } - - System.out.println("popcount = " + popCount.get()); - return new NGramBloomFilter(bitMap); - } +// public static NGramBloomFilter convertFromDictionaryFile(File file) throws IOException { +// DenseBitMap bitMap = new DenseBitMap(1024*1024*1024L); +// AtomicInteger popCount = new AtomicInteger(); +// try (var f = new KeywordLexiconJournalFile(file)) { +// f.loadFile(data -> { +// long bit = bitForWord(new String(data), bitMap.cardinality); +// if (!bitMap.set(bit)) +// popCount.incrementAndGet(); +// }); +// } +// +// System.out.println("popcount = " + popCount.get()); +// return new NGramBloomFilter(bitMap); +// } private static final Pattern underscore = Pattern.compile("_"); diff --git a/libraries/language-processing/src/main/java/nu/marginalia/language/statistics/TermFrequencyDict.java b/libraries/language-processing/src/main/java/nu/marginalia/language/statistics/TermFrequencyDict.java new file mode 100644 index 00000000..d96a9625 --- /dev/null +++ b/libraries/language-processing/src/main/java/nu/marginalia/language/statistics/TermFrequencyDict.java @@ -0,0 +1,206 @@ +package nu.marginalia.language.statistics; + +import ca.rmen.porterstemmer.PorterStemmer; +import gnu.trove.map.hash.TLongIntHashMap; +import nu.marginalia.LanguageModels; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Singleton +public class TermFrequencyDict { + + private final TLongIntHashMap wordRates = new TLongIntHashMap(1_000_000, 0.5f, 0, 0); + + private final Logger logger = LoggerFactory.getLogger(getClass()); + private static final Pattern separator = Pattern.compile("[_ ]+"); + private static final PorterStemmer ps = new PorterStemmer(); + + private static final long DOC_COUNT_KEY = ~0L; + private static long fileSize(Path p) throws IOException { + return Files.size(p); + } + + @Inject + public TermFrequencyDict(@Nullable LanguageModels models) { + if (models == null) { + return; + } + + if (models.termFrequencies != null) { + + try (var frequencyData = new DataInputStream(new BufferedInputStream(new FileInputStream(models.termFrequencies.toFile())))) { + + wordRates.ensureCapacity((int)(fileSize(models.termFrequencies)/16)); + + for (;;) { + wordRates.put(frequencyData.readLong(), (int) frequencyData.readLong()); + } + } catch (EOFException eof) { + // ok + } catch (IOException e) { + logger.error("IO Exception reading " + models.termFrequencies, e); + } + } + + logger.info("Read {} N-grams frequencies", wordRates.size()); + } + + + public int docCount() { + int cnt = wordRates.get(DOC_COUNT_KEY); + + if (cnt == 0) { + cnt = 11820118; // legacy + } + return cnt; + } + +// +// public static void main(String... args) throws IOException, InterruptedException { +// if (args.length != 2) { +// System.err.println("Expected arguments: plan.yaml out-file"); +// } +// String outFile = args[1]; +// +// var plan = new CrawlPlanLoader().load(Path.of(args[0])); +// +// ThreadLocal se = ThreadLocal.withInitial(() -> new SentenceExtractor(WmsaHome.getLanguageModels())); +// LanguageFilter lf = new LanguageFilter(); +// +// TLongIntHashMap counts = new TLongIntHashMap(100_000_000, 0.7f, -1, -1); +// +// ForkJoinPool fjp = new ForkJoinPool(24); +// AtomicInteger docCount = new AtomicInteger(); +// +// for (var domain : plan.domainsIterable()) { // leaks file descriptor, is fine +// +// if (domain.doc == null) +// continue; +// +// fjp.execute(() -> { +// +// TLongHashSet words = new TLongHashSet(10_000); +// +// for (var doc : domain.doc) { +// +// if (doc.documentBody == null) +// continue; +// docCount.incrementAndGet(); +// +// Document parsed = Jsoup.parse(doc.documentBody.decode()); +// parsed.body().filter(new DomPruningFilter(0.5)); +// +// DocumentLanguageData dld = se.get().extractSentences(parsed); +// +// if (lf.dictionaryAgreement(dld) < 0.1) { +// return; +// } +// +// for (var sent : dld.sentences) { +// for (var word : sent) { +// words.add(longHash(word.stemmed().getBytes(StandardCharsets.UTF_8))); +// } +// } +// +// synchronized (counts) { +// words.forEach(w -> { +// counts.adjustOrPutValue(w, 1, 1); +// return true; +// }); +// } +// +// words.clear(); +// } +// +// System.out.println(domain.domain + "\t" + counts.size()); +// }); +// +// +// } +// +// fjp.shutdown(); +// fjp.awaitTermination(10, TimeUnit.DAYS); +// +// try (var dos = new DataOutputStream(Files.newOutputStream(Path.of(outFile)))) { +// synchronized (counts) { +// counts.put(DOC_COUNT_KEY, docCount.get()); +// +// counts.forEachEntry((hash, cnt) -> { +// try { +// dos.writeLong(hash); +// dos.writeLong(cnt); +// } catch (IOException e) { +// throw new RuntimeException(e); +// } +// return true; +// }); +// } +// } +// +// System.out.println(docCount.get()); +// } + + public static long getStringHash(String s) { + String[] strings = separator.split(s); + if (s.length() > 1) { + byte[][] parts = new byte[strings.length][]; + for (int i = 0; i < parts.length; i++) { + parts[i] = ps.stemWord(strings[i]).getBytes(); + } + return longHash(parts); + } + else { + return longHash(s.getBytes()); + } + } + public long getTermFreqHash(long hash) { + return wordRates.get(hash); + } + public long getTermFreq(String s) { + return wordRates.get(getStringHash(s)); + } + public long getTermFreqStemmed(String s) { + return wordRates.get(longHash(s.getBytes())); + } + + public static String getStemmedString(String s) { + String[] strings = separator.split(s); + if (s.length() > 1) { + return Arrays.stream(strings).map(ps::stemWord).collect(Collectors.joining("_")); + } + else { + return s; + } + + } + + public static long longHash(byte[]... bytesSets) { + if (bytesSets == null || bytesSets.length == 0) + return 0; + + // https://cp-algorithms.com/string/string-hashing.html + int p = 127; + long m = (1L<<61)-1; + long p_power = 1; + long hash_val = 0; + + for (byte[] bytes: bytesSets) { + for (byte element : bytes) { + hash_val = (hash_val + (element + 1) * p_power) % m; + p_power = (p_power * p) % m; + } + } + return hash_val; + } + +} diff --git a/marginalia_nu/src/main/resources/dictionary/en-1000 b/libraries/language-processing/src/main/resources/dictionary/en-1000 similarity index 100% rename from marginalia_nu/src/main/resources/dictionary/en-1000 rename to libraries/language-processing/src/main/resources/dictionary/en-1000 diff --git a/marginalia_nu/src/main/resources/dictionary/en-stopwords b/libraries/language-processing/src/main/resources/dictionary/en-stopwords similarity index 100% rename from marginalia_nu/src/main/resources/dictionary/en-stopwords rename to libraries/language-processing/src/main/resources/dictionary/en-stopwords diff --git a/marginalia_nu/src/main/resources/dictionary/en-words b/libraries/language-processing/src/main/resources/dictionary/en-words similarity index 100% rename from marginalia_nu/src/main/resources/dictionary/en-words rename to libraries/language-processing/src/main/resources/dictionary/en-words diff --git a/marginalia_nu/src/main/resources/dictionary/latin-1000 b/libraries/language-processing/src/main/resources/dictionary/latin-1000 similarity index 100% rename from marginalia_nu/src/main/resources/dictionary/latin-1000 rename to libraries/language-processing/src/main/resources/dictionary/latin-1000 diff --git a/marginalia_nu/src/main/resources/dictionary/swe-1000 b/libraries/language-processing/src/main/resources/dictionary/swe-1000 similarity index 100% rename from marginalia_nu/src/main/resources/dictionary/swe-1000 rename to libraries/language-processing/src/main/resources/dictionary/swe-1000 diff --git a/marginalia_nu/src/main/resources/dictionary/word-frequency b/libraries/language-processing/src/main/resources/dictionary/word-frequency similarity index 100% rename from marginalia_nu/src/main/resources/dictionary/word-frequency rename to libraries/language-processing/src/main/resources/dictionary/word-frequency diff --git a/libraries/language-processing/src/test/java/nu/marginalia/language/encoding/AsciiFlattenerTest.java b/libraries/language-processing/src/test/java/nu/marginalia/language/encoding/AsciiFlattenerTest.java new file mode 100644 index 00000000..afa65a36 --- /dev/null +++ b/libraries/language-processing/src/test/java/nu/marginalia/language/encoding/AsciiFlattenerTest.java @@ -0,0 +1,38 @@ +package nu.marginalia.language.encoding; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class AsciiFlattenerTest { + + @Test + void flattenUnicodePlainAscii() { + String s = "abc"; + + // If the string is ascii, we don't want to allocate a copy + + assertSame(s, AsciiFlattener.flattenUnicode(s)); + } + + @Test + void flattenUnicode() { + String s = "Stülpnagelstraße"; + + assertEquals("Stulpnagelstrasse", AsciiFlattener.flattenUnicode(s)); + } + + @Test + void flattenUnicode2() { + String s = "Koncevičius"; + + assertEquals("Koncevicius", AsciiFlattener.flattenUnicode(s)); + } + + @Test + void omitNonFlattenable() { + String s = "[アグレッシブ烈子]"; + + assertEquals("[]", AsciiFlattener.flattenUnicode(s)); + } +} \ No newline at end of file diff --git a/libraries/misc/build.gradle b/libraries/misc/build.gradle new file mode 100644 index 00000000..366df5c0 --- /dev/null +++ b/libraries/misc/build.gradle @@ -0,0 +1,30 @@ +plugins { + id 'java' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.notnull + implementation libs.lz4 + implementation libs.fastutil + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/bigstring/BigString.java b/libraries/misc/src/main/java/nu/marginalia/bigstring/BigString.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/util/bigstring/BigString.java rename to libraries/misc/src/main/java/nu/marginalia/bigstring/BigString.java index 48c4c053..5bf88180 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/bigstring/BigString.java +++ b/libraries/misc/src/main/java/nu/marginalia/bigstring/BigString.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.bigstring; +package nu.marginalia.bigstring; public interface BigString { static BigString encode(String stringValue) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/bigstring/CompressedBigString.java b/libraries/misc/src/main/java/nu/marginalia/bigstring/CompressedBigString.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/util/bigstring/CompressedBigString.java rename to libraries/misc/src/main/java/nu/marginalia/bigstring/CompressedBigString.java index 1b84e576..dabd84a9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/bigstring/CompressedBigString.java +++ b/libraries/misc/src/main/java/nu/marginalia/bigstring/CompressedBigString.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.bigstring; +package nu.marginalia.bigstring; import net.jpountz.lz4.LZ4Compressor; import net.jpountz.lz4.LZ4Factory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/bigstring/PlainBigString.java b/libraries/misc/src/main/java/nu/marginalia/bigstring/PlainBigString.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/util/bigstring/PlainBigString.java rename to libraries/misc/src/main/java/nu/marginalia/bigstring/PlainBigString.java index 5af3a5c8..a90fae67 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/bigstring/PlainBigString.java +++ b/libraries/misc/src/main/java/nu/marginalia/bigstring/PlainBigString.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.bigstring; +package nu.marginalia.bigstring; import java.nio.charset.StandardCharsets; diff --git a/libraries/misc/src/main/java/nu/marginalia/dict/DictionaryData.java b/libraries/misc/src/main/java/nu/marginalia/dict/DictionaryData.java new file mode 100644 index 00000000..830ed4a7 --- /dev/null +++ b/libraries/misc/src/main/java/nu/marginalia/dict/DictionaryData.java @@ -0,0 +1,39 @@ +package nu.marginalia.dict; + +import java.util.ArrayList; + +public class DictionaryData { + private final int bankSize; + + private final ArrayList banks = new ArrayList<>(100); + + public DictionaryData(int bankSize) { + this.bankSize = bankSize; + + banks.add(new DictionaryDataBank(0, bankSize)); + } + + public int add(long key) { + var activeBank = banks.get(banks.size()-1); + int rb = activeBank.add(key); + + if (rb == -1) { + int end = activeBank.getEnd(); + var newBank = new DictionaryDataBank(end, bankSize); + rb = newBank.add(key); + + banks.add(newBank); + } + + return rb; + } + + + public long getKey(int offset) { + return banks.get(offset/ bankSize).getKey(offset); + } + public boolean keyEquals(int offset, long otherKey) { + return banks.get(offset/ bankSize).keyEquals(offset, otherKey); + } + +} diff --git a/libraries/misc/src/main/java/nu/marginalia/dict/DictionaryDataBank.java b/libraries/misc/src/main/java/nu/marginalia/dict/DictionaryDataBank.java new file mode 100644 index 00000000..75798dcb --- /dev/null +++ b/libraries/misc/src/main/java/nu/marginalia/dict/DictionaryDataBank.java @@ -0,0 +1,63 @@ +package nu.marginalia.dict; + +import java.nio.ByteBuffer; +import java.nio.LongBuffer; + +class DictionaryDataBank { + + private final int start_idx; + + // Humongous long-lived arrays seem to sometimes yield considerable memory overhead and + // can make the GC behave poorly. Using off-heap memory seems preferred when their + // lifetime is "forever" + + private final LongBuffer keys; + + private int size; + private final int capacity; + + + public DictionaryDataBank(int start_idx, int sz) { + this.start_idx = start_idx; + this.capacity = sz; + + keys = ByteBuffer.allocateDirect(8 * capacity).asLongBuffer(); + size = 0; + } + + public int getStart() { + return start_idx; + } + + public int getEnd() { + return start_idx + size; + } + + public long getKey(int idx) { + if (idx < start_idx || idx - start_idx >= size) { + throw new IndexOutOfBoundsException(idx); + } + return keys.get(idx - start_idx); + } + + public boolean keyEquals(int idx, long other) { + if (idx < start_idx || idx - start_idx >= size) { + throw new IndexOutOfBoundsException(idx); + } + + return keys.get(idx - start_idx) == other; + } + + public int add(long newKey) { + if (size >= capacity) + return -1; + + keys.put(size, newKey); + + return start_idx + size++; + } + + public int getSize() { + return size; + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/dict/DictionaryMap.java b/libraries/misc/src/main/java/nu/marginalia/dict/DictionaryMap.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/util/dict/DictionaryMap.java rename to libraries/misc/src/main/java/nu/marginalia/dict/DictionaryMap.java index fb13893e..64bd8030 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/dict/DictionaryMap.java +++ b/libraries/misc/src/main/java/nu/marginalia/dict/DictionaryMap.java @@ -1,10 +1,10 @@ -package nu.marginalia.util.dict; +package nu.marginalia.dict; public interface DictionaryMap { int NO_VALUE = Integer.MIN_VALUE; static DictionaryMap create() { - if (Boolean.getBoolean("small-ram")) { + if (!Boolean.getBoolean("large-ram")) { return new OnHeapDictionaryMap(); } else { diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/dict/OffHeapDictionaryHashMap.java b/libraries/misc/src/main/java/nu/marginalia/dict/OffHeapDictionaryHashMap.java similarity index 76% rename from marginalia_nu/src/main/java/nu/marginalia/util/dict/OffHeapDictionaryHashMap.java rename to libraries/misc/src/main/java/nu/marginalia/dict/OffHeapDictionaryHashMap.java index f906c45a..781c3b5c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/dict/OffHeapDictionaryHashMap.java +++ b/libraries/misc/src/main/java/nu/marginalia/dict/OffHeapDictionaryHashMap.java @@ -1,30 +1,22 @@ -package nu.marginalia.util.dict; +package nu.marginalia.dict; -import io.prometheus.client.Gauge; import nu.marginalia.util.PrimeUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.concurrent.atomic.AtomicInteger; import static java.lang.Math.round; -import static nu.marginalia.util.FileSizeUtil.readableSize; /** * Spiritually influenced by GNU Trove's hash maps * LGPL 2.1 */ public class OffHeapDictionaryHashMap implements DictionaryMap { - private static final Logger logger = LoggerFactory.getLogger(OffHeapDictionaryHashMap.class); - private static final Gauge probe_count_metrics - = Gauge.build("wmsa_dictionary_hash_map_probe_count", "Probing Count") - .register(); private final int bufferCount; - private final IntBuffer[] buffers; + private final IntBuffer[] buffers; private final DictionaryData dictionaryData; private final long hashTableSize; @@ -47,22 +39,8 @@ public class OffHeapDictionaryHashMap implements DictionaryMap { bufferSizeBytes = intSize*intsPerBuffer; maxProbeLength = sizeMemory/10; - logger.info("Allocating dictionary hash map of size {}, capacity: {}", - readableSize((long) bufferCount * bufferSizeBytes), - hashTableSize); - - logger.info("available-size:{} memory-size:{} buffer-count: {}, buffer-size:{} ints-per-buffer:{} max-probe-length:{}", - hashTableSize, sizeMemory, bufferCount, bufferSizeBytes, intsPerBuffer, maxProbeLength); - if (((long) bufferCount * intsPerBuffer) < sizeMemory) { - logger.error("Buffer memory is less than requested memory: {}*{} = {} < {}; this data structure is not safe to use", - bufferCount, - bufferSizeBytes, (long) bufferCount * bufferSizeBytes, - sizeMemory); - throw new Error("Irrecoverable logic error"); - } - else { - logger.debug("Buffer size sanity checked passed"); + throw new Error("Buffer memory is less than requested memory; this data structure is not safe to use"); } dictionaryData = new DictionaryData((int)Math.min(1<<27, Math.max(32L, sizeMemory/4))); @@ -124,8 +102,6 @@ public class OffHeapDictionaryHashMap implements DictionaryMap { final int val = getCell(idx); if (val == NO_VALUE) { - probe_count_metrics.set(j); - return setValue(key, idx); } else if (dictionaryData.keyEquals(val, key)) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/dict/OnHeapDictionaryMap.java b/libraries/misc/src/main/java/nu/marginalia/dict/OnHeapDictionaryMap.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/util/dict/OnHeapDictionaryMap.java rename to libraries/misc/src/main/java/nu/marginalia/dict/OnHeapDictionaryMap.java index a9f4063f..067c2cdc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/dict/OnHeapDictionaryMap.java +++ b/libraries/misc/src/main/java/nu/marginalia/dict/OnHeapDictionaryMap.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.dict; +package nu.marginalia.dict; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/gregex/GuardedRegex.java b/libraries/misc/src/main/java/nu/marginalia/gregex/GuardedRegex.java similarity index 73% rename from marginalia_nu/src/main/java/nu/marginalia/util/gregex/GuardedRegex.java rename to libraries/misc/src/main/java/nu/marginalia/gregex/GuardedRegex.java index 7ba29096..85353f0c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/gregex/GuardedRegex.java +++ b/libraries/misc/src/main/java/nu/marginalia/gregex/GuardedRegex.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.gregex; +package nu.marginalia.gregex; import java.util.function.Predicate; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/gregex/GuardedRegexFactory.java b/libraries/misc/src/main/java/nu/marginalia/gregex/GuardedRegexFactory.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/util/gregex/GuardedRegexFactory.java rename to libraries/misc/src/main/java/nu/marginalia/gregex/GuardedRegexFactory.java index 800fc621..50131bf7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/gregex/GuardedRegexFactory.java +++ b/libraries/misc/src/main/java/nu/marginalia/gregex/GuardedRegexFactory.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.gregex; +package nu.marginalia.gregex; import org.intellij.lang.annotations.Language; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/EasyLSH.java b/libraries/misc/src/main/java/nu/marginalia/lsh/EasyLSH.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/util/EasyLSH.java rename to libraries/misc/src/main/java/nu/marginalia/lsh/EasyLSH.java index 8b709da0..5b168f07 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/EasyLSH.java +++ b/libraries/misc/src/main/java/nu/marginalia/lsh/EasyLSH.java @@ -1,7 +1,4 @@ -package nu.marginalia.util; - -import java.util.List; -import java.util.Set; +package nu.marginalia.lsh; /** This is a very simple locality sensitive hash for collections of Java objects. *

@@ -36,30 +33,32 @@ public class EasyLSH { } public void addHashOrdered(int hashCode) { - hashCode = shingleHash(hashCode); addHashUnordered(shingleHash(hashCode)); } public void addHashUnordered(int hashCode) { - int value = 1-(hashCode & 2); + int value = 1- (hashCode & 2); // Try to extract all the remaining entropy // into selecting the field to update - int field = 63 & (((hashCode >>> 2) - ^ (hashCode >>> 10) - ^ (hashCode >>> 18) - ^ (hashCode >>> 26))); + int field = (hashCode >> 2) + ^ (hashCode >>> 8) + ^ (hashCode >>> 14) + ^ (hashCode >>> 20) + ^ (hashCode >>> 26); - fields[field] += value; + fields[field & 63] += value; } private int shingleHash(int nextHash) { prevHashes[prevHashIdx++ & (SHINGLING-1)] = nextHash; + int ret = 0; for (int hashPart : prevHashes) { - ret ^= hashPart; + ret = hashPart ^ ret; } + return ret; } @@ -73,10 +72,6 @@ public class EasyLSH { return val; } - public int hammingDistance(EasyLSH other) { - return hammingDistance(this, other); - } - public static int hammingDistance(long a, long b) { return Long.bitCount(a^b); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/FileSizeUtil.java b/libraries/misc/src/main/java/nu/marginalia/util/FileSizeUtil.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/util/FileSizeUtil.java rename to libraries/misc/src/main/java/nu/marginalia/util/FileSizeUtil.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/PrimeUtil.java b/libraries/misc/src/main/java/nu/marginalia/util/PrimeUtil.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/util/PrimeUtil.java rename to libraries/misc/src/main/java/nu/marginalia/util/PrimeUtil.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/RandomWriteFunnel.java b/libraries/misc/src/main/java/nu/marginalia/util/RandomWriteFunnel.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/util/RandomWriteFunnel.java rename to libraries/misc/src/main/java/nu/marginalia/util/RandomWriteFunnel.java index 4e21c76e..6c0b03b0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/RandomWriteFunnel.java +++ b/libraries/misc/src/main/java/nu/marginalia/util/RandomWriteFunnel.java @@ -1,6 +1,5 @@ package nu.marginalia.util; -import lombok.SneakyThrows; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,8 +33,7 @@ public class RandomWriteFunnel implements AutoCloseable { bins = new ArrayList<>(); } - @SneakyThrows - public void put(long address, long data) { + public void put(long address, long data) throws IOException { int bin = (int)(address / binSize); int offset = (int)(address%binSize); @@ -46,8 +44,7 @@ public class RandomWriteFunnel implements AutoCloseable { bins.get(bin).put(offset, data); } - @SneakyThrows - private void grow(int bin) { + private void grow(int bin) throws IOException { while (bins.size() <= bin) { bins.add(new DataBin(tempDir, binSize)); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/TransformList.java b/libraries/misc/src/main/java/nu/marginalia/util/TransformList.java similarity index 79% rename from marginalia_nu/src/main/java/nu/marginalia/util/TransformList.java rename to libraries/misc/src/main/java/nu/marginalia/util/TransformList.java index f8aac39e..352b39cb 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/TransformList.java +++ b/libraries/misc/src/main/java/nu/marginalia/util/TransformList.java @@ -5,6 +5,31 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; +/** Recursive descent-style parser utility. + *

+ * Allows readable and easy mutation of a list. + *

+ * Examples: + * + *
+ *     tl.transformEach(entity -> {
+ *         if (foo(entity.value))
+ *           entity.remove();
+ *         if (bar(entity.value))
+ *           entity.replace(Bar(10));
+ *     }
+ *
+ *     tl.transformEachPair((a,b) -> {
+ *          if ("-".equals(a.value.str) && Number.equals(b.value.type)) {
+ *              a.remove();
+ *              b.replace(new Number(-b.value.val));
+ *          }
+ *     }
+ *
+ *     list.scanAndTransform(TokenType.LPAREN, TokenType.RPAREN, itemsBetween -> { });
+ * 
+ *
+ */ public class TransformList { private final List backingList; @@ -85,12 +110,18 @@ public class TransformList { } + /** Represents a mutable item in the transform list */ public class Entity { - public T value; + private T value; private Action action; Entity(T value) { this.value = value; + this.action = Action.NO_OP; + } + + public T value() { + return value; } public void replace(T newValue) { diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/EasyLSHTest.java b/libraries/misc/src/test/java/nu/marginalia/lsh/EasyLSHTest.java similarity index 93% rename from marginalia_nu/src/test/java/nu/marginalia/util/EasyLSHTest.java rename to libraries/misc/src/test/java/nu/marginalia/lsh/EasyLSHTest.java index e8c3f147..e86425f0 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/EasyLSHTest.java +++ b/libraries/misc/src/test/java/nu/marginalia/lsh/EasyLSHTest.java @@ -1,5 +1,6 @@ -package nu.marginalia.util; +package nu.marginalia.lsh; +import nu.marginalia.lsh.EasyLSH; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -52,14 +53,14 @@ class EasyLSHTest { """; EasyLSH hashA = new EasyLSH(); - Arrays.stream(sA.split("\\s")).forEach(hashA::addOrdered); + Arrays.stream(sA.split("\\s+")).forEach(hashA::addOrdered); EasyLSH hashB = new EasyLSH(); - Arrays.stream(sB.split("\\s")).forEach(hashB::addOrdered); + Arrays.stream(sB.split("\\s+")).forEach(hashB::addOrdered); EasyLSH hashC = new EasyLSH(); - Arrays.stream(sC.split("\\s")).forEach(hashC::addOrdered); + Arrays.stream(sC.split("\\s+")).forEach(hashC::addOrdered); EasyLSH hashD = new EasyLSH(); - Arrays.stream(sD.split("\\s")).forEach(hashD::addOrdered); + Arrays.stream(sD.split("\\s+")).forEach(hashD::addOrdered); System.out.println(Long.toBinaryString(hashA.get())); System.out.println(Long.toBinaryString(hashB.get())); diff --git a/libraries/misc/src/test/java/nu/marginalia/test/TestUtil.java b/libraries/misc/src/test/java/nu/marginalia/test/TestUtil.java new file mode 100644 index 00000000..44a489bb --- /dev/null +++ b/libraries/misc/src/test/java/nu/marginalia/test/TestUtil.java @@ -0,0 +1,50 @@ +package nu.marginalia.test; + + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +public class TestUtil { + private static boolean isTempDir(Path dir) { + return dir.startsWith("/tmp") || dir.toString().contains("tmp"); + } + + public static void clearTempDir(Path dir) { + if (!isTempDir(dir)) { + throw new IllegalArgumentException("Refusing to recursively delete directory with that name"); + } + if (Files.isDirectory(dir)) { + for (File f : dir.toFile().listFiles()) { + File[] files = f.listFiles(); + if (files != null) { + Arrays.stream(files).map(File::toPath).forEach(TestUtil::clearTempDir); + } + System.out.println("Deleting " + f + " (" + fileSize(f.toPath()) + ")"); + f.delete(); + } + } + System.out.println("Deleting " + dir); + dir.toFile().delete(); + } + + private static String fileSize(Path path) { + try { + long sizeBytes = Files.size(path); + + if (sizeBytes > 1024 * 1024 * 1024) return round(sizeBytes / 1073741824.) + "Gb"; + if (sizeBytes > 1024 * 1024) return round(sizeBytes / 1048576.) + "Mb"; + if (sizeBytes > 1024) return round(sizeBytes / 1024.) + "Kb"; + return sizeBytes + "b"; + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static String round(double d) { + return String.format("%.2f", d); + } +} diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/TransformListTest.java b/libraries/misc/src/test/java/nu/marginalia/util/TransformListTest.java similarity index 81% rename from marginalia_nu/src/test/java/nu/marginalia/util/TransformListTest.java rename to libraries/misc/src/test/java/nu/marginalia/util/TransformListTest.java index 15b1ccde..2a9ea325 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/TransformListTest.java +++ b/libraries/misc/src/test/java/nu/marginalia/util/TransformListTest.java @@ -15,7 +15,7 @@ class TransformListTest { List values = Stream.of(1,2,3,4).collect(Collectors.toList()); new TransformList<>(values).transformEach(e -> { - int v = e.value; + int v = e.value(); if (v == 1) e.remove(); if (v == 2) e.replace(5); if (v == 4) e.remove(); @@ -28,11 +28,11 @@ class TransformListTest { void transformEachPairRemoveReplace() { List values = Stream.of(1,2,3,4,5,6).collect(Collectors.toList()); new TransformList<>(values).transformEachPair((a,b) -> { - System.out.println(a.value + ":" + b.value); - int v = a.value; + System.out.println(a.value() + ":" + b.value()); + int v = a.value(); if (v == 1 || v == 3 || v == 5) { a.remove(); - b.replace(-b.value); + b.replace(-b.value()); } }); @@ -44,8 +44,8 @@ class TransformListTest { void transformEachPairRemoveRemove() { List values = Stream.of(1,2,3,4,5,6).collect(Collectors.toList()); new TransformList<>(values).transformEachPair((a,b) -> { - System.out.println(a.value + ":" + b.value); - int v = a.value; + System.out.println(a.value() + ":" + b.value()); + int v = a.value(); if (v == 1 || v == 3 || v == 5) { a.remove(); b.remove(); @@ -60,10 +60,10 @@ class TransformListTest { void transformEachPairReplaceRemove() { List values = Stream.of(1,2,3,4,5,6).collect(Collectors.toList()); new TransformList<>(values).transformEachPair((a,b) -> { - System.out.println(a.value + ":" + b.value); - int v = a.value; + System.out.println(a.value() + ":" + b.value()); + int v = a.value(); if (v == 1 || v == 3 || v == 5) { - a.replace(-a.value); + a.replace(-a.value()); b.remove(); } @@ -76,11 +76,11 @@ class TransformListTest { void transformEachPairReplaceReplace() { List values = Stream.of(1,2,3,4,5,6).collect(Collectors.toList()); new TransformList<>(values).transformEachPair((a,b) -> { - System.out.println(a.value + ":" + b.value); - int v = a.value; + System.out.println(a.value() + ":" + b.value()); + int v = a.value(); if (v == 1 || v == 3 || v == 5) { - a.replace(-a.value); - b.replace(-b.value); + a.replace(-a.value()); + b.replace(-b.value()); } }); @@ -92,7 +92,7 @@ class TransformListTest { void scanAndTransform() { List values = Stream.of(1,2,3,4,5,6,7,8,9,10).collect(Collectors.toList()); new TransformList<>(values).scanAndTransform(Integer.valueOf(3)::equals, Integer.valueOf(7)::equals, entity -> { - entity.replace(entity.value * 2); + entity.replace(entity.value() * 2); }); assertEquals(List.of(1,2,6,8,10,12,14,8,9,10), values); @@ -102,7 +102,7 @@ class TransformListTest { void scanAndTransformEndsAtEnd() { List values = Stream.of(1,2,3,4,5,6,7,8,9,10).collect(Collectors.toList()); new TransformList<>(values).scanAndTransform(Integer.valueOf(3)::equals, Integer.valueOf(10)::equals, entity -> { - entity.replace(entity.value * 2); + entity.replace(entity.value() * 2); }); assertEquals(List.of(1,2,6,8,10,12,14,16,18,20), values); @@ -112,7 +112,7 @@ class TransformListTest { void scanAndTransformOverlap() { List values = Stream.of(1,2,3,3,5,7,7,8,9,10).collect(Collectors.toList()); new TransformList<>(values).scanAndTransform(Integer.valueOf(3)::equals, Integer.valueOf(7)::equals, entity -> { - entity.replace(entity.value * 2); + entity.replace(entity.value() * 2); }); assertEquals(List.of(1, 2, 6, 6, 10, 14, 7, 8, 9, 10), values); diff --git a/libraries/readme.md b/libraries/readme.md new file mode 100644 index 00000000..1179db65 --- /dev/null +++ b/libraries/readme.md @@ -0,0 +1,9 @@ +# Libraries + +These are libraries that are not strongly coupled to the search engine. + +* The [array](array/) library is for memory mapping large memory-areas, which Java has +bad support for. It's designed to be able to easily replaced when *Java's Foreign Function And Memory API* is released. +* The [btree](btree/) library offers a static BTree implementation based on the array library. +* [language-processing](language-processing/) contains primitives for sentence extraction and POS-tagging. +* [misc](misc/) is just random bits and bobs that didn't fit anywhere. \ No newline at end of file diff --git a/marginalia_nu/data/.gitignore b/marginalia_nu/data/.gitignore deleted file mode 100644 index f59ec20a..00000000 --- a/marginalia_nu/data/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* \ No newline at end of file diff --git a/marginalia_nu/data/models/.gitignore b/marginalia_nu/data/models/.gitignore deleted file mode 100644 index f59ec20a..00000000 --- a/marginalia_nu/data/models/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* \ No newline at end of file diff --git a/marginalia_nu/data/test/.gitignore b/marginalia_nu/data/test/.gitignore deleted file mode 100644 index f59ec20a..00000000 --- a/marginalia_nu/data/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* \ No newline at end of file diff --git a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/E2ETestBase.java b/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/E2ETestBase.java deleted file mode 100644 index da40a7fc..00000000 --- a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/E2ETestBase.java +++ /dev/null @@ -1,86 +0,0 @@ -package nu.marginalia.wmsa.edge; - -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.BindMode; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.MariaDBContainer; -import org.testcontainers.containers.Network; -import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.utility.MountableFile; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; - -public abstract class E2ETestBase { - public static Network network = Network.newNetwork(); - - public static MariaDBContainer getMariaDBContainer() { - return new MariaDBContainer<>("mariadb") - .withDatabaseName("WMSA_prod") - .withUsername("wmsa") - .withPassword("wmsa") - .withInitScript("sql/edge-crawler-cache.sql") - .withNetwork(network) - .withNetworkAliases("mariadb"); - } - - public static GenericContainer forService(ServiceDescriptor service, GenericContainer mariaDB) { - return new GenericContainer<>("openjdk:17-alpine") - .dependsOn(mariaDB) - .withCopyFileToContainer(jarFile(), "/WMSA.jar") - .withCopyFileToContainer(MountableFile.forClasspathResource("init.sh"), "/init.sh") - .withExposedPorts(service.port) - .withFileSystemBind(modelsPath(), "/wmsa/model", BindMode.READ_ONLY) - .withNetwork(network) - .withNetworkAliases(service.name) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(service.name))) - .withCommand("sh", "init.sh", service.name) - .waitingFor(Wait.forHttp("/internal/ping") - .forPort(service.port) - .withReadTimeout(Duration.ofSeconds(15))) - ; - } - public static GenericContainer forService(ServiceDescriptor service, GenericContainer mariaDB, String setupScript) { - return new GenericContainer<>("openjdk:17-alpine") - .dependsOn(mariaDB) - .withCopyFileToContainer(jarFile(), "/WMSA.jar") - .withCopyFileToContainer(MountableFile.forClasspathResource(setupScript), "/" + setupScript) - .withExposedPorts(service.port) - .withFileSystemBind(modelsPath(), "/wmsa/model", BindMode.READ_ONLY) - .withNetwork(network) - .withNetworkAliases(service.name) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(service.name))) - .withCommand("sh", setupScript, service.name) - .waitingFor(Wait.forHttp("/internal/ping") - .forPort(service.port) - .withReadTimeout(Duration.ofSeconds(15))) - ; - } - - public static MountableFile jarFile() { - Path cwd = Path.of(System.getProperty("user.dir")); - - cwd = cwd.resolve(".."); - var jarFile = cwd.resolve("build/libs/wmsa-SNAPSHOT-all.jar"); - if (!Files.exists(jarFile)) { - System.err.println("Could not find jarFile " + jarFile); - throw new RuntimeException(); - } - else { - System.out.println("jar file = " + jarFile); - } - return MountableFile.forHostPath(jarFile); - } - - public static String modelsPath() { - Path modelsPath = Path.of(System.getProperty("user.dir")).resolve("data/models"); - if (!Files.isDirectory(modelsPath)) { - System.err.println("Could not find models, looked in " + modelsPath.toAbsolutePath()); - throw new RuntimeException(); - } - return modelsPath.toString(); - } -} diff --git a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeCrawlBehaviorE2ETest.java b/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeCrawlBehaviorE2ETest.java deleted file mode 100644 index f8325d9d..00000000 --- a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeCrawlBehaviorE2ETest.java +++ /dev/null @@ -1,105 +0,0 @@ -package nu.marginalia.wmsa.edge; - - -import nu.marginalia.wmsa.edge.crawling.CrawlJobExtractorMain; -import nu.marginalia.wmsa.edge.crawling.model.CrawlingSpecification; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.BindMode; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.MountableFile; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -@Tag("e2e") -@Testcontainers -public class EdgeCrawlBehaviorE2ETest extends E2ETestBase { - @Container - public static GenericContainer mockContainer = new GenericContainer<>("openjdk:17-alpine") - .withCopyFileToContainer(jarFile(), "/WMSA.jar") - .withNetwork(network) - .withNetworkAliases("mock", "mock2") - .withExposedPorts(8080) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("mock"))) - .withCommand("java","-cp","WMSA.jar","nu.marginalia.wmsa.edge.crawling.CrawlerTestMain") - ; - - - @Container - public static GenericContainer crawlerContainer = new GenericContainer<>("openjdk:17-alpine") - .dependsOn(mockContainer) - .withNetwork(network) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("crawler"))) - .withFileSystemBind(modelsPath(), "/var/lib/wmsa/model", BindMode.READ_ONLY) - .withCopyFileToContainer(ipDatabasePath(), "/var/lib/wmsa/data/IP2LOCATION-LITE-DB1.CSV") - .withCopyFileToContainer(jarFile(), "/WMSA.jar") - .withCopyFileToContainer(MountableFile.forClasspathResource("crawl-mock.sh"), "/crawl-mock.sh") - .withFileSystemBind(getMockCrawlPath(), "/crawl/", BindMode.READ_WRITE) - .withCommand("sh", "crawl-mock.sh") - .waitingFor(Wait.forLogMessage(".*ALL DONE.*", 1).withStartupTimeout(Duration.ofMinutes(10))); - - - private static String getMockCrawlPath() { - Path crawlFiles = getCrawlPath(); - - - List urls = new ArrayList<>(); - try { - Files.createDirectories(crawlFiles); - - Files.writeString(crawlFiles.resolve("crawl.plan"), """ - jobSpec: "/crawl/crawl.spec" - crawl: - dir: "/crawl/crawl" - logName: "crawl.log" - process: - dir: "/crawl/process" - logName: "process.log" - """); - - Files.createDirectories(crawlFiles.resolve("crawl")); - Files.createDirectories(crawlFiles.resolve("process")); - Files.deleteIfExists(crawlFiles.resolve("process").resolve("process.log")); - Files.deleteIfExists(crawlFiles.resolve("crawl").resolve("crawl.log")); - - CrawlJobExtractorMain.writeSpec(crawlFiles.resolve("crawl.spec"), - new CrawlingSpecification("111111", 20, "mock", List.of("http://mock:8080/rate-limit/")), - new CrawlingSpecification("222222", 20, "mock2", List.of("http://mock2:8080/intermittent-error/"))); - } - catch (IOException ex) { - ex.printStackTrace(); - } - return crawlFiles.toString(); - } - - - public static MountableFile ipDatabasePath() { - Path modelsPath = Path.of(System.getProperty("user.dir")).resolve("data/models/IP2LOC/IP2LOCATION-LITE-DB1.CSV"); - if (!Files.isRegularFile(modelsPath)) { - System.err.println("Could not find models, looked in " + modelsPath.toAbsolutePath()); - throw new RuntimeException(); - } - return MountableFile.forHostPath(modelsPath.toString()); - } - - private static Path getCrawlPath() { - return Path.of(System.getProperty("user.dir")).resolve("build/tmp/crawl"); - } - - @Test - public void testRunTheThing() throws IOException { - // This is a test for examining the interaction between the crawler and various - // set-ups - } - -} diff --git a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeSearchE2ETest.java b/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeSearchE2ETest.java deleted file mode 100644 index 1e1fad4b..00000000 --- a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EdgeSearchE2ETest.java +++ /dev/null @@ -1,284 +0,0 @@ -package nu.marginalia.wmsa.edge; - - -import nu.marginalia.util.test.TestUtil; -import nu.marginalia.wmsa.edge.crawling.CrawlJobExtractorMain; -import org.jsoup.Jsoup; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openzim.ZIMTypes.ZIMFile; -import org.openzim.ZIMTypes.ZIMReader; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.BindMode; -import org.testcontainers.containers.BrowserWebDriverContainer; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.NginxContainer; -import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.MountableFile; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static nu.marginalia.wmsa.configuration.ServiceDescriptor.*; -import static org.junit.jupiter.api.Assertions.assertEquals; - -@Tag("e2e") -@Testcontainers -public class EdgeSearchE2ETest extends E2ETestBase { - @Container - public static GenericContainer mariaDB = getMariaDBContainer(); - - @Container - public static GenericContainer searchContainer = forService(EDGE_SEARCH, mariaDB); - @Container - public static GenericContainer assistantContainer = forService(EDGE_ASSISTANT, mariaDB); - @Container - public static GenericContainer indexContainer = forService(EDGE_INDEX, mariaDB); - - @Container - public static NginxContainer mockWikipedia = new NginxContainer<>("nginx:stable") - .dependsOn(searchContainer) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("wikipedia"))) - .withFileSystemBind(getWikipediaFiles(), "/usr/share/nginx/html/", BindMode.READ_ONLY) - .withNetwork(network) - .withNetworkAliases("wikipedia.local"); - - - @Container - public static BrowserWebDriverContainer chrome = new BrowserWebDriverContainer<>() - .withNetwork(network) - .withCapabilities(new ChromeOptions()); - - @Container - public static GenericContainer crawlerContainer = new GenericContainer<>("openjdk:17-alpine") - .dependsOn(mockWikipedia) - .dependsOn(indexContainer) - .withNetwork(network) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("crawler"))) - .withFileSystemBind(modelsPath(), "/var/lib/wmsa/model", BindMode.READ_ONLY) - .withCopyFileToContainer(ipDatabasePath(), "/var/lib/wmsa/data/IP2LOCATION-LITE-DB1.CSV") - .withCopyFileToContainer(jarFile(), "/WMSA.jar") - .withCopyFileToContainer(MountableFile.forClasspathResource("crawl.sh"), "/crawl.sh") - .withFileSystemBind(getCrawlPath().toString(), "/crawl/", BindMode.READ_WRITE) - .withCommand("sh", "crawl.sh") - .waitingFor(Wait.forLogMessage(".*ALL DONE.*", 1).withStartupTimeout(Duration.ofMinutes(10))); - - @Container - public static NginxContainer proxyNginx = new NginxContainer<>("nginx:stable") - .dependsOn(searchContainer) - .dependsOn(crawlerContainer) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("nginx"))) - .withCopyFileToContainer(MountableFile.forClasspathResource("nginx/search.conf"), "/etc/nginx/conf.d/default.conf") - .withNetwork(network) - .withNetworkAliases("proxyNginx"); - - public static MountableFile ipDatabasePath() { - Path modelsPath = Path.of(System.getProperty("user.dir")).resolve("data/models/IP2LOC/IP2LOCATION-LITE-DB1.CSV"); - if (!Files.isRegularFile(modelsPath)) { - System.err.println("Could not find models, looked in " + modelsPath.toAbsolutePath()); - throw new RuntimeException(); - } - return MountableFile.forHostPath(modelsPath.toString()); - } - - private static Path getCrawlPath() { - return Path.of(System.getProperty("user.dir")).resolve("build/tmp/crawl"); - } - - private static Path screenshotFilename(String operation) throws IOException { - var path = Path.of(System.getProperty("user.dir")).resolve("build/test/e2e/"); - Files.createDirectories(path); - - String name = String.format("test-%s-%s.png", operation, LocalDateTime.now()); - path = path.resolve(name); - - System.out.println("Screenshot in " + path); - return path; - } - - private static String getWikipediaFiles() { - Path wikipediaFiles = Path.of(System.getProperty("user.dir")).resolve("build/tmp/wikipedia"); - Path crawlFiles = getCrawlPath(); - Path zimFile = Path.of(System.getProperty("user.dir")).resolve("data/test/wikipedia_en_100_nopic.zim"); - - - List urls = new ArrayList<>(); - try { - TestUtil.clearTempDir(wikipediaFiles); - Files.createDirectories(wikipediaFiles); - Files.createDirectories(crawlFiles); - - Files.writeString(crawlFiles.resolve("crawl.plan"), """ - jobSpec: "/crawl/crawl.spec" - crawl: - dir: "/crawl/crawl" - logName: "crawl.log" - process: - dir: "/crawl/process" - logName: "process.log" - """); - - Files.createDirectories(crawlFiles.resolve("crawl")); - Files.createDirectories(crawlFiles.resolve("process")); - Files.deleteIfExists(crawlFiles.resolve("process").resolve("process.log")); - Files.deleteIfExists(crawlFiles.resolve("crawl").resolve("crawl.log")); - - var zr = new ZIMReader(new ZIMFile(zimFile.toString())); - zr.forEachArticles((url, art) -> { - urls.add("http://wikipedia.local/" + url + ".html"); - - if (art != null) { - try { - var doc = Jsoup.parse(art); - doc.getElementsByTag("script").remove(); - Files.writeString(wikipediaFiles.resolve(url+".html"), doc.html()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }, pred -> true); - urls.forEach(System.out::println); - Files.writeString(wikipediaFiles.resolve("index.html"), ""); - CrawlJobExtractorMain.writeSpec(crawlFiles.resolve("crawl.spec"), "wikipedia.local", urls); - } - catch (IOException ex) { - ex.printStackTrace(); - } - return wikipediaFiles.toString(); - } - - private List getTitlesFromSearchResults(String html) { - List ret = new ArrayList<>(); - - for (var title : Jsoup.parse(html).select(".card.search-result > h2")) { - ret.add(title.text()); - } - - return ret; - } - - @Test - public void testFrontPage() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/"); - System.out.println(driver.getTitle()); -// System.out.println(driver.findElement(new By.ByXPath("//*")).getAttribute("outerHTML")); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("frontpage")); - } - - @Test - public void testQuery() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/search?query=bird&profile=corpo"); - System.out.println(driver.getTitle()); - - var html = driver.findElement(new By.ByXPath("//*")).getAttribute("outerHTML"); - assertEquals(List.of("Bird"), getTitlesFromSearchResults(html)); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("query")); - } - - @Test - public void testQueryYesJs() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/search?query=bird&profile=corpo&js=yes-js"); - System.out.println(driver.getTitle()); - - var html = driver.findElement(new By.ByXPath("//*")).getAttribute("outerHTML"); - assertEquals(Collections.emptyList(), getTitlesFromSearchResults(html)); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("query-yes-js")); - } - - @Test - public void testQueryNoJs() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/search?query=bird&profile=corpo&js=no-js"); - System.out.println(driver.getTitle()); - - var html = driver.findElement(new By.ByXPath("//*")).getAttribute("outerHTML"); - assertEquals(List.of("Bird"), getTitlesFromSearchResults(html)); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("query-no-js")); - } - @Test - public void testSiteInfo() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/search?query=site:wikipedia.local"); - System.out.println(driver.getTitle()); - System.out.println(driver.findElement(new By.ByXPath("//*")).getAttribute("outerHTML")); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("site-info")); - } - - @Test - public void testSiteSearch() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/search?query=site:wikipedia.local%20frog"); - System.out.println(driver.getTitle()); - - var html = driver.findElement(new By.ByXPath("//*")).getAttribute("outerHTML"); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("site-search")); - - assertEquals(List.of("Frog", "Amphibian"), getTitlesFromSearchResults(html)); - } - - @Test - public void testBrowse() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/search?query=browse:wikipedia.local"); - System.out.println(driver.getTitle()); -// System.out.println(driver.findElement(new By.ByXPath("//*")).getAttribute("outerHTML")); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("browse")); - } - @Test - public void testDefine() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/search?query=define:adiabatic"); - System.out.println(driver.getTitle()); -// System.out.println(driver.findElement(new By.ByXPath("//*")).getAttribute("outerHTML")); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("define")); - } - @Test - public void testEval() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/search?query=3%2B3"); - System.out.println(driver.getTitle()); -// System.out.println(driver.findElement(new By.ByXPath("//*")).getAttribute("outerHTML")); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("eval")); - } - @Test - public void testBang() throws IOException { - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/search?query=!g test"); - - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("bang")); - } -} diff --git a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EncyclopediaE2ETest.java b/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EncyclopediaE2ETest.java deleted file mode 100644 index 4afa18c4..00000000 --- a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/EncyclopediaE2ETest.java +++ /dev/null @@ -1,154 +0,0 @@ -package nu.marginalia.wmsa.edge; - - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import nu.marginalia.wmsa.edge.assistant.dict.WikiArticles; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.mariadb.jdbc.Driver; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.chrome.ChromeOptions; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.*; -import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.MountableFile; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.Types; -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.concurrent.TimeUnit; - -import static nu.marginalia.wmsa.configuration.ServiceDescriptor.ENCYCLOPEDIA; - -@Tag("e2e") -@Testcontainers -public class EncyclopediaE2ETest extends E2ETestBase { - @Container - public MariaDBContainer mariaDB = getMariaDBContainer(); - - @Container - public GenericContainer encyclopediaContainer = forService(ENCYCLOPEDIA, mariaDB); - @Container - public GenericContainer encyclopediaLoader = new GenericContainer<>("openjdk:17") - .dependsOn(encyclopediaContainer) - .dependsOn(mariaDB) - .withNetwork(network) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("encyclopedia-loader"))) - .withCopyFileToContainer(jarFile(), "/WMSA.jar") - .withCopyFileToContainer(MountableFile.forClasspathResource("load-encyclopedia.sh"), "/load-encyclopedia.sh") - .withFileSystemBind(getModelData().toString(), "/data", BindMode.READ_ONLY) - .withCommand("sh", "load-encyclopedia.sh") - .waitingFor(Wait.forLogMessage(".*ALL DONE.*", 1).withStartupTimeout(Duration.ofMinutes(10))); - - @Container - public NginxContainer proxyNginx = new NginxContainer<>("nginx:stable") - .dependsOn(encyclopediaLoader) - .dependsOn(encyclopediaContainer) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("nginx"))) - .withCopyFileToContainer(MountableFile.forClasspathResource("nginx/encyclopedia.conf"), "/etc/nginx/conf.d/default.conf") - .withNetwork(network) - .withNetworkAliases("proxyNginx"); - - @Container - public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer<>() - .withNetwork(network) - .withCapabilities(new ChromeOptions()); - - private Gson gson = new GsonBuilder().create(); - private OkHttpClient httpClient = new OkHttpClient.Builder() - .connectTimeout(100, TimeUnit.MILLISECONDS) - .readTimeout(6000, TimeUnit.SECONDS) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build(); - - private Path getModelData() { - return Path.of(System.getProperty("user.dir")).resolve("data/test"); - } - - private static Path screenshotFilename(String operation) throws IOException { - var path = Path.of(System.getProperty("user.dir")).resolve("build/test/e2e/"); - Files.createDirectories(path); - - String name = String.format("test-encyclopedia-%s-%s.png", operation, LocalDateTime.now()); - path = path.resolve(name); - - System.out.println("Screenshot in " + path); - return path; - } - - @Test - public void run() throws IOException { - new Driver(); - - try (var conn = DriverManager.getConnection(mariaDB.getJdbcUrl(), "wmsa", "wmsa"); - var stmt = conn.prepareStatement("INSERT IGNORE INTO REF_WIKI_ARTICLE(NAME,REF_NAME) VALUES (?,?)")) { - - stmt.setString(1, "Forg"); - stmt.setString(2, "Frog"); - stmt.executeUpdate(); - - stmt.setString(1, "Frog"); - stmt.setNull(2, Types.VARCHAR); - stmt.executeUpdate(); - - } catch (SQLException e) { - throw new RuntimeException(e); - } - - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/wiki/Frog"); - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("get-article")); - - driver.get("http://proxyNginx/wiki/Forg"); - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("get-article-redir")); - - System.out.println(driver.getTitle()); - driver.get("http://proxyNginx/wiki-search?query=Forg"); - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("disambig")); - System.out.println(driver.getTitle()); - - var resultsForMarginalia = get(encyclopediaContainer.getHost(), - encyclopediaContainer.getMappedPort(ENCYCLOPEDIA.port), - "/encyclopedia/Marginalia", WikiArticles.class); - Assertions.assertTrue(resultsForMarginalia.getEntries().isEmpty()); - - var resultsForFrog = get(encyclopediaContainer.getHost(), - encyclopediaContainer.getMappedPort(ENCYCLOPEDIA.port), - "/encyclopedia/Frog", WikiArticles.class); - Assertions.assertFalse(resultsForFrog.getEntries().isEmpty()); - - var resultsForFoRg = get(encyclopediaContainer.getHost(), - encyclopediaContainer.getMappedPort(ENCYCLOPEDIA.port), - "/encyclopedia/Forg", WikiArticles.class); - Assertions.assertFalse(resultsForFoRg.getEntries().isEmpty()); - - - } - - - private T get(String host, Integer mappedPort, String path, Class clazz) throws MalformedURLException { - var req = new Request.Builder().get().url(new URL("http", host, mappedPort, path)).build(); - var call = httpClient.newCall(req); - try (var rsp = call.execute()) { - return gson.fromJson(rsp.body().charStream(), clazz); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/MemexE2ETest.java b/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/MemexE2ETest.java deleted file mode 100644 index 7410b3b3..00000000 --- a/marginalia_nu/src/e2e/java/nu/marginalia/wmsa/edge/MemexE2ETest.java +++ /dev/null @@ -1,95 +0,0 @@ -package nu.marginalia.wmsa.edge; - - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import okhttp3.OkHttpClient; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.mariadb.jdbc.Driver; -import org.openqa.selenium.OutputType; -import org.openqa.selenium.chrome.ChromeOptions; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.*; -import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.MountableFile; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.LocalDateTime; -import java.util.concurrent.TimeUnit; - -import static nu.marginalia.wmsa.configuration.ServiceDescriptor.AUTH; -import static nu.marginalia.wmsa.configuration.ServiceDescriptor.MEMEX; - -@Tag("e2e") -@Testcontainers -public class MemexE2ETest extends E2ETestBase { - @Container - public MariaDBContainer mariaDB = getMariaDBContainer(); - - @Container - public GenericContainer auth = forService(AUTH, mariaDB); - - @Container - public GenericContainer memexContainer = forService(MEMEX, mariaDB, "memex.sh") - .withClasspathResourceMapping("/memex", "/memex", BindMode.READ_ONLY); - - @Container - public NginxContainer proxyNginx = new NginxContainer<>("nginx:stable") - .dependsOn(auth) - .dependsOn(memexContainer) - .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("nginx"))) - .withCopyFileToContainer(MountableFile.forClasspathResource("nginx/memex.conf"), "/etc/nginx/conf.d/default.conf") - .withNetwork(network) - .withNetworkAliases("proxyNginx"); - - @Container - public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer<>() - .withNetwork(network) - .withCapabilities(new ChromeOptions()); - - private Gson gson = new GsonBuilder().create(); - private OkHttpClient httpClient = new OkHttpClient.Builder() - .connectTimeout(100, TimeUnit.MILLISECONDS) - .readTimeout(6000, TimeUnit.SECONDS) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build(); - - @Test - public void run() throws IOException, InterruptedException { - Thread.sleep(10_000); - new Driver(); - - var driver = chrome.getWebDriver(); - - driver.get("http://proxyNginx/"); - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("frontpage")); - - driver.get("http://proxyNginx/log/"); - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("log")); - - driver.get("http://proxyNginx/log/a.gmi"); - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("log-a.gmi")); - - driver.get("http://proxyNginx/log/b.gmi"); - Files.move(driver.getScreenshotAs(OutputType.FILE).toPath(), screenshotFilename("log-b.gmi")); - } - - private static Path screenshotFilename(String operation) throws IOException { - var path = Path.of(System.getProperty("user.dir")).resolve("build/test/e2e/"); - Files.createDirectories(path); - - String name = String.format("test-%s-%s.png", operation, LocalDateTime.now()); - path = path.resolve(name); - - System.out.println("Screenshot in " + path); - return path; - } - - -} diff --git a/marginalia_nu/src/e2e/resources/crawl-mock.sh b/marginalia_nu/src/e2e/resources/crawl-mock.sh deleted file mode 100644 index 4270929e..00000000 --- a/marginalia_nu/src/e2e/resources/crawl-mock.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -mkdir -p /var/lib/wmsa/conf/ -mkdir -p /var/lib/wmsa/data/ - -echo "search.marginalia.nu" > /var/lib/wmsa/conf/user-agent - -cat crawl/crawl.plan -cat << EOF - #### ##### ## # # # - # # # # # # # # # - # # # # # # # # - # ##### ###### # ## # # - # # # # # # ## ## # - #### # # # # # # ###### -EOF -java -jar WMSA.jar crawl crawl/crawl.plan - -echo "ALL DONE" \ No newline at end of file diff --git a/marginalia_nu/src/e2e/resources/crawl.sh b/marginalia_nu/src/e2e/resources/crawl.sh deleted file mode 100644 index 16d43fab..00000000 --- a/marginalia_nu/src/e2e/resources/crawl.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash - -mkdir -p /var/lib/wmsa/conf/ -mkdir -p /var/lib/wmsa/data/ - -echo "search.marginalia.nu" > /var/lib/wmsa/conf/user-agent - -cat > /var/lib/wmsa/conf/db.properties < /var/lib/wmsa/conf/hosts < ${HOME}/suggestions.txt < ${HOME}/conf/disks.properties < ${HOME}/conf/db.properties < ${HOME}/conf/ranking-settings.yaml < ${HOME}/conf/hosts < /var/lib/wmsa/conf/db.properties < /var/lib/wmsa/conf/hosts < ${HOME}/conf/db.properties < ${HOME}/conf/hosts < roar = new ArrayList<>(); - List acbs = new ArrayList<>(); - - List roarLow = new ArrayList<>(); - List roarHigh = new ArrayList<>(); - - List acbsLow = new ArrayList<>(); - List acbsHigh = new ArrayList<>(); - - @Setup(Level.Trial) - public void setUp() { - var rand = new Random(); - - for (int i = 0; i < 100; i++) { - int card = 1 + rand.nextInt(10); - - var rb = new RoaringBitmap(); - var cbs = new AndCardIntSet(); - - for (int j = 0; j < card; j++) { - int val = rand.nextInt(1_000_000); - rb.add(val); - cbs.add(val); - } - acbsLow.add(cbs); - roarLow.add(rb); - } - - for (int i = 0; i < 10; i++) { - int card = 1 + rand.nextInt(10000, 20000); - - var rb = new RoaringBitmap(); - - for (int j = 0; j < card; j++) { - int val = rand.nextInt(1_000_000); - rb.add(val); - } - acbsHigh.add(AndCardIntSet.of(rb)); - roarHigh.add(rb); - } - - - - for (int i = 0; i < 100000; i++) { - var rb = new RoaringBitmap(); - var cbs = new AndCardIntSet(); - - int val = rand.nextInt(1_000_000); - rb.add(val); - cbs.add(val); - - acbs.add(cbs); - roar.add(rb); - } - - for (int i = 0; i < 10000; i++) { - int card = 1 + rand.nextInt(10); - - var rb = new RoaringBitmap(); - var cbs = new AndCardIntSet(); - - for (int j = 0; j < card; j++) { - int val = rand.nextInt(1_000_000); - rb.add(val); - cbs.add(val); - } - acbs.add(cbs); - roar.add(rb); - } - for (int i = 0; i < 1000; i++) { - int card = 1 + rand.nextInt(100); - - var rb = new RoaringBitmap(); - var cbs = new AndCardIntSet(); - - for (int j = 0; j < card; j++) { - int val = rand.nextInt(1_000_000); - rb.add(val); - cbs.add(val); - } - acbs.add(cbs); - roar.add(rb); - } - for (int i = 0; i < 100; i++) { - int card = 1 + rand.nextInt(1000); - - var rb = new RoaringBitmap(); - var cbs = new AndCardIntSet(); - - for (int j = 0; j < card; j++) { - int val = rand.nextInt(1_000_000); - rb.add(val); - cbs.add(val); - } - acbs.add(cbs); - roar.add(rb); - } - for (int i = 0; i < 100; i++) { - int card = 1 + rand.nextInt(10000); - - var rb = new RoaringBitmap(); - var cbs = new AndCardIntSet(); - - for (int j = 0; j < card; j++) { - int val = rand.nextInt(1_000_000); - rb.add(val); - cbs.add(val); - } - acbs.add(cbs); - roar.add(rb); - } - - for (int i = 0; i < 2; i++) { - int card = 1 + rand.nextInt(100000); - - var rb = new RoaringBitmap(); - var cbs = new AndCardIntSet(); - - for (int j = 0; j < card; j++) { - int val = rand.nextInt(1_000_000); - rb.add(val); - cbs.add(val); - } - acbs.add(cbs); - roar.add(rb); - } - Collections.shuffle(acbs); - Collections.shuffle(roar); - } - } - -// -// @Benchmark -// @BenchmarkMode(Mode.Throughput) -// @Fork(value = 5, warmups = 5) -// public Object roaringCard(State state) { -// long val = 0; -// -// for (int i = 0; i < state.roar.size(); i++) { -// for (int j = i+1; j < state.roar.size(); j++) { -// val += RoaringBitmap.andCardinality(state.roar.get(i), state.roar.get(j)); -// } -// } -// -// return val; -// } -// @Benchmark -// @BenchmarkMode(Mode.Throughput) -// @Fork(value = 2, warmups = 2) -// public Object roaringCardNorm(State state) { -// long val = 0; -// -// for (int i = 0; i < state.roar.size()/1000; i++) { -// for (int j = i+1; j < state.roar.size(); j++) { -// -// var a = state.roar.get(i); -// var b = state.roar.get(j); -// val += RoaringBitmap.andCardinality(a, b) / (Math.sqrt(a.getCardinality()*b.getCardinality())); -// } -// } -// -// return val; -// } -// @Benchmark -// @BenchmarkMode(Mode.Throughput) -// @Fork(value = 5, warmups = 5) -// public Object cbsCard(State state) { -// long val = 0; -// -// for (int i = 0; i < state.roar.size(); i++) { -// for (int j = i+1; j < state.roar.size(); j++) { -// val += AndCardIntSet.andCardinality(state.acbs.get(i), state.acbs.get(j)); -// } -// } -// -// return val; -// } -// -// @Benchmark -// @BenchmarkMode(Mode.Throughput) -// @Fork(value = 1, warmups = 1) -// public Object cbsCardNorm(State state) { -// double val = 0; -// -// for (int i = 0; i < state.roar.size()/1000; i++) { -// for (int j = i+1; j < state.roar.size(); j++) { -// var a = state.acbs.get(i); -// var b = state.acbs.get(j); -// val += AndCardIntSet.andCardinality(a, b) / (Math.sqrt(a.cardinality()*b.cardinality())); -// } -// } -// -// return val; -// } - - @Benchmark - @BenchmarkMode(Mode.Throughput) - @Fork(value = 1, warmups = 1) - public Object cbsLowLow(State state) { - double val = 0; - - for (int i = 0; i < state.acbsLow.size(); i++) { - for (int j = 0; j < state.acbsLow.size(); j++) { - var a = state.acbsLow.get(i); - var b = state.acbsLow.get(j); - val += AndCardIntSet.andCardinality(a, b) / (Math.sqrt(a.getCardinality()*b.getCardinality())); - } - } - - return val; - } - - - @Benchmark - @BenchmarkMode(Mode.Throughput) - @Fork(value = 1, warmups = 1) - public Object cbsHighHigh(State state) { - double val = 0; - - for (int i = 0; i < state.acbsHigh.size(); i++) { - for (int j = 0; j < state.acbsHigh.size(); j++) { - var a = state.acbsHigh.get(i); - var b = state.acbsHigh.get(j); - val += AndCardIntSet.andCardinality(a, b) / (Math.sqrt(a.getCardinality()*b.getCardinality())); - } - } - - return val; - } - - @Benchmark - @BenchmarkMode(Mode.Throughput) - @Fork(value = 1, warmups = 1) - public Object cbsHighLow(State state) { - double val = 0; - - for (int i = 0; i < state.acbsHigh.size(); i++) { - for (int j = 0; j < state.acbsLow.size(); j++) { - var a = state.acbsHigh.get(i); - var b = state.acbsLow.get(j); - val += AndCardIntSet.andCardinality(a, b) / (Math.sqrt(a.getCardinality()*b.getCardinality())); - } - } - - return val; - } - - @Benchmark - @BenchmarkMode(Mode.Throughput) - @Fork(value = 1, warmups = 1) - public Object roarLowLow(State state) { - double val = 0; - - for (int i = 0; i < state.roarLow.size(); i++) { - for (int j = 0; j < state.roarLow.size(); j++) { - var a = state.roarLow.get(i); - var b = state.roarLow.get(j); - val += RoaringBitmap.andCardinality(a, b) / (Math.sqrt(a.getCardinality()*b.getCardinality())); - } - } - - return val; - } - - - @Benchmark - @BenchmarkMode(Mode.Throughput) - @Fork(value = 1, warmups = 1) - public Object roarHighLow(State state) { - double val = 0; - - for (int i = 0; i < state.roarHigh.size(); i++) { - for (int j = 0; j < state.roarLow.size(); j++) { - var a = state.roarHigh.get(i); - var b = state.roarLow.get(j); - val += RoaringBitmap.andCardinality(a, b) / (Math.sqrt(a.getCardinality()*b.getCardinality())); - } - } - - return val; - } - - @Benchmark - @BenchmarkMode(Mode.Throughput) - @Fork(value = 1, warmups = 1) - public Object roarHighHigh(State state) { - double val = 0; - - for (int i = 0; i < state.roarHigh.size(); i++) { - for (int j = 0; j < state.roarHigh.size(); j++) { - var a = state.roarHigh.get(i); - var b = state.roarHigh.get(j); - val += RoaringBitmap.andCardinality(a, b) / (Math.sqrt(a.getCardinality()*b.getCardinality())); - } - } - - return val; - } -} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/SeekDictionary.java b/marginalia_nu/src/main/java/nu/marginalia/util/SeekDictionary.java deleted file mode 100644 index a49544e4..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/SeekDictionary.java +++ /dev/null @@ -1,73 +0,0 @@ -package nu.marginalia.util; - -import gnu.trove.list.array.TIntArrayList; - -import java.util.ArrayList; -import java.util.function.ToIntFunction; - -public abstract class SeekDictionary { - private final ArrayList banks = new ArrayList<>(); - private final TIntArrayList offsets = new TIntArrayList(); - - public static SeekDictionary of(ToIntFunction length) { - return new SeekDictionary() { - @Override - public int length(T obj) { - return length.applyAsInt(obj); - } - }; - } - public T last() { - return banks.get(banks.size()-1); - } - public int lastStart() { - return offsets.get(offsets.size()-1); - } - - public abstract int length(T obj); - public int end() { - if (banks.isEmpty()) return 0; - - return (offsets.getQuick(offsets.size()-1) + length(last())); - } - - public void add(T obj) { - - if (banks.isEmpty()) { - banks.add(obj); - offsets.add(0); - } - else { - offsets.add(end()); - banks.add(obj); - } - } - - public T bankForOffset(int offset) { - return banks.get(idxForOffset(offset)); - } - - public int idxForOffset(int offset) { - - int high = offsets.size() - 1; - int low = 0; - - while ( low <= high ) { - int mid = ( low + high ) >>> 1; - int midVal = offsets.getQuick(mid); - - if ( midVal < offset ) { - low = mid + 1; - } - else if ( midVal > offset ) { - high = mid - 1; - } - else { - return mid; - } - } - return low-1; - - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeDogEar.java b/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeDogEar.java deleted file mode 100644 index 77a97e87..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeDogEar.java +++ /dev/null @@ -1,35 +0,0 @@ -package nu.marginalia.util.btree; - -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.btree.model.BTreeContext; -import nu.marginalia.util.btree.model.BTreeHeader; - -/* - * End-of-page mark that's used as a sentinel to verify that - * the BTreeWriter's caller actually writes as much as they say - * they want to. (Failing to do so will corrupt the tree) - * - */ -public class BTreeDogEar { - - private LongArray sentinelSlice; - - public BTreeDogEar(BTreeContext ctx, BTreeHeader header, LongArray base) { - if (header.numEntries() > 3) { - sentinelSlice = base.range( - (long) header.numEntries() * ctx.entrySize() - 3, - (long) header.numEntries() * ctx.entrySize()); - sentinelSlice.set(0, 4L); - sentinelSlice.set(1, 5L); - sentinelSlice.set(2, 1L); - } - } - - public boolean verify() { - if (sentinelSlice == null) - return true; - - return 4 != sentinelSlice.get(0) || 5 != sentinelSlice.get(1) || 1 != sentinelSlice.get(2); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeReaderIf.java b/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeReaderIf.java deleted file mode 100644 index c4b40386..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/btree/BTreeReaderIf.java +++ /dev/null @@ -1,21 +0,0 @@ -package nu.marginalia.util.btree; - -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.btree.model.BTreeHeader; - -public interface BTreeReaderIf { - BTreeHeader getHeader(); - - int numEntries(); - - void retainEntries(LongQueryBuffer buffer); - - void rejectEntries(LongQueryBuffer buffer); - - long findEntry(long keyRaw); - - void readData(long[] data, int n, long pos); - - long[] queryData(long[] urls, int offset); - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/btree/WriteCallback.java b/marginalia_nu/src/main/java/nu/marginalia/util/btree/WriteCallback.java deleted file mode 100644 index 6c51cdde..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/btree/WriteCallback.java +++ /dev/null @@ -1,9 +0,0 @@ -package nu.marginalia.util.btree; - -import nu.marginalia.util.array.LongArray; - -import java.io.IOException; - -public interface WriteCallback { - void write(LongArray slice) throws IOException; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/dict/DictionaryData.java b/marginalia_nu/src/main/java/nu/marginalia/util/dict/DictionaryData.java deleted file mode 100644 index 492417a0..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/dict/DictionaryData.java +++ /dev/null @@ -1,100 +0,0 @@ -package nu.marginalia.util.dict; - -import java.nio.ByteBuffer; -import java.nio.LongBuffer; -import java.util.ArrayList; - -public class DictionaryData { - - private final int DICTIONARY_BANK_SIZE; - - private final ArrayList banks = new ArrayList<>(100); - - public DictionaryData(int bankSize) { - DICTIONARY_BANK_SIZE = bankSize; - - banks.add(new DictionaryDataBank(0, bankSize)); - } - - public int add(long key) { - var activeBank = banks.get(banks.size()-1); - int rb = activeBank.add(key); - - if (rb == -1) { - int end = activeBank.getEnd(); - var newBank = new DictionaryDataBank(end, DICTIONARY_BANK_SIZE); - rb = newBank.add(key); - - banks.add(newBank); - } - - return rb; - } - - - public long getKey(int offset) { - return banks.get(offset/DICTIONARY_BANK_SIZE).getKey(offset); - } - public boolean keyEquals(int offset, long otherKey) { - return banks.get(offset/DICTIONARY_BANK_SIZE).keyEquals(offset, otherKey); - } - - private static class DictionaryDataBank { - - private final int start_idx; - - // Humongous long-lived arrays seem to sometimes yield considerable memory overhead and - // can make the GC behave poorly. Using off-heap memory seems preferred when their - // lifetime is "forever" - - private final LongBuffer keys; - - private int size; - private final int capacity; - - - public DictionaryDataBank(int start_idx, int sz) { - this.start_idx = start_idx; - this.capacity = sz; - - keys = ByteBuffer.allocateDirect(8*capacity).asLongBuffer(); - size = 0; - } - - public int getStart() { - return start_idx; - } - - public int getEnd() { - return start_idx + size; - } - - public long getKey(int idx) { - if (idx < start_idx || idx - start_idx >= size) { - throw new IndexOutOfBoundsException(idx); - } - return keys.get(idx - start_idx); - } - - public boolean keyEquals(int idx, long other) { - if (idx < start_idx || idx - start_idx >= size) { - throw new IndexOutOfBoundsException(idx); - } - - return keys.get(idx - start_idx) == other; - } - - public int add(long newKey) { - if (size >= capacity) - return -1; - - keys.put(size, newKey); - - return start_idx + size++; - } - - public int getSize() { - return size; - } - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/conf/LanguageModels.java b/marginalia_nu/src/main/java/nu/marginalia/util/language/conf/LanguageModels.java deleted file mode 100644 index 65cb21cb..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/conf/LanguageModels.java +++ /dev/null @@ -1,16 +0,0 @@ -package nu.marginalia.util.language.conf; - -import lombok.AllArgsConstructor; - -import java.nio.file.Path; - -@AllArgsConstructor -public class LanguageModels { - public final Path ngramBloomFilter; - public final Path termFrequencies; - - public final Path openNLPSentenceDetectionData; - public final Path posRules; - public final Path posDict; - public final Path openNLPTokenData; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/AsciiFlattener.java b/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/AsciiFlattener.java deleted file mode 100644 index 6abcbdb5..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/AsciiFlattener.java +++ /dev/null @@ -1,58 +0,0 @@ -package nu.marginalia.util.language.processing; - -import java.util.regex.Pattern; - -public class AsciiFlattener { - - private static final Pattern nonAscii = Pattern.compile("[^a-zA-Z0-9_.'+@#:\\-]+"); - - private static boolean isPlainAscii(String s) { - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if ((c & 0x80) != 0) { - return false; - } - } - return true; - } - public static String flattenUnicode(String s) { - - if (isPlainAscii(s)) { - return s; - } - - var cdata = s.toCharArray(); - var newCdata = new char[cdata.length]; - for (int i = 0; i < cdata.length; i++) { - if ("àáâãäåæ".indexOf(cdata[i]) >= 0) { - newCdata[i] = 'a'; - } - else if ("ç".indexOf(cdata[i]) >= 0) { - newCdata[i] = 'g'; - } - else if ("òóôõöø".indexOf(cdata[i]) >= 0) { - newCdata[i] = 'o'; - } - else if ("ùúûü".indexOf(cdata[i]) >= 0) { - newCdata[i] = 'u'; - } - else if ("ýÿÞþ".indexOf(cdata[i]) >= 0) { - newCdata[i] = 'y'; - } - else if ("ìíîï".indexOf(cdata[i]) >= 0) { - newCdata[i] = 'i'; - } - else if ("èéêë".indexOf(cdata[i]) >= 0) { - newCdata[i] = 'e'; - } - else if ("ß".indexOf(cdata[i]) >= 0) { - newCdata[i] = 's'; - } - else { - newCdata[i] = cdata[i]; - } - } - return nonAscii.matcher(new String(newCdata)).replaceAll(""); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordRef.java b/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordRef.java deleted file mode 100644 index 9ed6f1ae..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/WordRef.java +++ /dev/null @@ -1,46 +0,0 @@ -package nu.marginalia.util.language.processing.model; - -import lombok.AllArgsConstructor; - -import java.util.Objects; -import java.util.Optional; - -@AllArgsConstructor -public class WordRef { - public final int sentenceIndex; - public final int wordIndex; - - public String getWord(DocumentLanguageData dld) { - return dld.sentences[sentenceIndex].words[wordIndex]; - } - - public String getWordStemmed(DocumentLanguageData dld) { - return dld.sentences[sentenceIndex].stemmedWords[wordIndex]; - } - - public Optional next(DocumentLanguageData dld) { - if (wordIndex + 1 < dld.sentences[sentenceIndex].length()) { - return Optional.of(new WordRef(sentenceIndex, wordIndex+1)); - } - return Optional.empty(); - } - public Optional prev() { - if (wordIndex - 1 >= 0) { - return Optional.of(new WordRef(sentenceIndex, wordIndex-1)); - } - return Optional.empty(); - } - - @Override - public int hashCode() { - return Objects.hash(sentenceIndex, wordIndex); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - WordRef wordRef = (WordRef) o; - return sentenceIndex == wordRef.sentenceIndex && wordIndex == wordRef.wordIndex; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/tag/WordTag.java b/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/tag/WordTag.java deleted file mode 100644 index bbe974a6..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/language/processing/model/tag/WordTag.java +++ /dev/null @@ -1,8 +0,0 @@ -package nu.marginalia.util.language.processing.model.tag; - -public class WordTag { - public static int UNSET = 0; - public static int STOP_WORD = 1; - public static int NAME = 2; - public static int NOT_NAME = 3; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/tool/WikipediaInternalLinkExtractorMain.java b/marginalia_nu/src/main/java/nu/marginalia/util/tool/WikipediaInternalLinkExtractorMain.java deleted file mode 100644 index f4f9b3dc..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/util/tool/WikipediaInternalLinkExtractorMain.java +++ /dev/null @@ -1,43 +0,0 @@ -package nu.marginalia.util.tool; - -import nu.marginalia.wmsa.edge.integration.wikipedia.WikipediaReader; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import org.jsoup.Jsoup; - -import java.util.HashSet; -import java.util.Set; - -public class WikipediaInternalLinkExtractorMain { - public static void main(String... args) throws InterruptedException { - new WikipediaReader(args[0], new EdgeDomain("en.wikipedia.org"), wikipediaArticle -> { - - - var doc = Jsoup.parse(wikipediaArticle.body); - String path = wikipediaArticle.url.path.substring("/wiki/".length()); - - if (isIncluded(path)) { - Set seen = new HashSet<>(100); - - for (var atag : doc.getElementsByTag("a")) { - String href = atag.attr("href"); - - if (href.contains("#")) { - href = href.substring(0, href.indexOf('#')); - } - - if (isIncluded(href) && href.length() > 2 && seen.add(href)) { - System.out.println(path + "\t" + href); - } - } - } - - }).join(); - } - - private static boolean isIncluded(String href) { - return !href.contains(":") - && !href.contains("/") - && !href.contains("%") - && !href.startsWith("#"); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/ApiMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/ApiMain.java deleted file mode 100644 index 5690b807..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/ApiMain.java +++ /dev/null @@ -1,27 +0,0 @@ -package nu.marginalia.wmsa.api; - -import com.google.inject.Guice; -import com.google.inject.Inject; -import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.configuration.server.Initialization; - -public class ApiMain extends MainClass { - - @Inject - public ApiMain(ApiService service) { - } - - public static void main(String... args) { - init(ServiceDescriptor.API, args); - - Injector injector = Guice.createInjector( - new DatabaseModule(), - new ConfigurationModule()); - injector.getInstance(ApiMain.class); - injector.getInstance(Initialization.class).setReady(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResult.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResult.java deleted file mode 100644 index 4c301ef1..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiSearchResult.java +++ /dev/null @@ -1,55 +0,0 @@ -package nu.marginalia.wmsa.api.model; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordMetadata; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultKeywordScore; -import nu.marginalia.wmsa.edge.model.search.EdgeUrlDetails; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -@AllArgsConstructor @Getter -public class ApiSearchResult { - public String url; - public String title; - public String description; - public double quality; - - public List> details = new ArrayList<>(); - - public ApiSearchResult(EdgeUrlDetails url) { - this.url = url.url.toString(); - this.title = url.getTitle(); - this.description = url.getDescription(); - - this.quality = sanitizeNaN(url.getTermScore(), -100); - - if (url.resultItem != null) { - var bySet = url.resultItem.scores.stream().collect(Collectors.groupingBy(EdgeSearchResultKeywordScore::set)); - - outer: - for (var entries : bySet.values()) { - List lst = new ArrayList<>(); - for (var entry : entries) { - var metadata = new EdgePageWordMetadata(entry.encodedWordMetadata()); - if (metadata.isEmpty()) - continue outer; - - Set flags = metadata.flagSet().stream().map(Object::toString).collect(Collectors.toSet()); - lst.add(new ApiSearchResultQueryDetails(entry.keyword(), metadata.tfIdf(), metadata.count(), flags)); - } - details.add(lst); - } - } - } - - private double sanitizeNaN(double value, double alternative) { - if (!Double.isFinite(value)) { - return alternative; - } - return value; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/MainClass.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/MainClass.java deleted file mode 100644 index f52d3b87..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/MainClass.java +++ /dev/null @@ -1,56 +0,0 @@ -package nu.marginalia.wmsa.configuration; - -import io.prometheus.client.hotspot.DefaultExports; -import io.reactivex.rxjava3.exceptions.UndeliverableException; -import io.reactivex.rxjava3.plugins.RxJavaPlugins; -import lombok.SneakyThrows; -import nu.marginalia.wmsa.client.exception.NetworkException; -import org.mariadb.jdbc.Driver; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.SocketTimeoutException; -import java.net.UnknownHostException; -import java.util.Arrays; - -public abstract class MainClass { - private final Logger logger = LoggerFactory.getLogger(getClass()); - - public MainClass() { - - RxJavaPlugins.setErrorHandler(ex -> { - if (ex instanceof UndeliverableException) { - ex = ex.getCause(); - } - - if (ex instanceof SocketTimeoutException) { - logger.warn("SocketTimeoutException"); - } - else if (ex instanceof UnknownHostException) { - logger.warn("UnknownHostException"); - } - else if (ex instanceof NetworkException) { - logger.warn("NetworkException", ex); - } - else { - logger.error("Uncaught exception", ex); - } - }); - - } - - @SneakyThrows - protected static void init(ServiceDescriptor service, String... args) { - System.setProperty("log4j2.isThreadContextMapInheritable", "true"); - System.setProperty("isThreadContextMapInheritable", "true"); - System.setProperty("service-name", service.name); - - org.mariadb.jdbc.Driver driver = new Driver(); - - if (Arrays.asList(args).contains("go-no-go")) { - System.setProperty("go-no-go", "true"); - } - DefaultExports.initialize(); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/ServiceDescriptor.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/ServiceDescriptor.java deleted file mode 100644 index 6acbaea6..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/ServiceDescriptor.java +++ /dev/null @@ -1,107 +0,0 @@ -package nu.marginalia.wmsa.configuration; - -import nu.marginalia.wmsa.api.ApiMain; -import nu.marginalia.wmsa.auth.AuthMain; -import nu.marginalia.wmsa.configuration.command.*; -import nu.marginalia.wmsa.edge.assistant.EdgeAssistantMain; -import nu.marginalia.wmsa.edge.dating.DatingMain; -import nu.marginalia.wmsa.edge.explorer.ExplorerMain; -import nu.marginalia.wmsa.edge.index.EdgeIndexMain; -import nu.marginalia.wmsa.edge.search.EdgeSearchMain; -import nu.marginalia.wmsa.encyclopedia.EncyclopediaMain; -import nu.marginalia.wmsa.memex.MemexMain; -import nu.marginalia.wmsa.podcasts.PodcastScraperMain; -import nu.marginalia.wmsa.renderer.RendererMain; -import nu.marginalia.wmsa.resource_store.ResourceStoreMain; -import org.apache.logging.log4j.core.lookup.MainMapLookup; - -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public enum ServiceDescriptor { - RESOURCE_STORE("resource-store", 5000, ResourceStoreMain.class), - RENDERER("renderer", 5002, RendererMain.class), - AUTH("auth", 5003, AuthMain.class), - API("api", 5004, ApiMain.class), - - PODCST_SCRAPER("podcast-scraper", 5013, PodcastScraperMain.class), - - EDGE_INDEX("edge-index", 5021, EdgeIndexMain.class), - EDGE_SEARCH("edge-search", 5023, EdgeSearchMain.class), - EDGE_ASSISTANT("edge-assistant", 5025, EdgeAssistantMain.class), - - MEMEX("memex", 5030, MemexMain.class), - - ENCYCLOPEDIA("encyclopedia", 5040, EncyclopediaMain.class), - - DATING("dating", 5070, DatingMain.class), - EXPLORER("explorer", 5071, ExplorerMain.class), - - TEST_1("test-1", 0, null), - TEST_2("test-2", 0, null); - - private static HostsFile hostsFile; - public synchronized String getHost() { - if (hostsFile == null) { - hostsFile = WmsaHome.getHostsFile(); - } - return hostsFile.getHost(this); - } - - public static ServiceDescriptor byName(String name) { - for (var v : values()) { - if (v.name.equals(name)) { - return v; - } - } - throw new IllegalArgumentException("Invalid ServiceDescriptor " + name); - } - public final String name; - public final Class mainClass; - public final int port; - - ServiceDescriptor(String name, int port, Class mainClass) { - this.name = name; - this.port = port; - this.mainClass = mainClass; - } - - public String toString() { - return name; - } - - public String describeService() { - return String.format("%s %s", name, mainClass.getName()); - } - - public static void main(String... args) { - MainMapLookup.setMainArguments(args); - - Map functions = Stream.of( - new ListCommand(), - new StartCommand(), - new ConvertCommand(), - new CrawlCommand(), - new LoadCommand(), - new ReindexCommand(), - new VersionCommand(), - new IndexDataDumpCommand() - ).collect(Collectors.toMap(c -> c.name, c -> c)); - - if(args.length > 0) { - functions.getOrDefault(args[0], new Command("") { - @Override - public void execute(String... args) { - System.err.println("Unknown command"); - System.exit(1); - } - }).execute(args); - } - else { - System.err.println("Usage: " + String.join("|", functions.keySet())); - System.exit(1); - } - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/Command.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/Command.java deleted file mode 100644 index 5267045f..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/Command.java +++ /dev/null @@ -1,31 +0,0 @@ -package nu.marginalia.wmsa.configuration.command; - -import nu.marginalia.wmsa.configuration.ServiceDescriptor; - -import java.util.Arrays; -import java.util.Objects; - -public abstract class Command { - public final String name; - - protected Command(String name) { - this.name = name; - } - - public abstract void execute(String... args); - - static ServiceDescriptor getKind(String arg) { - - try { - return Arrays.stream(ServiceDescriptor.values()) - .filter(sd -> Objects.equals(arg, sd.name)) - .findFirst() - .orElseThrow(IllegalArgumentException::new) - ; - } catch (IllegalArgumentException ex) { - System.err.println("Unknown service '" + arg + "'"); - System.exit(1); - } - return null; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ConvertCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ConvertCommand.java deleted file mode 100644 index 7cecbc25..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ConvertCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package nu.marginalia.wmsa.configuration.command; - -import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.converting.ConverterMain; - -import java.util.Arrays; - -public class ConvertCommand extends Command { - public ConvertCommand() { - super("convert"); - } - - @Override - @SneakyThrows - public void execute(String... args) { - if (args.length < 2) { - System.err.println("Usage: convert plan.yaml"); - System.exit(255); - } - - String[] args2 = Arrays.copyOfRange(args, 1, args.length); - ConverterMain.main(args2); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/CrawlCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/CrawlCommand.java deleted file mode 100644 index 07c291bb..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/CrawlCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package nu.marginalia.wmsa.configuration.command; - -import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.crawling.CrawlerMain; - -import java.util.Arrays; - -public class CrawlCommand extends Command { - public CrawlCommand() { - super("crawl"); - } - - @Override - @SneakyThrows - public void execute(String... args) { - if (args.length < 2) { - System.err.println("Usage: crawl plan.yaml"); - System.exit(255); - } - - String[] args2 = Arrays.copyOfRange(args, 1, args.length); - CrawlerMain.main(args2); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/IndexDataDumpCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/IndexDataDumpCommand.java deleted file mode 100644 index 75ea02c7..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/IndexDataDumpCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package nu.marginalia.wmsa.configuration.command; - -import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.tools.IndexJournalDumpTool; - -import java.util.Arrays; - -public class IndexDataDumpCommand extends Command { - public IndexDataDumpCommand() { - super("index-dump"); - } - - @SneakyThrows - @Override - public void execute(String... args) { - if (args.length < 1) { - System.err.println("Usage: index-dump [sub-command] index.dat"); - System.exit(255); - } - - String[] args2 = Arrays.copyOfRange(args, 1, args.length); - IndexJournalDumpTool.main(args2); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ListCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ListCommand.java deleted file mode 100644 index 0bd2c3eb..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ListCommand.java +++ /dev/null @@ -1,23 +0,0 @@ -package nu.marginalia.wmsa.configuration.command; - -import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; - -import java.util.Arrays; -import java.util.Objects; - -public class ListCommand extends Command { - public ListCommand() { - super("list"); - } - - @Override - @SneakyThrows - public void execute(String... args) { - Arrays.stream(ServiceDescriptor.values()) - .filter(sd -> Objects.nonNull(sd.mainClass)) - .map(ServiceDescriptor::describeService) - .forEach(System.out::println); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/LoadCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/LoadCommand.java deleted file mode 100644 index 9b1e7120..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/LoadCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package nu.marginalia.wmsa.configuration.command; - -import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.converting.LoaderMain; - -import java.util.Arrays; - -public class LoadCommand extends Command { - public LoadCommand() { - super("load"); - } - - @Override - @SneakyThrows - public void execute(String... args) { - if (args.length < 2) { - System.err.println("Usage: load plan.yaml"); - System.exit(255); - } - - String[] args2 = Arrays.copyOfRange(args, 1, args.length); - LoaderMain.main(args2); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ReindexCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ReindexCommand.java deleted file mode 100644 index 18b3025e..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/ReindexCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package nu.marginalia.wmsa.configuration.command; - -import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.converting.ReindexTriggerMain; - -import java.util.Arrays; - -public class ReindexCommand extends Command { - public ReindexCommand() { - super("reindex"); - } - - @Override - @SneakyThrows - public void execute(String... args) { - if (args.length < 2) { - System.err.println("Usage: reindex host"); - System.exit(255); - } - - String[] args2 = Arrays.copyOfRange(args, 1, args.length); - ReindexTriggerMain.main(args2); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/StartCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/StartCommand.java deleted file mode 100644 index cd98a375..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/StartCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package nu.marginalia.wmsa.configuration.command; - -import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; - -import java.util.Arrays; - -public class StartCommand extends Command { - public StartCommand() { - super("start"); - } - - @Override - @SneakyThrows - public void execute(String... args) { - if (args.length < 2) { - System.err.println("Usage: start service-descriptor"); - System.err.println(); - System.err.println("Available services:"); - System.err.println(); - for (var d : ServiceDescriptor.values()) { - System.err.println("\t"+d.name); - } - System.exit(255); - } - var mainMethod = getKind(args[1]).mainClass.getMethod("main", String[].class); - String[] args2 = Arrays.copyOfRange(args, 2, args.length); - mainMethod.invoke(null, (Object) args2); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/VersionCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/VersionCommand.java deleted file mode 100644 index 179a0300..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/command/VersionCommand.java +++ /dev/null @@ -1,20 +0,0 @@ -package nu.marginalia.wmsa.configuration.command; - -import lombok.SneakyThrows; - -public class VersionCommand extends Command { - public VersionCommand() { - super("version"); - } - - @Override - @SneakyThrows - public void execute(String... args) { - try (var str = ClassLoader.getSystemResourceAsStream("_version.txt")) { - if (null == str) { - System.err.println("Bad jar, missing _version.txt"); - return; - } - } - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/ConfigurationModule.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/ConfigurationModule.java deleted file mode 100644 index 2727d31a..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/module/ConfigurationModule.java +++ /dev/null @@ -1,27 +0,0 @@ -package nu.marginalia.wmsa.configuration.module; - -import com.google.inject.AbstractModule; -import com.google.inject.Provides; -import com.google.inject.name.Named; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; - -import java.util.Objects; - -import static com.google.inject.name.Names.named; - -public class ConfigurationModule extends AbstractModule { - private static final String SERVICE_NAME = System.getProperty("service-name"); - - public void configure() { - bind(String.class).annotatedWith(named("service-name")).toInstance(Objects.requireNonNull(SERVICE_NAME)); - bind(String.class).annotatedWith(named("service-host")).toInstance(System.getProperty("service-host", "127.0.0.1")); - bind(Integer.class).annotatedWith(named("service-port")).toInstance(ServiceDescriptor.byName(System.getProperty("service-name")).port); - } - - @Provides - @Named("metrics-server-port") - public Integer provideMetricsServerPort(@Named("service-port") Integer servicePort) { - return servicePort + 1000; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Context.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Context.java deleted file mode 100644 index f7578b45..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/configuration/server/Context.java +++ /dev/null @@ -1,139 +0,0 @@ -package nu.marginalia.wmsa.configuration.server; - -import io.reactivex.rxjava3.schedulers.Schedulers; -import org.apache.logging.log4j.ThreadContext; -import spark.Request; - -import java.util.*; -import java.util.concurrent.TimeUnit; - -public class Context { - public static final String CONTEXT_HEADER = "X-Context"; - public static final String SESSION_HEADER = "Cookie"; - public static final String PUBLIC_HEADER = "X-Public"; - private static final Random random; - - private static volatile byte[] seed = new byte[12]; - - private static byte[] generateSalt() { - byte[] oldHash = seed; - - int hash1 = Long.hashCode(random.nextLong()); - int hash2 = Objects.hash(System.currentTimeMillis()); - int hash3 = Arrays.hashCode(oldHash); - - return new byte[]{ - (byte) (hash1 & 0xFF), - (byte) (hash1 >>> 8 & 0xFF), - (byte) (hash1 >>> 16 & 0xFF), - (byte) (hash1 >>> 24 & 0xFF), - (byte) (hash2 & 0xFF), - (byte) (hash2 >>> 8 & 0xFF), - (byte) (hash2 >>> 16 & 0xFF), - (byte) (hash2 >>> 24 & 0xFF), - (byte) (hash3 & 0xFF), - (byte) (hash3 >>> 8 & 0xFF), - (byte) (hash3 >>> 16 & 0xFF), - (byte) (hash3 >>> 24 & 0xFF) - }; - } - - static { - random = new Random(); - for (int i = 0; i < 1_000_000; i++) { - random.nextLong(); - } - random.nextBytes(seed); - - updateSeed(); - } - - private static void updateSeed() { - seed = generateSalt(); - - Schedulers.computation().scheduleDirect(Context::updateSeed, - 60*5000 + (int)(1000*60*10*Math.random()), - TimeUnit.MILLISECONDS); - } - - private final String id; - private final String session; - private boolean treatAsPublic; - - private Context(String id, String session) { - this.id = id; - this.session = session; - } - - public Context treatAsPublic() { - this.treatAsPublic = true; - return this; - } - - public static Context internal() { - return new Context(UUID.randomUUID().toString(), null); - } - public static Context internal(String hwat) { - return new Context(hwat, null); - } - - public static Context fromRequest(Request request) { - - if (Boolean.getBoolean("unit-test")) { - return Context.internal(); - } - - final var ctxHeader = hashPublicIp(request.headers(CONTEXT_HEADER)); - final var sessHeader = request.headers(SESSION_HEADER); - - ThreadContext.put("context", ctxHeader+"-"+sessHeader); - ThreadContext.put("outbound-request", "none"); - - return new Context(ctxHeader, sessHeader); - } - - private static String hashPublicIp(String header) { - - if (header != null && header.contains("-")) { - - byte[] hashData = Arrays.copyOf(seed, seed.length+4); - int hashi = Objects.hash(header.split("-", 2)[0]); - - for (int i = 0; i < 4; i++) { - hashData[seed.length] = (byte)(hashi & 0xFF); - hashData[seed.length+1] = (byte)(hashi>>>8 & 0xFF); - hashData[seed.length+2] = (byte)(hashi>>>16 & 0xFF); - hashData[seed.length+3] = (byte)(hashi>>>24 & 0xFF); - } - - return String.format("#%x", Arrays.hashCode(hashData)); - } - else { - return header; - } - } - - public okhttp3.Request.Builder paint(okhttp3.Request.Builder requestBuilder) { - requestBuilder.addHeader(CONTEXT_HEADER, id); - - if (session != null) { - requestBuilder.addHeader(SESSION_HEADER, session); - } - - if (treatAsPublic) { - requestBuilder.header(PUBLIC_HEADER, "1"); - } - - return requestBuilder; - } - - public Optional getIpHash() { - - if (id.startsWith("#")) { - return Optional.of(id); - } - - return Optional.empty(); - } - -} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantMain.java deleted file mode 100644 index db8dcd28..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantMain.java +++ /dev/null @@ -1,33 +0,0 @@ -package nu.marginalia.wmsa.edge.assistant; - -import com.google.inject.Guice; -import com.google.inject.Inject; -import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.configuration.server.Initialization; - -public class EdgeAssistantMain extends MainClass { - private final EdgeAssistantService service; - - @Inject - public EdgeAssistantMain(EdgeAssistantService service) { - this.service = service; - } - - public static void main(String... args) { - init(ServiceDescriptor.EDGE_ASSISTANT, args); - - Injector injector = Guice.createInjector( - new EdgeAssistantModule(), - new ConfigurationModule(), - new DatabaseModule() - ); - - injector.getInstance(EdgeAssistantMain.class); - injector.getInstance(Initialization.class).setReady(); - - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/TermFrequencyDict.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/TermFrequencyDict.java deleted file mode 100644 index 4d87ec96..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/TermFrequencyDict.java +++ /dev/null @@ -1,218 +0,0 @@ -package nu.marginalia.wmsa.edge.assistant.dict; - -import ca.rmen.porterstemmer.PorterStemmer; -import gnu.trove.map.hash.TLongIntHashMap; -import gnu.trove.set.hash.TLongHashSet; -import nu.marginalia.util.language.LanguageFilter; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.converting.processor.logic.DomPruningFilter; -import nu.marginalia.wmsa.edge.crawling.CrawlPlanLoader; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; -import javax.inject.Inject; -import javax.inject.Singleton; -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -@Singleton -public class TermFrequencyDict { - - private final TLongIntHashMap wordRates = new TLongIntHashMap(1_000_000, 0.5f, 0, 0); - - private final Logger logger = LoggerFactory.getLogger(getClass()); - private static final Pattern separator = Pattern.compile("[_ ]+"); - private static final PorterStemmer ps = new PorterStemmer(); - - private static final long DOC_COUNT_KEY = ~0L; - private static long fileSize(Path p) throws IOException { - return Files.size(p); - } - - @Inject - public TermFrequencyDict(@Nullable LanguageModels models) { - if (models == null) { - return; - } - - if (models.termFrequencies != null) { - - try (var frequencyData = new DataInputStream(new BufferedInputStream(new FileInputStream(models.termFrequencies.toFile())))) { - - wordRates.ensureCapacity((int)(fileSize(models.termFrequencies)/16)); - - for (;;) { - wordRates.put(frequencyData.readLong(), (int) frequencyData.readLong()); - } - } catch (EOFException eof) { - // ok - } catch (IOException e) { - logger.error("IO Exception reading " + models.termFrequencies, e); - } - } - - logger.info("Read {} N-grams frequencies", wordRates.size()); - } - - - public int docCount() { - int cnt = wordRates.get(DOC_COUNT_KEY); - - if (cnt == 0) { - cnt = 11820118; // legacy - } - return cnt; - } - - public static void main(String... args) throws IOException, InterruptedException { - if (args.length != 2) { - System.err.println("Expected arguments: plan.yaml out-file"); - } - String outFile = args[1]; - - var plan = new CrawlPlanLoader().load(Path.of(args[0])); - - ThreadLocal se = ThreadLocal.withInitial(() -> new SentenceExtractor(WmsaHome.getLanguageModels())); - LanguageFilter lf = new LanguageFilter(); - - TLongIntHashMap counts = new TLongIntHashMap(100_000_000, 0.7f, -1, -1); - - ForkJoinPool fjp = new ForkJoinPool(24); - AtomicInteger docCount = new AtomicInteger(); - - for (var domain : plan.domainsIterable()) { // leaks file descriptor, is fine - - if (domain.doc == null) - continue; - - fjp.execute(() -> { - - TLongHashSet words = new TLongHashSet(10_000); - - for (var doc : domain.doc) { - - if (doc.documentBody == null) - continue; - docCount.incrementAndGet(); - - Document parsed = Jsoup.parse(doc.documentBody.decode()); - parsed.body().filter(new DomPruningFilter(0.5)); - - DocumentLanguageData dld = se.get().extractSentences(parsed); - - if (lf.dictionaryAgreement(dld) < 0.1) { - return; - } - - for (var sent : dld.sentences) { - for (var word : sent) { - words.add(longHash(word.stemmed().getBytes(StandardCharsets.UTF_8))); - } - } - - synchronized (counts) { - words.forEach(w -> { - counts.adjustOrPutValue(w, 1, 1); - return true; - }); - } - - words.clear(); - } - - System.out.println(domain.domain + "\t" + counts.size()); - }); - - - } - - fjp.shutdown(); - fjp.awaitTermination(10, TimeUnit.DAYS); - - try (var dos = new DataOutputStream(Files.newOutputStream(Path.of(outFile)))) { - synchronized (counts) { - counts.put(DOC_COUNT_KEY, docCount.get()); - - counts.forEachEntry((hash, cnt) -> { - try { - dos.writeLong(hash); - dos.writeLong(cnt); - } catch (IOException e) { - throw new RuntimeException(e); - } - return true; - }); - } - } - - System.out.println(docCount.get()); - } - - public static long getStringHash(String s) { - String[] strings = separator.split(s); - if (s.length() > 1) { - byte[][] parts = new byte[strings.length][]; - for (int i = 0; i < parts.length; i++) { - parts[i] = ps.stemWord(strings[i]).getBytes(); - } - return longHash(parts); - } - else { - return longHash(s.getBytes()); - } - } - public long getTermFreqHash(long hash) { - return wordRates.get(hash); - } - public long getTermFreq(String s) { - return wordRates.get(getStringHash(s)); - } - public long getTermFreqStemmed(String s) { - return wordRates.get(longHash(s.getBytes())); - } - - public static String getStemmedString(String s) { - String[] strings = separator.split(s); - if (s.length() > 1) { - return Arrays.stream(strings).map(ps::stemWord).collect(Collectors.joining("_")); - } - else { - return s; - } - - } - - public static long longHash(byte[]... bytesSets) { - if (bytesSets == null || bytesSets.length == 0) - return 0; - - // https://cp-algorithms.com/string/string-hashing.html - int p = 127; - long m = (1L<<61)-1; - long p_power = 1; - long hash_val = 0; - - for (byte[] bytes: bytesSets) { - for (byte element : bytes) { - hash_val = (hash_val + (element + 1) * p_power) % m; - p_power = (p_power * p) % m; - } - } - return hash_val; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiArticles.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiArticles.java deleted file mode 100644 index 29cf187e..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiArticles.java +++ /dev/null @@ -1,23 +0,0 @@ -package nu.marginalia.wmsa.edge.assistant.dict; - -import lombok.Getter; -import lombok.ToString; - -import java.util.List; - -@ToString @Getter -public class WikiArticles { - public List entries; - - public WikiArticles(String... args) { - entries = List.of(args); - } - public String getPage() { - if (entries.isEmpty()) { - return null; - } - else { - return entries.get(0); - } - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiCleaner.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiCleaner.java deleted file mode 100644 index cc70f441..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiCleaner.java +++ /dev/null @@ -1,386 +0,0 @@ -package nu.marginalia.wmsa.edge.assistant.dict; - -import lombok.SneakyThrows; -import net.sourceforge.jeuclid.MathMLParserSupport; -import net.sourceforge.jeuclid.context.Display; -import net.sourceforge.jeuclid.context.LayoutContextImpl; -import net.sourceforge.jeuclid.context.Parameter; -import net.sourceforge.jeuclid.font.FontFactory; -import org.apache.commons.lang3.tuple.Pair; -import org.jetbrains.annotations.NotNull; -import org.jsoup.Jsoup; -import org.jsoup.nodes.*; -import org.jsoup.select.Elements; -import org.jsoup.select.NodeFilter; - -import java.awt.*; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.List; -import java.util.*; -import java.util.stream.Collectors; - - -public class WikiCleaner { - - static { - try (var font = ClassLoader.getSystemResourceAsStream("fonts/LM-regular.ttf")) { - FontFactory.getInstance().registerFont(Font.TRUETYPE_FONT, font); - } catch (IOException | FontFormatException e) { - e.printStackTrace(); - } - try (var font = ClassLoader.getSystemResourceAsStream("fonts/STIXTwoMath-Regular.ttf")) { - FontFactory.getInstance().registerFont(Font.TRUETYPE_FONT, font); - } catch (IOException | FontFormatException e) { - e.printStackTrace(); - } - } - public String cleanWikiJunk(String url, String html) { - return cleanWikiJunk(url, Jsoup.parse(html)); - } - - public List extractLinkWords(String data) { - var doc = Jsoup.parse(data); - return getWikiPageLinkText(doc); - } - - public String cleanWikiJunk(String url, Document doc) { - - if (doc.getElementById("content") == null) { - return null; - } - List> disambig = getDisambiguationLinks(doc); - List> topLinks = getWikiPageLinks(doc); - - removeTag(doc, "script", "object", "embed", "audio", "style", "noscript", "link", "meta", "img"); - doc.getElementsByClass("mwe-math-element").forEach(this::convertMathTag); - removeByClass(doc, "infobox", "collapsible", "navbar", "printfooter", - "mw-editsection", "thumb", "sidebar", "navbox", "mw-jump-link", - "vertical-navbox"); - removeByClass(doc, "mw-indicators", "noprint", "sistersitebox"); - removeIds(doc, "coordinates", "mw-page-base", "mw-head-base", "site-notice", "contentSub", "contentSub2"); - - doc.getElementsByAttributeValue("role", "presentation").remove(); - - doc.getElementsByTag("a").forEach(atag -> { - var href = atag.attr("href"); - var parent = atag.parent(); - - if ("li".equals(parent.tagName())) { - atag.removeAttr("title"); - if (href.startsWith("http://")) { - atag.addClass("extern-link"); - atag.attr("rel", "nofollow"); - return; - } - } - else { - atag.replaceWith(new TextNode(atag.text())); - } - }); - - doc.getElementsByTag("cite").tagName("span"); - - removeIds(doc, "toc", "catlinks", "Notes", "mw-navigation", "mw-data-after-content", "jump-to-nav"); - removeByClass(doc, "mw-references-wrap", "references", "reference", "siteSub", "refbegin"); - - // doc.getElementById("mw-content-text").insertChildren(0, doc.getElementById("firstHeading")); - doc.getElementById("content").tagName("article"); - doc.getAllElements().forEach(elem -> { - if (elem.parent() != null - && "summary".equals(elem.parent().tagName())) - { - elem.parent().replaceWith(elem); - } - }); - - doc.getElementsByTag("span").forEach(elem -> { - if ("pre".equals(elem.parent().tagName())) { - if (elem.hasClass("linenos")) { - elem.replaceWith(new TextNode(String.format("%-4s", elem.text()))); - } - else { - elem.replaceWith(new TextNode(elem.text())); - } - } - else { - elem.replaceWith(new TextNode(" " + elem.text() + " ")); - } - }); - - doc.getElementsByTag("details").forEach(deets -> { - if (deets.children().size() == 1) { - deets.replaceWith(deets.children().first()); - } - else { - deets.tagName("div"); - } - }); - - removeEmptyTags(doc, "li"); - removeEmptyTags(doc, "ul"); - removeEmptyTags(doc, "div"); - - doc.getElementsByTag("p").forEach(elem -> { - if ("blockquote".equals(elem.parent().tagName())) { - elem.replaceWith(new TextNode(elem.text())); - } - }); - - removeEmptyTags(doc, "p"); - - doc.getElementsByTag("h4").forEach(elem -> { - var next = elem.nextElementSibling(); - if (next == null) { - elem.remove(); - return; - } - String nextTagName = next.tagName(); - if ("h4".equals(nextTagName) || "h3".equals(nextTagName) || "h2".equals(nextTagName)) { - elem.remove(); - } - }); - - - doc.getElementsByTag("h3").forEach(elem -> { - var next = elem.nextElementSibling(); - if (next == null) { - elem.remove(); - return; - } - String nextTagName = next.tagName(); - if ("h3".equals(nextTagName) || "h2".equals(nextTagName)) { - elem.remove(); - } - }); - - doc.getElementsByTag("h2").forEach(elem -> { - var next = elem.nextElementSibling(); - if (next == null) { - elem.remove(); - return; - } - if ("h2".equals(next.tagName())) { - elem.remove(); - } - }); - doc.getElementsByTag("footer").remove(); - doc.getElementsByTag("table").forEach(table -> { - table.attr("border", "1"); - }); - doc.getElementsByTag("table").forEach(table -> { - if ("right".equals(table.attr("align"))) { - table.remove(); - } - }); - - doc.getElementsByTag("head").append(""); - doc.getElementsByTag("head").append(""); - doc.getElementsByTag("head").append(""); - doc.getElementsByTag("head").append(""); - doc.getElementsByTag("head").append(""); - doc.getElementsByTag("head").append(""); - - if (!topLinks.isEmpty()) { - doc.getElementsByTag("article").append("

Index of References

"); - } - - if (!disambig.isEmpty()) { - doc.getElementsByTag("h1").first().nextElementSibling().prepend("
See Also" + - disambig.stream().map(href -> ""+href.getValue()+"").collect(Collectors.joining("
")) - + ""); - } - - doc.getElementsByTag("article").first().parent().prepend("
"); - doc.getElementsByTag("article").first().parent().append(""); - - doc.getElementsByTag("div").forEach(tag -> { - if (tag.text().startsWith("This article is issued from Wikipedia")) { - tag.remove(); // we have our own - } - }); - doc.getAllElements().forEach(elem -> { - var classes = elem.classNames().stream().filter(this::isWikiClass).toList(); - classes.forEach(elem::removeClass); - elem.removeAttr("lang"); - elem.removeAttr("dir"); - elem.removeAttr("id"); - elem.removeAttr("role"); - elem.removeAttr("style"); - elem.removeAttr("tabindex"); - elem.removeAttr("aria-haspopup"); - elem.removeAttr("data-section-id"); - elem.removeAttr("aria-expanded"); - elem.removeAttr("aria-pressed"); - elem.removeAttr("open"); - elem.removeAttr("data-level"); - }); - - marginifyHeaders(doc); - - - doc.filter(new NodeFilter() { - @Override - public FilterResult head(Node node, int depth) { - if (node instanceof Comment) { - return FilterResult.REMOVE; - } - return FilterResult.CONTINUE; - } - - @Override - public FilterResult tail(Node node, int depth) { - if (node instanceof Comment) { - return FilterResult.REMOVE; - } - return FilterResult.CONTINUE; - } - }); - return doc.html(); - } - - @SneakyThrows - private void convertMathTag(Element math) { - - try { - var formula = math.getElementsByTag("math"); - var converter = net.sourceforge.jeuclid.converter.Converter.getInstance(); - var sos = new ByteArrayOutputStream(); - var alt = Optional.of(formula.attr("alttext")).filter(s -> !s.isBlank()) - .orElseGet(() -> math.getElementsByTag("annotation").text()); - - var layoutContext = new LayoutContextImpl(LayoutContextImpl.getDefaultLayoutContext()); - - String parentTag = math.parent().tag().getName(); - boolean topLevel = "dd".equals(parentTag) || "div".equals(parentTag) - || (math.nextElementSibling() == null && math.previousElementSibling() == null); - - int mathSize = 16; - if (topLevel) - mathSize = 24; - if ("h1".equals(parentTag)) { - mathSize = 28; - } - if ("h2".equals(parentTag)) { - mathSize = 24; - } - if ("h3".equals(parentTag)) { - mathSize = 22; - } - layoutContext.setParameter(Parameter.MATHSIZE, mathSize); - - layoutContext.setParameter(Parameter.ANTIALIAS, true); - layoutContext.setParameter(Parameter.SCRIPTMINSIZE, 8); - layoutContext.setParameter(Parameter.FONTS_SERIF, "STIX Two Math"); - layoutContext.setParameter(Parameter.FONTS_SCRIPT, "STIX Two Math"); - layoutContext.setParameter(Parameter.DISPLAY, topLevel ? Display.BLOCK : Display.INLINE); - - converter.convert(MathMLParserSupport.parseString( - formula.html().replace(" ", " ")), sos, - "image/png", - layoutContext).toString(); - - math.tagName("img") - .text("") - .attr("src", "data:image/png;base64," + Base64.getEncoder().encodeToString(sos.toByteArray())) - .attr("alt", alt); - - } - catch (Exception ex) { - ex.printStackTrace(); - } - } - - private void removeEmptyTags(Document doc, String tag) { - doc.getElementsByTag(tag).forEach(elem -> { - if (elem.text().isBlank() && elem.getElementsByTag("img").isEmpty()) { - elem.replaceWith(new TextNode(" ")); - } - - }); - } - - @NotNull - private List> getWikiPageLinks(Document doc) { - List> topLinks = new ArrayList<>(); - doc.select("p a").forEach(atag -> { - String href = atag.attr("href"); - - if (!href.isBlank() - && !href.contains(":") - && !href.startsWith("#") - ) { - topLinks.add(Pair.of(href, atag.attr("title"))); - } - }); - return topLinks; - } - - - @NotNull - private List getWikiPageLinkText(Document doc) { - List topLinks = new ArrayList<>(); - - doc.select("p a,h1,h2,h3,h4,i,em,strong,b").forEach(e -> topLinks.add(e.text())); - - return topLinks; - } - - @NotNull - private List> getDisambiguationLinks(Document doc) { - List> disambig = new ArrayList<>(); - - for (var note: doc.getElementsByClass("hatnote")) { - for (var atag : note.getElementsByTag("a")) { - String href = atag.attr("href"); - if (atag.hasClass("mw-disambig") && !href.isBlank()) { - disambig.add(Pair.of(href, atag.attr("title"))); - } - } - } - doc.getElementsByClass("hatnote").remove(); - - return disambig; - } - - private void removeTag(Document doc, String... tags) { - for (String tag : tags) { - doc.getElementsByTag(tag).remove(); - } - } - private void removeByClass(Document doc, String... classes) { - for (String clas: classes) { - doc.getElementsByClass(clas).remove(); - } - } - private void removeIds(Document doc, String... ids) { - Arrays.stream(ids) - .map(doc::getElementById) - .filter(Objects::nonNull) - .forEach(Element::remove); - } - - private void marginifyHeaders(Document doc) { - Elements headers = doc.getElementsByTag("h4"); - if (headers.size() == 0) { - headers = doc.getElementsByTag("h3"); - } - headers.addClass("margin-note"); - } - - boolean isWikiClass(String clazz) { - if ("verb".equals(clazz)) { - return false; - } - if ("extern-link".equals(clazz)) { - return false; - } - if ("margin-note".equals(clazz)) { - return false; - } - return true; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiSearchResult.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiSearchResult.java deleted file mode 100644 index f3e0f7ac..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/WikiSearchResult.java +++ /dev/null @@ -1,55 +0,0 @@ -package nu.marginalia.wmsa.edge.assistant.dict; - -import lombok.AllArgsConstructor; - -import javax.annotation.Nullable; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Optional; - -@AllArgsConstructor -public class WikiSearchResult { - private final String name; - @Nullable - private final String refName; - - public String getName() { - return name.replace('_', ' '); - } - @Nullable - public String getRefName() { - if (refName == null) - return null; - - return refName.replace('_', ' '); - } - - public String getUrl() { - return "https://encyclopedia.marginalia.nu/wiki/" + URLEncoder.encode(getRealName(), StandardCharsets.UTF_8); - } - - public String getRealName() { - return Optional.ofNullable(refName).orElse(name); - } - - public String getInternalName() { - return name; - } - - @Override - public int hashCode() { - return getRealName().hashCode(); - } - - public boolean equals(Object other) { - if (other == this) { - return true; - } - if (other instanceof WikiSearchResult) { - WikiSearchResult r = (WikiSearchResult) other; - return r.getRealName().equals(getRealName()); - } - return false; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LinkKeywordExtractorMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LinkKeywordExtractorMain.java deleted file mode 100644 index f9557c97..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LinkKeywordExtractorMain.java +++ /dev/null @@ -1,186 +0,0 @@ -package nu.marginalia.wmsa.edge.converting; - -import gnu.trove.set.hash.TIntHashSet; -import nu.marginalia.wmsa.edge.converting.atags.AnchorTextExtractor; -import nu.marginalia.wmsa.edge.crawling.CrawlPlanLoader; -import nu.marginalia.wmsa.edge.crawling.CrawledDomainReader; -import nu.marginalia.wmsa.edge.crawling.CrawlerSpecificationLoader; -import nu.marginalia.wmsa.edge.crawling.WorkLog; -import nu.marginalia.wmsa.edge.crawling.model.CrawlerDocumentStatus; -import nu.marginalia.wmsa.edge.integration.stackoverflow.StackOverflowPostsReader; -import nu.marginalia.wmsa.edge.integration.wikipedia.WikipediaReader; -import nu.marginalia.wmsa.edge.model.EdgeCrawlPlan; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedOutputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; - -public class LinkKeywordExtractorMain { - private static final Logger logger = LoggerFactory.getLogger(LinkKeywordExtractorMain.class); - - public static void main(String... args) throws IOException, InterruptedException { - - if (args.length < 2) { - System.err.println("Arguments: [crawl|so|wiki] crawl-plan.yaml [data]"); - System.exit(0); - } - - String command = args[0]; - var plan = new CrawlPlanLoader().load(Path.of(args[1])); - - switch (command) { - case "crawl": getKeywordsFromCrawl(plan); break; - case "so": getKeywordsFromSo(plan, args[2]); break; - case "wiki": getKeywordsFromWiki(plan, args[2]); break; - default: System.err.println("Unrecognized command"); - } - - } - - private static void getKeywordsFromWiki(EdgeCrawlPlan plan, String arg) throws IOException, InterruptedException { - - - HashSet crawledDomains = new HashSet<>(); - TIntHashSet crawledUrls = new TIntHashSet(50_000_000); - - logger.info("Loading URLs"); - Files.lines(Path.of("/home/vlofgren/good-urls3.txt")) - .filter(url -> !url.contains("stackoverflow") && !url.contains("stackexchange")) - .mapToInt(String::hashCode) - .forEach(crawledUrls::add); - - logger.info("Loading input spec"); - - CrawlerSpecificationLoader.readInputSpec(plan.getJobSpec(), - spec -> { crawledDomains.add(spec.domain); }); - - try (var output = new UrlKeywordTsvWriter(Path.of("links.tsv"))) { - AnchorTextExtractor anchorTextExtractor = new AnchorTextExtractor(domain -> crawledDomains.contains(domain) - && !domain.contains("wiki") - && !domain.contains("isni") - && !domain.contains("wiktionary"), - url -> crawledUrls.contains(url.toString().hashCode()), - output::write); - - new WikipediaReader(arg, new EdgeDomain("invalid.example"), article -> { - anchorTextExtractor.processDocument(article.getUrl().toString(), article.body); - }).join(); - } - catch (IOException ex) { - ex.printStackTrace(); - } - - - - } - - private static void getKeywordsFromSo(EdgeCrawlPlan plan, String arg) throws IOException, InterruptedException { - TIntHashSet crawledUrls = new TIntHashSet(50_000_000); - - logger.info("Loading URLs"); - Files.lines(Path.of("/home/vlofgren/good-urls3.txt")) - .filter(url -> !url.contains("stackoverflow") && !url.contains("stackexchange")) - .mapToInt(String::hashCode) - .forEach(crawledUrls::add); - - logger.info("Loading input spec"); - - HashSet crawledDomains = new HashSet<>(); - CrawlerSpecificationLoader.readInputSpec(plan.getJobSpec(), - spec -> crawledDomains.add(spec.domain)); - - crawledDomains.remove("jsfiddle.net"); // like 30% of SO's links go here - crawledDomains.remove("jsbin.com"); - crawledDomains.remove("codepad.org"); - - - try (var output = new UrlKeywordTsvWriter(Path.of("links.tsv"))) { - AnchorTextExtractor anchorTextExtractor = new AnchorTextExtractor(crawledDomains::contains, - url -> crawledUrls.contains(url.toString().hashCode()), - output::write); - - new StackOverflowPostsReader(arg, new EdgeDomain("invalid.example"), post -> { - anchorTextExtractor.processDocument(post.getUrl().toString(), post.fullBody); - }).join(); - } - catch (IOException ex) { - ex.printStackTrace(); - } - } - - - public static void getKeywordsFromCrawl(EdgeCrawlPlan plan) throws IOException { - - logger.info("Loading input spec"); - - HashSet crawledDomains = new HashSet<>(); - CrawlerSpecificationLoader.readInputSpec(plan.getJobSpec(), - spec -> crawledDomains.add(spec.domain)); - - List fileNames = new ArrayList<>(); - - logger.info("Replaying crawl log"); - WorkLog.readLog(plan.crawl.getLogFile(), - entry -> fileNames.add(entry.path())); - - try (var output = new UrlKeywordTsvWriter(Path.of("links.tsv"))) { - AnchorTextExtractor anchorTextExtractor = new AnchorTextExtractor(crawledDomains::contains, - (url) -> true, - //url -> crawledUrls.contains(url.toString().hashCode()), - output::write); - - logger.info("Reading files"); - for (var fn : fileNames) { - CrawledDomainReader crawledDomainReader = new CrawledDomainReader(); - var crawledDomain = crawledDomainReader.read(plan.getCrawledFilePath(fn)); - if (crawledDomain.doc == null) continue; - - System.out.println("# " + crawledDomain.domain); - - for (var doc : crawledDomain.doc) { - if (Objects.equals(doc.crawlerStatus, CrawlerDocumentStatus.OK.name())) { - anchorTextExtractor.processDocument(doc.url, doc.documentBody.decode()); - } - } - } - } - - } - - private static class UrlKeywordTsvWriter implements AutoCloseable { - - private final OutputStream stream; - - UrlKeywordTsvWriter(Path outputFile) throws IOException { - this.stream = new BufferedOutputStream(new FileOutputStream(outputFile.toFile())); - } - - void write(EdgeUrl url, String keyword) { - try { - stream.write(url.toString().getBytes()); - stream.write('\t'); - stream.write(keyword.getBytes()); - stream.write('\n'); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public void close() throws IOException { - stream.close(); - } - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LinkKeywordLoaderMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LinkKeywordLoaderMain.java deleted file mode 100644 index f00c54ee..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/LinkKeywordLoaderMain.java +++ /dev/null @@ -1,110 +0,0 @@ -package nu.marginalia.wmsa.edge.converting; - -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexClient; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.SQLException; -import java.util.*; -import java.util.function.Consumer; - -public class LinkKeywordLoaderMain { - - public static void main(String... args) { - - Map urlToId = getUrls(); - try (EdgeIndexClient indexClient = new EdgeIndexClient(); - var lines = Files.lines(Path.of(args[0])) - ) { - lines - .map(UrlKeyword::parseLine) - .filter(Objects::nonNull) - .forEach(new Uploader(urlToId, indexClient)); - - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private record UrlKeyword(String url, String keyword) { - public static UrlKeyword parseLine(String line) { - int idx = line.indexOf('\t'); - if (idx > 0) { - return new UrlKeyword(line.substring(0, idx), line.substring(idx+1)); - } - return null; - } - } - - private static class Uploader implements Consumer { - private Map urlToId; - private final EdgeIndexClient indexClient; - - private Uploader(Map urlToId, - EdgeIndexClient indexClient) { - this.urlToId = urlToId; - this.indexClient = indexClient; - } - - String lastLine = null; - Set keywords = new HashSet<>(100); - - @Override - public void accept(UrlKeyword urlKeyword) { - if (urlKeyword == null) return; - - if (lastLine == null) { - lastLine = urlKeyword.url; - keywords.add(urlKeyword.keyword); - } - else if (urlKeyword.url.equals(lastLine)) { - keywords.add(urlKeyword.keyword); - } - else { - Long id = urlToId.get(lastLine); - - if (id != null) { - int urlId = (int)(id & 0xFFFF_FFFFL); - int domainId = (int)(id >>> 32L); - - System.out.println(lastLine + " -/- " + domainId + ":" + urlId + " : " + keywords); - -// indexClient.putWords(Context.internal(), new EdgeId<>(domainId), new EdgeId<>(urlId), -// new DocumentKeywords(IndexBlock.Link, keywords.toArray(String[]::new)), 0); - } - - lastLine = urlKeyword.url; - keywords.clear(); - keywords.add(urlKeyword.keyword); - } - } - } - - private static Map getUrls() { - - Map urls = new HashMap<>(100_000); - - try (var ds = new DatabaseModule().provideConnection(); - var conn = ds.getConnection(); - var stmt = conn.createStatement()) - { - stmt.setFetchSize(10000); - var rsp = stmt.executeQuery("SELECT URL, ID, DOMAIN_ID FROM EC_URL_VIEW WHERE TITLE IS NOT NULL"); - - while (rsp.next()) { - long val = rsp.getInt(3); - val = (val << 32L) | rsp.getInt(2); - - urls.put(rsp.getString(1), val); - } - - } - catch (SQLException ex) { - throw new RuntimeException(ex); - } - - return urls; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ReindexTriggerMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ReindexTriggerMain.java deleted file mode 100644 index 9d67ae36..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/ReindexTriggerMain.java +++ /dev/null @@ -1,59 +0,0 @@ -package nu.marginalia.wmsa.edge.converting; - -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okio.BufferedSink; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.URL; -import java.nio.charset.Charset; -import java.sql.SQLException; -import java.util.concurrent.TimeUnit; - - -public class ReindexTriggerMain { - private static final Logger logger = LoggerFactory.getLogger(ReindexTriggerMain.class); - - public static void main(String... args) throws IOException, SQLException { - var db = new DatabaseModule(); - var client = new OkHttpClient.Builder() - .connectTimeout(100, TimeUnit.MILLISECONDS) - .readTimeout(15, TimeUnit.MINUTES) - .retryOnConnectionFailure(true) - .followRedirects(true) - .build(); - - logger.info("Updating statistics"); - var updateStatistics = new UpdateDomainStatistics(db.provideConnection()); - updateStatistics.run(); - - var rb = new RequestBody() { - - @Nullable - @Override - public MediaType contentType() { - return MediaType.parse("text/plain"); - } - - @Override - public void writeTo(BufferedSink sink) throws IOException { - sink.writeString("NOOP", Charset.defaultCharset()); - } - }; - - logger.info("Repartitioning"); - client.newCall(new Request.Builder().post(rb).url(new URL("http", args[0], ServiceDescriptor.EDGE_INDEX.port, "/ops/repartition")).build()).execute(); - logger.info("Reindexing"); - client.newCall(new Request.Builder().post(rb).url(new URL("http", args[0], ServiceDescriptor.EDGE_INDEX.port, "/ops/reindex")).build()).execute(); - - } - - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/atags/AnchorTextExtractor.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/atags/AnchorTextExtractor.java deleted file mode 100644 index e22f1e1b..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/atags/AnchorTextExtractor.java +++ /dev/null @@ -1,264 +0,0 @@ -package nu.marginalia.wmsa.edge.converting.atags; - -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; -import lombok.SneakyThrows; -import nu.marginalia.util.DenseBitMap; -import nu.marginalia.util.language.WordPatterns; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.assistant.dict.NGramBloomFilter; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.converting.processor.logic.LinkParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import org.apache.logging.log4j.util.Strings; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.jsoup.nodes.Element; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Objects; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -public class AnchorTextExtractor { - private final Predicate includeDomainPredicate; - private final Predicate includeUrlPredicate; - private final BiConsumer linkKeywordConsumer; - - private final LinkParser linkParser = new LinkParser(); - - private final HashFunction hashFunction = Hashing.murmur3_128(); - - // This bit map is used as a bloom filter to deduplicate url-keyword combinations - // false positives are expected, but that's an acceptable trade-off to not have to deal with - // de-duplicating billions of shuffled (url, word) tuples on limited hardware - private final DenseBitMap deduplicateHashBitset = new DenseBitMap(DenseBitMap.MAX_CAPACITY_2GB_16BN_ITEMS); - - private final NGramBloomFilter nGramBloomFilter; - private final TermFrequencyDict termFrequencyDict; - - public AnchorTextExtractor(Predicate includeDomainPredicate, - Predicate includeUrlPredicate, - BiConsumer linkKeywordConsumer) throws IOException { - this.includeDomainPredicate = includeDomainPredicate; - this.includeUrlPredicate = includeUrlPredicate; - this.linkKeywordConsumer = linkKeywordConsumer; - - nGramBloomFilter = new NGramBloomFilter(WmsaHome.getLanguageModels()); - termFrequencyDict = new TermFrequencyDict(WmsaHome.getLanguageModels()); - } - - @SneakyThrows - public void processDocument(String docUrl, String documentBody) { - final Document processed = Jsoup.parse(documentBody); - final EdgeUrl documentUrl = new EdgeUrl(docUrl); - - for (var link : processed.getElementsByTag("a")) { - if (link.hasAttr("href")) { - String href = link.attr("href"); - String text = getLinkText(link); - - processAnchor(documentUrl, href, text); - } - } - } - - private final Pattern anchorTextNoise = Pattern.compile("[ \t\n\"()“”]+"); - - private String getLinkText(Element link) { - String text = link.text(); - - if (link.text().isBlank()) { - for (var img: link.getElementsByTag("img")) { - if (img.hasAttr("alt")) { - text = img.attr("alt"); - break; - } - } - } - - return anchorTextNoise.matcher(text.toLowerCase()).replaceAll(" ").trim(); - } - - Set excludedTerminators = Set.of("a", "for", "of", "in", "with", "but", "as", "by", "on", "to", "at", "-"); - - private void processAnchor(EdgeUrl documentUrl, String href, String text) { - text = trimText(text); - - if (!isInterestingAnchorText(text)) { - return; - } - - var optLinkUrl = linkParser.parseLink(documentUrl, href); - if (optLinkUrl.isEmpty()) return; - - var linkUrl = optLinkUrl.get(); - - if (!isInterestingAnchorLink(linkUrl)) { - return; - } - - if (Objects.equals(domainHash(linkUrl), domainHash(documentUrl))) { - return; - } - - String[] wordParts = anchorTextNoise.split(text.toLowerCase()); - - if (wordParts.length > 1) { - String word = Strings.join(Arrays.asList(wordParts), '_'); - - addKeywordIfExistsInTermFreqDictionary(linkUrl, word); - - if (word.contains(".")) { - addKeywordIfExistsInTermFreqDictionary(linkUrl, removePeriods(word)); - } - - if (wordParts.length > 2) { - for (int i = 1; i < wordParts.length; i++) { - if (excludedTerminators.contains(wordParts[i])) continue; - if (excludedTerminators.contains(wordParts[i-1])) continue; - - word = wordParts[i-1] + "_" + wordParts[i]; - addKeywordIfExistsInTermFreqDictionary(linkUrl, word); - - if (word.contains(".")) { - addKeywordIfExistsInTermFreqDictionary(linkUrl, removePeriods(word)); - } - } - } - - if (wordParts.length > 3) { - for (int i = 2; i < wordParts.length; i++) { - if (excludedTerminators.contains(wordParts[i])) continue; - if (excludedTerminators.contains(wordParts[i-2])) continue; - - word = wordParts[i-2] + "_" + wordParts[i-1] + "_" + wordParts[i]; - - addKeywordIfExistsInTermFreqDictionary(linkUrl, word); - - if (word.contains(".")) { - word = removePeriods(word); - addKeywordIfExistsInTermFreqDictionary(linkUrl, removePeriods(word)); - } - } - } - - } - - for (String word: wordParts) { - if (!WordPatterns.isStopWord(word) - && WordPatterns.filter(word) - && isNewKeywordForLink(word, linkUrl.toString()) - ) { - linkKeywordConsumer.accept(linkUrl, word); - } - } - - for (String word: wordParts) { - if (word.length() > 2 && word.endsWith("'s")) { - word = word.substring(0, word.length()-2); - } - - if (!WordPatterns.isStopWord(word) - && WordPatterns.filter(word) - && isNewKeywordForLink(word, linkUrl.toString()) - ) { - linkKeywordConsumer.accept(linkUrl, word); - } - } - } - - private void addKeywordIfExistsInTermFreqDictionary(EdgeUrl linkUrl, String word) { - if (termFrequencyDict.getTermFreq(word) > 0 || nGramBloomFilter.isKnownNGram(word)) { - if (isNewKeywordForLink(word, linkUrl.toString())) { - linkKeywordConsumer.accept(linkUrl, word); - } - } - } - - Pattern p = Pattern.compile("\\."); - private String removePeriods(String s) { - return p.matcher(s).replaceAll(""); - } - - private String domainHash(EdgeUrl url) { - var domain = url.domain; - if ("www".equals(domain.subDomain)) { - return domain.domain; - } - return domain.toString(); - } - - private String trimText(String text) { - int start = text.length()-1; - int end = 0; - - for (int i = text.length(); i > 0; i--) { - if (Character.isLetterOrDigit(text.charAt(i-1))) { - end = i; - break; - } - } - - for (int i = 0; i < end; i++) { - if (Character.isLetterOrDigit(text.charAt(i))) { - start = i; - break; - } - } - - if (start >= 0 && start < end) { - return text.substring(start, end); - } - - return ""; - } - - // This pattern doesn't need to perfectly capture all anchor texts that are URLs, if it gets 95% that's fine - private final Predicate looksLikeAnURL = Pattern.compile("(\\p{Alpha}+://)?[\\p{Alnum}.]+(/[^/]+)+").asMatchPredicate(); - - private boolean isInterestingAnchorText(String text) { - if (text.isBlank()) return false; - if (text.length() > 32) return false; - - // Google loves questions, and so does SEO spammers - if (text.endsWith("?")) return false; - - if (text.startsWith("http:") || text.startsWith("https:")) return false; - - if (looksLikeAnURL.test(text)) return false; - - return switch (text) { - case "this", "here", "click", "click here", "download", "source" -> false; - default -> true; - }; - } - - private boolean isInterestingAnchorLink(EdgeUrl linkUrl) { - if (!(linkUrl.proto.endsWith("http") || linkUrl.proto.equals("https"))) { - return false; - } - - if (!includeUrlPredicate.test(linkUrl)) { - return false; - } - - return includeDomainPredicate.test(linkUrl.domain.toString()); - } - - private synchronized boolean isNewKeywordForLink(String href, String text) { - long hash = 0; - - hash ^= hashFunction.hashString(href, StandardCharsets.UTF_8).padToLong(); - hash ^= hashFunction.hashString(text, StandardCharsets.UTF_8).padToLong(); - - // Remove sign bit because we don't want a negative index in deduplicateHashBitset - hash &= 0x7FFF_FFFF_FFFF_FFFFL; - - return !deduplicateHashBitset.set(hash % deduplicateHashBitset.cardinality); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/RedirectCompiler.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/RedirectCompiler.java deleted file mode 100644 index 2a1e42f7..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/compiler/RedirectCompiler.java +++ /dev/null @@ -1,19 +0,0 @@ -package nu.marginalia.wmsa.edge.converting.compiler; - -import nu.marginalia.wmsa.edge.converting.interpreter.Instruction; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadDomain; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadDomainLink; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadDomainRedirect; -import nu.marginalia.wmsa.edge.model.EdgeDomain; - -import java.util.List; - -public class RedirectCompiler { - - public void compile(List ret, EdgeDomain from, EdgeDomain to) { - ret.add(new LoadDomain(to)); - ret.add(new LoadDomainLink(new DomainLink(from, to))); - ret.add(new LoadDomainRedirect(new DomainLink(from, to))); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/Interpreter.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/Interpreter.java deleted file mode 100644 index 5e9c3e4d..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/Interpreter.java +++ /dev/null @@ -1,25 +0,0 @@ -package nu.marginalia.wmsa.edge.converting.interpreter; - -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DomainLink; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocument; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.LoadProcessedDocumentWithError; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; - -public interface Interpreter { - void loadUrl(EdgeUrl[] url); - void loadDomain(EdgeDomain[] domain); - void loadRssFeed(EdgeUrl[] rssFeed); - void loadDomainLink(DomainLink[] links); - - void loadProcessedDomain(EdgeDomain domain, EdgeDomainIndexingState state, String ip); - void loadProcessedDocument(LoadProcessedDocument loadProcessedDocument); - void loadProcessedDocumentWithError(LoadProcessedDocumentWithError loadProcessedDocumentWithError); - - void loadKeywords(EdgeUrl url, EdgePageDocumentsMetadata metadata, DocumentKeywords words); - - void loadDomainRedirect(DomainLink link); -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/DomainLink.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/DomainLink.java deleted file mode 100644 index 338e345c..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/interpreter/instruction/DomainLink.java +++ /dev/null @@ -1,6 +0,0 @@ -package nu.marginalia.wmsa.edge.converting.interpreter.instruction; - -import nu.marginalia.wmsa.edge.model.EdgeDomain; - -public record DomainLink(EdgeDomain from, EdgeDomain to) { -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/DocumentProcessor.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/DocumentProcessor.java deleted file mode 100644 index 71ac3945..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/DocumentProcessor.java +++ /dev/null @@ -1,425 +0,0 @@ -package nu.marginalia.wmsa.edge.converting.processor; - -import com.google.common.hash.HashCode; -import com.google.inject.Inject; -import com.google.inject.name.Named; -import nu.marginalia.util.gregex.GuardedRegex; -import nu.marginalia.util.gregex.GuardedRegexFactory; -import nu.marginalia.util.language.LanguageFilter; -import nu.marginalia.util.language.processing.DocumentKeywordExtractor; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.util.language.processing.model.DocumentLanguageData; -import nu.marginalia.util.language.processing.model.KeywordMetadata; -import nu.marginalia.wmsa.edge.converting.model.DisqualifiedException; -import nu.marginalia.wmsa.edge.converting.model.DisqualifiedException.DisqualificationReason; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDocument; -import nu.marginalia.wmsa.edge.converting.model.ProcessedDocumentDetails; -import nu.marginalia.wmsa.edge.converting.processor.logic.*; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateSniffer; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDocument; -import nu.marginalia.wmsa.edge.crawling.model.CrawledDomain; -import nu.marginalia.wmsa.edge.crawling.model.CrawlerDocumentStatus; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentFlags; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; -import nu.marginalia.wmsa.edge.model.crawl.EdgePageWords; -import nu.marginalia.wmsa.edge.model.crawl.EdgeUrlState; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.util.*; - -import static nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard.UNKNOWN; - -public class DocumentProcessor { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final int minDocumentLength; - private final double minDocumentQuality; - - private static final Set acceptedContentTypes = Set.of("application/xhtml+xml", "application/xhtml", "text/html"); - - private final SentenceExtractor sentenceExtractor; - private final FeatureExtractor featureExtractor; - private final TitleExtractor titleExtractor; - private final DocumentKeywordExtractor keywordExtractor; - private final SummaryExtractor summaryExtractor; - private final PubDateSniffer pubDateSniffer; - - private static final DocumentValuator documentValuator = new DocumentValuator(); - private static final LanguageFilter languageFilter = new LanguageFilter(); - private static final LinkParser linkParser = new LinkParser(); - private static final FeedExtractor feedExtractor = new FeedExtractor(linkParser); - - @Inject - public DocumentProcessor(@Named("min-document-length") Integer minDocumentLength, - @Named("min-document-quality") Double minDocumentQuality, - SentenceExtractor sentenceExtractor, - FeatureExtractor featureExtractor, - TitleExtractor titleExtractor, - DocumentKeywordExtractor keywordExtractor, - SummaryExtractor summaryExtractor, - PubDateSniffer pubDateSniffer) - { - this.minDocumentLength = minDocumentLength; - this.minDocumentQuality = minDocumentQuality; - this.sentenceExtractor = sentenceExtractor; - this.featureExtractor = featureExtractor; - this.titleExtractor = titleExtractor; - this.keywordExtractor = keywordExtractor; - this.summaryExtractor = summaryExtractor; - this.pubDateSniffer = pubDateSniffer; - } - - public ProcessedDocument makeDisqualifiedStub(CrawledDocument crawledDocument) { - ProcessedDocument ret = new ProcessedDocument(); - - try { - ret.state = EdgeUrlState.DISQUALIFIED; - ret.url = getDocumentUrl(crawledDocument); - } - catch (Exception ex) {} - - return ret; - } - - public ProcessedDocument process(CrawledDocument crawledDocument, CrawledDomain crawledDomain) { - ProcessedDocument ret = new ProcessedDocument(); - - try { - processDocument(crawledDocument, crawledDomain, ret); - } - catch (DisqualifiedException ex) { - ret.state = EdgeUrlState.DISQUALIFIED; - ret.stateReason = ex.reason.toString(); - logger.debug("Disqualified {}: {}", ret.url, ex.reason); - } - catch (Exception ex) { - ret.state = EdgeUrlState.DISQUALIFIED; - ret.stateReason = DisqualificationReason.PROCESSING_EXCEPTION.toString(); - logger.info("Failed to convert " + crawledDocument.url, ex); - ex.printStackTrace(); - } - - return ret; - } - - private void processDocument(CrawledDocument crawledDocument, CrawledDomain crawledDomain, ProcessedDocument ret) throws URISyntaxException, DisqualifiedException { - - var crawlerStatus = CrawlerDocumentStatus.valueOf(crawledDocument.crawlerStatus); - if (crawlerStatus != CrawlerDocumentStatus.OK) { - throw new DisqualifiedException(crawlerStatus); - } - - if (AcceptableAds.hasAcceptableAdsHeader(crawledDocument)) { - throw new DisqualifiedException(DisqualificationReason.ACCEPTABLE_ADS); - } - - if (!isAcceptedContentType(crawledDocument)) { - throw new DisqualifiedException(DisqualificationReason.CONTENT_TYPE); - } - - - ret.url = getDocumentUrl(crawledDocument); - ret.state = crawlerStatusToUrlState(crawledDocument.crawlerStatus, crawledDocument.httpStatus); - - var detailsWithWords = createDetails(crawledDomain, crawledDocument); - - ret.details = detailsWithWords.details(); - ret.words = detailsWithWords.words(); - } - - - private EdgeUrl getDocumentUrl(CrawledDocument crawledDocument) - throws URISyntaxException - { - if (crawledDocument.canonicalUrl != null) { - try { - return new EdgeUrl(crawledDocument.canonicalUrl); - } - catch (URISyntaxException ex) { /* fallthrough */ } - } - - return new EdgeUrl(crawledDocument.url); - } - - public static boolean isAcceptedContentType(CrawledDocument crawledDocument) { - if (crawledDocument.contentType == null) { - return false; - } - - var ct = crawledDocument.contentType; - - if (acceptedContentTypes.contains(ct)) - return true; - - if (ct.contains(";")) { - return acceptedContentTypes.contains(ct.substring(0, ct.indexOf(';'))); - } - return false; - } - - private EdgeUrlState crawlerStatusToUrlState(String crawlerStatus, int httpStatus) { - return switch (CrawlerDocumentStatus.valueOf(crawlerStatus)) { - case OK -> httpStatus < 300 ? EdgeUrlState.OK : EdgeUrlState.DEAD; - case REDIRECT -> EdgeUrlState.REDIRECT; - default -> EdgeUrlState.DEAD; - }; - } - - private DetailsWithWords createDetails(CrawledDomain crawledDomain, CrawledDocument crawledDocument) - throws DisqualifiedException, URISyntaxException { - - String documentBody = crawledDocument.documentBody.decode(); - - if (languageFilter.isBlockedUnicodeRange(documentBody)) { - throw new DisqualifiedException(DisqualificationReason.LANGUAGE); - } - - Document doc = Jsoup.parse(documentBody); - - if (AcceptableAds.hasAcceptableAdsTag(doc)) { - // I've never encountered a website where this hasn't been a severe indicator - // of spam - - throw new DisqualifiedException(DisqualificationReason.ACCEPTABLE_ADS); - } - - if (doc.select("meta[name=robots]").attr("content").contains("noindex")) { - throw new DisqualifiedException(DisqualificationReason.FORBIDDEN); - } - - final EdgeUrl url = new EdgeUrl(crawledDocument.url); - - Document prunedDoc = doc.clone(); - - prunedDoc.getElementsByTag("svg").remove(); - prunedDoc.body().filter(new DomPruningFilter(0.5)); - - var dld = sentenceExtractor.extractSentences(prunedDoc); - - checkDocumentLanguage(dld); - - var ret = new ProcessedDocumentDetails(); - - ret.length = getLength(doc); - ret.standard = getHtmlStandard(doc); - ret.title = titleExtractor.getTitleAbbreviated(doc, dld, crawledDocument.url); - - ret.quality = documentValuator.getQuality(crawledDocument, ret.standard, doc, dld); - ret.hashCode = HashCode.fromString(crawledDocument.documentBodyHash).asLong(); - - KeywordMetadata keywordMetadata = new KeywordMetadata(); - - PubDate pubDate; - EdgePageWords words; - if (shouldDoSimpleProcessing(url, dld, ret)) { - /* Some documents we'll index, but only superficially. This is a compromise - to allow them to be discoverable, without having them show up without specific - queries. This also saves a lot of processing power. - */ - ret.features = Set.of(HtmlFeature.UNKNOWN); - words = keywordExtractor.extractKeywordsMinimal(dld, keywordMetadata); - ret.description = ""; - - pubDate = pubDateSniffer.getPubDate(crawledDocument.headers, url, doc, ret.standard, false); - - ret.metadata = new EdgePageDocumentsMetadata(url.depth(), pubDate.yearByte(), 0, (int) -ret.quality, EnumSet.of(EdgePageDocumentFlags.Simple)); - - } - else { - ret.features = featureExtractor.getFeatures(crawledDomain, doc, dld); - words = keywordExtractor.extractKeywords(dld, keywordMetadata); - ret.description = getDescription(doc); - - pubDate = pubDateSniffer.getPubDate(crawledDocument.headers, url, doc, ret.standard, true); - - ret.metadata = new EdgePageDocumentsMetadata(url.depth(), pubDate.yearByte(), 0, (int) -ret.quality, EnumSet.noneOf(EdgePageDocumentFlags.class)); - } - - addMetaWords(ret, url, pubDate, crawledDomain, words); - - getLinks(url, ret, doc, words); - - if (pubDate.hasYear()) { - ret.pubYear = pubDate.year(); - } - - return new DetailsWithWords(ret, words); - } - - private static final GuardedRegex mastodonFeedRegex = GuardedRegexFactory.startsWith("/@", "^/@[^/]+/?$"); - - private boolean shouldDoSimpleProcessing(EdgeUrl url, DocumentLanguageData dld, ProcessedDocumentDetails ret) { - if (ret.quality < minDocumentQuality) { - return true; - } - if (dld.totalNumWords() < minDocumentLength) { - return true; - } - // These pages shouldn't be publicly accessible - if ("phpinfo()".equals(ret.title)) { - return true; - } - - // Urls that look like /@foo are typically Mastodon or other twitter-like feeds, - // we don't want to index them because they change so rapidly; subdirectories are - // fine though - // - if (mastodonFeedRegex.test(url.path)) { - return true; - } - - // Annoying wordpress crap - if (url.path.startsWith("/tag/") && url.path.endsWith("/")) { - return true; - } - return false; - } - - private void addMetaWords(ProcessedDocumentDetails ret, EdgeUrl url, PubDate pubDate, CrawledDomain domain, EdgePageWords words) { - List tagWords = new ArrayList<>(); - - var edgeDomain = url.domain; - tagWords.add("format:"+ret.standard.toString().toLowerCase()); - - tagWords.add("site:" + edgeDomain.toString().toLowerCase()); - if (!Objects.equals(edgeDomain.toString(), edgeDomain.domain)) { - tagWords.add("site:" + edgeDomain.domain.toLowerCase()); - } - - tagWords.add("tld:" + edgeDomain.getTld()); - - tagWords.add("proto:"+url.proto.toLowerCase()); - tagWords.add("js:" + Boolean.toString(ret.features.contains(HtmlFeature.JS)).toLowerCase()); - - if (domain.ip != null) { - tagWords.add("ip:" + domain.ip.toLowerCase()); // lower case because IPv6 is hexadecimal - } - - ret.features.stream().map(HtmlFeature::getKeyword).forEach(tagWords::add); - - if (pubDate.year() > 1900) { - tagWords.add("year:" + pubDate.year()); - } - if (pubDate.dateIso8601() != null) { - tagWords.add("pub:" + pubDate.dateIso8601()); - } - - words.addAllSyntheticTerms(tagWords); - } - - private void getLinks(EdgeUrl baseUrl, ProcessedDocumentDetails ret, Document doc, EdgePageWords words) { - - final LinkProcessor lp = new LinkProcessor(ret, baseUrl); - - baseUrl = linkParser.getBaseLink(doc, baseUrl); - - EdgeDomain domain = baseUrl.domain; - - for (var atag : doc.getElementsByTag("a")) { - var linkOpt = linkParser.parseLinkPermissive(baseUrl, atag); - if (linkParser.shouldIndexLink(atag)) { - linkOpt.ifPresent(lp::accept); - } - else { - linkOpt - .filter(url -> linkParser.hasBinarySuffix(url.path.toLowerCase())) - .ifPresent(lp::acceptNonIndexable); - } - } - for (var frame : doc.getElementsByTag("frame")) { - linkParser.parseFrame(baseUrl, frame).ifPresent(lp::accept); - } - for (var frame : doc.getElementsByTag("iframe")) { - linkParser.parseFrame(baseUrl, frame).ifPresent(lp::accept); - } - for (var link : doc.select("link[rel=alternate]")) { - feedExtractor - .getFeedFromAlternateTag(baseUrl, link) - .ifPresent(lp::acceptFeed); - } - - createLinkKeywords(words, lp); - createFileLinkKeywords(words, lp, domain); - } - - private void createLinkKeywords(EdgePageWords words, LinkProcessor lp) { - final Set linkTerms = new HashSet<>(); - - for (var fd : lp.getForeignDomains()) { - linkTerms.add("links:"+fd.toString().toLowerCase()); - linkTerms.add("links:"+fd.getDomain().toLowerCase()); - } - words.addAllSyntheticTerms(linkTerms); - } - - private void createFileLinkKeywords(EdgePageWords words, LinkProcessor lp, EdgeDomain domain) { - Set fileKeywords = new HashSet<>(100); - for (var link : lp.getNonIndexableUrls()) { - - if (!domain.hasSameTopDomain(link.domain)) { - continue; - } - - synthesizeFilenameKeyword(fileKeywords, link); - - } - - words.addAllSyntheticTerms(fileKeywords); - } - - private void synthesizeFilenameKeyword(Set fileKeywords, EdgeUrl link) { - - Path pFilename = Path.of(link.path.toLowerCase()).getFileName(); - - if (pFilename == null) return; - - String filename = pFilename.toString(); - if (filename.length() > 32 - || filename.endsWith(".xml") - || filename.endsWith(".jpg") - || filename.endsWith(".png") - || filename.endsWith(".pdf") - || filename.endsWith(".gif")) - return; - - fileKeywords.add(filename.replace(' ', '_')); - } - - private void checkDocumentLanguage(DocumentLanguageData dld) throws DisqualifiedException { - double languageAgreement = languageFilter.dictionaryAgreement(dld); - if (languageAgreement < 0.1) { - throw new DisqualifiedException(DisqualificationReason.LANGUAGE); - } - } - - private EdgeHtmlStandard getHtmlStandard(Document doc) { - EdgeHtmlStandard htmlStandard = HtmlStandardExtractor.parseDocType(doc.documentType()); - - if (UNKNOWN.equals(htmlStandard)) { - return HtmlStandardExtractor.sniffHtmlStandard(doc); - } - return htmlStandard; - } - - private String getDescription(Document doc) { - return summaryExtractor.extractSummary(doc); - } - - private int getLength(Document doc) { - return doc.text().length(); - } - - private record DetailsWithWords(ProcessedDocumentDetails details, - EdgePageWords words) {} - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateEffortLevel.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateEffortLevel.java deleted file mode 100644 index b146c0d0..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/PubDateEffortLevel.java +++ /dev/null @@ -1,6 +0,0 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate; - -public enum PubDateEffortLevel { - LOW, - HIGH -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicGuessFromHtmlStandard.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicGuessFromHtmlStandard.java deleted file mode 100644 index e3d0e556..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/converting/processor/logic/pubdate/heuristic/PubDateHeuristicGuessFromHtmlStandard.java +++ /dev/null @@ -1,23 +0,0 @@ -package nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.heuristic; - -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDate; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateEffortLevel; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateHeuristic; -import nu.marginalia.wmsa.edge.converting.processor.logic.pubdate.PubDateParser; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeHtmlStandard; -import org.jsoup.nodes.Document; - -import java.util.Optional; - -public class PubDateHeuristicGuessFromHtmlStandard implements PubDateHeuristic { - - @Override - public Optional apply(PubDateEffortLevel effortLevel, String headers, EdgeUrl url, Document document, EdgeHtmlStandard htmlStandard) { - if (htmlStandard == EdgeHtmlStandard.UNKNOWN) - return Optional.empty(); - - return Optional.of(new PubDate(null, PubDateParser.guessYear(htmlStandard))); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerTestMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerTestMain.java deleted file mode 100644 index b26f501a..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/crawling/CrawlerTestMain.java +++ /dev/null @@ -1,116 +0,0 @@ -package nu.marginalia.wmsa.edge.crawling; - -import io.github.bucket4j.Bandwidth; -import io.github.bucket4j.Bucket; -import io.github.bucket4j.Refill; -import spark.Request; -import spark.Response; -import spark.Spark; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -public class CrawlerTestMain { - - static Bucket rateLimiter60RPM; - static List successfullyFetched = new ArrayList<>(); - - public static void main(String... args) { - var refill = Refill.greedy(1, Duration.ofSeconds(1)); - - var bw = Bandwidth.classic(10, refill); - rateLimiter60RPM = Bucket.builder().addLimit(bw).build(); - - Spark.port(8080); - Spark.before(CrawlerTestMain::before); - Spark.after(CrawlerTestMain::after); - Spark.get("/rate-limit/", CrawlerTestMain::index); - Spark.get("/rate-limit/:n", CrawlerTestMain::n); - - Spark.before("/rate-limit/:n", CrawlerTestMain::rateLimitRequest); - Spark.before("/intermittent-error/:n", CrawlerTestMain::simulateRandomTimeouts); - - Spark.get("/intermittent-error/", CrawlerTestMain::index); - Spark.get("/intermittent-error/:n", CrawlerTestMain::n); - - } - - private static void rateLimitRequest(Request request, Response response) { - if (!rateLimiter60RPM.tryConsume(1)) { - Spark.halt(429); - } - } - - private static void simulateRandomTimeouts(Request request, Response response) { - if (Math.random() < 0.25) { - System.out.println("Simulating error"); - Spark.halt(503); - } - } - - public static void before(Request request, Response response) { - System.out.println(request.pathInfo()); - successfullyFetched.add(request.pathInfo()); - } - public static void after(Request request, Response response) { - if (response.status() < 300) { - successfullyFetched.add(request.pathInfo()); - } - } - - private static Object n(Request request, Response response) { - - int num = Integer.parseInt(request.params("n")); - return """ - - - Index - -

Index

- """ + - String.format("Next, Next 2", num+1, num+2) - - + - """ - -

- Goddess, sing me the anger, of Achilles, Peleus’ son, that fatal anger that brought countless - sorrows on the Greeks, and sent many valiant souls of warriors down to Hades, leaving their - bodies as spoil for dogs and carrion birds: for thus was the will of Zeus brought to fulfilment. - - Sing of it from the moment when Agamemnon, Atreus’ son, that king of men, parted in wrath from noble Achilles. - Which of the gods set these two to quarrel? Apollo, the son of Leto and Zeus, angered by the king, brought an - evil plague on the army, so that the men were dying, for the son of Atreus had dishonoured Chryses the priest. - He it was who came to the swift Achaean ships, to free his daughter, bringing a wealth of ransom, carrying a - golden staff adorned with the ribbons of far-striking Apollo, and called out to the Achaeans, above all to the - two leaders of armies, those sons of Atreus: ‘Atreides, and all you bronze-greaved Achaeans, may the gods who - live on Olympus grant you to sack Priam’s city, and sail back home in safety; but take this ransom, and free - my darling child; show reverence for Zeus’s son, far-striking Apollo.’ - """; - } - - private static Object index(Request request, Response response) { - return """ - - - Index - -

Index

- Next -

- Goddess, sing me the anger, of Achilles, Peleus’ son, that fatal anger that brought countless - sorrows on the Greeks, and sent many valiant souls of warriors down to Hades, leaving their - bodies as spoil for dogs and carrion birds: for thus was the will of Zeus brought to fulfilment. - - Sing of it from the moment when Agamemnon, Atreus’ son, that king of men, parted in wrath from noble Achilles. - Which of the gods set these two to quarrel? Apollo, the son of Leto and Zeus, angered by the king, brought an - evil plague on the army, so that the men were dying, for the son of Atreus had dishonoured Chryses the priest. - He it was who came to the swift Achaean ships, to free his daughter, bringing a wealth of ransom, carrying a - golden staff adorned with the ribbons of far-striking Apollo, and called out to the Achaeans, above all to the - two leaders of armies, those sons of Atreus: ‘Atreides, and all you bronze-greaved Achaeans, may the gods who - live on Olympus grant you to sack Priam’s city, and sail back home in safety; but take this ransom, and free - my darling child; show reverence for Zeus’s son, far-striking Apollo.’ - """; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDataStoreDao.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDataStoreDao.java deleted file mode 100644 index 47053837..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dbcommon/EdgeDataStoreDao.java +++ /dev/null @@ -1,31 +0,0 @@ -package nu.marginalia.wmsa.edge.dbcommon; - -import com.google.inject.ImplementedBy; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeId; -import nu.marginalia.wmsa.edge.model.id.EdgeIdCollection; -import nu.marginalia.wmsa.edge.model.search.EdgeUrlDetails; -import nu.marginalia.wmsa.edge.search.model.BrowseResult; - -import java.util.List; -import java.util.Optional; - -@ImplementedBy(EdgeDataStoreDaoImpl.class) -public interface EdgeDataStoreDao { - EdgeId getDomainId(EdgeDomain domain); - - List getDomainNeighborsAdjacentCosine(EdgeId domainId, EdgeDomainBlacklist blacklist, int count); - - List getDomainNeighborsAdjacent(EdgeId domainId, EdgeDomainBlacklist backlist, int count); - - List getRandomDomains(int count, EdgeDomainBlacklist backlist, int set); - - List getBrowseResultFromUrlIds(EdgeIdCollection urlId); - - List getUrlDetailsMulti(EdgeIdCollection ids); - - Optional getDomain(EdgeId id); - - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexControl.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexControl.java deleted file mode 100644 index 87f65926..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexControl.java +++ /dev/null @@ -1,30 +0,0 @@ -package nu.marginalia.wmsa.edge.index; - - -import com.google.inject.Inject; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexSearchSetsService; - -import java.io.IOException; - - -public class EdgeIndexControl { - - private final IndexServicesFactory servicesFactory; - private final EdgeIndexSearchSetsService searchSetsService; - - @Inject - public EdgeIndexControl(IndexServicesFactory servicesFactory, EdgeIndexSearchSetsService searchSetsService) { - this.servicesFactory = servicesFactory; - this.searchSetsService = searchSetsService; - } - - public void regenerateIndex() throws IOException { - servicesFactory.convertIndex(searchSetsService.getDomainRankings()); - - System.gc(); - } - - public void switchIndexFiles() throws Exception { - servicesFactory.switchFilesJob().call(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexMain.java deleted file mode 100644 index 65dde030..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexMain.java +++ /dev/null @@ -1,34 +0,0 @@ -package nu.marginalia.wmsa.edge.index; - -import com.google.inject.Guice; -import com.google.inject.Inject; -import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.configuration.server.Initialization; - -public class EdgeIndexMain extends MainClass { - private final EdgeIndexService service; - - @Inject - public EdgeIndexMain(EdgeIndexService service) { - this.service = service; - } - - public static void main(String... args) { - init(ServiceDescriptor.EDGE_INDEX, args); - - Injector injector = Guice.createInjector( - new EdgeIndexTablesModule(), - new EdgeIndexModule(), - new DatabaseModule(), - new ConfigurationModule() - ); - - injector.getInstance(EdgeIndexMain.class); - injector.getInstance(Initialization.class).setReady(); - - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexModule.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexModule.java deleted file mode 100644 index 99a1e3f4..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexModule.java +++ /dev/null @@ -1,23 +0,0 @@ -package nu.marginalia.wmsa.edge.index; - -import com.google.inject.AbstractModule; -import com.google.inject.Provides; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.index.config.RankingSettings; - -import java.nio.file.Path; - -public class EdgeIndexModule extends AbstractModule { - - - - public void configure() { - } - - @Provides - public RankingSettings rankingSettings() { - Path dir = WmsaHome.getHomePath().resolve("conf/ranking-settings.yaml"); - return RankingSettings.from(dir); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexService.java deleted file mode 100644 index 6cb5ba36..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexService.java +++ /dev/null @@ -1,84 +0,0 @@ -package nu.marginalia.wmsa.edge.index; - -import com.google.gson.Gson; -import com.google.inject.Inject; -import com.google.inject.name.Named; -import io.reactivex.rxjava3.schedulers.Schedulers; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; -import nu.marginalia.wmsa.edge.index.postings.SearchIndexControl; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexDomainQueryService; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexLexiconService; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexOpsService; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexQueryService; -import org.jetbrains.annotations.NotNull; -import spark.Request; -import spark.Response; -import spark.Spark; - -import java.util.concurrent.TimeUnit; - -import static spark.Spark.get; - -public class EdgeIndexService extends Service { - - @NotNull - private final Initialization init; - private final SearchIndexControl indexes; - - - @Inject - public EdgeIndexService(@Named("service-host") String ip, - @Named("service-port") Integer port, - Initialization init, - MetricsServer metricsServer, - SearchIndexControl indexes, - - EdgeIndexOpsService opsService, - EdgeIndexLexiconService lexiconService, - EdgeIndexQueryService indexQueryService, - EdgeIndexDomainQueryService domainQueryService - ) - { - super(ip, port, init, metricsServer); - - final Gson gson = GsonFactory.get(); - - this.init = init; - this.indexes = indexes; - - Spark.post("/words/", lexiconService::putWords); - - Spark.post("/search/", indexQueryService::search, gson::toJson); - Spark.post("/search-domain/", domainQueryService::searchDomain, gson::toJson); - - Spark.get("/dictionary/*", lexiconService::getWordId, gson::toJson); - - Spark.post("/ops/repartition", opsService::repartitionEndpoint); - Spark.post("/ops/reindex", opsService::reindexEndpoint); - - get("/is-blocked", this::isBlocked, gson::toJson); - - Schedulers.newThread().scheduleDirect(this::initialize, 1, TimeUnit.MICROSECONDS); - } - - private Object isBlocked(Request request, Response response) { - return indexes.isBusy() || !initialized; - } - - volatile boolean initialized = false; - public void initialize() { - if (!initialized) { - init.waitReady(); - initialized = true; - indexes.initialize(init); - } - - } - - -} - - diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexTablesModule.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexTablesModule.java deleted file mode 100644 index 93014e4c..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/EdgeIndexTablesModule.java +++ /dev/null @@ -1,29 +0,0 @@ -package nu.marginalia.wmsa.edge.index; - -import com.google.inject.AbstractModule; -import com.google.inject.name.Names; -import nu.marginalia.wmsa.configuration.WmsaHome; - -import java.nio.file.Path; - -public class EdgeIndexTablesModule extends AbstractModule { - - public void configure() { - bind(Path.class).annotatedWith(Names.named("partition-root-slow")).toInstance(WmsaHome.getDisk("index-write")); - bind(Path.class).annotatedWith(Names.named("partition-root-fast")).toInstance(WmsaHome.getDisk("index-read")); - - bind(Path.class).annotatedWith(Names.named("partition-root-slow-tmp")).toInstance(WmsaHome.getDisk("tmp-slow")); - bind(Path.class).annotatedWith(Names.named("tmp-file-dir")).toInstance(WmsaHome.getDisk("tmp-fast")); - - bind(String.class).annotatedWith(Names.named("edge-writer-page-index-file")).toInstance("page-index.dat"); - bind(String.class).annotatedWith(Names.named("edge-writer-dictionary-file")).toInstance("dictionary.dat"); - - bind(String.class).annotatedWith(Names.named("edge-index-write-words-file")).toInstance("words.dat.wip"); - bind(String.class).annotatedWith(Names.named("edge-index-write-urls-file")).toInstance("urls.dat.wip"); - - bind(String.class).annotatedWith(Names.named("edge-index-read-words-file")).toInstance("words.dat"); - bind(String.class).annotatedWith(Names.named("edge-index-read-urls-file")).toInstance("urls.dat"); - - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexClient.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexClient.java deleted file mode 100644 index 32094fd9..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexClient.java +++ /dev/null @@ -1,87 +0,0 @@ -package nu.marginalia.wmsa.edge.index.client; - -import com.google.inject.Singleton; -import io.prometheus.client.Summary; -import io.reactivex.rxjava3.core.Observable; -import io.reactivex.rxjava3.schedulers.Schedulers; -import nu.marginalia.wmsa.client.AbstractDynamicClient; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeId; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultItem; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultSet; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSpecification; -import nu.marginalia.wmsa.edge.model.search.domain.EdgeDomainSearchResults; -import nu.marginalia.wmsa.edge.model.search.domain.EdgeDomainSearchSpecification; -import nu.wmsa.wmsa.edge.index.proto.IndexPutKeywordsReq; - -import javax.annotation.CheckReturnValue; -import java.util.List; -import java.util.concurrent.TimeUnit; - -@Singleton -public class EdgeIndexClient extends AbstractDynamicClient implements EdgeIndexWriterClient { - - private static final Summary wmsa_search_index_api_time = Summary.build().name("wmsa_search_index_api_time").help("-").register(); - - public EdgeIndexClient() { - super(ServiceDescriptor.EDGE_INDEX); - setTimeout(30); - } - - @Override - public void putWords(Context ctx, EdgeId domain, EdgeId url, - EdgePageDocumentsMetadata metadata, - DocumentKeywords wordSet, int writer - ) - { - - var keywordBuilder = - IndexPutKeywordsReq.newBuilder() - .setDomain(domain.id()) - .setUrl(url.id()) - .setMetadata(metadata.encode()) - .setIndex(writer); - - var wordSetBuilder = IndexPutKeywordsReq.WordSet.newBuilder(); - wordSetBuilder.addAllWords(List.of(wordSet.keywords())); - for (var meta : wordSet.metadata()) { - wordSetBuilder.addMeta(meta); - } - keywordBuilder.addWordSet(wordSetBuilder.build()); - - var req = keywordBuilder.build(); - - this.post(ctx, "/words/", req).blockingSubscribe(); - } - - - @CheckReturnValue - public List query(Context ctx, EdgeSearchSpecification specs) { - return wmsa_search_index_api_time.time( - () -> this.postGet(ctx, "/search/", specs, EdgeSearchResultSet.class).blockingFirst().getResults() - ); - } - - @CheckReturnValue - public List queryDomains(Context ctx, List specs) { - return Observable.fromIterable(specs) - .concatMap(s -> postGet(ctx, "/search-domain/", s, EdgeDomainSearchResults.class) - .subscribeOn(Schedulers.io()) - .timeout(1, TimeUnit.SECONDS) - .onErrorComplete()) - .toList() - .blockingGet(); - } - - - @CheckReturnValue - public Observable isBlocked(Context ctx) { - return super.get(ctx, "/is-blocked", Boolean.class); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexLocalService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexLocalService.java deleted file mode 100644 index 585c9a14..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexLocalService.java +++ /dev/null @@ -1,88 +0,0 @@ -package nu.marginalia.wmsa.edge.index.client; - -import com.google.inject.Inject; -import com.google.inject.Singleton; -import com.google.inject.name.Named; -import nu.marginalia.util.dict.OffHeapDictionaryHashMap; -import nu.marginalia.util.dict.DictionaryMap; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.KeywordListChunker; -import nu.marginalia.wmsa.edge.index.lexicon.KeywordLexicon; -import nu.marginalia.wmsa.edge.index.lexicon.journal.KeywordLexiconJournal; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntryHeader; -import nu.marginalia.wmsa.edge.index.postings.journal.writer.SearchIndexJournalWriterImpl; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; - -@Singleton -public class EdgeIndexLocalService implements EdgeIndexWriterClient { - - private final KeywordLexicon lexicon; - private final SearchIndexJournalWriterImpl indexWriter; - private static final Logger logger = LoggerFactory.getLogger(EdgeIndexLocalService.class); - - @Inject - public EdgeIndexLocalService(@Named("local-index-path") Path path) throws IOException { - - var lexiconJournal = new KeywordLexiconJournal(path.resolve("dictionary.dat").toFile()); - lexicon = new KeywordLexicon(lexiconJournal, DictionaryMap.create()); - indexWriter = new SearchIndexJournalWriterImpl(lexicon, path.resolve("index.dat").toFile()); - } - - public void putWords(Context ctx, EdgeId domain, EdgeId url, - EdgePageDocumentsMetadata metadata, - DocumentKeywords wordSet, int writer) { - if (wordSet.keywords().length == 0) - return; - - if (domain.id() <= 0 || url.id() <= 0) { - logger.warn("Bad ID: {}:{}", domain, url); - return; - } - - for (var chunk : KeywordListChunker.chopList(wordSet, SearchIndexJournalEntry.MAX_LENGTH)) { - - var entry = new SearchIndexJournalEntry(getOrInsertWordIds(chunk.keywords(), chunk.metadata())); - var header = new SearchIndexJournalEntryHeader(domain, url, metadata.encode()); - - indexWriter.put(header, entry); - } - - } - - private long[] getOrInsertWordIds(String[] words, long[] meta) { - long[] ids = new long[words.length*2]; - int putIdx = 0; - - for (int i = 0; i < words.length; i++) { - String word = words[i]; - - long id = lexicon.getOrInsert(word); - if (id != OffHeapDictionaryHashMap.NO_VALUE) { - ids[putIdx++] = id; - ids[putIdx++] = meta[i]; - } - } - - if (putIdx != words.length*2) { - ids = Arrays.copyOf(ids, putIdx); - } - return ids; - } - - @Override - public void close() throws Exception { - indexWriter.close(); - lexicon.close(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexWriterClient.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexWriterClient.java deleted file mode 100644 index ff405e7a..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/client/EdgeIndexWriterClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package nu.marginalia.wmsa.edge.index.client; - -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeId; - -public interface EdgeIndexWriterClient extends AutoCloseable { - - void putWords(Context ctx, EdgeId domain, EdgeId url, EdgePageDocumentsMetadata metadata, - DocumentKeywords wordSets, int writer); -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/config/RankingSettingsEntry.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/config/RankingSettingsEntry.java deleted file mode 100644 index f6aa501f..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/config/RankingSettingsEntry.java +++ /dev/null @@ -1,8 +0,0 @@ -package nu.marginalia.wmsa.edge.index.config; - -import java.util.List; - -public class RankingSettingsEntry { - public List domains; - public int max; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/IndexMetadataService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/IndexMetadataService.java deleted file mode 100644 index 6a359d0b..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/IndexMetadataService.java +++ /dev/null @@ -1,22 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings; - -public class IndexMetadataService { - private final SearchIndexControl indexes; - - public IndexMetadataService(SearchIndexControl indexes) { - this.indexes = indexes; - } - - public long getDocumentMetadata(long urlId) { - return indexes.getIndex().getDocumentMetadata(urlId); - } - - public int getDomainId(long urlId) { - return indexes.getIndex().getDomainId(urlId); - } - - public long[] getTermMetadata(int termId, long[] docIdsAll) { - return indexes.getIndex().getTermMetadata(termId, docIdsAll); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndexControl.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndexControl.java deleted file mode 100644 index 42e7e32f..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndexControl.java +++ /dev/null @@ -1,77 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings; - -import com.google.inject.Inject; -import com.google.inject.Singleton; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.edge.index.IndexServicesFactory; -import nu.marginalia.wmsa.edge.index.lexicon.KeywordLexiconReadOnlyView; -import nu.marginalia.wmsa.edge.index.postings.journal.writer.SearchIndexJournalWriterImpl; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexSearchSetsService; -import nu.marginalia.wmsa.edge.index.svc.EdgeOpsLockService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; - -@Singleton -public class SearchIndexControl { - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private final IndexServicesFactory servicesFactory; - private final SearchIndexJournalWriterImpl primaryIndexWriter; - private final SearchIndexJournalWriterImpl secondaryIndexWriter; - private volatile KeywordLexiconReadOnlyView keywordLexiconReadOnlyView; - - private final SearchIndex index; - private final EdgeOpsLockService opsLockService; - - @Inject - public SearchIndexControl(IndexServicesFactory servicesFactory, - EdgeOpsLockService opsLockService, - EdgeIndexSearchSetsService searchSetsService) { - this.servicesFactory = servicesFactory; - - this.primaryIndexWriter = servicesFactory.getIndexWriter(0); - this.secondaryIndexWriter = servicesFactory.getIndexWriter(1); - - index = servicesFactory.createIndexBucket(searchSetsService); - this.opsLockService = opsLockService; - } - - public boolean reindex() throws Exception { - return opsLockService.run(index::switchIndex).isPresent(); - } - - public boolean isBusy() { - return opsLockService.isLocked(); - } - - @Nullable - public KeywordLexiconReadOnlyView getLexiconReader() { - return keywordLexiconReadOnlyView; - } - - public void initialize(Initialization init) { - - logger.info("Waiting for init"); - init.waitReady(); - - if (!opsLockService.run(index::init)) throw new IllegalStateException("Failed to initialize " + getClass().getSimpleName()); - keywordLexiconReadOnlyView = servicesFactory.getDictionaryReader(); - } - - public SearchIndexJournalWriterImpl getIndexWriter(int idx) { - if (idx == 0) { - return primaryIndexWriter; - } - else { - return secondaryIndexWriter; - } - } - - public SearchIndex getIndex() { - return index; - } - - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalEntryHeader.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalEntryHeader.java deleted file mode 100644 index b5c4d554..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalEntryHeader.java +++ /dev/null @@ -1,22 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.model; - -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeId; - -public record SearchIndexJournalEntryHeader(int entrySize, long documentId, long documentMeta) { - - public static final int HEADER_SIZE_LONGS = 3; - - public SearchIndexJournalEntryHeader( EdgeId domainId, EdgeId urlId, long documentMeta) { - this(-1, combineIds(domainId, urlId), documentMeta); - } - - private static long combineIds(EdgeId domainId, EdgeId urlId) { - long did = domainId.id(); - long uid = urlId.id(); - - return (did << 32L) | uid; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalFileHeader.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalFileHeader.java deleted file mode 100644 index 4b65505c..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalFileHeader.java +++ /dev/null @@ -1,4 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.model; - -public record SearchIndexJournalFileHeader(long fileSize, long wordCount) { -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalStatistics.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalStatistics.java deleted file mode 100644 index 8b827174..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/model/SearchIndexJournalStatistics.java +++ /dev/null @@ -1,3 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.model; - -public record SearchIndexJournalStatistics(int highestWord, int documentCardinality) { } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalCleaner.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalCleaner.java deleted file mode 100644 index 8e685a2a..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalCleaner.java +++ /dev/null @@ -1,71 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.reader; - -import nu.marginalia.util.array.LongArray; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.LongBuffer; -import java.nio.file.Path; -import java.util.function.Predicate; - -public class SearchIndexJournalCleaner { - private final SearchIndexJournalReader reader; - - public SearchIndexJournalCleaner(SearchIndexJournalReader reader) { - this.reader = reader; - } - - private long dryRunForNewSize(Predicate entryPredicate) { - long pos = SearchIndexJournalReader.FILE_HEADER_SIZE_LONGS; - - var pt = new ProgressTracker(); - - for (var entry : reader) { - if (entryPredicate.test(entry)) { - pos += entry.totalEntrySizeLongs(); - pt.update(pos); - } - } - - return pos; - } - - public void clean(Path outFile, Predicate entryPredicate) throws IOException { - - System.out.println("Dry run"); - long size = dryRunForNewSize(entryPredicate); - - System.out.println("Copying"); - LongArray outputArray = LongArray.mmapForWriting(outFile, size); - - long pos = SearchIndexJournalReader.FILE_HEADER_SIZE_LONGS; - var pt = new ProgressTracker(); - - LongBuffer adequateBuffer = ByteBuffer.allocateDirect(100*1024*1024).asLongBuffer(); - - for (var entry : reader) { - if (entryPredicate.test(entry)) { - pos += entry.copyTo(pos, adequateBuffer, outputArray); - pt.update(pos); - } - } - - outputArray.set(0, pos*8); - outputArray.set(1, reader.fileHeader().wordCount()); - - outputArray.force(); - } -} - -class ProgressTracker { - long stepSize = 100*1024*1024; - long pos = 0; - - public void update(long pos) { - if (this.pos / stepSize != pos / stepSize) { - System.out.printf("%d Mb\n", (800*pos)/stepSize); - } - this.pos = pos; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReadEntry.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReadEntry.java deleted file mode 100644 index 97cd9e98..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReadEntry.java +++ /dev/null @@ -1,98 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.reader; - -import nu.marginalia.util.array.LongArray; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntryHeader; - -import java.nio.ByteBuffer; -import java.nio.LongBuffer; - -public class SearchIndexJournalReadEntry { - private final long offset; - public final SearchIndexJournalEntryHeader header; - private final LongArray map; - private final long committedSize; - - SearchIndexJournalReadEntry(long offset, LongArray map, long committedSize) { - this.map = map; - this.committedSize = committedSize; - - final long sizeBlock = this.map.get(offset); - final long docId = this.map.get(offset + 1); - final long meta = this.map.get(offset + 2); - - this.offset = offset; - this.header = new SearchIndexJournalEntryHeader( - (int) (sizeBlock >>> 32L), - docId, - meta); - } - - public boolean hasNext() { - return nextId() < committedSize; - } - - public long docId() { - return header.documentId(); - } - - public long docMeta() { - return header.documentMeta(); - } - - public int domainId() { - return (int) (docId() >>> 32L); - } - - public int urlId() { - return (int) (docId() & 0xFFFF_FFFFL); - } - - public int wordCount() { - return header.entrySize() / SearchIndexJournalEntry.ENTRY_SIZE; - } - - public SearchIndexJournalEntry readEntry() { - long[] dest = new long[header.entrySize()]; - - long offsetStart = offset + SearchIndexJournalEntryHeader.HEADER_SIZE_LONGS; - long offsetEnd = offsetStart + header.entrySize(); - - map.get(offsetStart, offsetEnd, dest); - - return new SearchIndexJournalEntry(header.entrySize(), dest); - } - - public SearchIndexJournalEntry readEntryUsingBuffer(long[] dest) { - if (dest.length >= header.entrySize()) { - long offsetStart = offset + SearchIndexJournalEntryHeader.HEADER_SIZE_LONGS; - long offsetEnd = offsetStart + header.entrySize(); - - map.get(offsetStart, offsetEnd, dest); - return new SearchIndexJournalEntry(header.entrySize(), dest); - } else { - return readEntry(); - } - } - - public long nextId() { - return offset + totalEntrySizeLongs(); - } - - public SearchIndexJournalReadEntry next() { - return new SearchIndexJournalReadEntry(nextId(), map, committedSize); - } - - public long copyTo(long pos, LongBuffer adequateBuffer, LongArray destArray) { - long size = totalEntrySizeLongs(); - - map.get(offset, offset + size, adequateBuffer, 0); - destArray.set(pos, pos + size, adequateBuffer, 0); - - return size; - } - - public long totalEntrySizeLongs() { - return header.entrySize() + SearchIndexJournalEntryHeader.HEADER_SIZE_LONGS; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReader.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReader.java deleted file mode 100644 index a8751f85..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReader.java +++ /dev/null @@ -1,52 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.reader; - -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalFileHeader; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalStatistics; -import org.jetbrains.annotations.NotNull; - -import java.util.Iterator; -import java.util.function.IntConsumer; - -public interface SearchIndexJournalReader extends Iterable { - long FILE_HEADER_SIZE_LONGS = 2; - long FILE_HEADER_SIZE_BYTES = 8 * FILE_HEADER_SIZE_LONGS; - - default long[] createAdequateTempBuffer() { - return new long[SearchIndexJournalEntry.MAX_LENGTH * SearchIndexJournalEntry.ENTRY_SIZE]; - } - - SearchIndexJournalFileHeader fileHeader(); - - SearchIndexJournalStatistics getStatistics(); - - void forEachWordId(IntConsumer consumer); - - void forEachUrlIdWordId(BiIntConsumer consumer); - - void forEachDocIdWordId(LongIntConsumer consumer); - - void forEachDocIdRecord(LongObjectConsumer consumer); - - void forEachUrlId(IntConsumer consumer); - - @NotNull - @Override - Iterator iterator(); - - interface BiIntConsumer { - void accept(int left, int right); - } - - interface LongIntConsumer { - void accept(long left, int right); - } - - interface LongObjectConsumer { - void accept(long left, T right); - } - - interface IntObjectConsumer { - void accept(int left, T right); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReaderSingleFile.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReaderSingleFile.java deleted file mode 100644 index 228d9a07..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/reader/SearchIndexJournalReaderSingleFile.java +++ /dev/null @@ -1,180 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.reader; - -import com.upserve.uppend.blobs.NativeIO; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalFileHeader; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalStatistics; -import org.jetbrains.annotations.NotNull; -import org.roaringbitmap.longlong.Roaring64Bitmap; - -import java.io.IOException; -import java.util.Iterator; -import java.util.function.IntConsumer; -import java.util.function.Predicate; - -public class SearchIndexJournalReaderSingleFile implements SearchIndexJournalReader { - - public final SearchIndexJournalFileHeader fileHeader; - - private final LongArray map; - private final long committedSize; - - final Predicate entryPredicate; - final Predicate recordPredicate; - - public SearchIndexJournalReaderSingleFile(LongArray map) throws IOException { - fileHeader = new SearchIndexJournalFileHeader(map.get(0), map.get(1)); - committedSize = map.get(0) / 8 - FILE_HEADER_SIZE_LONGS; - - map.advice(NativeIO.Advice.Sequential); - - this.map = map.shifted(FILE_HEADER_SIZE_LONGS); - this.recordPredicate = null; - this.entryPredicate = null; - } - - public SearchIndexJournalReaderSingleFile(LongArray map, Predicate entryPredicate, Predicate recordPredicate) throws IOException { - fileHeader = new SearchIndexJournalFileHeader(map.get(0), map.get(1)); - committedSize = map.get(0) / 8 - FILE_HEADER_SIZE_LONGS; - - map.advice(NativeIO.Advice.Sequential); - - this.map = map.shifted(FILE_HEADER_SIZE_LONGS); - - this.recordPredicate = recordPredicate; - this.entryPredicate = entryPredicate; - } - - public SearchIndexJournalFileHeader fileHeader() { - return fileHeader; - } - - public boolean filter(SearchIndexJournalReadEntry entry) { - return entryPredicate == null || entryPredicate.test(entry); - } - - public boolean filter(SearchIndexJournalReadEntry entry, SearchIndexJournalEntry.Record record) { - return (entryPredicate == null || entryPredicate.test(entry)) - && (recordPredicate == null || recordPredicate.test(record)); - } - - @Override - public SearchIndexJournalStatistics getStatistics() { - int highestWord = 0; - final long[] tmpWordsBuffer = createAdequateTempBuffer(); - - // Docs cardinality is a candidate for a HyperLogLog - Roaring64Bitmap docsBitmap = new Roaring64Bitmap(); - - for (var entry : this) { - var entryData = entry.readEntryUsingBuffer(tmpWordsBuffer); - - if (filter(entry)) { - docsBitmap.addLong(entry.docId() & 0x0000_0000_FFFF_FFFFL); - - for (var item : entryData) { - if (filter(entry, item)) { - highestWord = Integer.max(item.wordId(), highestWord); - } - } - } - } - - return new SearchIndexJournalStatistics(highestWord, docsBitmap.getIntCardinality()); - } - - @Override - public void forEachWordId(IntConsumer consumer) { - final long[] tmpWordsBuffer = createAdequateTempBuffer(); - for (var entry : this) { - var data = entry.readEntryUsingBuffer(tmpWordsBuffer); - for (var post : data) { - if (filter(entry, post)) { - consumer.accept(post.wordId()); - } - } - } - } - - @Override - public void forEachUrlIdWordId(BiIntConsumer consumer) { - final long[] tmpWordsBuffer = createAdequateTempBuffer(); - for (var entry : this) { - var data = entry.readEntryUsingBuffer(tmpWordsBuffer); - - for (var post : data) { - if (filter(entry, post)) { - consumer.accept(entry.urlId(), post.wordId()); - } - } - } - } - - @Override - public void forEachDocIdWordId(LongIntConsumer consumer) { - final long[] tmpWordsBuffer = createAdequateTempBuffer(); - for (var entry : this) { - var data = entry.readEntryUsingBuffer(tmpWordsBuffer); - - for (var post : data) { - if (filter(entry, post)) { - consumer.accept(entry.docId(), post.wordId()); - } - } - } - } - - @Override - public void forEachDocIdRecord(LongObjectConsumer consumer) { - final long[] tmpWordsBuffer = createAdequateTempBuffer(); - for (var entry : this) { - var data = entry.readEntryUsingBuffer(tmpWordsBuffer); - - for (var post : data) { - if (filter(entry, post)) { - consumer.accept(entry.docId(), post); - } - } - } - } - @Override - public void forEachUrlId(IntConsumer consumer) { - for (var entry : this) { - if (filter(entry)) { - consumer.accept(entry.urlId()); - } - } - } - - @NotNull - @Override - public Iterator iterator() { - return new JournalEntryIterator(); - } - - private class JournalEntryIterator implements Iterator { - private SearchIndexJournalReadEntry entry; - - @Override - public boolean hasNext() { - if (entry == null) { - return committedSize > 0; - } - - return entry.hasNext(); - } - - @Override - public SearchIndexJournalReadEntry next() { - if (entry == null) { - entry = new SearchIndexJournalReadEntry(0, map, committedSize); - } - else { - entry = entry.next(); - } - return entry; - } - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/writer/SearchIndexJournalWriter.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/writer/SearchIndexJournalWriter.java deleted file mode 100644 index 7d765006..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/writer/SearchIndexJournalWriter.java +++ /dev/null @@ -1,13 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.writer; - -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntryHeader; - -public interface SearchIndexJournalWriter { - void put(SearchIndexJournalEntryHeader header, SearchIndexJournalEntry entry); - - void forceWrite(); - - void flushWords(); - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/writer/SearchIndexJournalWriterImpl.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/writer/SearchIndexJournalWriterImpl.java deleted file mode 100644 index b57a1ea1..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/journal/writer/SearchIndexJournalWriterImpl.java +++ /dev/null @@ -1,134 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.journal.writer; - -import io.reactivex.rxjava3.disposables.Disposable; -import io.reactivex.rxjava3.schedulers.Schedulers; -import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.index.lexicon.KeywordLexicon; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntryHeader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.EOFException; -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -public class SearchIndexJournalWriterImpl implements SearchIndexJournalWriter { - private final KeywordLexicon lexicon; - - private final Logger logger = LoggerFactory.getLogger(getClass()); - private final Disposable writerTask; - private RandomAccessFile raf; - private FileChannel channel; - - public static final int MAX_BLOCK_SIZE = SearchIndexJournalEntry.MAX_LENGTH*128*8*4 + 8 * SearchIndexJournalEntryHeader.HEADER_SIZE_LONGS; - private final ByteBuffer byteBuffer; - private long pos; - - @SneakyThrows - public SearchIndexJournalWriterImpl(KeywordLexicon lexicon, File indexFile) { - this.lexicon = lexicon; - initializeIndexFile(indexFile); - - byteBuffer = ByteBuffer.allocate(MAX_BLOCK_SIZE); - - new Thread(this::journalWriterThread, "Journal Writer").start(); - - writerTask = Schedulers.io().schedulePeriodicallyDirect(this::forceWrite, 1, 1, TimeUnit.SECONDS); - Runtime.getRuntime().addShutdownHook(new Thread(this::forceWrite)); - } - - private void initializeIndexFile(File indexFile) throws IOException { - raf = new RandomAccessFile(indexFile, "rw"); - channel = raf.getChannel(); - - try { - pos = raf.readLong(); - raf.seek(pos); - logger.info("Resuming index file of size {}", pos); - } - catch (EOFException ex) { - logger.info("Clean index file"); - writePositionMarker(); - writePositionMarker(); - } - } - - private record WriteJob(SearchIndexJournalEntryHeader header, SearchIndexJournalEntry entryData) {} - private final LinkedBlockingQueue writeQueue = new LinkedBlockingQueue<>(512); - - @Override - @SneakyThrows - public void put(SearchIndexJournalEntryHeader header, SearchIndexJournalEntry entryData) { - writeQueue.put(new WriteJob(header, entryData)); - } - - @SneakyThrows - public void journalWriterThread() { - - while (true) { - var job = writeQueue.take(); - - writeEntry(job.header, job.entryData); - } - } - private synchronized void writeEntry(SearchIndexJournalEntryHeader header, SearchIndexJournalEntry entryData) { - - try { - byteBuffer.clear(); - - byteBuffer.putInt(entryData.size()); - byteBuffer.putInt(0); // unused - byteBuffer.putLong(header.documentId()); - byteBuffer.putLong(header.documentMeta()); - - entryData.write(byteBuffer); - - byteBuffer.limit(byteBuffer.position()); - byteBuffer.rewind(); - - while (byteBuffer.position() < byteBuffer.limit()) - channel.write(byteBuffer); - - writePositionMarker(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public synchronized void forceWrite() { - try { - channel.force(false); - } - catch (IOException ex) { - logger.error("IO Exception", ex); - } - } - - - @Override - @SneakyThrows - public void flushWords() { - lexicon.commitToDisk(); - } - - private void writePositionMarker() throws IOException { - pos = channel.size(); - raf.seek(0); - raf.writeLong(pos); - raf.writeLong(lexicon.size()); - raf.seek(pos); - } - - public synchronized void close() throws IOException { - writerTask.dispose(); - channel.close(); - raf.close(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexEntrySourceBehavior.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexEntrySourceBehavior.java deleted file mode 100644 index fc779403..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/reverse/query/ReverseIndexEntrySourceBehavior.java +++ /dev/null @@ -1,6 +0,0 @@ -package nu.marginalia.wmsa.edge.index.postings.reverse.query; - -public enum ReverseIndexEntrySourceBehavior { - DO_PREFER, - DO_NOT_PREFER -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQueryIf.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQueryIf.java deleted file mode 100644 index 0fd325ea..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexQueryIf.java +++ /dev/null @@ -1,12 +0,0 @@ -package nu.marginalia.wmsa.edge.index.query; - -import java.util.stream.LongStream; - -public interface IndexQueryIf { - IndexQueryIf also(int wordId); - IndexQueryIf alsoCached(int wordId); - - IndexQueryIf not(int wordId); - - LongStream stream(); -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/old/OldReversePageRankV2.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/old/OldReversePageRankV2.java deleted file mode 100644 index 59fcda0d..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/old/OldReversePageRankV2.java +++ /dev/null @@ -1,261 +0,0 @@ -package nu.marginalia.wmsa.edge.index.ranking.old; - - -import com.zaxxer.hikari.HikariDataSource; -import gnu.trove.list.TIntList; -import gnu.trove.list.array.TIntArrayList; -import gnu.trove.map.hash.TIntDoubleHashMap; -import gnu.trove.map.hash.TIntObjectHashMap; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.SQLException; -import java.util.*; - -public class OldReversePageRankV2 { - - private final TIntObjectHashMap domains = new TIntObjectHashMap<>(); - private final TIntObjectHashMap linkData = new TIntObjectHashMap<>(); - private final TIntObjectHashMap reverseLinkData = new TIntObjectHashMap<>(); - private final Logger logger = LoggerFactory.getLogger(getClass()); - - public final Set originDomains = new HashSet<>(); - public final Set originDomainIds = new HashSet<>(); - - public static void main(String... args) throws IOException { - new OldReversePageRankV2( -// "wiki.xxiivv.com", -// "stpeter.im", -// "datagubbe.se", "midnight.pub", -// "www.gameboomers.com", -// "www.wild-seven.org", "iocane-powder.net", "www.doujinshi.org", "ohmydarling.org", -// "lobste.rs", -// "dataswamp.org", "www.ohtori.nu", -// "lukesmith.xyz", "internetgirlfriend.club", -// "tilde.town", "tilde.team", -// "felix.plesoianu.ro", -// "www.neustadt.fr", - "memex.marginalia.nu" - ); - } - - public OldReversePageRankV2(String... seedDomains) throws IOException { - loadDataFromFile(); - - long start = System.currentTimeMillis(); - for (int i = 0; i < 100; i++) { - if (domains.contains(i)) { - int[] ids = pageRank(10).toArray(); - System.out.printf("%d %d\n", i, ids.length); - } -// Arrays.stream(ids).mapToObj(domains::get).map(data -> -// String.format("%3d %2.2f %s", Optional.ofNullable(reverseLinkData.get(data.id)).map(TIntArrayList::size).orElse(0), data.quality, data.name) -// ).forEach(System.out::println); - } - long end = System.currentTimeMillis(); - System.out.printf("%2.2f", (end - start)/1000.0); - } - - public OldReversePageRankV2(HikariDataSource dataSource) { - originDomains.add("memex.marginalia.nu"); - - try (var conn = dataSource.getConnection()) { - try (var stmt = conn.prepareStatement("SELECT ID,INDEXED,STATE FROM EC_DOMAIN WHERE INDEXED>1 AND IS_ALIVE")) { - stmt.setFetchSize(10000); - var rsp = stmt.executeQuery(); - while (rsp.next()) { - domains.put(rsp.getInt(1), new DomainData("", 0.0, rsp.getInt(1), rsp.getInt(2), rsp.getInt(3))); - } - } - try (var stmt = conn.prepareStatement("SELECT SOURCE_DOMAIN_ID, DEST_DOMAIN_ID FROM EC_DOMAIN_LINK")) { - stmt.setFetchSize(10000); - - var rsp = stmt.executeQuery(); - - while (rsp.next()) { - int src = rsp.getInt(1); - int dst = rsp.getInt(2); - if (domains.contains(src) && domains.contains(dst) && domains.get(src).quality >= -5) { - if (!linkData.contains(src)) { - linkData.put(src, new TIntArrayList()); - } - linkData.get(src).add(dst); - } - } - } - - try (var stmt = conn.prepareStatement("SELECT ID FROM EC_DOMAIN WHERE DOMAIN_NAME=?")) { - stmt.setFetchSize(10000); - - for (var seed : this.originDomains) { - stmt.setString(1, seed); - var rsp = stmt.executeQuery(); - if (rsp.next()) { - originDomainIds.add(rsp.getInt(1)); - } - } - } - - } catch (SQLException throwables) { - logger.error("SQL error", throwables); - } - - } - - public int size() { - return domains.size(); - } - - public TIntList pageRank(int resultCount) { - RankVector rank = new RankVector(1.d / domains.size()); - - for (int i = 0; i < 100; i++) { - RankVector newRank = createNewRankVector(rank); - - double oldNorm = rank.norm(); - double newNorm = newRank.norm(); - double dNorm = oldNorm - newNorm ; - originDomainIds.forEach(id -> newRank.increment(id, dNorm/oldNorm)); -// newRank.increment(14880, dNorm/rank.norm()); - rank = newRank; - } - - for (var id : originDomainIds) { - rank.increment(id, -1); - } - - return rank.getRanking(resultCount); - } - - @NotNull - private RankVector createNewRankVector(RankVector rank) { - - final TIntArrayList empty = new TIntArrayList(); - - double rankNorm = rank.norm(); - RankVector newRank = new RankVector(0); - - for (DomainData domain : domains.values(new DomainData[domains.size()])) { - - var links = Optional.ofNullable(linkData.get(domain.id)).orElse(empty); - if (links.size() > 0) { - double newRankValue = 0; - for (int linkedDomain : links.toArray()) { - newRankValue += rank.get(linkedDomain) / links.size(); - } - - newRank.set(domain.id, 0.85*newRankValue/rankNorm); - } - } - return newRank; - } - - private void loadDataFromFile() throws IOException { - - try (var str = Files.lines(Path.of("/home/vlofgren/Work/data-domains.txt"))) { - str.map(DomainData::new) - .filter(domain -> domain.indexed>1) - .filter(domain -> domain.state>=1) - .peek(domain -> { - if (originDomains.contains(domain.name)) { - originDomainIds.add(domain.id); - } - }) - .forEach(data -> domains.put(data.id, data)); - } - - try (var str = Files.lines(Path.of("/home/vlofgren/Work/data-links.txt"))) { - str.map(s->s.split("\\s+")).forEach(bits -> { - - int src = Integer.parseInt(bits[0]); - int dst = Integer.parseInt(bits[1]); - - if (domains.contains(src) && domains.contains(dst) && domains.get(src).quality >= -5) { - if (!linkData.contains(src)) { - linkData.put(src, new TIntArrayList()); - } - linkData.get(src).add(dst); - } - - - if (!reverseLinkData.contains(dst)) { - reverseLinkData.put(dst, new TIntArrayList()); - } - reverseLinkData.get(dst).add(src); - }); - } - } - - private class RankVector { - private final TIntDoubleHashMap rank; - private final double defaultValue; - public RankVector(double defaultValue) { - rank = new TIntDoubleHashMap(domains.size(), 0.75f, -1, defaultValue); - this.defaultValue = defaultValue; - } - - public void set(int id, double value) { - rank.put(id, value); - } - - - public void increment(int id, double value) { - rank.adjustOrPutValue(id, value, value); - } - - public double get(int id) { - return rank.get(id); - } - - public double norm() { - if (rank.isEmpty()) { - return defaultValue * domains.size(); - } - return Arrays.stream(rank.values()).map(Math::abs).sum(); - } - - public double norm(RankVector other) { - return Arrays.stream(rank.keys()).mapToDouble(k -> Math.abs(rank.get(k) - other.get(k))).sum(); - } - - public TIntList getRanking(int numResults) { - TIntArrayList list = new TIntArrayList(numResults); - - Comparator comparator = Comparator.comparing(e -> rank.get(e.id)); - - domains.valueCollection().stream() - .sorted(comparator.reversed()) - .map(DomainData::getId) - .limit(numResults) - .forEach(list::add); - - return list; - } - - } - @Data @AllArgsConstructor - static class DomainData { - - public DomainData(String str) { - String[] parts = str.split("\\s+"); - - id = Integer.parseInt(parts[0]); - quality = Double.parseDouble(parts[1]); - name = parts[2]; - indexed = Integer.parseInt(parts[3]); - state = Integer.parseInt(parts[4]); - } - public final String name; - public final double quality; - public final int id; - public final int indexed; - public final int state; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/old/StandardPageRank.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/old/StandardPageRank.java deleted file mode 100644 index cd58f7be..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/ranking/old/StandardPageRank.java +++ /dev/null @@ -1,266 +0,0 @@ -package nu.marginalia.wmsa.edge.index.ranking.old; - - -import com.zaxxer.hikari.HikariDataSource; -import gnu.trove.list.TIntList; -import gnu.trove.list.array.TIntArrayList; -import gnu.trove.map.hash.TIntDoubleHashMap; -import gnu.trove.map.hash.TIntObjectHashMap; -import gnu.trove.set.hash.TIntHashSet; -import lombok.AllArgsConstructor; -import lombok.Data; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.SQLException; -import java.util.*; -import java.util.function.IntToDoubleFunction; - -public class StandardPageRank { - - private final TIntObjectHashMap domains = new TIntObjectHashMap<>(); - private final TIntObjectHashMap linkData = new TIntObjectHashMap<>(); - private final TIntObjectHashMap reverseLinkData = new TIntObjectHashMap<>(); - private final Logger logger = LoggerFactory.getLogger(getClass()); - - public final Set originDomains = new HashSet<>(); - public final Set originDomainIds = new HashSet<>(); - - public StandardPageRank(IntToDoubleFunction weight, String... seedDomains) throws IOException { - originDomains.addAll(Arrays.asList(seedDomains)); - loadDataFromFile(); - - int[] ids = pageRank(weight, 1000).toArray(); - Arrays.stream(ids).mapToObj(domains::get).map(data -> - String.format("%3d %2.2f %s", Optional.ofNullable(reverseLinkData.get(data.id)).map(TIntArrayList::size).orElse(0), data.quality, data.name) - ).forEach(System.out::println); - } - - public String domainNameFromId(int id) { - return domains.get(id).name; - } - - public StandardPageRank(HikariDataSource dataSource, String... origins) { - originDomains.addAll(Arrays.asList(origins)); - - try (var conn = dataSource.getConnection()) { - try (var stmt = conn.prepareStatement("SELECT ID,INDEXED,STATE,DOMAIN_NAME FROM EC_DOMAIN WHERE INDEXED>1 AND IS_ALIVE AND QUALITY>=-10")) { - stmt.setFetchSize(10000); - var rsp = stmt.executeQuery(); - while (rsp.next()) { - domains.put(rsp.getInt(1), new DomainData(rsp.getInt(1), rsp.getString(4), rsp.getInt(2), rsp.getInt(3), 0)); - } - } - try (var stmt = conn.prepareStatement("SELECT SOURCE_DOMAIN_ID, DEST_DOMAIN_ID FROM EC_DOMAIN_LINK")) { - stmt.setFetchSize(10000); - - var rsp = stmt.executeQuery(); - - while (rsp.next()) { - int src = rsp.getInt(1); - int dst = rsp.getInt(2); - - if (domains.contains(src) && domains.contains(dst) && domains.get(src).quality >= -5) { - if (!linkData.contains(src)) { - linkData.put(src, new TIntArrayList()); - } - linkData.get(src).add(dst); - - if (!reverseLinkData.contains(dst)) { - reverseLinkData.put(dst, new TIntArrayList()); - } - reverseLinkData.get(dst).add(src); - } - } - } - - try (var stmt = conn.prepareStatement("SELECT ID FROM EC_DOMAIN WHERE DOMAIN_NAME=?")) { - for (var seed : this.originDomains) { - stmt.setString(1, seed); - var rsp = stmt.executeQuery(); - if (rsp.next()) { - originDomainIds.add(rsp.getInt(1)); - } - } - } - - } catch (SQLException throwables) { - logger.error("SQL error", throwables); - } - - } - - public int size() { - return domains.size(); - } - - public TIntList pageRank(IntToDoubleFunction weight, int resultCount) { - RankVector rank = new RankVector(1.d / domains.size()); - - int iter_max = 100; - for (int i = 0; i < iter_max; i++) { - RankVector newRank = createNewRankVector(rank); - - double oldNorm = rank.norm(); - double newNorm = newRank.norm(); - double dNorm = oldNorm - newNorm; - if (i < iter_max-1) { - originDomainIds.forEach(id -> newRank.increment(id, dNorm/originDomainIds.size())); - newRank.incrementAll(0.14*dNorm/rank.size()); - } - logger.debug("{} {} {}", dNorm, newNorm, rank.norm(newRank)); - rank = newRank; - } - - - return rank.getRanking(weight, resultCount); - } - - @NotNull - private RankVector createNewRankVector(RankVector rank) { - - final TIntArrayList empty = new TIntArrayList(); - - RankVector newRank = new RankVector(0); - - for (DomainData domain : domains.valueCollection()) { - - var links = Optional.ofNullable(reverseLinkData.get(domain.id)).orElse(empty); - double newRankValue = 0; - if (links.size() > 0) { - for (int linkedDomain : links.toArray()) { - newRankValue += rank.get(linkedDomain) / linkData.get(linkedDomain).size(); - } - } - - newRank.set(domain.id, 0.85 * newRankValue); - } - return newRank; - } - - private void loadDataFromFile() throws IOException { - - try (var str = Files.lines(Path.of("/home/vlofgren/Work/data-domains.txt"))) { - str.map(DomainData::new) - .filter(domain -> domain.indexed>1) - .filter(domain -> domain.quality>=0.1) - .peek(domain -> { - if (originDomains.contains(domain.name)) { - originDomainIds.add(domain.id); - } - }) - .forEach(data -> domains.put(data.id, data)); - } - - try (var str = Files.lines(Path.of("/home/vlofgren/Work/data-links.txt"))) { - str.map(s->s.split("\\s+")).forEach(bits -> { - - int src = Integer.parseInt(bits[0]); - int dst = Integer.parseInt(bits[1]); - - if (domains.contains(src) && domains.contains(dst) && domains.get(src).quality >= -5) { - if (!linkData.contains(src)) { - linkData.put(src, new TIntArrayList()); - } - linkData.get(src).add(dst); - - if (!reverseLinkData.contains(dst)) { - reverseLinkData.put(dst, new TIntArrayList()); - } - reverseLinkData.get(dst).add(src); - } - }); - } - } - - private class RankVector { - private final TIntDoubleHashMap rank; - private final double defaultValue; - public RankVector(double defaultValue) { - rank = new TIntDoubleHashMap(domains.size(), 0.75f, -1, defaultValue); - this.defaultValue = defaultValue; - } - - public void set(int id, double value) { - rank.put(id, value); - } - - public void increment(int id, double value) { - rank.adjustOrPutValue(id, value, value); - } - - public double get(int id) { - return rank.get(id); - } - - public double norm() { - if (rank.isEmpty()) { - return defaultValue * domains.size(); - } - return Arrays.stream(rank.values()).map(Math::abs).sum(); - } - - public double norm(RankVector other) { - return Arrays.stream(rank.keys()).mapToDouble(k -> Math.abs(rank.get(k) - other.get(k))).sum(); - } - - public TIntList getRanking(IntToDoubleFunction other, int numResults) { - TIntArrayList list = new TIntArrayList(numResults); - - Comparator comparator = Comparator.comparing(e -> Math.sqrt(other.applyAsDouble(e.id) * rank.get(e.id))); - - domains.valueCollection().stream() - .sorted(comparator.reversed()) - .map(DomainData::getId) - .limit(numResults) - .forEach(list::add); - - return list; - } - - public TIntList getRanking2(int numResults) { - TIntArrayList list = new TIntArrayList(numResults); - - Comparator comparator = Comparator.comparing(e -> rank.get(e.id)); - - domains.valueCollection().stream() - .sorted(comparator.reversed()) - .map(DomainData::getId) - .limit(numResults) - .forEach(list::add); - - return list; - } - - public void incrementAll(double v) { - rank.transformValues(oldv -> oldv + v); - } - - int size() { - return domains.size(); - } - } - @Data @AllArgsConstructor - static class DomainData { - - public DomainData(String str) { - String[] parts = str.split("\\s+"); - - id = Integer.parseInt(parts[0]); - name = parts[2]; - indexed = Integer.parseInt(parts[3]); - state = Integer.parseInt(parts[4]); - quality = Double.parseDouble(parts[5]); - } - public final int id; - public final String name; - public final int indexed; - public final int state; - public double quality; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexDomainQueryService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexDomainQueryService.java deleted file mode 100644 index 90ac84a4..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexDomainQueryService.java +++ /dev/null @@ -1,110 +0,0 @@ -package nu.marginalia.wmsa.edge.index.svc; - -import com.google.gson.Gson; -import com.google.inject.Inject; -import com.google.inject.Singleton; -import io.prometheus.client.Histogram; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.dict.OffHeapDictionaryHashMap; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.edge.index.postings.SearchIndexControl; -import nu.marginalia.wmsa.edge.index.query.IndexResultDomainDeduplicator; -import nu.marginalia.wmsa.edge.index.query.IndexSearchBudget; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeIdList; -import nu.marginalia.wmsa.edge.model.search.domain.EdgeDomainSearchResults; -import nu.marginalia.wmsa.edge.model.search.domain.EdgeDomainSearchSpecification; -import org.apache.http.HttpStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spark.HaltException; -import spark.Request; -import spark.Response; -import spark.Spark; - -import java.util.OptionalInt; - -import static spark.Spark.halt; - -@Singleton -public class EdgeIndexDomainQueryService { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private static final Histogram wmsa_edge_index_domain_query_time = Histogram.build().name("wmsa_edge_index_domain_query_time").linearBuckets(25/1000., 25/1000., 15).help("-").register(); - - private final Gson gson = GsonFactory.get(); - - private final SearchIndexControl indexes; - - @Inject - public EdgeIndexDomainQueryService(SearchIndexControl indexes) { - this.indexes = indexes; - } - - public Object searchDomain(Request request, Response response) { - if (indexes.getLexiconReader() == null) { - logger.warn("Dictionary reader not yet initialized"); - halt(HttpStatus.SC_SERVICE_UNAVAILABLE, "Come back in a few minutes"); - } - - String json = request.body(); - EdgeDomainSearchSpecification specsSet = gson.fromJson(json, EdgeDomainSearchSpecification.class); - - try { - return new EdgeDomainSearchResults("", new EdgeIdList<>()); - // fixme - // return wmsa_edge_index_domain_query_time.time(() -> queryDomain(specsSet)); - } - catch (HaltException ex) { - logger.warn("Halt", ex); - throw ex; - } - catch (Exception ex) { - logger.info("Error during domain search {}({}) (query: {})", ex.getClass().getSimpleName(), ex.getMessage(), json); - logger.info("Error", ex); - Spark.halt(500, "Error"); - return null; - } - } - - public EdgeDomainSearchResults queryDomain(EdgeDomainSearchSpecification specsSet) { - - final OptionalInt wordId = lookUpWord(specsSet.keyword); - final EdgeIdList urlIds = new EdgeIdList<>(); - - final IndexSearchBudget budget = new IndexSearchBudget(50); - - if (wordId.isEmpty()) { - return new EdgeDomainSearchResults(specsSet.keyword, urlIds); - } - - LongQueryBuffer buffer = new LongQueryBuffer(512); - - - final IndexResultDomainDeduplicator localFilter = new IndexResultDomainDeduplicator(1); - var query = indexes.getIndex().getDomainQuery(wordId.getAsInt(), localFilter); - - while (query.hasMore() && urlIds.size() < specsSet.maxResults) { - query.getMoreResults(buffer); - - for (int i = 0; i < buffer.end && urlIds.size() < specsSet.maxResults; i++) { - long result = buffer.data[i]; - if (localFilter.test(result)) { - urlIds.add((int) (result & 0xFFFF_FFFFL)); - } - } - } - - return new EdgeDomainSearchResults(specsSet.keyword, urlIds); - } - - private OptionalInt lookUpWord(String s) { - int ret = indexes.getLexiconReader().get(s); - if (ret == OffHeapDictionaryHashMap.NO_VALUE) { - return OptionalInt.empty(); - } - return OptionalInt.of(ret); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexLexiconService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexLexiconService.java deleted file mode 100644 index 40f7cf64..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexLexiconService.java +++ /dev/null @@ -1,126 +0,0 @@ -package nu.marginalia.wmsa.edge.index.svc; - -import com.google.inject.Inject; -import com.google.inject.Singleton; -import com.google.protobuf.InvalidProtocolBufferException; -import nu.marginalia.util.dict.OffHeapDictionaryHashMap; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.KeywordListChunker; -import nu.marginalia.wmsa.edge.index.IndexServicesFactory; -import nu.marginalia.wmsa.edge.index.lexicon.KeywordLexicon; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.index.postings.SearchIndexControl; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntryHeader; -import nu.marginalia.wmsa.edge.index.postings.journal.writer.SearchIndexJournalWriterImpl; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeId; -import nu.wmsa.wmsa.edge.index.proto.IndexPutKeywordsReq; -import org.apache.http.HttpStatus; -import spark.Request; -import spark.Response; - -import java.util.Arrays; - -@Singleton -public class EdgeIndexLexiconService { - - private final SearchIndexControl indexes; - private final KeywordLexicon keywordLexicon; - - @Inject - public EdgeIndexLexiconService(SearchIndexControl indexes, IndexServicesFactory servicesFactory) { - this.indexes = indexes; - this.keywordLexicon = servicesFactory.getKeywordLexicon(); - } - - public EdgeIndexLexiconService(SearchIndexControl indexes, KeywordLexicon lexicon) { - this.indexes = indexes; - this.keywordLexicon = lexicon; - } - - public Object getWordId(Request request, Response response) { - final String word = request.splat()[0]; - - var lr = indexes.getLexiconReader(); - if (null == lr) { - response.status(HttpStatus.SC_FAILED_DEPENDENCY); - return ""; - } - - final int wordId = lr.get(word); - - if (OffHeapDictionaryHashMap.NO_VALUE == wordId) { - response.status(404); - return ""; - } - - return wordId; - } - - public long getOrInsertWord(String word) { - return keywordLexicon.getOrInsert(word); - } - - public Object putWords(Request request, Response response) throws InvalidProtocolBufferException { - var req = IndexPutKeywordsReq.parseFrom(request.bodyAsBytes()); - - EdgeId domainId = new EdgeId<>(req.getDomain()); - EdgeId urlId = new EdgeId<>(req.getUrl()); - int idx = req.getIndex(); - - for (int ws = 0; ws < req.getWordSetCount(); ws++) { - putWords(domainId, urlId, req.getWordSet(ws), idx); - } - - response.status(HttpStatus.SC_ACCEPTED); - return ""; - } - - public void putWords(int idx, SearchIndexJournalEntryHeader header, SearchIndexJournalEntry entry) { - SearchIndexJournalWriterImpl indexWriter = indexes.getIndexWriter(idx); - - indexWriter.put(header, entry); - } - - public void putWords(EdgeId domainId, EdgeId urlId, - IndexPutKeywordsReq.WordSet words, int idx - ) { - - SearchIndexJournalWriterImpl indexWriter = indexes.getIndexWriter(idx); - - var wordArray = words.getWordsList().toArray(String[]::new); - var metaArray = words.getMetaList().stream().mapToLong(Long::valueOf).toArray(); - - DocumentKeywords documentKeywords = new DocumentKeywords(wordArray, metaArray); - for (var chunk : KeywordListChunker.chopList(documentKeywords, SearchIndexJournalEntry.MAX_LENGTH)) { - var entry = new SearchIndexJournalEntry(getOrInsertWordIds(chunk.keywords(), chunk.metadata())); - var header = new SearchIndexJournalEntryHeader(domainId, urlId, EdgePageDocumentsMetadata.defaultValue()); - - indexWriter.put(header, entry); - } - } - - private long[] getOrInsertWordIds(String[] words, long[] meta) { - long[] ids = new long[words.length*2]; - int putIdx = 0; - - for (int i = 0; i < words.length; i++) { - String word = words[i]; - - long id = keywordLexicon.getOrInsert(word); - if (id != OffHeapDictionaryHashMap.NO_VALUE) { - ids[putIdx++] = id; - ids[putIdx++] = meta[i]; - } - } - - if (putIdx != words.length*2) { - ids = Arrays.copyOf(ids, putIdx); - } - return ids; - } - - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexOpsService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexOpsService.java deleted file mode 100644 index bf1fd459..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexOpsService.java +++ /dev/null @@ -1,42 +0,0 @@ -package nu.marginalia.wmsa.edge.index.svc; - -import com.google.inject.Inject; -import com.google.inject.Singleton; -import nu.marginalia.wmsa.edge.index.postings.SearchIndexControl; -import spark.Request; -import spark.Response; -import spark.Spark; - -@Singleton -public class EdgeIndexOpsService { - - private final SearchIndexControl indexes; - private final EdgeOpsLockService opsLockService; - private final EdgeIndexSearchSetsService searchSetService; - - @Inject - public EdgeIndexOpsService(SearchIndexControl indexes, - EdgeOpsLockService opsLockService, - EdgeIndexSearchSetsService searchSetService) { - this.indexes = indexes; - this.opsLockService = opsLockService; - this.searchSetService = searchSetService; - } - - public Object repartitionEndpoint(Request request, Response response) throws Exception { - - if (!opsLockService.run(searchSetService::recalculateAll)) { - Spark.halt(503, "Operations busy"); - } - return "OK"; - } - - public Object reindexEndpoint(Request request, Response response) throws Exception { - - if (!indexes.reindex()) { - Spark.halt(503, "Operations busy"); - } - return "OK"; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexQueryService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexQueryService.java deleted file mode 100644 index 5988df3b..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexQueryService.java +++ /dev/null @@ -1,305 +0,0 @@ -package nu.marginalia.wmsa.edge.index.svc; - -import com.google.gson.Gson; -import com.google.inject.Inject; -import com.google.inject.Singleton; -import gnu.trove.list.TLongList; -import gnu.trove.list.array.TLongArrayList; -import gnu.trove.set.hash.TLongHashSet; -import io.prometheus.client.Counter; -import io.prometheus.client.Gauge; -import io.prometheus.client.Histogram; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import nu.marginalia.util.array.buffer.LongQueryBuffer; -import nu.marginalia.util.dict.OffHeapDictionaryHashMap; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.edge.index.postings.EdgeIndexQuerySearchTerms; -import nu.marginalia.wmsa.edge.index.postings.IndexResultValuator; -import nu.marginalia.wmsa.edge.index.postings.SearchIndexControl; -import nu.marginalia.wmsa.edge.index.query.IndexQuery; -import nu.marginalia.wmsa.edge.index.query.IndexQueryParams; -import nu.marginalia.wmsa.edge.index.query.IndexResultDomainDeduplicator; -import nu.marginalia.wmsa.edge.index.query.IndexSearchBudget; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSet; -import nu.marginalia.wmsa.edge.index.svc.searchset.SmallSearchSet; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultItem; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultSet; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSpecification; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSubquery; -import org.apache.http.HttpStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spark.HaltException; -import spark.Request; -import spark.Response; -import spark.Spark; - -import java.util.ArrayList; -import java.util.List; -import java.util.OptionalInt; -import java.util.function.LongPredicate; - -import static java.util.Comparator.comparingDouble; -import static spark.Spark.halt; - -@Singleton -public class EdgeIndexQueryService { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - private static final Counter wmsa_edge_index_query_timeouts = Counter.build().name("wmsa_edge_index_query_timeouts").help("-").register(); - private static final Gauge wmsa_edge_index_query_cost = Gauge.build().name("wmsa_edge_index_query_cost").help("-").register(); - private static final Histogram wmsa_edge_index_query_time = Histogram.build().name("wmsa_edge_index_query_time").linearBuckets(25/1000., 25/1000., 15).help("-").register(); - - private final Gson gson = GsonFactory.get(); - - private final SearchIndexControl indexes; - private final EdgeIndexSearchSetsService searchSetsService; - - @Inject - public EdgeIndexQueryService(SearchIndexControl indexes, EdgeIndexSearchSetsService searchSetsService) { - this.indexes = indexes; - this.searchSetsService = searchSetsService; - } - - public Object search(Request request, Response response) { - if (indexes.getLexiconReader() == null) { - logger.warn("Dictionary reader not yet initialized"); - halt(HttpStatus.SC_SERVICE_UNAVAILABLE, "Come back in a few minutes"); - } - - String json = request.body(); - EdgeSearchSpecification specsSet = gson.fromJson(json, EdgeSearchSpecification.class); - - try { - return wmsa_edge_index_query_time.time(() -> query(specsSet)); - } - catch (HaltException ex) { - logger.warn("Halt", ex); - throw ex; - } - catch (Exception ex) { - logger.info("Error during search {}({}) (query: {})", ex.getClass().getSimpleName(), ex.getMessage(), json); - logger.info("Error", ex); - Spark.halt(500, "Error"); - return null; - } - } - - - public EdgeSearchResultSet query(EdgeSearchSpecification specsSet) { - SearchQuery searchQuery = new SearchQuery(specsSet); - - List results = searchQuery.execute(); - - wmsa_edge_index_query_cost.set(searchQuery.getDataCost()); - - if (!searchQuery.hasTimeLeft()) { - wmsa_edge_index_query_timeouts.inc(); - } - - return new EdgeSearchResultSet(results); - } - - private class SearchQuery { - private final int fetchSize; - private final IndexSearchBudget budget; - private final List subqueries; - private long dataCost = 0; - private final IndexQueryParams queryParams; - - private final int limitByDomain; - private final int limitTotal; - - TLongHashSet consideredUrlIds; - - public SearchQuery(EdgeSearchSpecification specsSet) { - var limits = specsSet.queryLimits; - - this.fetchSize = limits.fetchSize(); - this.budget = new IndexSearchBudget(limits.timeoutMs()); - this.subqueries = specsSet.subqueries; - this.limitByDomain = limits.resultsByDomain(); - this.limitTotal = limits.resultsTotal(); - - this.consideredUrlIds = new TLongHashSet(fetchSize * 4); - - queryParams = new IndexQueryParams( - specsSet.quality, - specsSet.year, - specsSet.size, - specsSet.rank, - getSearchSet(specsSet), - specsSet.queryStrategy); - } - - private List execute() { - final TLongList results = new TLongArrayList(fetchSize); - - for (var sq : subqueries) { - final EdgeIndexQuerySearchTerms searchTerms = getSearchTerms(sq); - - if (searchTerms.isEmpty()) { - continue; - } - - TLongArrayList resultsForSubquery = performSearch(searchTerms); - results.addAll(resultsForSubquery); - - if (!budget.hasTimeLeft()) { - logger.info("Query timed out {}, ({}), -{}", - sq.searchTermsInclude, sq.searchTermsAdvice, sq.searchTermsExclude); - break; - } - } - - final var evaluator = new IndexResultValuator(indexes, results, subqueries, queryParams); - - ArrayList items = new ArrayList<>(results.size()); - ArrayList refusedItems = new ArrayList<>(results.size()); - - // Sorting the result ids results in better paging characteristics - results.sort(); - - results.forEach(id -> { - var item = evaluator.evaluateResult(id); - - // Score value is zero when the best query variant consists of low-value terms that are just scattered - // throughout the document, with no indicators of importance associated with them. - if (item.getScoreValue() < 0) { - items.add(item); - } - else { - refusedItems.add(item); - } - - return true; - }); - - if (items.isEmpty()) { - items.addAll(refusedItems); - } - - return selectResults(items); - } - - - private TLongArrayList performSearch(EdgeIndexQuerySearchTerms terms) - { - final TLongArrayList results = new TLongArrayList(fetchSize); - final LongQueryBuffer buffer = new LongQueryBuffer(fetchSize); - - - IndexQuery query = getQuery(terms, queryParams, consideredUrlIds::add); - - while (query.hasMore() && results.size() < fetchSize && budget.hasTimeLeft()) { - buffer.reset(); - query.getMoreResults(buffer); - - for (int i = 0; i < buffer.size() && results.size() < fetchSize; i++) { - results.add(buffer.data[i]); - } - } - - dataCost += query.dataCost(); - - return results; - } - - private SearchSet getSearchSet(EdgeSearchSpecification specsSet) { - - if (specsSet.domains != null && !specsSet.domains.isEmpty()) { - return new SmallSearchSet(specsSet.domains); - } - - return searchSetsService.getSearchSetByName(specsSet.searchSetIdentifier); - } - - private List selectResults(List results) { - - var domainCountFilter = new IndexResultDomainDeduplicator(limitByDomain); - - results.sort(comparingDouble(EdgeSearchResultItem::getScore) - .thenComparingInt(EdgeSearchResultItem::getRanking) - .thenComparingInt(EdgeSearchResultItem::getUrlIdInt)); - - List resultsList = new ArrayList<>(results.size()); - - for (var item : results) { - if (domainCountFilter.test(item)) { - resultsList.add(item); - } - } - - if (resultsList.size() > limitTotal) { - // This can't be made a stream limit() operation because we need domainCountFilter - // to run over the entire list to provide accurate statistics - - resultsList.subList(limitTotal, resultsList.size()).clear(); - } - - for (var result : resultsList) { - result.resultsFromDomain = domainCountFilter.getCount(result); - } - - return resultsList; - } - - private IndexQuery getQuery(EdgeIndexQuerySearchTerms terms, IndexQueryParams params, LongPredicate includePred) { - return indexes.getIndex().getQuery(terms, params, includePred); - } - - public boolean hasTimeLeft() { - return budget.hasTimeLeft(); - } - - public long getDataCost() { - return dataCost; - } - - } - - private EdgeIndexQuerySearchTerms getSearchTerms(EdgeSearchSubquery request) { - final IntList excludes = new IntArrayList(); - final IntList includes = new IntArrayList(); - final IntList priority = new IntArrayList(); - - for (var include : request.searchTermsInclude) { - var word = lookUpWord(include); - if (word.isEmpty()) { - logger.debug("Unknown search term: " + include); - return new EdgeIndexQuerySearchTerms(); - } - includes.add(word.getAsInt()); - } - - for (var advice : request.searchTermsAdvice) { - var word = lookUpWord(advice); - if (word.isEmpty()) { - logger.debug("Unknown search term: " + advice); - return new EdgeIndexQuerySearchTerms(); - } - includes.add(word.getAsInt()); - } - - for (var exclude : request.searchTermsExclude) { - lookUpWord(exclude).ifPresent(excludes::add); - } - for (var exclude : request.searchTermsPriority) { - lookUpWord(exclude).ifPresent(priority::add); - } - - return new EdgeIndexQuerySearchTerms(includes, excludes, priority); - } - - - private OptionalInt lookUpWord(String s) { - int ret = indexes.getLexiconReader().get(s); - if (ret == OffHeapDictionaryHashMap.NO_VALUE) { - return OptionalInt.empty(); - } - return OptionalInt.of(ret); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeOpsLockService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeOpsLockService.java deleted file mode 100644 index 99dbd5fb..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeOpsLockService.java +++ /dev/null @@ -1,42 +0,0 @@ -package nu.marginalia.wmsa.edge.index.svc; - -import javax.annotation.CheckReturnValue; -import javax.inject.Singleton; -import java.util.Optional; -import java.util.concurrent.Callable; -import java.util.concurrent.locks.ReentrantLock; - -@Singleton -public class EdgeOpsLockService { - public ReentrantLock opsLock = new ReentrantLock(); - - @CheckReturnValue - public Optional run(Callable c) throws Exception { - if (!opsLock.tryLock()) - return Optional.empty(); - try { - return Optional.of(c.call()); - } - finally { - opsLock.unlock(); - } - } - - - @CheckReturnValue - public boolean run(Runnable r) { - if (!opsLock.tryLock()) - return false; - try { - r.run(); - return true; - } - finally { - opsLock.unlock(); - } - } - - public boolean isLocked() { - return opsLock.isLocked(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/arxiv/ArxivParser.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/arxiv/ArxivParser.java deleted file mode 100644 index cc02196c..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/arxiv/ArxivParser.java +++ /dev/null @@ -1,29 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.arxiv; - -import com.google.gson.Gson; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.edge.integration.arxiv.model.ArxivMetadata; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; - -public class ArxivParser { - private final Gson gson = GsonFactory.get(); - - public ArxivParser() { - - } - - public List parse(File jsonFile) throws IOException { - - List ret = new ArrayList<>(); - try (var lines = Files.lines(jsonFile.toPath())) { - lines.map(line -> gson.fromJson(line, ArxivMetadata.class)).forEach(ret::add); - } - - return ret; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/arxiv/model/ArxivMetadata.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/arxiv/model/ArxivMetadata.java deleted file mode 100644 index ba6307a8..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/arxiv/model/ArxivMetadata.java +++ /dev/null @@ -1,21 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.arxiv.model; - -import com.google.gson.annotations.SerializedName; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@AllArgsConstructor @NoArgsConstructor -public class ArxivMetadata { - public String id; - public String submitter; - public String authors; - public String title; - @SerializedName("abstract") - public String _abstract; - - public String getAbstract() { - return _abstract; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/model/BasicDocumentData.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/model/BasicDocumentData.java deleted file mode 100644 index 4e620fc3..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/model/BasicDocumentData.java +++ /dev/null @@ -1,22 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainLink; -import nu.marginalia.wmsa.edge.model.crawl.EdgePageWords; - - -@Data -@AllArgsConstructor -public class BasicDocumentData { - public final EdgeUrl url; - - public final String title; - public final String description; - public int hashCode; - - public final EdgePageWords words; - public final EdgeDomainLink[] domainLinks; - public final int wordCount; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostProcessor.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostProcessor.java deleted file mode 100644 index 3c8bda78..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostProcessor.java +++ /dev/null @@ -1,83 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.stackoverflow; - -import com.google.inject.Inject; -import nu.marginalia.util.language.processing.DocumentKeywordExtractor; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.util.language.processing.model.KeywordMetadata; -import nu.marginalia.wmsa.edge.converting.processor.logic.LinkParser; -import nu.marginalia.wmsa.edge.integration.model.BasicDocumentData; -import nu.marginalia.wmsa.edge.integration.stackoverflow.model.StackOverflowPost; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainLink; -import org.apache.commons.lang3.StringUtils; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class StackOverflowPostProcessor { - private final LinkParser linkParser = new LinkParser(); - - private final SentenceExtractor sentenceExtractor; - private final DocumentKeywordExtractor documentKeywordExtractor; - - @Inject - public StackOverflowPostProcessor(SentenceExtractor sentenceExtractor, DocumentKeywordExtractor documentKeywordExtractor) { - this.sentenceExtractor = sentenceExtractor; - this.documentKeywordExtractor = documentKeywordExtractor; - } - - public BasicDocumentData process(StackOverflowPost post) { - - final var docUrl = post.getUrl(); - final var doc = Jsoup.parseBodyFragment(""+post.getTitle()+"" + post.getFullBody()); - - EdgeDomainLink[] domainLinks = getDomainLinks(docUrl, doc); - - for (var tag : doc.getElementsByTag("code")) { - if (tag.text().length() > 32) { - tag.remove(); - } - } - - var dld = sentenceExtractor.extractSentences(doc); - var keywords = documentKeywordExtractor.extractKeywords(dld, new KeywordMetadata()); - - keywords.addJustNoMeta("site:"+post.getUrl().domain); - keywords.addJustNoMeta("site:"+post.getUrl().domain); - keywords.addJustNoMeta("special:wikipedia"); - keywords.addJustNoMeta("special:wikipedia"); - keywords.addJustNoMeta("js:true"); - - String title = StringUtils.abbreviate(post.getTitle(), 255); - String description = StringUtils.abbreviate(Jsoup.parseBodyFragment(post.getJustBody()).text(), 255); - - return new BasicDocumentData(docUrl, title, description, post.fullBody.hashCode(), keywords, domainLinks, - dld.totalNumWords()); - - } - - private EdgeDomainLink[] getDomainLinks(EdgeUrl docUrl, Document doc) { - List links = new ArrayList<>(10); - - for (var tag : doc.getElementsByTag("a")) { - if (!tag.hasAttr("href")) { - continue; - } - String href = tag.attr("href"); - if (href.length()<10 || !href.contains(".") || !href.contains("://")) { - continue; - } - - linkParser.parseLink(docUrl, tag) - .filter(url -> !Objects.equals(docUrl.getDomain(), url.getDomain())) - .ifPresent(links::add); - } - - return links.stream().map(EdgeUrl::getDomain).map(domain -> new EdgeDomainLink(docUrl.domain, domain)) - .distinct().toArray(EdgeDomainLink[]::new); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostsReader.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostsReader.java deleted file mode 100644 index 88921be1..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostsReader.java +++ /dev/null @@ -1,123 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.stackoverflow; - -import gnu.trove.map.hash.TIntObjectHashMap; -import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.integration.stackoverflow.model.StackOverflowPost; -import nu.marginalia.wmsa.edge.integration.stackoverflow.model.StackOverflowQuestionData; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import java.util.ArrayList; -import java.util.Deque; -import java.util.LinkedList; -import java.util.function.Consumer; - -public class StackOverflowPostsReader extends DefaultHandler { - private static final int MAX_QUESTION_WINDOW_SIZE = 10_000; - - private final Thread runThread; - private final String postsFile; - private final EdgeDomain domain; - private final Consumer postConsumer; - - private final Deque questionWindow = new LinkedList<>(); - private final TIntObjectHashMap questionsById = new TIntObjectHashMap<>(1_000_000); - - public StackOverflowPostsReader(String postsFile, EdgeDomain domain, Consumer postConsumer) { - this.postsFile = postsFile; - this.domain = domain; - this.postConsumer = postConsumer; - runThread = new Thread(this::run, "StackOverflowPostReader"); - runThread.start(); - - } - - @Override - public void startElement(String uri, String lName, String qName, Attributes attr) throws SAXException { - if (!"row".equals(qName)) { - return; - } - - if ("1".equals(attr.getValue("PostTypeId"))) { - onQuestion(attr); - } - if ("2".equals(attr.getValue("PostTypeId"))) { - onReply(attr); - } - - while (questionWindow.size() > MAX_QUESTION_WINDOW_SIZE) { - var data = questionWindow.removeFirst(); - finalizeQuestion(data); - } - - } - - private void finalizeQuestion(StackOverflowQuestionData data) { - questionsById.remove(data.getId()); - var post = createPost(data); - postConsumer.accept(post); - } - - private StackOverflowPost createPost(StackOverflowQuestionData data) { - EdgeUrl url = new EdgeUrl("https", domain, null, "/questions/"+data.getId(), null); - - StringBuilder body = new StringBuilder(); - body.append(data.getQuestion()); - data.getReplies().forEach(body::append); - - return new StackOverflowPost(url, data.getTitle(), body.toString(), data.getQuestion()); - } - - - private void onQuestion(Attributes attr) { - String id = attr.getValue("Id"); - String title = attr.getValue("Title"); - String body = attr.getValue("Body"); - String score = attr.getValue("Score"); - if (parseInt(score) < 0) - return; - - var data = new StackOverflowQuestionData(parseInt(id), title, body, new ArrayList<>()); - questionsById.put(data.getId(), data); - questionWindow.addLast(data); - } - - private void onReply(Attributes attr) { - String parentId = attr.getValue("ParentId"); - String body = attr.getValue("Body"); - String score = attr.getValue("Score"); - if (parseInt(score) < 0) - return; - - var data = questionsById.get(parseInt(parentId)); - if (data != null) { - data.getReplies().add(body); - } - } - - private int parseInt(String id) { - return Integer.parseInt(id); - } - - @SneakyThrows - private void run() { - SAXParserFactory factory = SAXParserFactory.newInstance(); - SAXParser saxParser = factory.newSAXParser(); - - saxParser.parse(postsFile, this); - - while (!questionWindow.isEmpty()) { - var data = questionWindow.removeFirst(); - finalizeQuestion(data); - } - } - - public void join() throws InterruptedException { - runThread.join(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/model/StackOverflowPost.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/model/StackOverflowPost.java deleted file mode 100644 index 03cc1c90..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/model/StackOverflowPost.java +++ /dev/null @@ -1,14 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.stackoverflow.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.ToString; -import nu.marginalia.wmsa.edge.model.EdgeUrl; - -@Data @AllArgsConstructor @ToString -public class StackOverflowPost { - public EdgeUrl url; - public String title; - public String fullBody; - public String justBody; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/model/StackOverflowQuestionData.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/model/StackOverflowQuestionData.java deleted file mode 100644 index 52e2ff6e..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/stackoverflow/model/StackOverflowQuestionData.java +++ /dev/null @@ -1,14 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.stackoverflow.model; - -import lombok.AllArgsConstructor; -import lombok.Data; - -import java.util.List; - -@Data @AllArgsConstructor -public class StackOverflowQuestionData { - int id; - String title; - String question; - List replies; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaProcessor.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaProcessor.java deleted file mode 100644 index 67c51751..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaProcessor.java +++ /dev/null @@ -1,81 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.wikipedia; - -import nu.marginalia.util.language.processing.DocumentKeywordExtractor; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.util.language.processing.model.KeywordMetadata; -import nu.marginalia.wmsa.edge.converting.processor.logic.LinkParser; -import nu.marginalia.wmsa.edge.integration.model.BasicDocumentData; -import nu.marginalia.wmsa.edge.integration.wikipedia.model.WikipediaArticle; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainLink; -import org.apache.commons.lang3.StringUtils; -import org.jsoup.Jsoup; -import org.jsoup.nodes.Document; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class WikipediaProcessor { - private final LinkParser linkParser = new LinkParser(); - - private final SentenceExtractor sentenceExtractor; - private final DocumentKeywordExtractor documentKeywordExtractor; - - public WikipediaProcessor(SentenceExtractor sentenceExtractor, DocumentKeywordExtractor documentKeywordExtractor) { - this.sentenceExtractor = sentenceExtractor; - this.documentKeywordExtractor = documentKeywordExtractor; - } - - - public BasicDocumentData process(WikipediaArticle post) { - - final var docUrl = post.getUrl(); - final var doc = Jsoup.parseBodyFragment(post.body); - - String title = StringUtils.abbreviate(doc.getElementsByTag("title").text(), 255); - String description = getSummary(doc); - - EdgeDomainLink[] domainLinks = getDomainLinks(docUrl, doc); - - var dld = sentenceExtractor.extractSentences(doc); - var keywords = documentKeywordExtractor.extractKeywords(dld, new KeywordMetadata()); - - keywords.addJustNoMeta("site:"+post.getUrl().domain); - keywords.addJustNoMeta("special:stackoverflow"); - keywords.addJustNoMeta("special:stackoverflow"); - keywords.addJustNoMeta("js:true"); - - return new BasicDocumentData(docUrl, title, description, post.body.hashCode(), keywords, domainLinks, - dld.totalNumWords()); - - } - - private String getSummary(Document doc) { - doc = doc.clone(); - doc.select("table,sup,.reference").remove(); - return StringUtils.abbreviate(doc.select("#bodyContent p").text(), 255); - } - - private EdgeDomainLink[] getDomainLinks(EdgeUrl docUrl, Document doc) { - List links = new ArrayList<>(10); - - for (var tag : doc.getElementsByTag("a")) { - if (!tag.hasAttr("href")) { - continue; - } - String href = tag.attr("href"); - if (href.length()<10 || !href.contains(".") || !href.contains("://")) { - continue; - } - - linkParser.parseLink(docUrl, tag) - .filter(url -> !Objects.equals(docUrl.getDomain(), url.getDomain())) - .ifPresent(links::add); - } - - return links.stream().map(EdgeUrl::getDomain).map(domain -> new EdgeDomainLink(docUrl.domain, domain)) - .distinct().toArray(EdgeDomainLink[]::new); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaReader.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaReader.java deleted file mode 100644 index fa5904c9..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaReader.java +++ /dev/null @@ -1,46 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.wikipedia; - -import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.integration.wikipedia.model.WikipediaArticle; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import org.openzim.ZIMTypes.ZIMFile; -import org.openzim.ZIMTypes.ZIMReader; - -import java.util.function.Consumer; - -public class WikipediaReader { - - private final Thread runThread; - private final String zimFile; - private final EdgeDomain domain; - private final Consumer postConsumer; - - public WikipediaReader(String zimFile, EdgeDomain domain, Consumer postConsumer) { - this.zimFile = zimFile; - this.domain = domain; - this.postConsumer = postConsumer; - - runThread = new Thread(this::run, "WikipediaReader"); - runThread.start(); - } - - @SneakyThrows - private void run() { - var zr = new ZIMReader(new ZIMFile(zimFile)); - - zr.forEachArticles((originalUrl, art) -> { - if (art != null) { - postConsumer.accept(new WikipediaArticle(synthesizeUrl(originalUrl), art)); - } - }, p -> true); - } - - private EdgeUrl synthesizeUrl(String originalUrl) { - return new EdgeUrl("https", domain, null, "/wiki/"+originalUrl, null); - } - - public void join() throws InterruptedException { - runThread.join(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/model/WikipediaArticle.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/model/WikipediaArticle.java deleted file mode 100644 index bc221492..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/integration/wikipedia/model/WikipediaArticle.java +++ /dev/null @@ -1,12 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.wikipedia.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import nu.marginalia.wmsa.edge.model.EdgeUrl; - -@Data -@AllArgsConstructor -public class WikipediaArticle { - public final EdgeUrl url; - public final String body; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/WideHashable.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/WideHashable.java deleted file mode 100644 index 3b95711d..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/WideHashable.java +++ /dev/null @@ -1,5 +0,0 @@ -package nu.marginalia.wmsa.edge.model; - -public interface WideHashable { - long wideHash(); -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeIndexTask.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeIndexTask.java deleted file mode 100644 index 1212bfa9..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeIndexTask.java +++ /dev/null @@ -1,33 +0,0 @@ -package nu.marginalia.wmsa.edge.model.crawl; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.ToString; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.EdgeUrl; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -@Getter @AllArgsConstructor @ToString -public class EdgeIndexTask { - public final EdgeDomain domain; - public final List visited = new ArrayList<>(); - public final List urls = new ArrayList<>(); - public final int pass; - public final int limit; - public double rank; - - public boolean isEmpty() { - return domain == null || urls.isEmpty(); - } - - public Stream streamUrls() { - return urls.stream(); - } - - public int size() { - return urls.size(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageContent.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageContent.java deleted file mode 100644 index 596d389a..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageContent.java +++ /dev/null @@ -1,25 +0,0 @@ -package nu.marginalia.wmsa.edge.model.crawl; - -import lombok.Data; -import nu.marginalia.wmsa.edge.model.EdgeUrl; - -import java.util.Map; -import java.util.Set; - -@Data -public class EdgePageContent { - public final EdgeUrl url; - public final EdgePageWords words; - public final Map> linkWords; - public final EdgePageMetadata metadata; - public final int hash; - public final String ipAddress; - - public boolean hasHotLink(EdgeUrl url) { - return linkWords.containsKey(url); - } - - public int numWords() { - return metadata.totalWords; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageMetadata.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageMetadata.java deleted file mode 100644 index bb192f9e..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgePageMetadata.java +++ /dev/null @@ -1,50 +0,0 @@ -package nu.marginalia.wmsa.edge.model.crawl; - -import lombok.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@AllArgsConstructor @EqualsAndHashCode @Getter @Setter @ToString @With -public class EdgePageMetadata { - public final int features; - public final int scriptTags; - public final int rawLength; - public final int textBodyLength; - public final int textDistinctWords; - public final String title; - public final String description; - public final double smutCoefficient; - public final int totalWords; - public final EdgeHtmlStandard htmlStandard; - private static final Logger logger = LoggerFactory.getLogger(EdgePageMetadata.class); - private static EdgePageMetadata _empty - = new EdgePageMetadata(0, 0, - 0, - 0, - 0, - "", - "", - 0., - 1, - EdgeHtmlStandard.UNKNOWN); - public static EdgePageMetadata empty() { - return _empty; - } - - public double quality() { - if (rawLength == 0 || textBodyLength == 0) { - return -5.; - } - -/* double dictionaryFactor = textDistinctWords / 10000.; - if (dictionaryFactor < 0.1) { - dictionaryFactor = 0; - }*/ - - return Math.log(textBodyLength / (double) rawLength)*htmlStandard.scale - + htmlStandard.offset - - scriptTags - // - dictionaryFactor - - smutCoefficient; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeRawPageContents.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeRawPageContents.java deleted file mode 100644 index 8bc5c8d2..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeRawPageContents.java +++ /dev/null @@ -1,24 +0,0 @@ -package nu.marginalia.wmsa.edge.model.crawl; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; -import nu.marginalia.wmsa.edge.model.EdgeUrl; - -@Data @Getter @AllArgsConstructor -public class EdgeRawPageContents { - public final EdgeUrl url; - public final EdgeUrl redirectUrl; - public final String data; - public final EdgeContentType contentType; - public final String ip; - public boolean hasCookies; - public final String fetchTimestamp; - - public boolean isAfter(String dateIso8601) { - if (fetchTimestamp == null) { - return false; - } - return fetchTimestamp.compareTo(dateIso8601) >= 0; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeRobotsTxt.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeRobotsTxt.java deleted file mode 100644 index 5a8bfaea..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeRobotsTxt.java +++ /dev/null @@ -1,10 +0,0 @@ -package nu.marginalia.wmsa.edge.model.crawl; - -import lombok.*; -import nu.marginalia.wmsa.edge.model.EdgeDomain; - -@AllArgsConstructor @EqualsAndHashCode @Getter @Setter @Builder -public class EdgeRobotsTxt { - public final EdgeDomain domain; - public final String robotsTxt; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeUrlVisit.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeUrlVisit.java deleted file mode 100644 index 07d7492e..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/crawl/EdgeUrlVisit.java +++ /dev/null @@ -1,21 +0,0 @@ -package nu.marginalia.wmsa.edge.model.crawl; - -import lombok.Data; -import nu.marginalia.wmsa.edge.model.EdgeUrl; - -@Data -public class EdgeUrlVisit { - public final EdgeUrl url; - public final Integer data_hash_code; - public final Double quality; - public final String title; - public final String description; - public final String ipAddress; - public final String format; - public final int features; - - public final int wordCountDistinct; - public final int wordCountTotal; - - public final EdgeUrlState urlState; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultsKey.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultsKey.java deleted file mode 100644 index aefee330..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeSearchResultsKey.java +++ /dev/null @@ -1,13 +0,0 @@ -package nu.marginalia.wmsa.edge.model.search; - -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; - -@EqualsAndHashCode -@AllArgsConstructor -@Getter -public class EdgeSearchResultsKey { - public final int bucket; - public final int searchTermCount; -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchMain.java deleted file mode 100644 index 66347d2d..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchMain.java +++ /dev/null @@ -1,38 +0,0 @@ -package nu.marginalia.wmsa.edge.search; - -import com.google.inject.Guice; -import com.google.inject.Inject; -import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.configuration.server.Initialization; -import spark.Spark; - -import java.io.IOException; - -public class EdgeSearchMain extends MainClass { - private final EdgeSearchService service; - - @Inject - public EdgeSearchMain(EdgeSearchService service) { - this.service = service; - } - - public static void main(String... args) { - init(ServiceDescriptor.EDGE_SEARCH, args); - - Spark.staticFileLocation("/static/edge/"); - - Injector injector = Guice.createInjector( - new EdgeSearchModule(), - new ConfigurationModule(), - new DatabaseModule() - ); - - injector.getInstance(EdgeSearchMain.class); - injector.getInstance(Initialization.class).setReady(); - - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/IndexCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/IndexCommand.java deleted file mode 100644 index 46a8a437..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/IndexCommand.java +++ /dev/null @@ -1,40 +0,0 @@ -package nu.marginalia.wmsa.edge.search.command; - -import com.google.inject.Inject; -import com.google.inject.Singleton; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklist; -import nu.marginalia.wmsa.edge.search.model.BrowseResultSet; -import nu.marginalia.wmsa.edge.search.results.BrowseResultCleaner; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; -import spark.Request; -import spark.Response; - -import java.io.IOException; - -@Singleton -public class IndexCommand { - - private final EdgeDataStoreDao dataStoreDao; - private final BrowseResultCleaner browseResultCleaner; - private final MustacheRenderer template; - private final EdgeDomainBlacklist blacklist; - @Inject - public IndexCommand(EdgeDataStoreDao dataStoreDao, RendererFactory rendererFactory, BrowseResultCleaner browseResultCleaner, EdgeDomainBlacklist blacklist) throws IOException { - this.dataStoreDao = dataStoreDao; - this.browseResultCleaner = browseResultCleaner; - - template = rendererFactory.renderer("edge/index"); - this.blacklist = blacklist; - } - - public String render(Request request, Response response) { - response.header("Cache-control", "public,max-age=3600"); - - var results = dataStoreDao.getRandomDomains(5, blacklist, 0); - results.removeIf(browseResultCleaner.shouldRemoveResultPredicate()); - - return template.render(new BrowseResultSet(results.stream().limit(1).toList())); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchParameters.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchParameters.java deleted file mode 100644 index ff5662ea..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchParameters.java +++ /dev/null @@ -1,9 +0,0 @@ -package nu.marginalia.wmsa.edge.search.command; - -import nu.marginalia.wmsa.edge.search.model.EdgeSearchProfile; - -public record SearchParameters(EdgeSearchProfile profile, SearchJsParameter js, boolean detailedResults) { - public String profileStr() { - return profile.name; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/ConvertCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/ConvertCommand.java deleted file mode 100644 index 933d93e1..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/ConvertCommand.java +++ /dev/null @@ -1,35 +0,0 @@ -package nu.marginalia.wmsa.edge.search.command.commands; - -import com.google.inject.Inject; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; -import nu.marginalia.wmsa.edge.search.command.SearchParameters; -import nu.marginalia.wmsa.edge.search.svc.EdgeSearchUnitConversionService; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; - -import java.io.IOException; -import java.util.Map; -import java.util.Optional; - -public class ConvertCommand implements SearchCommandInterface { - private final EdgeSearchUnitConversionService edgeSearchUnitConversionService; - private final MustacheRenderer> conversionRenderer; - - @Inject - public ConvertCommand(EdgeSearchUnitConversionService edgeSearchUnitConversionService, RendererFactory rendererFactory) throws IOException { - this.edgeSearchUnitConversionService = edgeSearchUnitConversionService; - - conversionRenderer = rendererFactory.renderer("edge/conversion-results"); - } - - @Override - public Optional process(Context ctx, SearchParameters parameters, String query) { - var conversion = edgeSearchUnitConversionService.tryConversion(ctx, query); - if (conversion.isEmpty()) { - return Optional.empty(); - } - - return Optional.of(conversionRenderer.render(Map.of("query", query, "result", conversion.get(), "profile", parameters.profileStr()))); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SearchCommand.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SearchCommand.java deleted file mode 100644 index ccbb91ec..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SearchCommand.java +++ /dev/null @@ -1,59 +0,0 @@ -package nu.marginalia.wmsa.edge.search.command.commands; - -import com.google.inject.Inject; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklist; -import nu.marginalia.wmsa.edge.search.EdgeSearchOperator; -import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; -import nu.marginalia.wmsa.edge.search.command.SearchParameters; -import nu.marginalia.wmsa.edge.search.model.DecoratedSearchResults; -import nu.marginalia.wmsa.edge.search.query.model.EdgeUserSearchParameters; -import nu.marginalia.wmsa.edge.search.results.BrowseResultCleaner; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; - -import java.io.IOException; -import java.util.Optional; - -public class SearchCommand implements SearchCommandInterface { - private final EdgeDomainBlacklist blacklist; - private final EdgeDataStoreDao dataStoreDao; - private final EdgeSearchOperator searchOperator; - private final MustacheRenderer searchResultsRenderer; - private final BrowseResultCleaner browseResultCleaner; - - public static final int MAX_DOMAIN_RESULTS = 3; - - @Inject - public SearchCommand(EdgeDomainBlacklist blacklist, - EdgeDataStoreDao dataStoreDao, - EdgeSearchOperator searchOperator, - RendererFactory rendererFactory, - BrowseResultCleaner browseResultCleaner - ) throws IOException { - this.blacklist = blacklist; - this.dataStoreDao = dataStoreDao; - this.searchOperator = searchOperator; - this.browseResultCleaner = browseResultCleaner; - - searchResultsRenderer = rendererFactory.renderer("edge/search-results"); - } - - @Override - public Optional process(Context ctx, SearchParameters parameters, String query) { - - EdgeUserSearchParameters params = new EdgeUserSearchParameters(query, parameters.profile(), parameters.js()); - DecoratedSearchResults results = searchOperator.doSearch(ctx, params); - - results.results.removeIf(detail -> blacklist.isBlacklisted(dataStoreDao.getDomainId(detail.url.domain))); - - results.domainResults.removeIf(browseResultCleaner.shouldRemoveResultPredicate()); - - if (results.domainResults.size() > MAX_DOMAIN_RESULTS) { - results.domainResults.subList(MAX_DOMAIN_RESULTS, results.domainResults.size()).clear(); - } - - return Optional.of(searchResultsRenderer.render(results)); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryParser.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryParser.java deleted file mode 100644 index 5551a67a..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryParser.java +++ /dev/null @@ -1,529 +0,0 @@ -package nu.marginalia.wmsa.edge.search.query; - -import lombok.EqualsAndHashCode; -import lombok.ToString; -import nu.marginalia.util.TransformList; -import nu.marginalia.util.language.WordPatterns; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.function.Predicate; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.util.stream.Stream.concat; - -public class QueryParser { - private static final Logger logger = LoggerFactory.getLogger(QueryParser.class); - - private final EnglishDictionary englishDictionary; - private final QueryVariants queryVariants; - - public QueryParser(EnglishDictionary englishDictionary, QueryVariants queryVariants) { - this.englishDictionary = englishDictionary; - this.queryVariants = queryVariants; - } - - public List parse(String query) { - List basicTokens = extractBasicTokens(query); - - TransformList list = new TransformList<>(basicTokens); - - list.transformEach(QueryParser::handleQuoteTokens); - list.transformEach(QueryParser::trimLiterals); - list.transformEachPair(QueryParser::createNegatedTerms); - list.transformEachPair(QueryParser::createPriorityTerms); - list.transformEach(QueryParser::handleSpecialOperations); - list.scanAndTransform(TokenType.LPAREN, TokenType.RPAREN, QueryParser::handleAdvisoryTerms); - - return list.getBackingList(); - } - - private static void handleQuoteTokens(TransformList.Entity entity) { - var t = entity.value; - if (t.type == TokenType.QUOT) { - entity.replace(new Token(TokenType.QUOT_TERM, - t.str.replaceAll("\\s+", WordPatterns.WORD_TOKEN_JOINER), - t.displayStr)); - } - } - - private static void trimLiterals(TransformList.Entity entity) { - var t = entity.value; - - if (t.type == TokenType.LITERAL_TERM - && (t.str.endsWith(":") || t.str.endsWith(".")) - && t.str.length() > 1) { - entity.replace(new Token(TokenType.LITERAL_TERM, t.str.substring(0, t.str.length() - 1), t.displayStr)); - } - - } - - private static void createNegatedTerms(TransformList.Entity first, TransformList.Entity second) { - var t = first.value; - var tn = second.value; - - if (t.type == TokenType.MINUS && tn.type == TokenType.LITERAL_TERM) { - first.remove(); - second.replace(new Token(TokenType.EXCLUDE_TERM, tn.str, "-" + tn.str)); - } - } - private static void createPriorityTerms(TransformList.Entity first, TransformList.Entity second) { - var t = first.value; - var tn = second.value; - - if (t.type == TokenType.QMARK && tn.type == TokenType.LITERAL_TERM) { - first.remove(); - second.replace(new Token(TokenType.PRIORTY_TERM, tn.str, "?" + tn.str)); - } - } - private static void handleSpecialOperations(TransformList.Entity entity) { - var t = entity.value; - if (t.type == TokenType.LITERAL_TERM) { - if (t.str.startsWith("q") && t.str.matches("q[=><]\\d+")) { - entity.replace(new Token(TokenType.QUALITY_TERM, t.str.substring(1), t.displayStr)); - } else if (t.str.startsWith("near:")) { - entity.replace(new Token(TokenType.NEAR_TERM, t.str.substring(5), t.displayStr)); - } else if (t.str.startsWith("year") && t.str.matches("year[=><]\\d{4}")) { - entity.replace(new Token(TokenType.YEAR_TERM, t.str.substring(4), t.displayStr)); - } else if (t.str.startsWith("size") && t.str.matches("size[=><]\\d+")) { - entity.replace(new Token(TokenType.SIZE_TERM, t.str.substring(4), t.displayStr)); - } else if (t.str.startsWith("rank") && t.str.matches("rank[=><]\\d+")) { - entity.replace(new Token(TokenType.RANK_TERM, t.str.substring(4), t.displayStr)); - } else if (t.str.startsWith("qs=")) { - entity.replace(new Token(TokenType.QS_TERM, t.str.substring(3), t.displayStr)); - } else if (t.str.contains(":")) { - entity.replace(new Token(TokenType.ADVICE_TERM, t.str, t.displayStr)); - } - } - } - - private static void handleAdvisoryTerms(TransformList.Entity entity) { - var t = entity.value; - if (t.type == TokenType.LPAREN) { - entity.remove(); - } else if (t.type == TokenType.RPAREN) { - entity.remove(); - } else if (t.type == TokenType.LITERAL_TERM) { - entity.replace(new Token(TokenType.ADVICE_TERM, t.str, "(" + t.str + ")")); - } - } - - private static final Pattern noisePattern = Pattern.compile("[,]"); - - public List extractBasicTokens(String rawQuery) { - List tokens = new ArrayList<>(); - - String query = noisePattern.matcher(rawQuery).replaceAll(" "); - - for (int i = 0; i < query.length(); i++) { - int chr = query.charAt(i); - - if ('(' == chr) { - tokens.add(new Token(TokenType.LPAREN, query.substring(i, i+1).toLowerCase(), query.substring(i, i+1))); - } - else if (')' == chr) { - tokens.add(new Token(TokenType.RPAREN, query.substring(i, i+1).toLowerCase(), query.substring(i, i+1))); - } - else if ('"' == chr) { - int end = query.indexOf('"', i+1); - if (end == -1) { - end = query.length(); - } - tokens.add(new Token(TokenType.QUOT, - query.substring(i+1, end).toLowerCase(), - query.substring(i, Math.min(query.length(), end+1)))); - i = end; - } - else if ('\u201C' == chr) { - int end = query.indexOf('\u201D', i+1); - if (end == -1) { - end = query.length(); - } - tokens.add(new Token(TokenType.QUOT, - query.substring(i+1, end).toLowerCase(), - query.substring(i, Math.min(query.length(), end+1)))); - i = end; - } - else if ('-' == chr) { - tokens.add(new Token(TokenType.MINUS, "-")); - } - else if ('?' == chr) { - tokens.add(new Token(TokenType.QMARK, "?")); - } - else if (Character.isSpaceChar(chr)) { - // - } - else { - - int end = i+1; - for (; end < query.length(); end++) { - if (query.charAt(end) == ' ' || query.charAt(end) == ')') - break; - } - tokens.add(new Token(TokenType.LITERAL_TERM, - query.substring(i, end).toLowerCase(), - query.substring(i, end))); - i = end-1; - } - } - return tokens; - } - - - public List> variantQueries(List items) { - int start = -1; - int end = items.size(); - - for (int i = 0; i < items.size(); i++) { - var token = items.get(i); - - if (start < 0) { - if (token.type == TokenType.LITERAL_TERM && WordPatterns.wordQualitiesPredicate.test(token.str)) { - start = i; - } - } - else { - if (token.type != TokenType.LITERAL_TERM || !WordPatterns.wordPredicateEither.test(token.str)) { - end = i; - break; - } - } - } - - if (start >= 0 && end - start > 1) { - List> variantParts = getVariantSearchTerms(items.subList(start, end)); - int s = start; - int e = end; - return variantParts.stream().map(part -> - concat(items.subList(0, s).stream(), concat(part.stream(), items.subList(e, items.size()).stream())) - .collect(Collectors.toList())) - .peek(lst -> lst.removeIf(this::isJunkWord)) - .limit(24) - .collect(Collectors.toList()); - } - else { - return List.of(items); - } - } - - - - public List> permuteQueries(List items) { - int start = -1; - int end = items.size(); - - for (int i = 0; i < items.size(); i++) { - var token = items.get(i); - - if (start < 0) { - if (token.type == TokenType.LITERAL_TERM && WordPatterns.wordQualitiesPredicate.test(token.str)) { - start = i; - } - } - else { - if (token.type != TokenType.LITERAL_TERM || !WordPatterns.wordPredicateEither.test(token.str)) { - end = i; - break; - } - } - } - - if (start >= 0 && end - start > 1) { - List> permuteParts = combineSearchTerms(items.subList(start, end)); - int s = start; - int e = end; - return permuteParts.stream().map(part -> - concat(items.subList(0, s).stream(), concat(part.stream(), items.subList(e, items.size()).stream())) - .collect(Collectors.toList())) - .peek(lst -> lst.removeIf(this::isJunkWord)) - .limit(24) - .collect(Collectors.toList()); - } - else { - return List.of(items); - } - } - - - public List> permuteQueriesNew(List items) { - int start = -1; - int end = items.size(); - - for (int i = 0; i < items.size(); i++) { - var token = items.get(i); - - if (start < 0) { - if (token.type == TokenType.LITERAL_TERM && WordPatterns.wordQualitiesPredicate.test(token.str)) { - start = i; - } - } - else { - if (token.type != TokenType.LITERAL_TERM || !WordPatterns.wordPredicateEither.test(token.str)) { - end = i; - break; - } - } - } - - if (start >= 0 && end - start >= 1) { - var result = queryVariants.getQueryVariants(items.subList(start, end)); - - logger.debug("{}", result); - - if (result.isEmpty()) { - logger.warn("Empty variants result, falling back on old code"); - return permuteQueries(items); - } - - List> queryVariants = new ArrayList<>(); - for (var query : result.faithful) { - var tokens = query.terms.stream().map(term -> new Token(TokenType.LITERAL_TERM, term)).collect(Collectors.toList()); - tokens.addAll(result.nonLiterals); - - queryVariants.add(tokens); - } - for (var query : result.alternative) { - if (queryVariants.size() >= 6) - break; - - var tokens = query.terms.stream().map(term -> new Token(TokenType.LITERAL_TERM, term)).collect(Collectors.toList()); - tokens.addAll(result.nonLiterals); - - queryVariants.add(tokens); - } - - List> returnValue = new ArrayList<>(queryVariants.size()); - for (var variant: queryVariants) { - List r = new ArrayList<>(start + variant.size() + (items.size() - end)); - r.addAll(items.subList(0, start)); - r.addAll(variant); - r.addAll(items.subList(end, items.size())); - returnValue.add(r); - } - - return returnValue; - - } - else { - return List.of(items); - } - } - - private boolean isJunkWord(Token token) { - if (WordPatterns.isStopWord(token.str) && - !token.str.matches("^(\\d+|([a-z]+:.*))$")) { - return true; - } - return switch (token.str) { - case "vs", "versus", "or", "and" -> true; - default -> false; - }; - } - - private List> getVariantSearchTerms(List subList) { - int size = subList.size(); - if (size < 1) { - return Collections.emptyList(); - } - else if (size == 1) { - if (WordPatterns.isStopWord(subList.get(0).str)) { - return Collections.emptyList(); - } - return getWordVariants(subList.get(0)).map(List::of).collect(Collectors.toList()); - } - - List> cdrs = getVariantSearchTerms(subList.subList(1, subList.size())); - List cars = getWordVariants(subList.get(0)).collect(Collectors.toList()); - - List> ret = new ArrayList<>(cars.size() * cdrs.size()); - for (var car : cars) { - if (ret.size() >= 32) { - break; - } - for (var cdr : cdrs) { - ret.add(List.of(joinTokens(prepend(car, cdr)))); - } - } - return ret; - } - - private Stream getWordVariants(Token token) { - var s = token.str; - int sl = s.length(); - Stream base = Stream.of(token); - Stream alternatives; - if (sl < 2) { - return base; - } - if (s.endsWith("s")) { - alternatives = Stream.of(s.substring(0, sl-1), s + "es"); - } - else if (s.matches(".*(\\w)\\1ing$") && sl > 4) { // humming, clapping - var basea = s.substring(0, sl-4); - var baseb = s.substring(0, sl-3); - alternatives = Stream.of(basea, baseb + "ed"); - } - else { - alternatives = Stream.of(s+"s", s+"ing", s+"ed"); - } - - return Stream.concat(Stream.of(token), alternatives.filter(englishDictionary::isWord).map(str -> new Token(token.type, str, token.displayStr))); - } - - private List prepend(Token t, List lst) { - List ret = new ArrayList<>(lst.size() + 1); - ret.add(t); - ret.addAll(lst); - return ret; - } - - private List> combineSearchTerms(List subList) { - int size = subList.size(); - if (size < 1) { - return Collections.emptyList(); - } - else if (size == 1) { - if (WordPatterns.isStopWord(subList.get(0).str)) { - return Collections.emptyList(); - } - return List.of(subList); - } - - List> results = new ArrayList<>(size*(size+1)/2); - - if (subList.size() <= 4 && subList.get(0).str.length() >= 2 && !isPrefixWord(subList.get(subList.size()-1).str)) { - results.add(List.of(joinTokens(subList))); - } -outer: for (int i = size - 1; i >= 1; i--) { - - var left = combineSearchTerms(subList.subList(0, i)); - var right = combineSearchTerms(subList.subList(i, size)); - - for (var l : left) { - if (results.size() > 48) { - break outer; - } - - for (var r : right) { - if (results.size() > 48) { - break outer; - } - - List combined = new ArrayList<>(l.size() + r.size()); - combined.addAll(l); - combined.addAll(r); - if (!results.contains(combined)) { - results.add(combined); - } - } - } - } - if (!results.contains(subList)) { - results.add(subList); - } - Comparator> tc = (o1, o2) -> { - int dJoininess = o2.stream().mapToInt(s->(int)Math.pow(joininess(s.str), 2)).sum() - - o1.stream().mapToInt(s->(int)Math.pow(joininess(s.str), 2)).sum(); - if (dJoininess == 0) { - return (o2.stream().mapToInt(s->(int)Math.pow(rightiness(s.str), 2)).sum() - - o1.stream().mapToInt(s->(int)Math.pow(rightiness(s.str), 2)).sum()); - } - return (int) Math.signum(dJoininess); - }; - results.sort(tc); - return results; - } - - private boolean isPrefixWord(String str) { - return switch (str) { - case "the", "of", "when" -> true; - default -> false; - }; - } - - private boolean isSuffixWord(String str) { - return (str.length() < 2); - } - - - int joininess(String s) { - return (int) s.chars().filter(c -> c == '_').count(); - } - int rightiness(String s) { - int rightiness = 0; - for (int i = 0; i < s.length(); i++) { - if (s.charAt(i) == '_') { - rightiness+=i; - } - } - return rightiness; - } - - private Token joinTokens(List subList) { - return new Token(TokenType.LITERAL_TERM, - subList.stream().map(t -> t.str).collect(Collectors.joining("_")), - subList.stream().map(t -> t.str).collect(Collectors.joining(" "))); - } -} - -@ToString @EqualsAndHashCode -class Token { - public TokenType type; - public String str; - public final String displayStr; - - Token(TokenType type, String str, String displayStr) { - this.type = type; - this.str = str; - this.displayStr = safeString(displayStr); - } - - - Token(TokenType type, String str) { - this.type = type; - this.str = str; - this.displayStr = safeString(str); - } - - private static String safeString(String s) { - return s.replaceAll("<", "<") - .replaceAll(">", ">"); - } -} - -enum TokenType implements Predicate { - TERM, - - - LITERAL_TERM, - QUOT_TERM, - EXCLUDE_TERM, - ADVICE_TERM, - PRIORTY_TERM, - - QUALITY_TERM, - YEAR_TERM, - SIZE_TERM, - RANK_TERM, - NEAR_TERM, - - QS_TERM, - - QUOT, - MINUS, - QMARK, - LPAREN, - RPAREN, - - IGNORE; - - public boolean test(Token t) { - return t.type == this; - } -} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/model/EdgeUserSearchParameters.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/model/EdgeUserSearchParameters.java deleted file mode 100644 index a84273fb..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/model/EdgeUserSearchParameters.java +++ /dev/null @@ -1,7 +0,0 @@ -package nu.marginalia.wmsa.edge.search.query.model; - -import nu.marginalia.wmsa.edge.search.command.SearchJsParameter; -import nu.marginalia.wmsa.edge.search.model.EdgeSearchProfile; - -public record EdgeUserSearchParameters (String humanQuery, EdgeSearchProfile profile, SearchJsParameter jsSetting) { -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/UrlDeduplicator.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/UrlDeduplicator.java deleted file mode 100644 index 603731a7..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/UrlDeduplicator.java +++ /dev/null @@ -1,39 +0,0 @@ -package nu.marginalia.wmsa.edge.search.results; - -import gnu.trove.map.hash.TObjectIntHashMap; -import gnu.trove.set.hash.TIntHashSet; -import nu.marginalia.wmsa.edge.model.search.EdgeUrlDetails; - -public class UrlDeduplicator { - private final TIntHashSet seenSuperficialhashes = new TIntHashSet(200); - private final TIntHashSet seenDataHashes = new TIntHashSet(200); - private final TObjectIntHashMap keyCount = new TObjectIntHashMap<>(200, 0.75f, 0); - - private final int resultsPerKey; - public UrlDeduplicator(int resultsPerKey) { - this.resultsPerKey = resultsPerKey; - } - - public boolean shouldRemove(EdgeUrlDetails details) { - return !filter(details); - } - public synchronized boolean filter(EdgeUrlDetails details) { - if (!seenSuperficialhashes.add(details.getSuperficialHash())) { - return false; - } - if (!seenDataHashes.add(details.getDataHash())) { - return false; - } - final var domain = details.getUrl().getDomain(); - final String key; - - if (!details.isSpecialDomain()) { - key = domain.getLongDomainKey(); - } - else { - key = domain.getDomainKey(); - } - - return keyCount.adjustOrPutValue(key, 1, 1) < resultsPerKey; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchApiQueryService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchApiQueryService.java deleted file mode 100644 index 6c5ae7fd..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchApiQueryService.java +++ /dev/null @@ -1,55 +0,0 @@ -package nu.marginalia.wmsa.edge.search.svc; - -import com.google.common.base.Strings; -import com.google.inject.Inject; -import lombok.SneakyThrows; -import nu.marginalia.wmsa.api.model.ApiSearchResult; -import nu.marginalia.wmsa.api.model.ApiSearchResults; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.search.EdgeSearchOperator; -import nu.marginalia.wmsa.edge.search.command.SearchJsParameter; -import nu.marginalia.wmsa.edge.search.model.EdgeSearchProfile; -import nu.marginalia.wmsa.edge.search.query.model.EdgeUserSearchParameters; -import spark.Request; -import spark.Response; - -import java.util.stream.Collectors; - -public class EdgeSearchApiQueryService { - private EdgeSearchOperator searchOperator; - - @Inject - public EdgeSearchApiQueryService(EdgeSearchOperator searchOperator) { - this.searchOperator = searchOperator; - } - - @SneakyThrows - public Object apiSearch(Request request, Response response) { - - final var ctx = Context.fromRequest(request); - final String queryParam = request.queryParams("query"); - final int limit; - EdgeSearchProfile profile = EdgeSearchProfile.YOLO; - - String count = request.queryParamOrDefault("count", "20"); - limit = Integer.parseInt(count); - - String index = request.queryParamOrDefault("index", "0"); - if (!Strings.isNullOrEmpty(index)) { - profile = switch (index) { - case "0" -> EdgeSearchProfile.YOLO; - case "1" -> EdgeSearchProfile.MODERN; - case "2" -> EdgeSearchProfile.DEFAULT; - case "3" -> EdgeSearchProfile.CORPO_CLEAN; - default -> EdgeSearchProfile.CORPO_CLEAN; - }; - } - - final String humanQuery = queryParam.trim(); - - var results = searchOperator.doApiSearch(ctx, new EdgeUserSearchParameters(humanQuery, profile, SearchJsParameter.DEFAULT)); - - return new ApiSearchResults("RESTRICTED", humanQuery, results.stream().map(ApiSearchResult::new).limit(limit).collect(Collectors.toList())); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchDomainSearchService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchDomainSearchService.java deleted file mode 100644 index 74e8681c..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchDomainSearchService.java +++ /dev/null @@ -1,65 +0,0 @@ -package nu.marginalia.wmsa.edge.search.svc; - -import com.google.inject.Inject; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexClient; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.id.EdgeIdList; -import nu.marginalia.wmsa.edge.model.id.EdgeIdSet; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSpecification; -import nu.marginalia.wmsa.edge.model.search.domain.EdgeDomainSearchSpecification; -import nu.marginalia.wmsa.edge.search.model.BrowseResult; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class EdgeSearchDomainSearchService { - - private final EdgeIndexClient indexClient; - private final EdgeDataStoreDao edgeDataStoreDao; - - @Inject - public EdgeSearchDomainSearchService(EdgeIndexClient indexClient, EdgeDataStoreDao edgeDataStoreDao) { - this.indexClient = indexClient; - this.edgeDataStoreDao = edgeDataStoreDao; - } - - - public List getDomainResults(Context ctx, EdgeSearchSpecification specs) { - - List keywords = getKeywordsFromSpecs(specs); - - if (keywords.isEmpty()) - return Collections.emptyList(); - - List requests = new ArrayList<>(keywords.size()); - - for (var keyword : keywords) { - requests.add(new EdgeDomainSearchSpecification(keyword, - 1_000_000, 3, 25)); - } - - EdgeIdSet dedup = new EdgeIdSet<>(); - EdgeIdList values = new EdgeIdList<>(); - - for (var result : indexClient.queryDomains(ctx, requests)) { - for (int id : result.getResults().values()) { - if (dedup.add(id)) - values.add(id); - } - } - - return edgeDataStoreDao.getBrowseResultFromUrlIds(values); - } - - - private List getKeywordsFromSpecs(EdgeSearchSpecification specs) { - return specs.subqueries.stream() - .filter(sq -> sq.searchTermsExclude.isEmpty() && sq.searchTermsInclude.size() == 1 && sq.searchTermsAdvice.isEmpty()) - .map(sq -> sq.searchTermsInclude.get(0)) - .distinct() - .toList(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchWikiArticlesService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchWikiArticlesService.java deleted file mode 100644 index 7a197763..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchWikiArticlesService.java +++ /dev/null @@ -1,41 +0,0 @@ -package nu.marginalia.wmsa.edge.search.svc; - -import com.google.inject.Inject; -import com.google.inject.Singleton; -import io.reactivex.rxjava3.core.Observable; -import io.reactivex.rxjava3.schedulers.Schedulers; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.assistant.dict.WikiArticles; -import nu.marginalia.wmsa.encyclopedia.EncyclopediaClient; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.Future; - -@Singleton -public class EdgeSearchWikiArticlesService { - private final EncyclopediaClient encyclopediaClient; - - @Inject - public EdgeSearchWikiArticlesService(EncyclopediaClient encyclopediaClient) { - this.encyclopediaClient = encyclopediaClient; - } - - @NotNull - public Future getWikiArticle(Context ctx, String humanQuery) { - - if (!encyclopediaClient.isAlive()) { - return Observable.just(new WikiArticles()).toFuture(); - } - - return encyclopediaClient - .encyclopediaLookup(ctx, - humanQuery.replaceAll("\\s+", "_") - .replaceAll("\"", "") - ) - .subscribeOn(Schedulers.io()) - .onErrorReturn(e -> new WikiArticles()) - .toFuture() - ; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/EncyclopediaLoaderTool.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/EncyclopediaLoaderTool.java deleted file mode 100644 index f3582b12..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/EncyclopediaLoaderTool.java +++ /dev/null @@ -1,85 +0,0 @@ -package nu.marginalia.wmsa.edge.tools; - -import com.github.luben.zstd.Zstd; -import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.util.ParallelPipe; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.assistant.dict.WikiCleaner; -import org.mariadb.jdbc.Driver; -import org.openzim.ZIMTypes.ZIMFile; -import org.openzim.ZIMTypes.ZIMReader; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -public class EncyclopediaLoaderTool extends ParallelPipe implements AutoCloseable { - - public static void main(String[] args) throws IOException, InterruptedException, SQLException { - - org.mariadb.jdbc.Driver driver = new Driver(); - - try (var loader = new EncyclopediaLoaderTool(new DatabaseModule().provideConnection())) { - var zr = new ZIMReader(new ZIMFile(args[0])); - - zr.forEachArticles((url, art) -> { - if (art != null) { - loader.accept(new ArticleRaw(url, art)); - } - }, p->true); - - } - catch (Exception ex) { - ex.printStackTrace(); - } - System.exit(0); - } - - public record ArticleRaw(String url, String art) { - public ArticleProcessed toProcessed(String data) { - return new ArticleProcessed(url, data); - } - } - public record ArticleProcessed(String url, String art) {} - - - private final HikariDataSource dataSource; - private final Connection connection; - private final PreparedStatement insertArticleDataStatement; - - private final WikiCleaner wikiCleaner = new WikiCleaner(); - - public EncyclopediaLoaderTool(HikariDataSource dataSource) throws SQLException { - super("EncyclopediaPipe", 24, 4, 2); - this.dataSource = dataSource; - this.connection = dataSource.getConnection(); - this.insertArticleDataStatement = connection.prepareStatement("REPLACE INTO REF_WIKI_ARTICLE(NAME, ENTRY) VALUES (?, ?)"); - - } - - @Override - protected ArticleProcessed onProcess(ArticleRaw articleRaw) { - return articleRaw.toProcessed(wikiCleaner.cleanWikiJunk("https://en.wikipedia.org/wiki/" + articleRaw.url, articleRaw.art)); - } - - @Override - protected void onReceive(ArticleProcessed articleProcessed) throws Exception { - if (articleProcessed.art == null) return; - - try (var bs = new ByteArrayInputStream(Zstd.compress(articleProcessed.art.getBytes(StandardCharsets.UTF_8)))) { - insertArticleDataStatement.setString(1, articleProcessed.url); - insertArticleDataStatement.setBlob(2, bs); - insertArticleDataStatement.executeUpdate(); - } - } - - public void close() throws Exception { - join(); - if (insertArticleDataStatement != null) insertArticleDataStatement.close(); - if (connection != null) connection.close(); - if (dataSource != null) dataSource.close(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/FeaturesLoaderTool.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/FeaturesLoaderTool.java deleted file mode 100644 index a20fc294..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/FeaturesLoaderTool.java +++ /dev/null @@ -1,85 +0,0 @@ -package nu.marginalia.wmsa.edge.tools; - -import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.converting.interpreter.instruction.DocumentKeywords; -import nu.marginalia.wmsa.edge.converting.processor.logic.HtmlFeature; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexClient; -import nu.marginalia.wmsa.edge.index.model.EdgePageDocumentsMetadata; -import nu.marginalia.wmsa.edge.model.id.EdgeId; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -public class FeaturesLoaderTool { - public static void main(String... args) { - - HtmlFeature feature = HtmlFeature.valueOf(args[0]); - Path file = Path.of(args[1]); - - try (EdgeIndexClient client = new EdgeIndexClient(); - HikariDataSource ds = new DatabaseModule().provideConnection(); - Connection conn = ds.getConnection(); - PreparedStatement ps = conn.prepareStatement("UPDATE EC_PAGE_DATA SET FEATURES = FEATURES | ? WHERE ID=?"); - var linesStream = Files.lines(file)) { - - var urls = getUrls(ds); - linesStream - .map(urls::get) - .filter(Objects::nonNull) - .forEach(id -> { - int urlId = (int)(id & 0xFFFF_FFFFL); - int domainId = (int)(id >>> 32L); - - try { - ps.setInt(2, urlId); - ps.setInt(1, feature.getFeatureBit()); - ps.executeUpdate(); - } - catch (SQLException ex) { - throw new RuntimeException(ex); - } - - client.putWords(Context.internal(), new EdgeId<>(domainId), new EdgeId<>(urlId), - new EdgePageDocumentsMetadata(EdgePageDocumentsMetadata.defaultValue()), - new DocumentKeywords(new String[] { feature.getKeyword() }, new long[] { 0 }) - , 0); - }); - - } catch (IOException | SQLException e) { - throw new RuntimeException(e); - } - } - - private static Map getUrls(HikariDataSource ds) { - - Map urls = new HashMap<>(100_000); - - try (var conn = ds.getConnection(); - var stmt = conn.createStatement()) - { - var rsp = stmt.executeQuery("SELECT URL, ID, DOMAIN_ID FROM EC_URL_VIEW WHERE TITLE IS NOT NULL"); - - while (rsp.next()) { - long val = rsp.getInt(3); - val = (val << 32L) | rsp.getInt(2); - - urls.put(rsp.getString(1), val); - } - - } - catch (SQLException ex) { - throw new RuntimeException(ex); - } - - return urls; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/IndexJournalDumpTool.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/IndexJournalDumpTool.java deleted file mode 100644 index 323b1279..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/IndexJournalDumpTool.java +++ /dev/null @@ -1,47 +0,0 @@ -package nu.marginalia.wmsa.edge.tools; - -import com.google.common.hash.Hashing; -import net.agkn.hll.HLL; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReaderSingleFile; - -import java.io.IOException; -import java.nio.file.Path; - -public class IndexJournalDumpTool { - public static void main(String... args) throws IOException { - final String operation = args.length > 0 ? args[0] : "help"; - - switch (operation) { - case "dump": - dump(Path.of(args[1])); - break; - case "cardinality": - cardinality(Path.of(args[1])); - break; - default: - System.err.println("Usage: dump|cardinality index-file.dat"); - break; - } - - } - - private static void cardinality(Path file) throws IOException { - var reader = new SearchIndexJournalReaderSingleFile(LongArray.mmapRead(file)); - HLL hyperloglog = new HLL(30, 1); - var hashFunction = Hashing.murmur3_128(); - - for (var entry : reader) { - hyperloglog.addRaw(hashFunction.hashLong(entry.docId()).padToLong()); - } - - System.out.println(hyperloglog.cardinality()); - } - - private static void dump(Path file) throws IOException { - var reader = new SearchIndexJournalReaderSingleFile(LongArray.mmapRead(file)); - for (var entry : reader) { - System.out.printf("%s\t%010d\t%06d:%08d\n", entry.docId(), entry.domainId(), entry.urlId()); - } - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/SearchIndexScrubberMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/SearchIndexScrubberMain.java deleted file mode 100644 index 9cb945e4..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/SearchIndexScrubberMain.java +++ /dev/null @@ -1,69 +0,0 @@ -package nu.marginalia.wmsa.edge.tools; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.file.Path; - -public class SearchIndexScrubberMain { - public static final Logger logger = LoggerFactory.getLogger(SearchIndexScrubberMain.class); - private static final int CHUNK_HEADER_SIZE = 16; - - public static void main(String... args) throws IOException { - var inputFile = Path.of(args[0]).toFile(); - var outputFile = Path.of(args[1]).toFile(); - - logger.info("Scrubbing {}", inputFile); - - final RandomAccessFile raf = new RandomAccessFile(inputFile, "r"); - - var fileLength = raf.readLong(); - var wordCount = raf.readInt(); - - logger.info("Word Count: {}", wordCount); - logger.info("File Length: {}", fileLength); - - var channel = raf.getChannel(); - - ByteBuffer inByteBuffer = ByteBuffer.allocateDirect(10_000); - - RandomAccessFile[] randomAccessFiles = new RandomAccessFile[1]; - - for (int i = 0; i < randomAccessFiles.length; i++) { - randomAccessFiles[i] = new RandomAccessFile(outputFile, "rw"); - randomAccessFiles[i].seek(12); - } - FileChannel[] fileChannels = new FileChannel[1]; - for (int i = 0; i < fileChannels.length; i++) { - fileChannels[i] = randomAccessFiles[i].getChannel(); - } - - while (channel.position() < fileLength) { - inByteBuffer.clear(); - inByteBuffer.limit(CHUNK_HEADER_SIZE); - channel.read(inByteBuffer); - inByteBuffer.flip(); - long urlId = inByteBuffer.getLong(); - int chunkBlock = inByteBuffer.getInt(); - int count = inByteBuffer.getInt(); - inByteBuffer.clear(); - inByteBuffer.limit(count*4+CHUNK_HEADER_SIZE); - inByteBuffer.putLong(urlId); - inByteBuffer.putInt(chunkBlock); - inByteBuffer.putInt(count); - channel.read(inByteBuffer); - } - - long size = randomAccessFiles[0].getFilePointer(); - - randomAccessFiles[0].seek(0); - randomAccessFiles[0].writeLong(size); - randomAccessFiles[0].writeInt(wordCount); - - randomAccessFiles[0].close(); - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/StripSimpleJournalEntriesToolMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/StripSimpleJournalEntriesToolMain.java deleted file mode 100644 index c3d4a1e6..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/StripSimpleJournalEntriesToolMain.java +++ /dev/null @@ -1,28 +0,0 @@ -package nu.marginalia.wmsa.edge.tools; - -import nu.marginalia.util.array.LongArray; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalCleaner; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReadEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReaderSingleFile; - -import java.io.IOException; -import java.nio.file.Path; - -import static nu.marginalia.wmsa.edge.index.model.EdgePageDocumentFlags.Simple; - -public class StripSimpleJournalEntriesToolMain { - - public static void main(String[] args) throws IOException { - Path input = Path.of(args[0]); - Path output = Path.of(args[1]); - - new SearchIndexJournalCleaner(new SearchIndexJournalReaderSingleFile(LongArray.mmapRead(input))) - .clean(output, StripSimpleJournalEntriesToolMain::retainEntry); - - System.out.println("All done!"); - } - - private static boolean retainEntry(SearchIndexJournalReadEntry entry) { - return (entry.header.documentMeta() & Simple.asBit()) == 0; - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaClient.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaClient.java deleted file mode 100644 index c2215526..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaClient.java +++ /dev/null @@ -1,35 +0,0 @@ -package nu.marginalia.wmsa.encyclopedia; - -import io.reactivex.rxjava3.core.Observable; -import nu.marginalia.wmsa.client.AbstractDynamicClient; -import nu.marginalia.wmsa.client.HttpStatusCode; -import nu.marginalia.wmsa.client.exception.RouteNotConfiguredException; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.assistant.dict.WikiArticles; -import okhttp3.MediaType; -import org.eclipse.jetty.util.UrlEncoded; - -import javax.annotation.CheckReturnValue; - -public class EncyclopediaClient extends AbstractDynamicClient { - public EncyclopediaClient() { - super(ServiceDescriptor.ENCYCLOPEDIA); - } - - @CheckReturnValue - public Observable submitWiki(Context ctx, String url, String data) { - return super.post(ctx, "/wiki/submit?url="+UrlEncoded.encodeString(url), data, MediaType.parse("text/plain; charset=UTF-8")); - } - - @CheckReturnValue - public Observable encyclopediaLookup(Context ctx, String word) { - try { - return super.get(ctx, "/encyclopedia/" + UrlEncoded.encodeString(word), WikiArticles.class); - } - catch (RouteNotConfiguredException ex) { - return Observable.fromSupplier(WikiArticles::new); - } - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaDao.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaDao.java deleted file mode 100644 index d9b9d5e9..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaDao.java +++ /dev/null @@ -1,154 +0,0 @@ -package nu.marginalia.wmsa.encyclopedia; - -import com.github.luben.zstd.ZstdInputStream; -import com.google.inject.Inject; -import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.edge.assistant.dict.WikiArticles; -import nu.marginalia.wmsa.edge.assistant.dict.WikiSearchResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.OutputStream; -import java.util.*; -import java.util.stream.Collectors; - -public class EncyclopediaDao { - - private final HikariDataSource dataSource; - private static final Logger logger = LoggerFactory.getLogger(EncyclopediaDao.class); - - @Inject - public EncyclopediaDao(HikariDataSource dataSource) { - this.dataSource = dataSource; - } - - public boolean getWikiArticleData(String name, OutputStream outputStream) { - try (var conn = dataSource.getConnection(); - var stmt = conn.prepareStatement("SELECT ENTRY FROM REF_WIKI_ARTICLE WHERE NAME=? AND ENTRY IS NOT NULL")) - { - stmt.setString(1, name); - var rsp = stmt.executeQuery(); - if (rsp.next()) { - new ZstdInputStream(rsp.getBlob(1).getBinaryStream()).transferTo(outputStream); - return true; - } - } - catch (Exception ex) { - logger.error("Failed to fetch article", ex); - } - return false; - } - - public WikiArticles encyclopedia(String term) { - WikiArticles response = new WikiArticles(); - response.entries = new ArrayList<>(); - - try (var connection = dataSource.getConnection()) { - var stmt = connection.prepareStatement("SELECT DISTINCT(NAME) FROM REF_WIKI_ARTICLE WHERE NAME=?"); - stmt.setString(1, term); - - var rsp = stmt.executeQuery(); - while (rsp.next()) { - response.entries.add(capitalizeWikiString(rsp.getString(1))); - } - } - catch (Exception ex) { - logger.error("Failed to fetch articles", ex); - return new WikiArticles(); - } - - return response; - } - - public Optional resolveEncylopediaRedirect(String term) { - final List matches = new ArrayList<>(); - - try (var connection = dataSource.getConnection()) { - try (var stmt = connection.prepareStatement("SELECT NAME, REF_NAME FROM REF_WIKI_ARTICLE WHERE NAME=?")) { - stmt.setString(1, term); - - var rsp = stmt.executeQuery(); - while (rsp.next()) { - if (term.equals(rsp.getString(1)) - || rsp.getString(2) == null) { - return Optional.ofNullable(rsp.getString(2)); - } else { - matches.add(rsp.getString(2)); - } - } - } - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - - if (!matches.isEmpty()) { - return Optional.of(matches.get(0)); - } - return Optional.empty(); - } - - - public List findEncyclopediaPages(String term) { - final List directMatches = new ArrayList<>(); - final Set directSearchMatches = new HashSet<>(); - final Set indirectMatches = new HashSet<>(); - - try (var connection = dataSource.getConnection()) { - - try (var stmt = connection.prepareStatement("SELECT NAME, REF_NAME FROM REF_WIKI_ARTICLE WHERE NAME=?")) { - stmt.setString(1, term.replace(' ', '_')); - - var rsp = stmt.executeQuery(); - while (rsp.next()) { - String name = rsp.getString(1); - String refName = rsp.getString(2); - - if (refName == null) { - directMatches.add(new WikiSearchResult(name, null)); - } else { - indirectMatches.add(new WikiSearchResult(name, refName)); - } - } - } - - try (var stmt = connection.prepareStatement("SELECT NAME, REF_NAME FROM REF_WIKI_ARTICLE WHERE NAME LIKE ? LIMIT 10")) { - stmt.setString(1, term.replace(' ', '_').replaceAll("%", "\\%").toLowerCase() + "%"); - - var rsp = stmt.executeQuery(); - while (rsp.next()) { - String name = rsp.getString(1); - String refName = rsp.getString(2); - - if (refName == null) { - directSearchMatches.add(new WikiSearchResult(name, null)); - } else { - indirectMatches.add(new WikiSearchResult(name, refName)); - } - } - } - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - - directMatches.forEach(indirectMatches::remove); - indirectMatches.removeAll(directSearchMatches); - directMatches.forEach(directSearchMatches::remove); - directMatches.addAll(indirectMatches); - directMatches.addAll(directSearchMatches); - return directMatches; - } - - private String capitalizeWikiString(String string) { - if (string.contains("_")) { - return Arrays.stream(string.split("_")).map(this::capitalizeWikiString).collect(Collectors.joining("_")); - } - if (string.length() < 2) { - return string.toUpperCase(); - } - return Character.toUpperCase(string.charAt(0)) + string.substring(1).toLowerCase(); - } - - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaMain.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaMain.java deleted file mode 100644 index ee364dcc..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaMain.java +++ /dev/null @@ -1,29 +0,0 @@ -package nu.marginalia.wmsa.encyclopedia; - -import com.google.inject.Guice; -import com.google.inject.Inject; -import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; - -public class EncyclopediaMain extends MainClass { - private final EncyclopediaService service; - - public static void main(String... args) { - init(ServiceDescriptor.ENCYCLOPEDIA, args); - - Injector injector = Guice.createInjector( - new EncyclopediaModule(), - new DatabaseModule(), - new ConfigurationModule()); - injector.getInstance(EncyclopediaMain.class); - } - - @Inject - public EncyclopediaMain(EncyclopediaService service) { - this.service = service; - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaModule.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaModule.java deleted file mode 100644 index 74301b2d..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaModule.java +++ /dev/null @@ -1,9 +0,0 @@ -package nu.marginalia.wmsa.encyclopedia; - -import com.google.inject.AbstractModule; - -public class EncyclopediaModule extends AbstractModule { - @Override - public void configure() { - } -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaService.java deleted file mode 100644 index 468e5f22..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/encyclopedia/EncyclopediaService.java +++ /dev/null @@ -1,94 +0,0 @@ -package nu.marginalia.wmsa.encyclopedia; - -import com.google.gson.Gson; -import com.google.inject.Inject; -import com.google.inject.name.Named; -import lombok.SneakyThrows; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import spark.Request; -import spark.Response; -import spark.Spark; - -import java.io.IOException; -import java.util.Map; - -public class EncyclopediaService extends Service { - - private static final Logger logger = LoggerFactory.getLogger(EncyclopediaService.class); - private final MustacheRenderer wikiErrorPageRenderer; - private final MustacheRenderer wikiSearchResultRenderer; - - private final EncyclopediaDao encyclopediaDao; - - @Inject - public EncyclopediaService(@Named("service-host") String ip, - @Named("service-port") Integer port, - EncyclopediaDao encyclopediaDao, - RendererFactory rendererFactory, - Initialization initialization, - MetricsServer metricsServer) - throws IOException { - - super(ip, port, initialization, metricsServer); - this.encyclopediaDao = encyclopediaDao; - - if (rendererFactory != null) { - wikiErrorPageRenderer = rendererFactory.renderer("encyclopedia/wiki-error"); - wikiSearchResultRenderer = rendererFactory.renderer("encyclopedia/wiki-search"); - } - else { - wikiErrorPageRenderer = null; - wikiSearchResultRenderer = null; - } - - Gson gson = GsonFactory.get(); - - Spark.get("/public/wiki/*", this::getWikiPage); - Spark.get("/public/wiki-search", this::searchWikiPage); - Spark.get("/encyclopedia/:term", (rq, rsp) -> encyclopediaDao.encyclopedia(rq.params("term")), gson::toJson); - - Spark.awaitInitialization(); - } - - @SneakyThrows - private Object getWikiPage(Request req, Response rsp) { - final String[] splats = req.splat(); - - if (splats.length == 0) - rsp.redirect("https://encyclopedia.marginalia.nu/wiki-start.html"); - - final String name = splats[0]; - - String pageName = encyclopediaDao.resolveEncylopediaRedirect(name).orElse(name); - logger.info("Resolved {} -> {}", name, pageName); - - if (!encyclopediaDao.getWikiArticleData(name, rsp.raw().getOutputStream())) { - return wikiErrorPageRenderer.render("https://en.wikipedia.org/wiki/" + name); - } - return ""; - } - - @SneakyThrows - private Object searchWikiPage(Request req, Response rsp) { - String term = req.queryParams("query"); - - if (null == term) { - rsp.redirect("https://encyclopedia.marginalia.nu/wiki-start.html"); - return ""; - } - - return wikiSearchResultRenderer.render( - Map.of("query", term, - "results", - encyclopediaDao.findEncyclopediaPages(term)) - ); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/client/MemexApiClient.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/client/MemexApiClient.java deleted file mode 100644 index b038637d..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/client/MemexApiClient.java +++ /dev/null @@ -1,14 +0,0 @@ -package nu.marginalia.wmsa.memex.client; - -import com.google.inject.Inject; -import nu.marginalia.wmsa.client.AbstractDynamicClient; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; - - -public class MemexApiClient extends AbstractDynamicClient { - @Inject - public MemexApiClient() { - super(ServiceDescriptor.MEMEX); - } - -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererableDirect.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererableDirect.java deleted file mode 100644 index 571d5f56..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererableDirect.java +++ /dev/null @@ -1,7 +0,0 @@ -package nu.marginalia.wmsa.memex.model.render; - -import nu.marginalia.wmsa.memex.renderer.MemexHtmlRenderer; - -public interface MemexRendererableDirect { - String render(MemexHtmlRenderer renderer); -} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/StatusRendererService.java b/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/StatusRendererService.java deleted file mode 100644 index 3d2e3d4e..00000000 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/StatusRendererService.java +++ /dev/null @@ -1,81 +0,0 @@ -package nu.marginalia.wmsa.renderer; - -import com.google.inject.Inject; -import io.reactivex.rxjava3.schedulers.Schedulers; -import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; -import nu.marginalia.wmsa.resource_store.ResourceStoreClient; -import nu.marginalia.wmsa.resource_store.model.RenderedResource; -import okhttp3.OkHttpClient; -import okhttp3.Request; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -public class StatusRendererService { - private final MustacheRenderer statusRenderer; - private final ResourceStoreClient resourceStoreClient; - - private final OkHttpClient client; - - private final RendererFactory rendererFactory = new RendererFactory(); - - @Inject - @SneakyThrows - public StatusRendererService(ResourceStoreClient resourceStoreClient) { - this.resourceStoreClient = resourceStoreClient; - - client = new OkHttpClient.Builder() - .connectTimeout(50, TimeUnit.MILLISECONDS) - .readTimeout(1, TimeUnit.SECONDS) - .retryOnConnectionFailure(false) - .followRedirects(false) - .build(); - statusRenderer = rendererFactory.renderer( "status/server-status"); - } - - public void start() { - Schedulers.io().schedulePeriodicallyDirect(this::renderStatusPage, 1, 60, TimeUnit.SECONDS); - } - public void renderStatusPage() { - try { - var status = getStatus(); - var page = statusRenderer.render(Map.of("status", status)); - resourceStoreClient - .putResource(Context.internal(), "status", - new RenderedResource("index.html", LocalDateTime.now().plus(2, ChronoUnit.MINUTES), page)) - .blockingSubscribe(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private List getStatus() { - List status = new ArrayList<>(ServiceDescriptor.values().length); - - for (ServiceDescriptor sd : ServiceDescriptor.values()) { - if (sd.port == 0) { - continue; - } - try { - var req = new Request.Builder().url("http://127.0.0.1:" + sd.port + "/internal/ping").get().build(); - var call = client.newCall(req); - - call.execute().close(); - status.add(new ServerStatusModel(sd.name, "UP")); - - } catch (Exception e) { - status.add(new ServerStatusModel(sd.name, "DOWN")); - } - } - return status; - } - -} diff --git a/marginalia_nu/src/main/resources/data/smhi/stader.csv b/marginalia_nu/src/main/resources/data/smhi/stader.csv deleted file mode 100644 index 0dc649c4..00000000 --- a/marginalia_nu/src/main/resources/data/smhi/stader.csv +++ /dev/null @@ -1,134 +0,0 @@ -"Åkersberga",59.47944,18.29967 -"Alby",59.2335,17.8538 -"Alingsås",57.93033,12.53345 -"Ängelholm",56.2428,12.86219 -"Arboga",59.39387,15.83882 -"Årsta",59.2978,18.0514 -"Arvika",59.65528,12.58518 -"Avesta",60.14274,16.16295 -"Bålsta",59.5671,17.52781 -"Boden",65.82518,21.68864 -"Bollnäs",61.34817,16.39464 -"Boo",59.33333,18.28333 -"Borås",57.72101,12.9401 -"Borlänge",60.4858,15.43714 -"Bromma",59.34,17.94 -"Enköping",59.63607,17.07768 -"Eskilstuna",59.36661,16.5077 -"Eslöv",55.83928,13.30393 -"Fagersta",60.00418,15.79316 -"Falkenberg",56.90552,12.49118 -"Falköping",58.17347,13.55068 -"Falun",60.60357,15.62597 -"Finspång",58.70578,15.76739 -"Gävle",60.67452,17.14174 -"Gislaved",57.3044,13.54078 -"Göteborg",57.70716,11.96679 -"Hallstahammar",59.61395,16.22846 -"Halmstad",56.67446,12.85676 -"Handen",59.16809,18.13796 -"Haninge",59.16775,18.14478 -"Härnösand",62.63228,17.93794 -"Hässleholm",56.15905,13.76638 -"Helsingborg",56.04673,12.69437 -"Höganäs",56.19971,12.55795 -"Höllviken",55.40982,12.9558 -"Huddinge",59.23705,17.98192 -"Hudiksvall",61.72897,17.10358 -"Huskvarna",57.78596,14.30214 -"Jakobsberg",59.42268,17.83508 -"Jönköping",57.78145,14.15618 -"Kalmar",56.66157,16.36163 -"Karlshamn",56.1706,14.86188 -"Karlskoga",59.32667,14.52386 -"Karlskrona",56.16156,15.58661 -"Karlstad",59.3793,13.50357 -"Katrineholm",58.99587,16.20721 -"Kävlinge",55.79188,13.11021 -"Kinna",57.50728,12.69463 -"Kiruna",67.85572,20.22513 -"Kista",59.40316,17.94479 -"Köping",59.51404,15.99255 -"Kristianstad",56.03129,14.15242 -"Kristinehamn",59.30978,14.10808 -"Kumla",59.1277,15.14341 -"Kungälv",57.87096,11.98054 -"Kungsbacka",57.48719,12.07612 -"Landskrona",55.8708,12.83016 -"Lerum",57.77051,12.26904 -"Lidingö",59.36667,18.13333 -"Lidköping",58.50517,13.15765 -"Lindome",57.56667,12.08333 -"Linköping",58.41086,15.62157 -"Ljungby",56.83324,13.94082 -"Ludvika",60.14959,15.18776 -"Luleå",65.58415,22.15465 -"Lund",55.70584,13.19321 -"Majorna",57.69195,11.91605 -"Malmö",55.60587,13.00073 -"Mariestad",58.70971,13.82367 -"Märsta",59.62157,17.85476 -"Mjölby",58.32595,15.12365 -"Mölndal",57.6554,12.01378 -"Mölnlycke",57.65893,12.11792 -"Mora",61.00704,14.54316 -"Motala",58.53706,15.03649 -"Nacka",59.31053,18.16372 -"Nässjö",57.65307,14.69676 -"Norrköping",58.59419,16.1826 -"Norrtälje",59.75799,18.70496 -"Nybro",56.74461,15.90714 -"Nyköping",58.753,17.00788 -"Nynäshamn",58.90337,17.94793 -"Onsala",57.42531,12.02903 -"Örebro",59.27412,15.2066 -"Örnsköldsvik",63.29091,18.71525 -"Oskarshamn",57.26455,16.44837 -"Östermalm",59.33879,18.08487 -"Östersund",63.1792,14.63566 -"Oxelösund",58.67057,17.10152 -"Partille",57.7395,12.10642 -"Piteå",65.31717,21.47944 -"Råsunda",59.36667,17.98333 -"Ronneby",56.20999,15.27602 -"Sala",59.91993,16.60655 -"Salem",59.20186,17.76646 -"Sandviken",60.61667,16.76667 -"Segeltorp",59.27597,17.93072 -"Skara",58.38659,13.43836 -"Skellefteå",64.75067,20.95279 -"Skoghall",59.32324,13.46552 -"Skövde",58.39118,13.84506 -"Söderhamn",61.30373,17.05921 -"Södertälje",59.19554,17.62525 -"Sollentuna",59.42804,17.95093 -"Solna",59.36004,18.00086 -"Staffanstorp",55.64277,13.20638 -"Stenungsund",58.07046,11.8181 -"Stockholm",59.33258,18.0649 -"Strängnäs",59.37741,17.03119 -"Sundbyberg",59.36128,17.97114 -"Sundsvall",62.39129,17.3063 -"Täby",59.4439,18.06872 -"Timrå",62.48703,17.3257 -"Torslanda",57.72432,11.77013 -"Tranås",58.03717,14.9782 -"Trelleborg",55.37514,13.15691 -"Trollhättan",58.28365,12.28864 -"Tullinge",59.2,17.9 -"Tumba",59.19858,17.83317 -"Uddevalla",58.34784,11.9424 -"Umeå",63.82842,20.25972 -"Upplands Väsby",59.51839,17.91128 -"Uppsala",59.85882,17.63889 -"Vallentuna",59.53436,18.07758 -"Vänersborg",58.38075,12.3234 -"Varberg",57.10557,12.25078 -"Värnamo",57.18604,14.04001 -"Västerås",59.61617,16.55276 -"Västerhaninge",59.11667,18.1 -"Västervik",57.7584,16.63733 -"Växjö",56.87767,14.80906 -"Vetlanda",57.42887,15.07762 -"Visby",57.64089,18.29602 -"Ystad",55.42966,13.82041 diff --git a/marginalia_nu/src/main/resources/fonts/LM-regular.ttf b/marginalia_nu/src/main/resources/fonts/LM-regular.ttf deleted file mode 100644 index 6b4f6b8a250401ca8af4d12dd6277ec3151745ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150124 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�Cv-G`xDidpv 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 Ju)ADy{~xO_x*Y%j diff --git a/marginalia_nu/src/main/resources/fonts/STIXTwoMath-Regular.ttf b/marginalia_nu/src/main/resources/fonts/STIXTwoMath-Regular.ttf deleted file mode 100644 index a4705fc7273cefa3f07108285e68755c81fd442c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1518116 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︶MvA%`Rw@LRUv#2O-y_e1+qS+!lDEq(!%0Da?`6x%Ef0!Rvwey21 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

6s#`)7=;6)ODX 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

W#sUm+}gy6;5m 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

?*oOXq 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 (Wv{tf8KT)rr=okN{lF1bQ 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 z%0A3IjPT*+;f&3XFpnV3k?J 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 S00T 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 - - - - Website Explorer - - - - -

    Website Explorer

    -
    -

    Instructions

    -

    Press the thumbnail to visit the website.

    -

    Press ➡️ to view the next website.

    -

    Press 🤩 to look for websites similar to the website you are seeing.

    -

    Press 🔀 to return to the default flavor of websites.

    -

    Cookie Consent

    -

    - The game uses a session cookie to keep track of which websites you have been shown so that - you do not see the same websites too repeatedly, and which websites you would like to see more of. -

    -

    - Consent To The Cookie And Begin -

    -

    About

    -

    - These websites are not manually curated. Most of them are clean, but if you do happen to see something particularly - objectionable, please let me know by sending me an email. kontakt@marginalia.nu -

    -

    - A less principled person would probably have plastered the page in ads, as it's a game basically revolving around - refreshing the same page over and over. Instead I invite you to consider supporting me - if you enjoy game. -

    - - diff --git a/marginalia_nu/src/main/resources/static/dating/robots.txt b/marginalia_nu/src/main/resources/static/dating/robots.txt deleted file mode 100644 index 5199c74f..00000000 --- a/marginalia_nu/src/main/resources/static/dating/robots.txt +++ /dev/null @@ -1,4 +0,0 @@ -User-agent: * -Disallow: /init -Disallow: /random -Disallow: /similar diff --git a/marginalia_nu/src/main/resources/static/edge/index.html b/marginalia_nu/src/main/resources/static/edge/index.html deleted file mode 100644 index c27c6efc..00000000 --- a/marginalia_nu/src/main/resources/static/edge/index.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - Marginalia Search - - - - - - - - - - - - - -
    - -
    - -
    -
    - -
    - -
    -
    -

    About

    -
    -

    This is an independent DIY search engine that focuses on non-commercial content, and attempts to - show you sites you perhaps weren't aware of in favor of the sort of sites you probably already knew - existed.

    -

    - The software for this search engine is all custom-built, and all crawling and indexing is - done in-house. The project is open source. Feel free to poke about in the source code or contribute - to the development! -

    -

    Consider supporting the - project!

    -
    -
    - Read More -
    -
    - -
    -

    Tips

    -
    -

    - This search engine isn't particularly well equipped to answering queries - posed like questions, instead try to imagine some text that might appear - in the website you are looking for, and search for that.

    -

    - Where this search engine really shines is finding small, old and obscure websites about some - given topic, perhaps - old video games, - a mystery, - theology, - the occult, - knitting, - computer science, - or art. -

    - -
    - -
    - - -
    -

    Updates

    -
    -

    ☛ A recipe filter has been added to the algorithm selector.

    -

    ☛ The Random Mode has been overhauled, and is - quite entertaining. I encourage you to give it a spin.

    -

    ☛ A simple public API is now available.

    -
    - -
    - -
    -

    Publicity, Discussion and Events

    -
    -
    -
    Kritik an Googles Suche - Platzhirsch auf dem Nebenschauplatz
    -
    Deutschlandfunk Kultur 🇩🇪, 2022-08-18
    -
    Marginalia Goes Open Source
    -
    Hacker News, 2022-05-28
    -
    You Should Check Out the Indie Web 🎞️
    -
    YouTube, You've Got Kat, 2022-03-15
    -
    - What Google Search Isn't Showing You -
    -
    The New Yorker 🎩, 2022-03-10
    -
    - Marginalia Search - Serendipity Engineering -
    -
    MetaFilter, 2022-03-09
    -
    - 🎂 First anniversary! 🎊 -
    -
    - 2022-02-26 -
    -
    - A Search Engine Designed To Surprise You -
    -
    Clive Thompson OneZero, 2021-09-16
    -
    - A search engine that favors text-heavy sites and punishes modern web design -
    -
    - Hacker News, 2021-09-16 -
    -
    -
    -
    -
    -
    - -
    - This website complies with the GDPR by not collecting any personal - information, and with the EU Cookie Directive by not using - cookies. More Information. -

    - Reach me at kontakt@marginalia.nu. -

    - - \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/static/encyclopedia/index.html b/marginalia_nu/src/main/resources/static/encyclopedia/index.html deleted file mode 100644 index 1b3f81ed..00000000 --- a/marginalia_nu/src/main/resources/static/encyclopedia/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Marginalia Encyclopedia - - - - - - - - \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/static/encyclopedia/robots.txt b/marginalia_nu/src/main/resources/static/encyclopedia/robots.txt deleted file mode 100644 index 77470cb3..00000000 --- a/marginalia_nu/src/main/resources/static/encyclopedia/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: / \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/static/encyclopedia/wiki-clean.html b/marginalia_nu/src/main/resources/static/encyclopedia/wiki-clean.html deleted file mode 100644 index cd928003..00000000 --- a/marginalia_nu/src/main/resources/static/encyclopedia/wiki-clean.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - Marginalia Search - About: Easy Read Wikipedia - - - - - -
    - -
    -
    -

    About: High Readability Encyclopedia

    -
    -

    - This is an encyclopedia based on Wikipedia's database, that strips away most links and - almost all visual clutter to provide a more book-like reading experience with fewer - distractions. -

    -

    - This is primarily a helpful utility for a search engine focusing on similarly text-oriented - websites. -

    -

    - You are welcome to use it for general article reading as well. This may be useful - if you are on a low bandwidth connection, since the download size is typically reduced - from megabytes to dozens of kilobytes. -

    -

    - What's taken away is all the design elements that your brain would have to filter out - to read the text of the article. It seems as though overburdening this mental process - causes the reader to start scanning the text instead of reading it, which is experienced - as an inability to pay focus. -

    -

    - The cleaning process is not perfect and will occasionally produce strange results, - but significant problems should be relatively rare. -

    - About the Search Engine - -

    Limitations

    -

    This is a "stale" copy of wikipedia, based on an archived copy from January 2021. On the - other hand, we used to abide printed encyclopedias that didn't update at all.

    -

    - Be aware that the cleaning strips away a lot of information, including most references, - footnotes, quality warnings, and so forth. Refer to the original wikipedia article for - that information. -

    -
    -

    Legal

    -
    - The original Wikipedia text is available under the the Creative Commons Attribution-ShareAlike 3.0 license, - and so is the wikipedia text forwarded to you through this service. -
    -
    -

    Further reading

    -
    Blom et al. 2017 - Comprehension and navigation of networked hypertexts
    -
    https://onlinelibrary.wiley.com/doi/pdf/10.1111/jcal.12243
    -
    -

    Have something to say?

    -
    -

    Send me an e-mail at kontakt@marginalia.nu. -

    -
    -
    - \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/static/encyclopedia/wiki-start.html b/marginalia_nu/src/main/resources/static/encyclopedia/wiki-start.html deleted file mode 100644 index f0f86947..00000000 --- a/marginalia_nu/src/main/resources/static/encyclopedia/wiki-start.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Marginalia Search - Easy Read Wikipedia Search - - - - - -
    - -
    -
    -

    Search the Encyclopedia

    - -
    - \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/static/explore/style.css b/marginalia_nu/src/main/resources/static/explore/style.css deleted file mode 100644 index b7af8d17..00000000 --- a/marginalia_nu/src/main/resources/static/explore/style.css +++ /dev/null @@ -1,20 +0,0 @@ -body { - max-width: 80ch; - margin: auto; - font-size: 14pt; - font-family: sans-serif; - color: #222; - line-height: 1.5; -} -th { text-align: left; } -input { font-family: monospace; font-size: 14pt; } -input[type="text"] { width: 50%; } -table { width: 100%; font-size: 14pt; } - -a.external { - color: darkcyan !important; -} -a.external:before { - content: '\01F30E'; - padding-right: .25ch; -} \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/static/fonts/LM-bold-italic.ttf b/marginalia_nu/src/main/resources/static/fonts/LM-bold-italic.ttf deleted file mode 100644 index 7b684ee68e2ced91e2a7d9dc21c167d2d3c93db9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 191324 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

    pr$t<*SX%rwXJC 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⁣T((^Cbro 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 diff --git a/marginalia_nu/src/main/resources/static/fonts/LM-bold-italic.woff b/marginalia_nu/src/main/resources/static/fonts/LM-bold-italic.woff deleted file mode 100644 index d54af37161eaafc4c9bf02ab5ec723597f7ba5fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66172 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
    =YJ@B3!XaIs9h7+ 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 diff --git a/marginalia_nu/src/main/resources/static/fonts/LM-bold.ttf b/marginalia_nu/src/main/resources/static/fonts/LM-bold.ttf deleted file mode 100644 index 17624d45493593ef6d88704df6c3b2ce131010c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 144572 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 diff --git a/marginalia_nu/src/main/resources/static/fonts/LM-bold.woff b/marginalia_nu/src/main/resources/static/fonts/LM-bold.woff deleted file mode 100644 index 318a3ad2b8d19b77d82d53751e22c90738bc3dfe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52888 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 diff --git a/marginalia_nu/src/main/resources/static/fonts/LM-bold.woff2 b/marginalia_nu/src/main/resources/static/fonts/LM-bold.woff2 deleted file mode 100644 index c14c6204f5f0252f986b7c4f4b1decfdcbdcecd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33548 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 cgd3f5#5c`iRRAav_>?$2VCQM2RO&ZnOO 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 z zk|sM0Un)NlWyxx9pl8q}sg#TuwhX8YOj0V18sHAP%>#Gq1=k2!|ip>xa@s4J8w37qpe;tV_^tUapGzU z`2Dc++vjmR$**&5QEO6cU9t-RKuoPIDY^~zft!WpH-x~=AclER_+NsqFbs5%kMeq_ zF&u>kZ7$7TE6r|3I4UL^wpLbVgr&oNaUQcolSe-Cj zOs2@)M$Q`b`vA5oISUKYvc1;3#5-|DA_02Ja^$3Jeq)_+fIj<-{R%XHhOgV zzf>DIX*^3g>6SZq-o5-aIt19t_|~~1it8@E)6DjPpCL`_vCga7Hd3;h$%B;Vd3Y7S z{VFweyFIVvrfkr(=DHmg5IlK~!LiLJ=Oj3`sy9yJedn-mmHrm;#R>45E(aP|@dHe7 z23~K-9I>uG?9YQ}EY%+X90?-%mh4M7zRL%20~LmFtZ5@FmQO}fP8whzLPi2ZIrx{U zX*$F{1eBfcQ8RSc@u5ua3p2dYW;7u>8`oqqyUD*sZfr)Bo(>Qk4dTD!%%OHNHEXsx zEQGVVhOjeF$URFqj%~4^4eSe~CI1E5>=10OWP}q;aR*p!oBb?^bNM$nF)5maYv9|^ zOBylqw!Sbm*&cH8c5?&gv=KYV*nGA6Q}k_EeOt2YAZetGl?E~0hI)*6JyK91^|4=a zSJ|HRH(pbsN1ge6+y&tCK*JSyo_`8~XE5_kp8@;O$uJkhp8Ja40NYpvEEPRnmO7=+ zy!XUAri#y9SV7gf`^3Sy(F4%Axp2pk!AJuQ9k}~{mwU(^{Jxg%PH4G&emst_TZnnX zIYr;91+raC#nE%+vXH}7bg9ZNxQ{nulprcR{e)>VXl*VUfdT)!O%50NsiLKnn6{mI z=0?tv`__xsooxl}72MAmZJz~DCIHO~k^^2vC#b-s{R_RhFPSX4A8)ugNH{zj!$fEG zoVr$X)>gZzty;t0v~K~g=inYeUk}(z4B^=`0Q*(M_gs1b3a<&qU+l5KI|dJf*ue=e zoZlQ`iF9r*VyEer_)xYv7`6yA`S2!mtoJA^0d!f%ll(UC@y20X6@hQ*E^MA}eeRbb)-9F!SSgm#&R zr=pQfbV(@F%sWH;L+YYFM0QLN`cGc>CU9kZE_Wo!9sCSnh6rg`e7ecwnsC~QEs~mv z{rjaRAr=k$+#5Z9SX1ZrG}w(>9}YCzz3$B(*SOPRwWTM+Ui*7)G{V1wK>}vnJe2q? z_Fq2>#Ri)r#t|#}Kwd&k>Bj#eA%{&s&_aio*zHF9Q-2?EMNIgGFsw<62YuEjbDy?b z1;Qgg{zKvizN3J91CJ#%Z4kt^qF^5T1UV$!VY=(*W8%X z*ATE;?5#5=CX2PCDQhvAe4DmAO(v3XMB;XL*zR!ezS^i!8N1s$-e|t+zM)WmUpzt^ z0r)7?+nDOywR6y{Ru(hQTXxzG?d%x!Hw!`+7vyY>k*1#c&T}F1c_**Z9#2DVzWHDG zL$1$o8t%y-#XB$BSI~4+eb5H+@&nZjAqXeN;8{gK!VF)690z4Xek{?)8kTr~sRF*C z7ePO490kz6TS52FWLjExkGCJ()z1qM*0^r00&CiWZPLYVlz$7VNP+q-Q?$F6(!wvO)TU+4lr67MZGXQGRvTr@G?cj@FE zPnyAu;s?UoiX9Vg_me(@#@^5uIM)*YX3MfBarFb8b+yW<@S4vBk9K z+}p=7BX!~fn2+jQY}|F#pBGQEv=ro|5-tm$C{9czT&{#FIhFg-jeiRXs(V;|>Z#jr zCu35U3V49~DM#TQ`5^S7i^zWH70HQ54dL*NqMsx_>T+HU#Hj4H6Gqh#vVd(3s$zn=WYBWo zlvOt$Fsvz;_AXhQG4|RHcQpieb+f8x3ojrP3^DK$qtp!e7h(&O=q2WFEpEF7dy5Y% zizi^Ak&+o9!QapoO}DwnySGjFES#0=fIc8_h)g&y?CVi9VoU0x|^nfOaM<*_oSP8I3AkWv#zHeNj`lt&cVx*H(W;`xBPz z5A;;lMU%=fsb)R;kcJtxYtavj7+{21N(0>vI5d3>vFK2-9$`Q$dc0t0S@5RKLY2EZ z+CgyL6$De!%~4`n;q-Uq8Utt0+!!s470k5&jum;4JH_zyHW>~m@GM>oxSCU-W- z^ycko5l&TQ6uyesRLn1-=fS5(Ofy4^lni5e3y`ZaX|Is9+N+nq*HusKdE*WU)pGkZ zzP-=CHxR;<28%%NMdyA`*Oty!j{}zo=AM;(ia$8M%~l(-q@!F%7n2m8hUMp&i{Fr* zYio9ybJxr7DK#{&jFNuv8`=%SOl~shO=&NIc?ZmAPzsKO1(y)(Z0_Z6S33jf!pF>< z&GkC@ln>FSe)r%&))gi%XdZp$v!9PBUVD6_zz3w~IozGLm2C#D6Svj9WOR+Z|Ao@) ztwj*rodv&_V>0;+7iTho4eek}Ym$n}pRYnjq(@0kH4@|QFOUK6>OA=GV(sfPq@s|o zWY?sQ)i0&rUhhg+?g3eM^RMUHYGMkv9e8vB@CZ7TAvel+B=5CUdSGHq!Y*XqmsfpF z+JnGqO90ReR)_tktT)eXD|qvZGd3q@qpjAc)5*IynK0fTmi9I+}O2 z^&IROD|Yrp-wcevK5iTI3BXMF+N+0IMNm^?CGjUR!EEBH>XPDh1>1}GkK3Tf6!Bxq z_)M$Fxj14|+GMJb@y>CNqr6P5@eM7CfbFzQo=Tpl8Wi$Wi_ev4Duil^MG1xYy-u=I zzLqu0F4rehSDWx&{x?~A=<~werFeJiB|jyBHOnveLz%>y1gMO)XJE(o)7)`3M|-9C z4zctIW)&b!sc2i$nD&CN%PN)N=t9XxG)DIv@VQeKyCsuyO8BpDeu$%(ahi$ zmuDn9JIXCv^xD3~dx3onviz+LXgu0&aoP_+P`9}>^pD)$)ewbCqAGX=X< zrcCe1>%SBZ&2rQ9Y4Brr0eaQ{&+`3)%!PW&cX?ldRUlu;d~BS9ABFqtmE_B^Wl~SZy5C?VSdmPw?|sh8o9tJ5s_P>Cf@dz?7Z=Q; zpbqS);g+kkK*gXMbwahzxoQoeg@OXiJcF)?GtVr32YZ#3S_!1qB+LwB-U7<7-2^7O z*}^~xB`Sm{N~l^;mSkxTEYAj^HW>=4%zEw9&cBxL!I~zWcgS}GW}M+Zi!;v9Ws%QU z;r+w~VmOaD%|UUCX1k5~%4$;v@Y%qi19d=J!l%VfCHOfEM^L`!M%yzzp2c<5ZVnO53usRm7|iJW^0sFfjHfgnpBdZPg>u}pIY8W zdp{rqfM>Lx`)Dz@g8&)Wt^w1X^sK4I)UIWfpKfU>vBFsj%lV@G@Ml)_& zyWZ?kAabrvX^2@hc+E+>uV=y5TyD62eXWfm{iPgEuD|v)$hDY!)wX{6Q?vpX>IC9G z?EAp(91^$8Fz>Z4mZuEQYFDM4%$t0jYHbSjRZWQw?eK2!=RX6neJ%TBm2pZflX1D9 z$zGD)8M)lqyjurnhnf%hcZjv?yPH(yt=D0n?9(!lH7q7AWZAIBbrIB0U*aMxu2f~^pm&c;v(Id<7@mKKn6TSupkME9KCI5=`>?8o zJJ!WS3($KOD3pdg+x)p%*mfsL?5dK$WbMA@UwFUJ9d7K0q9H4(| z*vY4AbgTk-c0R9EdT_{pCLdeeiTF786q`B6d0<7axqiN5J36=(dJre79#AK@Rm7;! zFEDzC9yANQTOzLTXPGRI0$=23LDny%`r>(|rCxUZ6ZIk-10VaN-(y{pvt|ZSojL z0EXVi!3vp&)$XN;E#1lWG98%3Nv4{#U{%Viko%LX(X314yxhK#g#G2q?jIfiJlW}O>GmoJMJ1{@Id7BwJk z)p~(MrDq?N@q$WVkJYSUWpIW>8+0LOm2?U_t84K(l`&5r)E(Y$R(q84yD`R{V0U~gqJ-BZXMLQgLh8@0!UJsh)};V+Qw8$HH-J93Ub>Lg zx?ELTf2}SAYxOs^ssL4SmB%UT!od3ILJ=p{pbHuv)T9a;^2J!=Ipf8WO}MFM+g|b} zU<<;!?MuIqqRem)Ja4O)*YM4yg((YgU}x`(*b{zXPjPlRy7hcJqLC^kETG9DP`^ z22(fPP*J`HMaca?rnx4uzx?GqxnjMEia46*@E#P67-`|7*0hmL#eqkqHJmAV=u3u_ z0*KM48N_Ia;gV9*$l~pQOFFe@qRYjbIFp-_5y16wQ%gh7g&T1w?3SKQJ#8@<;1*oD zkIT%d8@^^?WDIiZ*Fb&ln@}IY9czG{fb&UmIvu;`Dr{I&1~gS}RQrfbm)`Yhxi*!Y zA%6Ke)JRSmUcdfwt^&P#oxoL76&;$osRzAFhl2z8Hq@OL$qxbN26g9n)w%4_3#fB5ecTB8_^S2S#|r5D>!gD=*{Lb=+Gav^V>v`}BM zPqcYQp*||wJmr)i4ue$o_BA$7H}lt`ww0M$lP2q9nQS+qK8pJNhF4g>E2XJk0W-Q< zQE|PrnK?$qJ6}Zooq}4dsJ|!H_GVXL?pkzqHKx_3t)*BZ;;H-`YO$!BI1#$e*$$Pj zsEqty3FDeC?5>m6DeJzN6L|@Cr=gzBi5uCgiZ*0BsV(_dsZ%=?Ro^`Nr(XT}O3At} z?AC&rgdF5e`ot{;O{ygZg;{@i4@%-%RTH%9T|EEy6~lEM&{mi03e?$b3i%7jS#+~H zul`N+RU|9)z*}vpNVA^x^gq@^G{_wE`9+u?Ks+;ubE=iOvj15(UfEr8dattV-d)J+ zF`LGAS9Sx2{w8MO0ayYW3@W0JDf`To#);?=M@&BLlkx|8*#8yj;LDs$0q(;Y<@}&u z4nPU+!1*7^a^Of{iOmVh46<_b&Hf8#X76b0&g@zkzpW?RpZkV)K$aWgDIY8nGla)KdE!3g0Em?>suY_nk-Q_H1fv zZk&&W(y$+JQz)2xfBp!;D<40! zRN_Mw8P-+UWU6?p%Ce9Ar=GjS8O}k@a~b4|eBDxsuhm$~a%`WkOjBujkW+dn!k!1V z>jQF5D63AxWMyf|8%0ZYj;|`8Ra(T$i)Qy~sSP+k$b@1~ILf45+P$%gwy%2nvg&`~ zYDpSWMg)dcPQGToYmx(c`L0O>=wy3Q=r8dd`?G7}yXN%z_^v&>9=2;wF7mw_au%Y# zBfdffE3MHO-GQ%OkyH+HbxKK3SS#Ka?2{Jdw!fkkQ>Qw0Q?E~?I%wA;Wj&PVZ5_n( zqU@okvnttF2paXud$j;jucS+bgwmbFeq~X%fN8q=%dgVx3e@S7=c=^nl4MEBMA?CF z1@W#ZyU{9Tr*sPS3MwCU>Xb?$ip2AR4<_x6M;~lRuPlnf#&WK~K}CR=ZwoH-6d6`Z z287I4#P@%KyWh|XZ$DxoNgX2KE{ZFkLTvjmdXFOvR~|8S*!~iT7rUWdzzjbi?j{@= z_F+v|fn~s%ve#a7*Yv4)){(vL+Pjt}(+!mOhchu>h*}0yc2njuU(?3!Bl~xygUR`x zWBd0mC|Z57&6Qm6*2~X~_Rk(19C+KAE5=Pp67q$!1ll94&7)SgW&Xn9=EdQ$eS=32 zK6GGicxm+F1NAtQ&0$R-n@HHYghMfsvL*Za8aMo7o*5J@L6_$FJ9aI)t}0#_HJKMnMYNt3^nOsk*r4> znI2uVMm?&~Em6JhoLHq{q81ZzD{~QCPSsj?Sw|jVIwGQS)oSA_aG+Yf`1{r9uU;bn zJ~bPQYsiOM73Q8MxB$}u%uTH29anivRlngcPu0>|F%-yw{?xC4jP}X<1|$q@vEx#?Y(TvTT%N2#Tt8`JAg)kUdiD%M z`J&+4mbP6c9534YecC$Hx#hmlA+|zyE;-|JP|mlQrNv^o|B?y9 zCgRD-(aF&VH-zGM*i2R~(bLh`33>0onc6vS50K^-L^97k2f%)?R!j-s!sPQyz$kwT zGUd=mzc%?)F{e?j_#UeG&{QkdZ|itvpv944Gxb6Ka~iiNuSwojqK-62aV-00{7KOUxg9t0MkF=nnezju}(#&ePkrZtt5(q{-bj z*vn|97HSbV4*q~3m`$c4_C>g^0JAPCMQ&BcoU%?m1$XZ5b+%FZD#3G(krCX7AY~HRw}PRp*8e+Wv9?S% z{fg%seGLhl#hlsg_u|9HG?uTaot-E*|PfWbH)R4H_9~DeivgoyAiox#S!<;quO=2?|t#-R9 zz!DE8t~5sZ05wAVuzN(qU4K7tPbADR>p?&O{RJGQkC z2mKbOKNt|KiD36qv}LGdR-$#nEG8xPs=YXF*R4I-NF(n_Bsk9K^Pf21QYJew)j%?k zoPe0=HCmqy^G0VNZ?q35E!RQ*$_v4*UG@rNu~wL%wOj0N7J?khijJ zuqI*W7gE(C=H=fM-|?qF#xVJ}4l=GLA+>fDc(S5jNY|>!CAM z@UbS93RL7=ExPuiF8^74XEuTkUb`+jSjlf*yWYJ~_U}rJDX&@M-c{1IV|md5+5z*) z3$V?;9y(UR8LmmM+GWm^2qkLKD8iNE27%1yljKsv?+rf45IYHMOU8Z5fHMoriO^9Y z$Ed~q8Jh)?o`UQ1fR^!;U?e1%tA2H&KCvo| za7-n|_&1*+572f-Gy9uGrw$bpLatR)@(J~)8ont0AaV)+=H`_t@|TQH@Hb0vs>I)X zPW?ADe^Sj$&0i#jzglS}e}uPb9JmV7%)*w70OW9_|7*&yz9bMd77<{x=0HE}V#8J? zP#X4c*hl2oj#Q*I;WZmwiLSVx5UUw-|4sd_q{kW$35F{q|E+TQW(`>})dG-SFMcnLW*IZ7tiT2dLl# z9!-D`hVSVB=IjZI1&@u#fivRaRzEGY85Ias{D8PO3F#XW=>ze?FJ&JG$xBEA5@|FO zi;zxPgvKzB#AU3??^u(KeEvJPav@JB(H;~G3un>LIE3Vcv~Ef;e5Eq2-)-j>W}gVP%$oYm@g*aPsj z*5rl0Y?e4YO}?2%YPG@rC5?tXbT7FT`ZC4)FSVG_z2`TNY`kXU*akvpuih}c@hlYX z5nJUvDC31-No|IZA&r^{yp?`NxfVkC@%LLJ{%~8^AO7c}4`xEGVQ89*dNltZ{}yl9 zzVapRw_x9FY-`1K!$I7-(xUi_oZpPfiEVOZ2tLR{DK~!}tXhdOQdZbagBjSC3f3N$ z?-9a2p{yPwek^^1`|Z^mItFjv)Yb((oK1W<-VH#aCMIM z)X3RMnVn@=4DZu>;9lH7ehk`r1-m9Sc@nAE_^P*n&NMQ=2bE&|AyhE6bE%T zEBw;m8I2d;1;2vaaQ^Hq7Rz0yjCemxhDQt``aSY}Hc2&Tm=QZ2vfZ=?*1csjGfOGB_Na6!n^z{SCYM1pMz6ph?S{;NTHiN`~zk$cUvahE&slLwuU zLE+4=eFi`K@q_kbrnuYVH5ua`xA%YH#~uMce31Ruh+lZ2@YNQljZohB=wl(9E%fmx z+zyA8+8%urKl|7dUUH`=6!O6T{>2jtC&QtS!T;dX2gT38=6nupPL{p^c3=ni|0)aM z16%B3bd3zo5S|rQ#nP*22d2pk@j(hnOfq2xhCNmwN7xo^9?Y8IQ?j8o;)`IL#!mvR zvEU+28jWTV<;2P;(3})i~rjFy|e9U3;z^a$#)^yMAo?tXN z6V8TxkxY~tnkIUpo*8-?YPFnYG_jD4TTRx`miC7DpxRf8ud2*qa!@B&}kPO zmZSY6XJSUPMQ|?d_X)nv#t?P6TjEVAqt{|1@H>g0Mqy{p+wph8ronX66zK*9fOMVg zvVkH{`ndX+anhflgR0+WXMY2AkYKihejM^YF8G8OtuZJx0XHB>A19{ef3a8v3x#KS zT(H3he`zu;-(~^RXQZ#O*x~1AR*dBTQ1=~Raurv)x9Y~e9d7459j1G_C-uahoJShP zku=H?#+g>}X`u(o#{uovTi<8!u`eb_jv=lxZ;d#0yn zK*)IC`yRHYZ-;xU>eQ)oPMz@A_vwFOeT11WEz+TIs`kz%;SMO z^Ndlkil@OiBsd$Zh(FN&3y0FVUbQsAO%*+wYEv6s)OT`Nv1vA2X1-H()LWX9M3WWK zv$#Rkp?gV_s;p3d?$75XN9H4Pjb%y7l25tAkBrD21G*~XAfJ|$(f|I8HQ6K*wiu|c zcWCaQEg^j*?h>r+FnfR-L{D@K#nnlSskD5qd&0v{9aw9TP(tylP1t*!Mn zskS5NbZ5VAmzxsS-VZ07(>=CysxE6ltD+wZxX@+YhT+=NY`F29$H4~7^!h4+(ES)g z2w8evj7M0e0%6^%Mv4RGfeg|i(x!GaVdDh-$&Pp(B%YaX>3)lk4>*a*&)Yu927IXL5FKetz#<>`aBbCZ84?>349p3Ahns=kquSE@TJMGnQl< zBgh+Wa|~etF?oXdY;QBbe@w2T4gnKNP+!SR48K@Ez!Jo$IJTHRxcE&e(Qec&xOkLG z24<4(xa!m=c2Uvk5b1{2Aqhj56jE6E?vv{~@~i3lUa%=T@m5)v9zMH|oII}S-j$oi zsAgCci+0;ZN1uKjP#6tOS+PWxiA+W1>?V4r=fvXA4sO^5TX<~pNAyLk%Zs=@z=qN! zW!X#7n(gf3wl25h`>@DnzGEV^s3Q3CYr>7pkmhE-pIOumSnV_4#jpu5?Jb!fV5}<4 z0Ha@bukP^HhSSa(#eeOx`kd9ar?0tArFX;HrBDi@WPKy0H%`C1P7*^muNfP6aoxgH zi755whBpL7>0YZSL6L}(WPLlOcTT@Mqgvz9fn>{SJ0or3GO@n3X$36tSnsae7VWp% zV@93Q(J|Q2(i?ABN2shq{?KY?>B?{0d)MX>Cs#X}meX5peh4fLeIw55($zJs$#7yx z-ms*NSZzAqh>a1`!N9NLd6T;kJ3A;Gf&GQGlY*>b1O$~pB>pNgL7T~nDPEF71K=dQ zFCJQkdR4YV=_8m1_JY%)FkfIVW)}DZo^~EKfl$H2c!p(G4P;YlvMUAUp5jS*&EsGx z(Ye&N)O;PKilXR>wBxB}Nz}t$kK34+7&h#l30Kn+LzIlAHuI%d96%JsW?bo1Yy-H4BP z#Cabxj5fpgwOxbD#IDG;!V8}$WoQNdX;dkaGh`? z5HoKR?qKx8>X}VID0C=7F$HbU3CSr1Im!qIqlXDpkOwSRj3_y191F4&#v0?^@-MLr zusqFgE5c0#WX+}m?P@AeW4?%4)jYX!+et=Q~|TW zV!u_qioKlD;e+xAL35{joCT+XRWRQy4^AjZJ#)r<>1^ zZEaQrs0u#1A}ZfOPdWE!fc+^6og&*29i<3k-<$vg~s>N89 z=CWG9euu6PNj3}n$ZFAH2RPNIKkBum*kp;aSa=JYkk~_i!#@(*kK9t|g3igG&~ErQ zhJ>Xosxuq48Enc;R>qblak&)4**7J&H}hwi?q>b~__*-<_9RaBhQws0OU&|sgftBr zDqG#*hDf8<*`FV}X(As@9aj`0IgZ;!$q%D=t736b5^4l#M5A$^67p3!B-!FlRHh@^(>?(Gqe5t-83|qBsh> z0>11(-l=>JLe;R4br3BiQ57A?C102HB$Ladj8EZ5`G2b?#o!xh{G}C64o{T zV&F`CG66bu4Y|k-dMFC~x~nt83$-wMra6+FM*J%Ur`L0tiB*on1F0@5B-qv8wK|Zo zx5y%uMXeqZQfoA?bM@onJV%l`r!@7zX}kkFwW;D-Jc*iTYc0OV5g}qk9#}uGZDDzxzh( z^Ap_`o%x-H@aMZ&qx~jYLYYKU6+V4th}RTkSbAKPapquIr5HIEV{GaH?*MARxr$;n zobC192X?$Qv7gW0KEEVIA~uVXSy{URtHo%ID832ym1E+NnKTZp;>L*`8%Da<{3A&iH0+|@IZdwZyT$Zwa

    8OSg-5a#V7t78urK>@~Kbnoznr>*AfUN)~0N4t6=&4(7ed?)OZ+WWru`8~8?6JQ-x_D>r1vNC~wAxyNEmlpnR5_)U zc<+3orMF{I-q5AG@$R_8Mb?@3J$1_!k3IU=#~ypWed10{f-j0vRg<6!#;jJ=+UD<6 zoDZa2BWwuRXZ@N!jdAQ?@iP-mJInl{SgJ$F=r$Js%-rckjqj#cHBbI7U(MK$@ePY= zdVC?zO46LI5i#EccpR)IzSt>!+S{F36{}XUorRTKM-5k3_V7q?)uU`E!!kJ7vcG<6 zxoew`T(~FOtnN}2he+g0V6r%Zb#@FX!fE~)8{CvyR-m-f0NF3w{%YZLpw z=8QSTwd9AYW+62bG-dVHTJ#J|ycX-Y2u_@^$|U(!S=E;r=!oNh4+A9rw$)vRFHHHDk4 z202vttS*NL<751*B^`{7k#O?}S*?kfhlhy|yz>ZlXSYL^!HSz9xIswxy4paFa%$o& zNCv`l-DBRvXDx}Jsc9w<%jckM3b-ib2B*sBU}``^7w90vU- zsA@yDzQwQ^!-jLvu)6e&50rLzdmDTTW=Kp^WESNOLWydPnhV`(YYOH-k1Ch6=0}P} zlqg9P%!18osr6zOY|YkXOdd>VvRIr3+KO0#sHELdH=*vrlUD3(h-$+!vFR={3b|Q5 z3<9X>R!Jst3*^ZI-KO$Pi9;}0LfS3qGWvf2r@AeiW2n@1Q;Z1vC&O5L*Nyc_}7 zgPEGziQf7RE7}yzMlHtWZZHrk?Ikgw>q|waJNup2?LNJ1d}GyEtJP)KRLyuJv7NHA z_-yN=!(+!=mW(&7xNcQ#_jud<9F&>+GOHt&6R8-EjkfOVzv`n$HjZ7reqX;4jk#3F zMS?xZZ$uCM02>pt>bkIm;o+b#ZP-Zg3F|aB@6hLI_F-BO_AC4&rse|ycxebNP3fP?Z-xTbcV+Iuef#7DXKVag*+9Bqb;}Sp7pZLo_2Vw;r4XC^WwfsCHI9b z6YHDT-amT%g?lZ@fqKbe701)t+H*&=Uu|!z>dYrnOTPj-zhUx^;*(SW4ib8lO8Vg@ z0d3(~!q%;0Py{D*<(bLFYGxEQ*(N?&eRlbn)oassiask*5i41EqFN$w&B|Vj*n2U3 zfAp+>!Twf{MfY4YA11vlsqhXYk1r%xgp^uUw{{ZVNwj_Y{bg z(!mQe+F9d_RTbO;tWCs3D zQ%MA3)F*yI)hY*ic3rfZ{tG9?F3Ds4tEh^Q!83>Ho}$DUA_fxL?tp*nx3K*ZXqz!E zV*>0%SXiJF3o_sVHw07>fBh=?`OE=IvnbG8J~%fYKK(iyzH+vOkKP=w@N@b+am>hT zcs_adx$0_KO&qn*rDU=ie{O&-ox@*Du6qUk;wywS705S{hYOl=#%@NWms>I>Sc?4O(C3mk(&AAKh>BGS(gQwe&e#t(so7F5$FyfUQ;C?X((VKvErY zP$mwm_L9F$4bF3iOIX^!}N#j3}(K=bjUsxUJ>T3Wz5Bf)y3USoFd#E;^Vou zZoXUf`{=_YIv?9eXlHTgS-szZV6djT!B-+tlOb3Ufu(-U3NuJ2Z6C-CY2Ixv`ehGP zl|*H7O<{reR8kDUNmD(3L1?>8t>w3@i8z`*)TDUnHnMNYH8u%TEFy2=7vhuTDZzm# z6_Z9;W*B1Kv7K*CCvzUCff%er^cdI8Oa_}C$~fO)kf$Tq1=R!w_8>amSbH&T<}cSr z9Iiwr(7&fYc+;v=Beok(uU!%h-n8=K5$latuU-0Y{fSt!@8F`A)!m=QzeoCG$tK^y zdE0NjHT>=~JIoi@6W33ENzBolkmEgb5VX!23FPbSs)1#UKiK6nC57leTPd*JnfEi` z&vs_*q?3M_`YgxmS~Lqn8$@YN7H+r6n2AqY0~C-WnnjN2w^|*d=(t7m`kIg1cDdEU zMP#hrtJ|e~PfoMJ0hDXGU$Hyn``hakXg#g91MYd9dhgw6iZ=5 z#oixc@2~gQ6(+hi?tR?o*tGZYP_UkcbZOnhp;PZ!unu7qT(3N6UKgI-JiqW@?`?)C?vh)kp3g;EMwcV-MU9g5u*NOyu>auyok-}ex{SHaAiAxH< z>yi^p=n}z;SjH9eqSL<<1t$q16l9YLg*{<- zL~56va#|~(J+ZKn*c5o=Y#5uJJmz;wM(ex2R4>~_QdKxL%=QL5fV=n==$5=NhUSX~ zLzkz>M;slLF*&0I79Z9kCe2hf2E|*yI>-|bz*NOO1o_JCYdQw4V~dNMrQKdp3Iu|x zrtMO6xWSAbNyCOnS<~3v*1GU=Un&%9?{sLHR3O<)Vn;r{exx-?T>X46v}UP6O`~h6MIo!n_En-qp*u8+7$P|-9r7uB55NwBe1InnJf$QUDUB%b5SRSP1!(xf5I!d#Y}zA5v9jaVui_` z=FWu++s3vu813YAT68Hv>Q49nunT?^4LaupbWTiY!I>NkiHglLcPkea5Ay>-eQ;Zf zqp5P0%Pn2D0%dMQvSrAuMQ(58v&{b)fE(b%J=@b&YE-1ow)T0eZrN^2CBWYj-i@wq znAaMQ$!^J?Z^)+Bo+^B(c)>oF+$WuxM*N?&?tI80`t_=9BikR)4QjXORa=L<`iY(B zq^Ys{a9!ZGQ^nV{=Eq--*Ce*Lu|JsZ$@?bXOut9B2~JoG3x##Ok^@VpWHmCAz3A~` z#m*RN3=uB2pJHKT*fN7~0&s^X<{7(P+)AxH?#bj1MhvplVC`j6Y{-lrXzlcBE!`Hd zT(;IS+*b%)zD^tLmoHmm$@RE=qC>-?_K6lTWrwDFBzBRh$QGN4(vi5OpbHD9^cUw9~3QOz+CDckxKOKKf z^Uk4FnXE+ciDG!9!k-HdlH-!v+VQ5c;VA=g?UPvWqq z1(`d|P~1&n|3jjIL@XRAVl#uGds1+BAW%B&ecCDhj!>)R8&=6NUxrhGkWW>2z`yI- zzfk!}yI1)^)MgVO*Dbm#g+*AavT|0rk%(_H{(dQi+7#&IA0WrRi*^%`^#Vck2=s=j z$o!4m(dL1}8T98{P@h_C`5E5o*U@SZ*1HC8_9v_JtXa>~&K9#}`pn;#7273{w)D?8 z5_{AZD>h6ge=*rEj$n^>B|pE6Zxi?)hzU=~bd%q)RgVqsudlxr`9wAgR3U%@Z%z1Xa|^;mPVT@q{R z0_|Gdj>z=Z9}jjOinYePc3;3}52t4?;AuS92XyA{XkCC9P@X) zq8s9U75y;hF8A_tH|wnE%DJmJ0J?JOvNMmiHs_6vxn_LL?cSHy855h z?@BVhf__&}_FVmT1ki6Hya1WtD9M(&_?tQ^_?spx7#y;N2!FvE_u|Z0E~Dmr4)W_} ztP|Qj$8#oouvXTT*2-MZSyS;CRHe>bZ{}LrDyGqw{7doBIbdsN@y*K}JUokIc!kTX z!)Qz%VP`JCihfn{#mn}qk|XA-ya9pWI`|1q(hCF|-lz&9B%LfT)vwUdTUGgB3B#N& z+$oAXOW@@A>Es4l5H1lvfcTq$P1V$W#~!pOTLCk>>kf-9-gTR%-ByH=#XZ7r$&aX& zZsu*3V?@KuD#LjvFP7R7(I~U^*nP;+!hHz*lkz)fsjL}Kx~@zwb7a^VPrkE4NmJpE zROGnuub>@Ad5_E6;Rr~f@)1{zRn=mx%)bkg0uRnN;CS(HBpIA zmeXILukdcqpzFEB-V9n!p>oer6ghK&-a40LF=@td#m~PVGw3wck2y*ZD=0LVI)ojWk!9eBefgbv8C-Fx5nKu(2_{xcjOQ7iQ9R0 zHg1NPV?Lrdb2c)kXz3k!L3HU-PE{OLUhF&HqPgVhcjWH~+Fs$YMglk4s8PCN&(~0- z^k-lZocsBI65YCp=i94%{P}Kq`W>}QJ>M#po)581Cl}E_3EkLp@aH4jzO2VH```64 zkB3vI)cqNc$GZ3p`6>tY6}3l>&c`r#Dnc{aLMjYo_2AiH+9L&El@}Zu>}SK#!F!dMn%BSvE>G z;;&b<&Bwif{x@$s{W9Y&)3M4NgHFuQ)|K>qWTy6p&zg^Uo!EYU?am%>^Ne;YNBdA^ zyL56~%m~}W>v+3DDZ{CfXI7QyIYn4;TLmGR-<|W{xp@X2Jx)EoGr-!NgM9f)=#fea z9+-(6XVNczZl0Od_G}W?RJL77#XU3ICc>}CPmvS33^bgvPRP0vq$#bQhKYc!b}wjT z6K`g}FNd$Nm3}U6Fa4Yl8)V)Wihhntx_zOtr(|Z&(4ENx^u@_Z)6ao`3zs-7P6M(_ z6(}c&EohemzR+;pii}3wOXgu*f2L2cSOtstyuy7RdGVa^2lQwqmcbnAlaK}EdGa^- z*v_;4znr`MD=OQc!_**?*U{ezec~rs`{!{IRL?n^7cZ@BzGC>?(xLE5|Beno-?5k= zI1BV=?k3a86`1*!Jm5T}>2`IE!)tThQ+7>nvwE(uXezbc6fkUM*U(4HppQ;--oW!K z5H>5GA1Zrf`Qyd6ls$6BL%2dkw1~=rtT>w+aT7_MxL`7*YReq3zgl0 zN0S!1g#4ZEFSb*D&);pv-Ta=rL-c~FR#@Nuf$>bXhzo@)#67GXw$`V|b4B@Z#7)x; zQSxQ-HF^`h8ux_IEK7w05)yO!4wb86x*G?(QQGLOa%d#8I2UVN+?dYNfpneMn{oz& znp!_Em24QP16@V_%>NME_%rdzOFp$yq0imQXqjH+JFMBQetf28WqtS_b^3MUW&Hf=>|Dz8y11I;uYyy zudkA{;ESKaR?5;vh)kGnnm&98*8W884w;}yQ4Yc_D2oRu!@-P z!nsUlJilkMk6bzVPWF81<)2TkobmjK-Cp+m9dkY&IwCKo;5)d1J^viAC6|;QKgTvi zy#6JgPuAcx)-3%f=VqMPX;200jZ3mY5g~6s%>suQbvUQ8I>DCj5Uget+1Q-)?UF@C z`HZ}&8o_0J?qZc0D8qxT(Knf+&jL?v47tV|AYWK`pJgJbpm}>+2g*}eN+{fp_!zSR zXE8w2%zDd!hfN1dOJiY}6f~QkWq0`zvS71T9!YqsZz9v2*Bsj~Faqk(4^<_J z2d@g-A}D8IfN3a@l^v-FUF_CeXK(e{S(-kf?ahgrZcQOa$il)$DKa}QP+$-!+%?Q- zuoEzl<)JT~Mps)O;Gl~0w0Q~2oT54++E zHjZBSWM4L{IttI5Fzz}~-&KEogpc^=Puf#`!_BFiQ_1etbZ}-22FY>QOqFuYXINb3 z$(St9e0I`!WimL{)q25AN661vMuGso1OZfJ93%09?KF2dhhu&8Es`_wn!&MkR>>+& zv}%K|@exUpP*?LFU#PurNq<}+hJ<9&rWV?lrw)yowWOX#=^6SlmM_91*NlfsDPug+ zjm#f}HYv+$r)N42h;^vHbdEYH@|+c_rMqN5av${YVaa324*EVVGelz+=b)219Wt_7 zBCV2Fl8r%XX9I$pv22c!)>E$dre_&b#Fj8z2nfM}o7q(GbOS~KE`V7Ia|fm^&gL#Q zM=imP>8pq9It&HnrCp8no2xt#-O`Fq{9LNJ&hc9P{-NbH?TshWTJVCHq>^t zp{{0ZL0@0ZiN@N7k(}yc6@_mk%&5t=Ph7@_LagL(qWN4DHdNZ^u{}&tC^p2T179N^>7u$oCdq*BB?ZIv= zZNcDI8HAkuC3MXxR9_-X)XeCfQX613rg|U}O2!@d5XA?eE-1{Ta1xxS1VChF5U>bY z1~ZMvaGQV)hb23)r|`-LdM8>Ov{wx(cP+m%>{JF;_T>yk7G)GDH)>Eie8s?C~2UR&XJ#HC5p9q#FDajA4)g?fVwxCnG7&~M$SC@I(RQ`!yc9b%ht ztXWhul{>+-TS>opn0CX@U_b-BbH*l$+-H)_wnBBxU;54sO zZnC!(Rr)u=Dbbyn-vqVq&ywZk8jcD}Q1APG?0c68Z(>b4WB-2sOWydq#$KwObpPX5C5J_7C6s`yKa>rF}MlqI|;&{Mo9+qWQoc zib6X&bg^dJhP&o>+gORe_BCJvI3m%Z6^DmL{gTb^_SX%(ihIF}YbOQrF~YD_R={qU zmPVjB!&c#oJJSUT%9cEKYRKbZmKP4T;I&L#NN|gc@ho%`%kyE|1BQvf>p_TlJ~t3n zypg6Axe{(ls*Yo=h;a$&mDKu@P%EEp0k%pD`8Ob|xPY)ySnL{GxxmVCQ{XqZ57o^? zTd4vg%z#@deA|Ru!2yM#zGNT6)*~tS!yhTy?Pd{+*i4!bnQ z&`yf_MxR}F8$DL4!f@9ub~;Zqbce#!QsTIu$IVLE5nK6bd5j^8nmWUkxtTa+Vn(nm zY(^gZDFcltGB?aGvl3PG^D4n&-W8Lq9r^ea@Beh?NmpBw8MMz-ggj3#BQKedTj}HLYs!Ekj@^J+PyUkqY8R}0t_z>Wum7O z7cBYAz@lwT@7b|m8V22CIK>E@h8+`u{Nffp)5a3pGx-2yH|OW%J(3P?9Hz*kmsI4Y z+q#k5WkJaw978Yt6@9C0MMf4~B04M@(G}TATwAH3>ezBPZKynd=`Xo$u@0D#$(O;5 z3;{Fp)*@!)?Jt2D`FfHi*OF}lJ9!!CYvN9-PKG;4+AD!4DRB6fk$q&L z;Ai=OHC~6z7{biDWDC#N1#N+>l+q`PkD%5SwU(0jZQvt`+SlAf#4(ZIGAKy?4VQEQ zFhhW)L2Sr{w2PRqj~Z55BNG5<5X=>Cuj{@7=t;d(HM9;O*Ua?)G}16Eme7k9l_CM?C5uL>rFAerz96GB*I$PQGe%Ft6xolL363fqFTnmW%qEKSR(Ch%A5*Hwh33f|`PZ5F;21A+9#Ae>i-NUyJ{hz-OOWJ| z1!fyS%FQqZD%5|KIaK?Wgu5o@_V-mYO{RIHP2qT>Z8*$6KxLu^fgDzBl1s{I#i8C= zsrHBZ{W)*zuGOu9=+u#Aby;8Pp(Xz4l{R3KRW&!pDbjUIBUQ>(KKN)W>W&}4ymn93 zPGicrWeIg+9=^tux9Lb?v&?o6VCC|ep5-??Vsj+?@=4dF3)<$}oj!}`3MM1)v^2+S z$Bh$`s@vUB8y1q!zd7Jq;B@3GW{}=%R~-IA?bwdKR)5^kh|lfIkXU+sFZreeaY#rn zK7ryVE9Y(U2b1l5-Wpl;4N7224hcW}58z8^KQ;&?ikVh%>3q2g1MP>#_WopN!sT|6 zl-r-oyXkf{GOtaPWYw^RQ|-~(Ci&8`x#;TO6paU?UR&C#kF?pyHPJxrmB_)@#duRg zyraf#kPFHQuL_RIXQhMmM?x4m>wQ>NYlY1me@+3Q1hz=bi_+04faS1~m}@|hQGkQh z!<_>JRSC@S&v)5rk{Rors?3g7Zco4_V`F1$OYN$`9YZ+93*Zhsc^40p_NG7ZtHSs0 z3=Q21q^s-xIB-&R=$#t|r9!aP+G5+j(`Au5iIcWGQ4c49O6C>5fWZ0PvZmqGul;W; ztbWna)X_q8x^-orf9+o6fCKFZ#TT`ffx-umKA!0RkXT<>L#zxdLjxkfK*~e!zj0Fd z+@*5BEjj3algmb(He_Ub?UMaVpGPT2cD~JU|EJvRjGhV+yG+6@+U zfii~`rl4|%#19CUl&J+<+#gv-*6qLeGrM?zHQgm#S@QoY&yDe<-C6(U##GK=DRpj^vlJCRx@)F zI_D*Z1G>yLnhH6E1H_d;V@b{y$6{O6(sS?Dk(CST((%zqAO)B8f`C7e>x5u$&@WRKpBy+vZjoBM&+w(mQ^{2^e%hv=EJ~lZ-H^G;LUAM$C`LPK4h=)<1 zS1+)Sm_BJI*ZJ?%?BUMss}Hs;y8QNNRjn$CxArvOQ)|<_cB!kjqP8nDHrC%Mv8$6xJty-lM2j6-rpY(DlXr z^;9~^99X|(@jnjs%iFvO_`)U*vuR(vk~UMXvdvwEpOI@JnOrOq+2v(*JuI`9m8QZp z@&`oIrt#zl)grRmFSK2cOnYJ0nlw#V3Ctf-COvPhpqHb3+me& z3qSNYWD+D!*+47~B@qV99Sj-vs2kYPJI@n4`n5GP0I|;-JNi*zM*}etwjk&XiSSD3 z{PJ^qZtUoH1LOOT=Q&RQs~RT}uEN|B%q^lCaKf1L3T4>E%4Ys*P9l{oZo66~hcHEa zzMPJTjf5o@A{Bm4R+59j>EU>l*e5Wh1=h$9D(y_Rkb~AGaQbL4l=Vi;KZLiH`ICM0 z-E#UFCFJn|n@l1hSo(%$Lj{h3InFGwjHC>(71sTn?Bina1 zU_XncTMoYgd?O0&Zo;`hYfcxhFh!c>&M}rzzydF{#ediPH~D?|Gn|>Y>vfqGcQs-s z=?N=Jbv8hKx}!=}subDc)=^^Q)Aeo1Y&IQ(HRZ9u{HP9iTyE3`1#1#MA9-%=C6}zl zzjxks@Zensw~KDC$0LP;QeqpdP3&b*7O#H}_$6xG@2j)uai`N^_r;a_o$!KDeVfMz znqGiqu%6st>M-s`=38ELoUC_!Rab4Y^&1wT&Y#lMC9+GErE%u3CZwADn*ITCTg1`0 zf0fw|@XhepN{N4&6+!<1ix{5T=8J|ei&4#DaY(t(IO4+#8>t$mcF{_`&W?t9(Q8H7 z=Wf3qYGdn=2%jRqK|a7~GaCT&%${Ei-Jt9VI1(^L*rd||hiuT*Z5=zp1gCqdy6Ah& zJk{R2o7$7ah9a?+hFl%-LqOQN2VonqzW-0*o=ze{<)?4uaS7&gh7Ot0XLhcD>BFUd z*6WT$X3R|`729p%Sl>!DF}HyB#^7hr)I=(q5K#(bm1(Y5CMc68;8uao19~GOQkD+|uAV^tD7vvj0CFJCHh&2WF${A~Cr(9QSJkerhi~Sb{+c&T=R7_gbWKsi6$Tk(_-%Puy&XI{i zAInUGAdVJ(KW{cPVi6;)_&aw~MCm8)0TVc_>_hmnIH+RX%jCAWOQ4CwT6q4Y;L?f& z4gL2Yi}-1!56=G7jEjklJVVBCsJa}nTGKf9z@l>5`bT;|oIn=G-ZFkI%GHvqf$^~E z-)k}@Z3!m>qIZyK9&)Y*mR!bL9V@5o65_aU7Rj|6teXw(7LC!TLd5E%>b-IG1{NJ%nOoFY>jG%6#h70r#~k@kFWR5M=8B>F zA~ITn)MLp$0B4Mz{Sgm-MpGkoX#-eFb1??jyIUd4eg)pBM+}t7GB^@UyW8{unpOf2 zl(6D)&H?=0IXHj>5!#J3rdyONXHNJTC0r9xMXT|R($MCbX`D^t%(rKaAo)WDa>m&& z1pRf#LN76;RCok(B!l@}3KZx$u<4jm3c%aX6Amy?Ocxs>6UZgz=d){C8$2A!qU&J( za~({kQU#a~dh7B=&8c1|*?_7NRE>}?bTAy+A9mRd=TI7-1L3E7$D)*u(6`(Nj7a3H zAkeU}M2tD+nLO)I?UJ=G-dsg#{Or%M_roWWw|VvMhq|&Gut__VMAk;LkOR1)_2>&)%j?qSza`OVcCtfS-mPE z)fC?>Q6rf?H{>z>Sg}18NX###RRie?wst0 z@AEZ?S25TxtZzuC4%5?#bl*-NKYoKP0~l&%&>yy%=so$-9d-WN9hZ(Bed7{bY`wg~ zp-Db*{F&ED59s#$SQg<4T z_L^O}IF5Iw?jCD7)G_`jZ9`EnSF{m=gfL{aC=PfvE zf=PVrX0u=b(@2ve5&rinD52ODZ?u zU>3mrYUhH^+L~dU_p3!$)dcI^p7tAe_jK&Ns3y5D;qofh8fRd^$dGS%$w!-N35#ODDdv@rEUxNWyWZ#C*OlbEIlDk&wSYYdc0$dTZUWfts3->X0N&w5E)7 zYdSjb@(+}VjS&v%v?1t0@U9#eGM5wiG^zw1LBU__e>g)BG z1cMY@ong zHU0%&6?V4FKFmiuy~oTpD4&n1fFol#CZ5^Oo$u)xQ?br-mTP8t(|F@2zkguo!2Dj9 zrz(j3+d#T{JmyLcjQ8n^ose-ERW8jVqeFmRZr{91X>rNnsOZi$T0~;~NYiGl%`4H@0noaZ@1Pj>@Pri3-;4`5M}XNg zT$`X%F^V9a2})4}A*I4Qwq?3P4%*h!d3rFHkA&P#IqbJ~j*s=E`^IZ~tL#@BwQg6s zd2!a8$Sxs{V^37q_eY%zzc?|zy1u5W#$#9Af!g(pZ(U!VZ~1)e?bX>}aM6M7)d?(L z(DPRG@hJL;-6#BjOoXvT45nrNIToGZIjU@Znry<>CM+1%S%53z4Y%OvQOmN{6?xeb zbUS=TXI=FkWN~>Mc89C3dnhbh4P`r=`9@%LzQ+&Xd#lktY)Ci&;5NyA>fn37;tTp* zPNnaTmGd98R{QO?#KLw>7j0j)O1juK9&zO7#7w-klF=x;On8-~2=o?eq+STnN> zGs~7&z|T|ZunyxC!A@XT0|~Gsn#l`yZXMldPp#~4Qi9j4>|VZV>7Y_&wAb}+jgDKy zaJDAA#A&!zEn3&#GIsRq>(7i2_;N!d(K;>OG=BBaksawyHA|`+T2{4X0>i42$o3ZA z?yGhhW7Q{nTKaY~U3nq;$>d@^M|5PiFcWx1jnAYXi!7TI=1jq)b(UxUv0s|VNwmq{ zovXiOq@|xtyA<*}`{obzrF)hq+hZQ}ojYqfLUuZmNG1Bh7H?)@%hHU8{IYt-dxrZr z)McZXdBqi^>E%oBSRYL1($1rgC+qv7O_`2twJY_u#QLR&kioqah;q27-#V5?&>6eij53?GFhF74UcTymP{OqIMhtIr=c-YWvH15f^XIu;3jdfr)v;nckY7y@_DL2J~e+ zX^)4Jj^OT|k=^61!8J|2KDQ-yDicGb9szUX2sN7L^?UptnBha+zQ&s7k-AjlrVdnv z1l1_cfXKQM?bYE>w5@M`fAd7Zmh#nxs3Q_Yc_pW9^JFa&9lrC=*H@4JgZj$y>hx9P zTLuY7n+UpO2W zB~-zL<+bxLbqpQ5_zu>-MOtGQ)!L)4jb7`2Ma8?j`A)2{m@hyQc70^8l5x6;q05QGpX|qRVv=-UB#Xgd=LmS>RqCvfe@JJM|2S`* zwF5M*f3-U6_y6-cs~hJ7{{?h?H9Cv@<@|M4=Dc+l;W=9)rN=Ay1JdtUCZo9 zm?mdL-Ho(eR9i8JHykp~F!^qs%_-{poG>xZegPE<3QJ>yOgSh_iea23&f*NkQeBq2 zgwH%@PqAc0H@(?Sbb$TK_=zl>n0$)f2kgnsI4xwKa3MP}1Q9@n!P*|cxe_9G&_H7$ zn<>))af%USk&%jVn1&Tof81hC0qv9L*C1-nMr-Cgv%LZX&-9C)OiC5AUEDdA&mwia znIP@Ci{94}lcRpMw_!m~|IuX;BzjpSoTf~|cHNKMJfw_EPK({LtIOeyc^L3tc173d znnj&D4nc9EToD!`3N?_YZMou?3#3HNP`v+*`R7*6>)dv1-ZHx{LkHGJ0&$XS(+WRz zu4!9#W!RFo0Fp%sVh;u6cpOtL8W~Yc?pg14c&kX=Qqtv^Xc^s`aHm{hAIf^-4vT1u zL=p7~QK@j&@h4FF^kzHQQr(j2j4&(l;MALPNQuz+tWt@2fS~q8wh`t& zi2(Ul>;B$o(72_e&ux=Mo&DIr&luoYIj6(SI6SHB^fhL zb>HTpnxSNMu=G_#ufK$BEqtc%lPSPzoa{B;v>u1rvk>mSWP5kKI>~jj8REeHzKu^@Dnwe1;F{4p2@q|q{^G|akm4km5che?`Ko~X!sOe%qHv?ht*02pX zUBVz1(k=3$r}}N-Y)7J;5-DGDtcd9C_8hq233 zq}sDAbXWM|z}5{d<)dA32ie01yLHcsr3{K&azv!4%h#H!xj@uZrSNqVssTL2@z3d% z81p!f%VTQ!JkRC~uuS=oEAY;}#gH{0rdWYzj+5bw!#*zzbMe;FVE0zQluwWSl>w7W zma|WpL!OB~UYv9BPK^KN$~mVcXK!iRK>@aFnUge39L!iWeDe98z+5wBO9m`d^dW;! znC}oPwSrN^n@SY>7Jw9zF1>1DI?LBZ$QejiCnJX88hrsvDc)iN!aG9FkgnL=3kNyk z(Wu+CZfo-rwvd4B-nJ&BrC#uPeaQ{IcO}v;(dP-TSeJ zTPmK2=uSqOTi1~Fh0hj#JjHD8cvUdBi5%vo;@j45iPhlO4BUOt_r605BX!Xw(W)Z* zWvh?7n`UsIiM{^_X#ZN_t_tdx_^^!jMH0`3;jf_lREP}=LYE*OB^vpGk+>P#VqFs3W~ z0!ryEPG=yJst-+r^v}iM`BY07;QXQT^?h&Wa#O-Ns$@?ps@GpbRu?{1_}TO{teWV- zBry55CpAO9#f`Hi^yL%7L9}H6atOHHu%L&QYt~x zV4^)<69?hRy~kKgycoXdfrk4TbvRRLK`a~xKH&x0V!L>S38_#x45bpld4~gUyuw~r zLi^G6bUsYFHJ_Ky7Wt+Pm@NThlcP?SDsn0^yb0&+&KvJ-aIgasXcs<2ink@4TQ$T| zhBkLB%*O+EQPhmWhiUY&>rEI1kltet{o&gIdkj8KmM(@8NwGRd!@vW=tWc}%F$i92 zvyb)r-8h~1`D_m}50oF^q#B#3<}7~CUwI;tzpJ+^0YiVMV)YijhM3M4-i47?uQ@8ysoRU1r(cIxb zbbfSMvI=n^f_T+1nLu_ns)+bWBkZhQ^ISo;*zqQSSn$N>L-vqgi9*V*U5cRsKpXWi44*VkZ^S(> zhqF2xS&~*FNrM0HDm*7PLYA%Kr^ztMKcgNVtB6?uFJ1pWl^4R61yTWuWua3w@GPao z^qb+)+>#S3i{TI7bj9cQ%vJo}f4a4^eb4xuGIJeCO?u9Rb+o(Jlp5-{Q;1Ex-pJA3_^g|4AjR|j%i_wfN zeL!0^VfC9tiou`ONge{S@KoPBj0?vvW#y%AN2IZ<*6Y-L9UWcs&aCNet9UDdtez?cuMiBmm4>^=j zD%?|F^M;hgn+hPIi%k9&w)ZIJ8yFI$xCO)SvX?E63OP;=4AA;a3<$# zuFn>X%;J@8$#^W{GH_fAb?-h*E=Hl*$z*d|WT<`qk#2vuwijk1))&JCSp71%084=j zVAk|&2CM1+Qe{a$*2o~%$Y$t>ZNh69)j$nQDdT1Uy}=y{ZDYc}uzkxbNk2wz0#-_v zKY+k#(YnZ_%~_GH-MaV>J&nsH+G z$d-@1))I`k3r|h49@vnlV7rsDK?28bYqpr!Ii*bF@|01QryVMMhSWqnIPaYKv0YRd zzo+o-fI)7lSU#ZFQj`k6h}yZ0!XZX4u#CAfit@pH2NvSug3)p6%(e45eh3M}6_SPg zY>oqnDiVrc$@xP6yPQaKA1*XIqxjrM2o?S^gH)Wa=rQos0N0RZrUy8}Bo7#kWvIi@ zF0u&}B&10tKGSR=`;Vm)7yh$+@IV`QpuSiGF~tLO&N>Uqn^`Aw z%<4H)h2K!#>oe!h4X&67E+?L+OKATT9%~s+D(x=^N-aZVHRZw@%pa@TXOS07jA`ZY z^T1=2od^zLQIr*!QFg*Sn@?sV_ydtLDZqrl>{;ZsS8o6^rJ^g+GuKF>9`<_N#=OL^ zVfRe9UMCJ%VZ0kx0g-oxvB@^N_Uu_FP6>Bdh@CBT zk2sII?HV`sWZT(4lMg$!I=;gH{)?G2%5q9p7oG!Z?Z;rzaqUsm3p45jiVK}hvZ?m4 zyiHp!FuO`%_JHY<>B4|zy)HY&3D0(RZkZIRB{Cs-US@22z2EJmMd=Ju7NR% z!i<|m{+Z@Kjviv`lcy&@gBHzyX7Vy3;k)UDTW0+;3!!A)c8NbJ?gO5P=j+3}uDRsX zJ0q!<80np1@y8eW&02)}FVwb3DK*fukqnf2UovRfiYd zv3>cxp`M6o_}la|$1nQc4LT5=N*4aeh}WZsU##oFvAUAw2l(rVx`V#LZ@GQ%>+0k7 zy`?Q@H$t{+o;|tqvgLroHA*57fribk57AZcf{RNw2Ru zY(>(oh}i1q#ZH`?kwTP?)k;CxuyJ9FV(u)b3}Y-@j1RunEQEE*rbs<&yOS{Rhe7NCk-R5M z!4(S+0{3BPiv`ER+qcZ?Sh9F%`L~AhIdWZRV?kKFaA5f}=ZgoNe!K&ER`0|_1dP&k=)__3glH7_~*(eP1Sm#jyds}l!q=u7$kln53+o;Y{| zm#bexP5XXc(>`TKV~$IPG#Q>*zD~XztR1S`G*quk_T`5>u;0EpqU8pNL~Zkt z3-@H3HAONU!8$tyry1Jy3%WKabogwb2kca;M6=Z!Ii$0dT z^w{Eo$&nx@VgeWu=aTbmLCluyS^oq}6#DPFq-i@d&g?Z)TVo7d=wBEJ_LPb8KM`3`i;ye5L59xGl`!A9>aUW-B} zeP8if5)$-m@fvw0IPb~4R}m88kBir;@Gi+)ymks{Sub9@(1&@&Yd1dMVP2y@Hu){E zjgAV(giBE&e*ifu`-Kxi0zB1%|FuJBl&*VlH3~_aK;`oe)a4w)T^D1$9>QG-d~yWd z3HBZP#WMbPIsb+ETlSk;yg!21ox&cpvO_qGTE+xEV}IA+vkiDXi6`zsPmbeyA6}2* zlVf;S$DgB-j^hct@a++PX96df=kRHQzhn0>dYnKXdT~y8Gp!12VzM=v^*!0SbO4)57T$DJme@|lJ+SO|k3yvN+kr+R)YtND6dlI=^B5{1rp2YqWCywgR9(1tDecv)iA(F=DRNwnh4Von2-qQc@6JJ5gtrflMDnTJbwEiVsSjw|-K z>4&jLjmR#wX=e&FNE$c8tj6)Cbxo7KRx?2;N)tR5{ z$FEL+!x-yqUFntFwtL9)P zRu?b!A3d?_=#f*2R@4XOd-^WiacIxc6MLHuAJ~Z>HnrxvJ8#B zwR9AtD6OcuWCvS;yYPz=eb^c)UxlSL!=7>x#?LsO(ZO8LJMby{oYCQA*;9^i8kuXA zuZs!Jv|!%7jAxH>ww+!p$M@_y!RWvB;=qx_vZGikM@)_tI0GkUa$RdnqMXNOGknGG z%h-SF#yM93v<4upCja}zPfWH!rq1!7*o6-n@`woR6IoCM73Z#7;0?FpY&Sb{=nR}s z4xNCCJ}=P5{qRu-fsPUeLQoXQF)>)E2_UUi!?I4J<}U+$rCLbfdf<{bVsSM=vbA8y zZQ#p1xD(iS;L;vQ$Ub-y27m)FBn%_(VH9$1K4jbi$iYP*&JrwH#)``!3nx&!x{Ax< zwebI~hwR@dY(kdg7U2S*32hU$3p)rE?iXGsyk2+`F}z;_SNm2(Qr|ATPk4Zc!cD?= z5!<^1xV5ry3sHm{gwF}j5mk7H@P6Ts!XJbeg?9-b6#h;4lJGG2XqRvs=JG3;&8LJf z3ttnyD*U_fAHuJ|(@zUu7rudt(|yA8!tKI;3f~gG4T=97;djD~*ijz9+Pn}e;fU}~ zaKme$1=zZ0TzLw*@7IvrmqBk{juXDG5I!Q@CtQsP)oI}x;kUx?g^%H^IKbxs)!jzy z!dan!u$4gonJP>Y0eD#+;>B6!KAde80G3dQgh_-%Nflu^^atkWPfWx=9aCz3wCZ1gFajKM{Tk@Afbm zA)`2_X+9alX-Nwag>xYIF0vb00DFOtuvTwhCGM_x~^BiEA~$c@;8+)Um; zZXvglGvqdMJ9#6ygWO5pMD8MQCU=v2$Xm!;$=k@=$veossA9Q~yo=mV9w6@)z9alt z_@3~6;RnKxglB~xk_X9q$a~5A$ot7dF`M>?j($9ni85Ov`yPIC4njg^2vkpN+3O60Vg2@ zNNAz}NzLPuI)uGTo%zrI|DT!v?efO| z7a}j>)ilPN%p&YFxwzn#^QJ0Z>Cg90c#Ad5D>Z!acbb~cmvLsQS?om3;cZEC)jV~$ zI)eSw`Dy`Q^n0s1O1(`jR7a~t>g~)jk5TVn2WknwpFNImk=OCtt$Nj<8u<<1Qnid< zR<6M6Zsy(j?_x#m5BMU(AE}d63%d@h)f(RGwN|ZD>(vI<+fG#*Rh!zR+SO+D$7+k( zsPb(%U|o#Fh8lUHXtw>Y;tcRM#ZH#_(6-jBPS4?EX6_p0sASDmk^KT+>i?@?zt zd(_$Ly(*zPRhQ~kNwq`us9u#)=ct`3t@>2I^AUaxH=xc{S(Q_RYRDN?=c!%J=bewK z^VM#3f%;SRK72{uul`(Js6L=BQh%X7sQ!{S*L+xAtp18GA^nZ|i27UgcYJ^1qv{{j z$J9TnOVr2JC)B0tGWAb<_2QH2a`h>7g}PFG8h`B1sL!g;sn4sc)IY1M)ivr`z7=x4 z`hxnRxZ|H&>Q41_b(i{vx?A0&?p5DZ z-%|Ife^uXB-%%s#yXt=RfO=4UkNNDsslDnU^{{$GJ*o=o2kM9F-_<_#BlVd2v3gwn zL_MMYLwV{+^;7jT^_2R#`h_a0U#d~{pXyiY*Xn8B680NBu%1!BQ_rgB)br{E^`d%7 zjk%5+;j0s_TjrMYLeC1mK@sEK996tGwuWt>Y3_7)hC9=p#V^L^xQDrO-Ffce?h)>h z?tFKF`xf`D?osaB+=cGZ?jrZ?yk+4S_Z{xB?h@YZah!WRZ#6!_t#=#TMmD>by35?< zELyE}o81%Lce$(FKXCaLk9(5a;-2iTcGtMA?pk-9yWZVUG1Q-`uWzZh;hO5fU75Pv z&_FVq%4BN?d$Y-8aB9+=ysSFYABt@0OLS#3{S^rlMq4_w$@7xYgbpfNGCic;S&=Yd z)#|QPwri+wM>=_aRo6HjU)`M1A7_!gMVV+?~2jv~vRDVzTkQ{3|!cR~$6h5oyusu3t!m889>r=HW zM4uU=caPK2?dHiY&_R5Az>@I`0F>$KT4fAonHQF~_hz#FI?4igONpthqsmCPn{Dbu{QLlaMft0r-9wIpv(HgR5ZJg+{jNDdBV z`;*<#b>@COI;d#3ZI&@%^c-__ooR|5(-bYz^oC3XWJOD>j;cr`by(G&ODA%@hR%%B zv6k-6w5~!d5m2oGl?Z#K1rTA=cN~oltErC_x#?~-OI+N+lu2{EE#n+@${Yf1e?+(b= zdjEQ?H=x%0oKBn_0#)nv_}AI1ddKP74SovEY_+Kgr!f(%rXC_)H8m}WGXiE>aK)U* z+WeHUKA(zjg0ecZKOkcgYf6_$O~tnam*d%hj7h9XDUqfgL7c5eaK+YRtDiEK3#e@Y zHRx0EZ9!wk2Lm#Cx*lfHT_!kPw@#F0FSJwabia{ey8=o?oT;HmI71BvS6hO(gipm= zdNY}wiO$S<$#^0lr?-p?QGK)NiPDHr3SGHy3LP;!lp-j2cBq^nGJH3!J;G(_h*_bs z{F=`S73IfO8CyzMxyl$+hBooKUN&CS(@cC&lW8VTaBj=fb+Gv~U2F3Bx2-kV{L`42 zRHp64qS92nH7GQm49IA!E-sqX!SvSBMoyMSgi@GR52Y}z9!e41W_Bo55E;IkxqT>= zj+hlH%YTkpp+tUMd_z!ZJQa|!4gU35Dxlf}D&tde6t2&KXkANAZ!dlHOld?Yg=vdW z3ey&$6hXnWL#cwu@ZC&Xgi@Kd2$kj6d{!utA16R3}t9cyZ- zV}q=<`buyrCDi;;lNXK^!;X5J8EKGw%hLZZ7yubC$MyYyOURjPk5nayT86WH0h zh$gLb8|#1q*^O^pkzGOBN8yMK)WyR(k6a z>A{*rj;BrKcGi)%w~}XO89Cdd*-3Og^8WlA7D5Nf<>EKd_&^Dcp38%fn4uzgFrSzFzNlv%XU88#?j^v(T(M2Ka>{${--LeN`Whsiu|p-dEop_&F)b8BvAUEAji8%)LL)TW67-en;R$r8 zpp%6TXKQMV8xvJ7)Q*!PIQ%OjLqfdWWI4HhspP4YiIfl%nyzKarVg&y>b6MUn3m>3 zO}J6LO{4k5ijBcsL$zHU({iNRE|KYzSZ7KvM5U9JIYhBMgq15~vttOVvWpF75qNIb zuD&Xe$RU;4-HJm~i4?o9)&6|5jtph+RQEVz5fiRwV^g(VC^I#(t7ZDc-Fo)cs-AvK z>ufeT*q^dpO9>S{Ly2snKQ*zdxDfB%JQ z(kD^&%Vh!U937M)^>_GP%5AFc5`oEQ*9r7V%>WgH1qXVi{AnoB!QRYJjy?AaP*>B= z9m=4B5c6!H*LC%koaa!Wq3hfzNFXgvh9H5~2$MwQX6hnxFqXMVP>7C7N{`fLlRYWX zYsqe@WF>2_1FWy)rVplAwh$d_`*C(@L|uP!*UTW2X>F1Puw3%6{i7shEv%6;>yCA# zjVJrMSq#yP)nRzNacykKr)o~Ie6K|YW`Xr%ko2M9&RLaRyp$_dc0p5@y55%16zdJB zjRBPosBHl?7*KxP%8K@ErUyfzB4fh1bX=WYyNPL17^+g%tc8f1^=i#zJ2FFA9U0q^ zIxnfI%3SJvlP<@CO1~kK;+|m=_OleAsp-1tL3lmgof~9;O_LSx@VVSEbUwq}I21Eo z-+w}cR3j8IO==T5pJ6H$jxlu$#Z1>#3`JBTded;rIh9gFdGJ}fD(5mJ;i;VI=kw3>a(-|kPv)Q4^?(1gMnBW|nLLnxRxMBCpVXR12~N#(_@_1cA%Zhu z@gOH=)eevX>2m0^Q~H>;GP8SVAllMeCqW`9L6u#+6*_E0a5^sKIH_FcFZcFXqP7Q< z8cEQJUkeF*StV&t_0#V3iH$yywuCQ!B>5))B1XAhQjWHpDrR&Q<7U}W-^8ps3R$Bi zgBB#5)#@`NMx@9sevx8{$||+hD_k=^1Cg;Yd;}t#@H01dw8gJr2Irlii+{zQ`JX|* zpB-(*-rm#D|HbaQa(=5CoQUiuI*R?o70@dcZ^dOdaRB;UbuM()jXM$crK%kHrr})Z zdG37Z1@3anDbsi30PWzJ@KWR|$6d2_<2Gk@*Y0fEsZS>c`|%c#bPn~zUyhUtkA{lC zli@(u>YR#}?XCEEt=_VYV_W;iqnxugZCrDdbN=Ryryk{ebW3|1G26C4ubuJ^;X9Xw z?kw@~P>sgDE5bR-OLF+nApRJEH%0=H=WJ%#u*W$=<|)oukod&tV_F}x`uM&y6C~$@ z=}cGJ`Rk0(A03e6CkEwsxr|199<*!9@1v={!XR+Kjf^EGnb?If|z*e`v_hl zrhVlo?Ha*rM2_-#uZZ>-5&yk<{5(#=mt!;j8t=tVVgL__3-P`9C}||!`TA}VyfE~6 zx=O$xx^X9$KxvJdUmyMWAFNI z{6rqWALNI4QT!Bdj{kC=!)qgoFUL&0HQs_(#*#=qJ{PMZCp-Kmmz`S11^cgYj?>gL z9H*<_ah#!^?S{aw_*Q^%BP#{BX#HCl0yr#vvCTIppHm$c1MP zx$w>*7als~!b?Xvv&g5M*={YzIpkK(VdPfMTyoPYfc@vg@EuZcOIgqtaFgEy^vNk)jpH*^#&IT3v=EOO7ix}#Y8~PG_4!;0 zIj0ri!&-PuQq7f6$BHDFEiFY^b9L`ezV}_T-+s8XmW)5)>lptL{Tw3cy79aHE&WlW zpT?F=iphMWDtPXA>E1jghMA|>e~x}R+-#)iTxOyq{>hBhdLpP1x3xUv?LKCXE>>QtM1iUz??Y_dBK(tr3c z(<+;gLFbT;Tv}VLmZMrGxkMY`Aq)pE3XR|Zrw zlxV9c`Nd~$q4>_oxq6(V@Q#s0aU|bF{k2HzFX43l&=w+vrCmzTeLYpD^;DhKQ*~NT z)oDFdr}b2w)>F97hDvg~266+=?45WaI!EME7l^O2sf`?^Hyq(e1m)TIubFYArKWyv z(nR)c@BN3;2zF37@fB@fEux z@)dl_+pyaNojxGZvfwd8Zw{%K!Is z9#Lju7DX4t7hmmPUAXVqO=UM8dx?L3^s0(aAA8l3s|8DLf9Ia#)N$+QKPUeS_Z`jM z4349GA9dFsyUD+=g!LyLdr8C94Oce~H&sgdy6YvzN7KH^|0VS+!MxL!J|_Rmqs!+n zZ(Ol*#ikYAD;rmyv2sWAVDpEXFKfPP{_V(+Wn&krMpN1@J}caRay!dMC*TS2AK*#a@#oaB2z~{g0nZZm zJmHIAY^)G*LHXE7qyof1)tDElCaeLqU>cYXW`LPon?*PW%ms&oBf$djR`52kXe=Lj zJ0ZLfIR?A~91Gx!$UDJt;CN66P5||w0W^*cN16zil7AWDazc0~aw1p-TEJES4@5cu zJP@J(BlLfy5A>5hLpVY?v|Z$W!UqT+B>WyBZ5N^KBD7tEwu{hi5!x(5dqrrg$dlk_ z;OC$SM!~PZ)8IGY8SpH49=tdry?Gp>q^D~~bdF`hiel4r+9p7Bv+ zY2yX0ZxNH zgYY5v^kL{n2p=UZ5K`NL(A3 zr~$^MKO;Yo{C@&{Fw*~(OZpKc{RonN1W7+~NK1O87E*o$DL;agADJN0e;;Ws5?rLW zNbcjo37`Rpq!uY1me2>1&LWXT8Y79d6yzN1TkL~U+n|&aME8r{e>J*#D!m-mzxPr0 zx4{UwA3O-Y5B36d#sqyUx>oe8=vdJiH2`_9btCd#>pszAqQgXgiOv#zCAv!Vl;|kY zPokSdFH!#Mu9HIgh!q61H&Q&06wf2Y^GNYLQaq0o&ub}u7vbIDUY_J3@CYb?AA)`0 zG4MEe0(ju3;3@D6@`!ynQI|;nuOzON2U>|MGsF=NH(MX~WbYvYM-6dTVA#!d#>*mq*(h+2N4&A_+9wnB>~hBXmbWABE3514v94^L@%T}{4g!0%&$eGLx&I@XlP z3}cDqttFP%mY9*eML4|(mlu(|MI>*2Qj6?IT>CNj2@q=s%Nve07MWOMVu=|mOlBw( zEip4gDKbOJGeeo$GF!$qqtA-WIEvaz+eRoBnn>?HLXmk71Ce{iO3Pa-EpM%~ytUHu z)=JA;D=lxWw7j*_^43brTPrPpP*$2)Xkwj-WhPdcSY%?2i6tghm{?$9eTn52vbxZ@ zMfFbNjt3{u#%Js&izl^Ve;J2%ZhasdeiR$Nz&v^s8-5fUeiR#iG&GMM#g;EH zj~>OAA4N`%V#gPlGmm1&k7CD<>N)c_2=4*kkjYqW&|0B-Dj{h;C*z!Li#Gco3@fl=>vE@gtoEx=rZZw$Z9?9Hz!Puys+m6B= z1?HopaL1^XQ=`^~_vKUpE*W87haRxArvkI50<)(Av!}wO&%R60IgH97d~|F)#B|nSIF|Rc2o@PxWVC1*_=_XgZmN6&U|A6DuGC zJbKi#GQgupJ$lr$voen!EuiUSW>%m_Wp-AeM`eaq5Sf5>(<>Yy*&sb!K$Cg&Z~+lHpl7;jxPGwAN^%%|w$O4@84CF!q-b8d>4N4gQR-faa1}UBS-k z3W40n5chh|@Ls*_@bpT>rLW<7fN<%7WXaTAdM){`gX69z6b>~u*THe;)LdD*CNLTI zO|>TA%Msx+v6qC)#9o@5%fv>SjHCX)uL~TrpGG4+6 znb4~)Vp+SU!uRyis`9FRv_Z{*Qdo2+EWD#kp@-x*k8>yb3!b*}?kR|n6> zH*uGn;p~uBK)<@6C38&2|XbYc8W* ztHDBWlGDU~dlUQVO==}*29JP8xh6f*Mvt`7BW?6Z8$E(wfm)80#lAlM!d!s2xUdIv z2<^woy8jztKh9r^d42Zd%v#KwdvQNw#{F|p1fzhJ?a0&MH$Yan|0nk1u6}dGFhXwy zw}FG-!G3eZyxi{ff%ZmTX6HKCIhHxHtkD0D>=w)JFK;Y+BkS|fPK$G>jpR3dQJ#dq zUd|WAtm+=xy6?3b$=4hry2f9H5e}W`m0@wEtpZB)AN zFD=YmuVfCV+eCDG0Z*`k_5wSQHolTkb*QgoV5_jtTL`?M}c(RwAkC4Qc=?k4+9 zvWg@AfwKr*xwp)#%=I?u2h?j-G8+#GyC---KbAPLJ$JF0_k$nYrV0aU@@7MDGVMm3h z=C_FlGj|ugem8paA@B%zwcgvyUeR1W{40mj{f>LkhTlgk%D%+wy%%wCcH^nE;U?hk z_)O;8_Xc@lZHm%(1$Hnf(usin0f*0h+! zij^&`C9#i^wuxVRwjENfcCanR%!~Iiv`u*O7{B=3f3NLC;`WC2++6j{*rET$7r`-#OUBV7=OmENXCCx`>`p0L3<6~y10%v7JR`$ z-tqo@c@zI_&h5^f_LsHy@x=>#{`D6wzQ^x-_c{;rg@hmQr3CpEt$gKyuTt7Al{I68-YufQ|Qf(yY@P3oxS&u({;M~ zILnBL0Du4h01$8h0O98ee8Kc{|9>wLk$-=GbS6K8SUe>JR5Cs4LsLGE{`jkn0$5_|?hr6En(Lnwe-`@ZbV+&U!008A505JL+ z0IVguq2L^9VyJ8IGvAL7!2AQ2n^M$#lOOVji}{I(en1RK04{A}>EQCyC^-NC<_-YB zZft#XKw4Pq|L9;oex_Ic;dVeai2^KjU4HTcHT?MSfAAZ`7m#PAYiS4oY&!h(VfRzt zBEn+Su8p<*PrP&T)9>Mrj2|?Lirr;RU&la4M@P?Ko$mUg-^DAv8g^rX)`t*wn8#S9 z_r%-i`>NKcwt#_--UdJ~3js*CgBSft^1nXuPv>Y~U*^~B)o#Kisvs0_ad6@u3wS_U zIJoQoX;_R?rh9q;z*2k@kO8o;@H#vo0E|>1vi})3sx!{$=^5+kSr|mQ{igP34Kac= zj?kacG0+15D}h7G{?um72b{aL>jfJD3*D`nviP-5pP?ge_)lI(Cj&N4Ut_uft0E8# z;1>?Z``@}MlT0oCDWVKdcUJ!~*N2= zv!!U+?aLzug~qU8Mnu*SyNM6O`pB6hT|kJdc^M4|4Z}_lBHn%FbiQ(*kSeCY_Za2< zcAnb2R=uJp+2dhEXYw!LGHjCcj)`HJr$y*rw*7s^d+2H=r(kfV?A~&&PIShH%!)m3&IO(dg>o9twr-_b^Uo) zEFN@xiLz=oubT}ud3mu?eerWr(++%(kA9O8LWvBsiCo|G0vLY&grBd$8a;Y8R z70K??k3c|^qe zb*K~P;G4N)|HgfC)d}!pQL)T*8u42rz@Acy#RboUskm<%5l)|IDBNXtz6R@A3txHH zFb=k0<1_%;9;*|CS8-?bs9qz)oaAUo&CRg%S%EpSw ztlqaqYIm3Bct_hSjm4?xL1WGc#*pJ(WRufgRFU(3|hv!dst;wteX`B5N zK0n-2Zk!f|aqt|%1B1X^mib6#BR-UmVJMzybon%iwP8phvOCNh-X1B`p_qBXl7$NE z^kVb;0(h%<5=bt{bdn%SkI=+zpkiQpPJ4`?^E^}t* zOhCTjNv;-rjr}&Xy4p~|MkIQz-Sc!87-+MS9ci}rWI?sR(p>Iy|EuXjde`U?^+~zk z9e6+bJ-D1=48Gs-A;+wup-t2bKMJx>8rGc47IiEzf!#7jkTBSbs%qVIt3`Uq#s*U6 z${FpikTc{#aP1?p)vtaLd}kn6xS+Kxw81p~u2a~hPzBFUb@^0-X^vfc4DO-pzY*qz z_cnM%Ej8|W#<}82^mgi0RH6J0$8qSNk+ig<{<`vNU2V~@IfwKxf<}1| zhPMi9UbQ0Sar!Z>pX_#2!vBm_GL#(UQ+v{Am3H!!xkC+Wm;%*r<>3&;j`##YvOtzg zfY!gfOOHq5MwSEp*;n4yatYibr0%MeKKYPnk9QDML-j`6czKTBci2f~}7BA+#PP0 zcKP|r-zu69qOe7_W#Mb52Q8z!BY5rmhIQS$icIpzqdF!zeQAbov2a`(+LUcWY|F`y z<~eI!rB0s3<@3ZYE$y@ON6f28SG6mhFU5lv$0u;JI;Vr%fnh`Ov~a7h_Ky$Aj}Pbe zHxqVi3*d#oQyuCPn=TGcHwaD5G}mO2LF>XL|Ai*k#&Nbv`}_>tW$Z89NIjl1PKT*) zfU0j!?a#aSa^X~FK@aH;IdZB3D{HdN)H2Nn5$0*LU zLDuL&`~*l3pEf>tp6E)^Nl1WRx+N%IpTi8k?h1e1=s$Tut>5pXx^ogV@zvt2t)j%Q zu^&=Um!c3e{7h*>v$;&^Q^nbHv?k5Ej29vWNS4IdrXoqRW(^25JF#4u(MS5N!O3^R z9bwixksYb{huvW&NR)fRc{_b1a|3L#WTc@2a)d_V?0JeAXqI`J8i>jnLGkRZ3T&EF z4#q+CtEl^7_Vmhm;q4luVt9#h8E&kWf7c2h+@ImR8+b>rpOVGWamXP?F-8siq~h`o zlN`pmRLR2+nN$gmN5Sk;H7Lvv!CUi#A7HOS;ZeZGKs0^Teb+lFIL3 ziQd9q9mTtnXiqKQWq#B+nIOsOg*e%S=n9wwOYP3WgXxWJ<8TPT-y-17HT3s-vqebR z2I9~ZZ7;0JLSvd4|tTq08JH-ID)83bkgwikp~Fl^1hv@4l|KW_wa>vPKkX7}9_ zg-=}rlAw_EEV3%YE|b($LRSnoQ*tz`S1UGKlu)cpDjPJ*Rm=IasKG4i`cL4-tcKBr zu!VaCaRaRap$fVRk_Ia7H{OC8Sw*K(eB{h>y!oKbV}us6yx>;ef{5N_AQ+K&pFP1^(*Ro>H8#? z@au|}c<=THrG4tR&&cbvU(1{<>?|B+xaKNk?LAZVPEJXUwec5hoI_DHMovDL`hP7D z%njOCotApeRB_dDy?MWaUB`AAsY9UDBThh zfl@#ScZ%T~ixD{M0;?b6TRSqqW}wT3Hw@Ym8;Ro*6V4?yKzxu72qf<_K+%WJ;)9Z) zN1dm`H6#}WGujcK7@(RMQb_SHrboY*4SiMdF|I)jufdS4K^cE|*B-)S(xpwag}vTE zk=|okvjI1rje^e@2GbBCquFJs+R@Y)ba%nW-viO;Cu@nKNelj4A_Av0FTN6{WFbUd zIe=C%>)h?P-+(n8n4y$+MeAff%eQ6=kFSqjR+FU^>o@boVNv|55`{h+l2052o2%-; zHJOXY;oEi_%VQhgVoH%I9DZUz)GJniELUx=L_^&p=>peYi%`7>fZs!W+K0oL#jM_^ z?ucZ)^|2tG-dB|uZHjZVAc8Z%UcH0ZXow(kS+iVnPgu6c1HXAbHg6koAWFnyW=J`) znj0)xC|M;|h;twaKonS_4nV}tL(Ib=2uNC4F_BP$BrpifOK_4fHIUz3TSWT8_8OyZh<9xuH-fl|)76j;D2&`}|h$7LVI=?{aXoJh!Cj zdw5${*86B~ciNXnMn#c@#Q?w_Q|y_7fvxb*-ajt?Q=UoKpb~b?YF#(D_(hv(BAbj@ zgpX{B?^vcx!_SIWB;#{W_$>Pu3`6PTJLf9T<6B#v?)=F^U^SY5&aqkOslynUT3pn7 zzwEpJ9qHRfGdGWSv};sT-e~f_voW^Ly(NBpEM}Ew^p{WGVjbV7i6bqSwJ%Ln-+ysn zMiqa*O(C(mc!N~3?q%@p|3xOWD!N9yZxtRwo;+5%Z;h;4H)I$8B<8rj@gUQ7PCMS- zkV%?1;BqeQOiyyZk?FOig?qkbKs{!9zjpTQx%aP{=jJWg?mxq$-^T3V4m+k_PHi#1 z`)-NAOP)oBBQ#3CkALDie12{?E`H4-x;K9DT3W0B;10H?iJng1zpwM=N28r%o_@SG zdX9TcHqE$>=<+*O^wMg)#&=lncTidt6yL681&ikmY zYFd=t81}N!HU0Q5aQR2AJHWeN*-T0OJMF4Ev)cOM4ec6B1!g;S^4Vp%RonU-fypAr zHop!3(~3p)J=$J7ciKp+d1p0mSf(a1$g-t*_MOqfxrx&CbCAk;rW@qBn&EBn{g3Na z!lAX@I!XNI$HnaV^JZ)w{X}>In#fp@&{{DnRb&DqseB_;Y#Bo6KYfdWw0)w*GECEM zH(5*yXRm@ad%{=vqBeD+MRnBDFF&z&nJ5Q7s1o)XD*66AvB(^H9MHd5LS}8~l!S+> z&zq7E>+=)F?W>D)=u1LoT99t6&l??olda&Kc|W#O~b9fIU~hI21<2rI(> z^{4|)0dGOGh;UQ@L_ntc?Q1 z+i|@w59hOj;p+);HKE^7M+@@P^mCADw#OEPtu(pu)p1Hyvh~<;MP*ps+Sa{2v6>#!@9 zb$F!rjYB+Sirq^xf>IA{F!xnYOhs9psjtc9!v-K?#*dy=0xER^DCq$2ZGHB_J zVGiknn!-&xgq)K1aLuO;-?XemlzZ6ls+jP%n;cN#mS^lLx0ZKtr_UdswtjzO8#4K3 zr=N7GdLi3xjLlm*Y|y-OP8>^fdtl9JPHk>>Y!SUv*wA*lU$)9z{yWw!)0#JF)&7Qf zaOZ16*n8fTKv5z=1ben;3~VK?=<2FXMD|WWSHA`;361b{ormz=i)tVzG0%a zcX0#%u7Be$cB*Pbzv&Ed&vNZL_%bxM?8596ST1it+tJ#2Yx~ejUKdE)?ztzO4nOD? zV>z5MZ{-@hq#jnN@}k!apLD71NG{RN+g6|2mp_-hbWMNqF7+O^NKSoZc)lKc=k6|k zK5<7FerNlbn74c5jmf=Md5ejTuJDu^CcAr0C`x4GiQhb)C-U6&O8nGz`%bJ-<^<6` ze~rZHKD<^pwu!V@-cdc*s7QQQ>-N8%_;zpgynb$->U#XBaz0u*fAs&to^S(+#=fyi zn2vt4xa%-=Er4dMmQ~NSs&AWQ*s|D0tKZ(+dZzgt7?&|>LVaejUu}9mb4|zmu()r|{o4c*Xv1cUc|W zr*U)M-@IgP58mc(c^a{c-s(QTZ9m7a@;-SVUK+^*1oaq@2ak=&6GTK)-P4+~ASnqq z$rz`=E5;r^gjcAk8Z1icN*FK)_4kdKHTi9RhkKFk8Hh?P@UNX@%h{Wz68i7wkRz-eGo0&B*HTZjK z9>yCT)S()Sy8!_AZ8LJOC9ON+)&ajsv8LXB_vrlSPHU_=o~eyR-J!t1UDa*f>Abd3 z5)+kVi(AS+SR89){#s&clv@UuQO8?b1XXQJR)BcpoN;Yu-+iz>`lfgg0QrX)Ns@@8$^d_1+XMo7iwrK!s@bGzFd_Y94YND_AP zYukEzR>f1Lcwo*(K3t1liXReM+P3#j`}|JLywJp)1aZO4x#sJV*>#U`%_>{^&RNx2 z%@TP>WaQM;5;WvQ*{0(To`iT8abfLa=g7a1Y2AD;{rO(m=ydiVGEQZydd@6MosD2( zJnI~9vREy<+HBWaKd)TfVqLXh87+F2h7=bQ0|ssB0ooN{yCN5-Ng!^3IUJFTa&pFj zI(PxrmhcjV#9`KXeeYr@LM%6eKFewIx=tRJxI=|0huMCfo1ku6tl@;-QO!jq3#<#C zeG4kqoT%zDkmx{h6)^)C6vV%@%hcshln$afbBO$mvAz4Q z<++$gj&@lN_5DtX797g|0;Mp@oaONtL(S58=<5vbO(}C;Hik%;FL&Uw}>v4 zFpA_K>pabk3;(_DZdxzFeN*yvSv3+S(|vcck-rnSz^QOJJ^RNLD0a;G0WxAY`4F9Q zTj8216>|P+@OTH-W0lDujc)R{6#XV*RBHuew%9MA69+oN7`}!DFo~2j*dm8GC$>bI zn1OE9KY6r)@=;Je1&|mVkYOkI@E~l1hnDbwe)y==KT>ZSNE8ya&lh|CA0wn|8i<00w@m%?X8k2=b#B`nrUyEH=pY=8`ZXu>WKDRo6i^EiD1w!LOZDnr2dz+XCYuyb6C zQ61jqvGwSZZ-w8Hxe3^Du5e)|HMmog?(W$IAcH%ctSh*gS9we#K`qc0dFE!7zlv>) zlPmkNAqq!|Ko?wTU%ppS3k!6aSd)9GmgzGQK1y9{ z9k0{PX8sMeATN1|%lH3!+W80lc8OPRfVqL5f!gn%N+X2ww>a9ALv!(@j1(KoqObdz zHJph!HlyV)DKs*if-H^hykCPnF`}!_z%AG+Cr>&T%aG07Q+lvRf3*#EyD($_i$TXn-R}&!i*%ph-4-mqS5^?6)rA z7dBReHh{>6`)vaDOKPA5R-XHzmgued1Cnk9Z((tX#&lZ|iVb(h@_6zCkM^`aevs8R z;>_*cFz-*z3DTiHSe;FE>fki-4Lkp#b0;mK`gU`+5~|l`!fFNYuF41bU}ZWJk;;|k z2ZINglir7v8I#nB&+qgk7|OOCvELBdP|4`S7~&+s-xxm&LqvU&i~R%QchOjN+vOO( zypIzM70H)ezjgm!$Z2p9GU=+ppVA;l+9alp8PP8rhm2#Y_ZU^NVNcD}y~TJO(^Ot< zGv~sY?oHh{log{lZR9;84N%mp3Q#IBy&iDcDZGYzX& zq(H*;q?gx3-+FFHpPqQ%pCpgo^oL5CmNbjZds3|^%5(nS%EEVcIJW|2&Ezl7yS7HV z5L|iGAo(xY9$8HvhT&8-4S_CFXi{m_EoQeQD?r<(cByO3A^EX1zdqR~VIg8#W~Jn4 zbkqeInd-PyUVEPBmk|QoIvC*G^>LFQhGFnRPr67NNO0k$TRYb|KyFK)@i$9{2sNF? z4-iP3t?tO630!X|ohu;}IyE;nTet4w{R5|fV80bl#+PTeW%9eD4skyTBBw&M2IB;< z8-_`NVk1NVY`9nyz3=~q@q#^*1?$8ttV6om7R(#*HcfKDAg{>SR`vDS8S}|O2~taU%^OO<8 z{g)Q4o_Rhba*@xdkK5j4U_@pYntK`WH$@66fkqMn$xAh0n`=lW8GiGD6a-QNj#E&K z8FMfQ=)Q-KB^GGWTQ*UU3I+bAznhzhNwa2*2Oq;ieHr=5^1)WDVlt!NTysGBi157n zjt!YOfOuLW&}aEO2v<^^J|i{$RrcR>7`g-<>Mvj}=$uB!NQF4tva zGK6^am&d=b#{myyRw)Dot;4AEmjd+Q-h0SwD3I)fxJiDGd}a*#^{}={6!A!Epq&AR z0A^X>5K*F)4<6oAw9}T=Ym$AC*@2wNaa*1&$*Zs{TDgY54+1=#Ui}%uQbX+a?wshq z*VrD4L>ofD+u0+~<^X~vOdw&xG$1D8^Hw+M&-j>W7tIu2 z=z>1EDh4z|q406Du!}l4VEWn0 z)_aeLnbJu6OE#~HQ}6kp()ZR7$&^9)BO`l&Dj;haZLQQ($fRo4nIwl<#@^#h`4FlJ zsjFXy1n-f&A=XXB*?Y*_%7PvgZj=b63BLeLTi*sCy3L$dEtTAMD%DAOdqi>22pV{X z?E-F?vkpQoLN-tDRuM`9poTkwkvc9RwGir>J0&?niCN^Jl4{)8al>+h`9l)oMx1?N zdsxI6duVxGbUd0#+6-&Loi>{aoxRR4=q{7(EI%PU?2Y>3BixND;w;Y}qI$1NCA(Vh zzgh|0k4I9fBjKDTHyP~iY$`^G`3v&h%~^JF3=3rIx<9Pk2mbsOt~xu%V?%!r+=rZE z0X62FA^=2n)jTW;>#O{cM9uBlA}qz07=n}}u&94+4f*V zmrV(d9)Pie!GRGL1i`CMQ`zQDm+hG*3jX$Tuu)c@3ucp9*PvtJeP7C~QZb%z8;#$6 z0@7B_i-}mt^Tu9dci-jT4Bqvi0>Kmfnp3@|PfYAMM*9H%*0#<6eJ#;u_`v%`b@1Le zEic7C`Q8tHt^FQ5J>1TT5w*G-M}h)fFjeYdlk52{lvX@yDLO$;4#+wiCUWiczU>b2 zI$>Ou^C&K4{O!x5Id4MTj<(|?!xvikQa3Mw=;V?sRBI%*g3bfqrK_mw3xqBsp66j?+Y@O%y}mf88_YI)itzUD z-4R}5 z(0+~l3=~5?P1$)pYSY(`!`RGqns(byLxd@u&is7K{1XJf(NW*O2$!oUw>GeFJs1QK z_g<%Qp6xttr^2Gvt($-Djn1l2ddI}4PZmR;ss!UT;v75n^Xz$C?ms%0r?#9hu^%EX zQ+RG??Q76U#=PTl-M{)eqx79OZXUy@-TQxojL?2$j=b${4M!IoJDqR|V8QVQxg>~c zl!dq1tqL%V{kF+;8w$eHC;QnY0H6J<=@oLh1$ap{RmLZ}s@h54@TQ`zSJp9EG$v^H zG_w;CKuy>x`2yq0M%hz+w%lW?io~Y;dh40Q>mBEVA2wtFBw#%D!7-Stw@h0BJ$(B- z9v1&i%fu0+>w@Gp2+Q?Yy?NB7mZH#;lMlmce0+`H4PG1st*jaz<|bF;X;JgQ4S%{Mum^q-57 zOR!qC9RyNa3)Qv;CUww}%X3wBt}d{;$Qu^<629g+P2Ha{!^CxODtuwq)Dm}yM=Nec z-R&{O6$4MbMz|nhFlg5vs+p84d7}NItRN#1vS^oRKtLjQ2pCuh>Q#4#e!X9^b`ede z5xbmUmdR>49-_^1E}#>rBk#)t`Uw6;HGwbj@_Nd9M8@Xf^>{c@RIbbLJl`5^)^L9H z?!3yt3V9%(`A&*J z#L$I=eEmT=b$SVr&Gy=eos4`__2@pmy9u+s2j)w zFr3u^tPGf$l3)oZsJiQnqc-{Gk*D)g3B((%L(Ae7@!3S7OzcM`-r|eXs@5q&LfhUo zK|v&VAEx_NAaT#?TJbQGJA(ZRDvCsIFV^nS$*yXfOadMtyYc7=Lv*9NTAJitDkt#tfZA>uLCTwg zw=Mdx61inkqC{^PLgH`sTa5rjX`7E^vqHQ?DVN~GFNief9=lq=Bx(wwK=>DoCX_dc ziuQ67QXI4>yAWBsez(GASy<%5Tb&5)4M;>zqa-!K#~92gPwcMg(>ozGqQ}UQ>^I>o zmA`BCvBCI>LN%j$WC$`kpmy{x)~=|3*uNO-13S-(`#kh^v)Jd0B}%(tt@lSP&m6?q<2gc;LvCx%>=pZQU@^dxcYDVc6wC?k=Z%eP zgG*++6ZL`+(Z>rfZupaQhyq$9A<){Pmtx;hdhKZ93k>8^I_!&#iEr6drG`tfl?fv; z3Bi(--vI;|`|&%-q12^+!={>U@5YhAkIk~Muq?P@+dCe9#{?&Fcg(*er_m2nfqJ~6 z%FU#xxc*bL9WV;#lf2UYuG;PGi+1cXZ|F%KyhfyUOPbe%s?O-`5}D{(T4-{jim{8% z?h1ztK(Yw)Y&R2~rm~Ah0J(UEal-|CTOz=wvV%&YYE1XZ$ZiCt1naW<=xxPACTR&i z&1jd2;m*bsP)PPE!u;l<2qA!QOuTG!Ton&rv?8^rs#%6Tm7-%N(-ddO5PpJ6`*Z2I zwxlZjq6I_EF;-gI55gUlGXUIfA>+emVa)r|B*rNACs(>PY~w=s23O;#@m?IbG_;ds zmS2_Vjh{hYQVSdk#qMs3>LcziPEvxZ>OZ@OTlc&qQXwN`Hk7B3FiSNw1{~VxOZPLU z`f*D0hUESP&Y2)YUP$(??`34f{|lufdqHp<94{p2qlqoUC{*D;XwlZhHD?D~$}UCVKJ$ZDz6qgcif+mjbPO{DhnL9LqHIj~k?Zhc!Y4 zvy;1N0GUs+FXmlT#!62*SSL@_Zo!zQ9huy+;YOG#QQhq*3QSKEQigi{XJcOKy3#TmkR2Q>?iE)+2TAbo2U*@3lNrmW1ni`f$|W zKIG=?Qu%N&`F?MO?{L+!A6<@V4b#cJm3YK;nEd{UzU%VQJE$9pMn9ipigjG&W}2i$ z7>ehQO;9N>h+iB_3x_Nu7tY%d0Oj=y5v`ncW_NAC(Frra{Qw;C212xgcs#vX`cZ2> zb<*ic*0U5&>N*Pk(b(sv#Xr;X(g#JMb}B|+TSsc{)a^3U`%jd^!-GnX_XT^asoDN< zQ1FzqTEW0T=t^X>_CKD9B8{#*-okrF(AC%L-)YtixJzYS9NLtzd%s@uiwnA=Xq!^o zHWw+Q$RP+C>{5o%AiMLQNblz3DibWe8<$INJUG|Vzp2ZXjH%HPo{`2^+txA3fqm@!#Si^VD{GV!NiW;&PCP7FN+n!oE zh#TIDH{re_dm7nYfYR3%JA=~%L5l)6z0tiVzNDiaN>uX$zPzVCk*K|g8w*9SHqXb% zzX=+fm~S+h`vzVnZr9DWhUq>SLeT+)E4O!p>YPBeZNA*3lgCJlHWPylerWjB*`OO& zSSWK~9<#JT`g?heih3bV-Vw8|p01A?e*4ojiPLXcB7T*4Py-u+N%5HTRGE6EBe((` zh`R9D4X0iaCJ{hEJ z6-WR)B=a-SCa`Fn&^8!e8X2{Wnb(k%*~cq%9sjyxw(*LrpqldI8TfF6wn;;_J1)Xy zD*F_k+EM%Tp~&34GpDn8kUbTX9>&z2x>NW5N|Y>QN7d}#Y^h+8AZrfDsme4doi{YU z#|?=OVqlMmJ`*W1;!qllNG4D_Ct=3h(86JOGFzJ7U;o&yslP$SbH(7`f!B7OMwckk z8KOHf42dE7_tw7Kh8lxy(n6qn!FXLDfykD#%BsOW zP7WrE!R~uiYDEAIbYY-Df}Z@b36r%KfvSwtcxMd2Vz6<{8sqclQjRFzJfyW(N+a;i z|BwuFoaqV92Qoh?{ z_vIu<5)H34NT_?b)-6HLpN|=)+YKl#rr8Cmr&R4Pl>F=tt?zuEq^~fzA0M>#muT}r z(G3WCUg37GP4*vew833Ad47(d6I2W>i@ujP%tbkO~IFwR<_)Oh2-9&-KwFCK!h}5a`Sy*uRO(pwBS1 zVS1zbQb(b~z#a=Yb46v)OKeeg7t!(hz!sOi6Pb$0+t7}Ufa~s)8S>)r(ds1Mm&!PT z+?yx#^4Vekg@6%nrgAs-%5Jr_~6XL!#rY1Qhau{n-vq7IglX4yGI6ZPh1E9$ye= z`vSIZJ+__O3!34yVdKBA3);_$ULCNl?g4WJm19G}Bsb@W_@XJVW<#_V|LX&Da)UV4 zbTonoyup{(ce=M&D(?t_2s6I~9~Az^v~NuASZ$&q7dbST#e*|qzjI!s&N=2;gW9K%1oFi=V-@RgpzkgF4sVZfcLW1)5T|lj@_NmNq`Y%3 z3F5>C#|zkW@ifkzCtv7a^NbkMF}E$R43&wbh9b83gQr_7os$vVZ6 zd|%%1%h#->ShYcj`aQ$(;{5S(S>eKKZRcRP4N4J{iLJFYDE^a{8PRH2k_O_)>?V8C zfc${+CT zeLY|3PYyyak*knG(Ds8r1soYES;sQlH&s~}gYOzz_4-_w(#@s`AN6DsBe|nAe4$YV zMbEtI6Q-Ui{gQJ^R3r~+1y`6gf6E%$6*-6+^yLOaC>^D}>2uUBq}vY~TU9uiqT{Vh z&pFh>;0FA--=?XyTK=f8!nT8%Zs4(_T=_P)fUt}n5gmqXzdNgWX?l%lb2zhMReb1( z4x`zi($0}><1e15@ZEgD&eDg<^iyz$RD!|Ody7;3S==8ZKvg&$p!6#*3_q@dSv5$f za+@zqFgv^m6foWUy6oiXBXJo`=YSgfRr4N%VxB8mOgXi&;V2qHHO4ax`NywKUdUlY0rBG^FG8Yi1#O9qqyx3?-J z{}ZI});|_wyVpA)z7hvv*CYSUd)vy{=^6f0bM~qm%6Wxvo`tx5c*nDdCZk)K9@CfO zkK=afo(7 zv*}q&Zt3kwu0d1C29nEJgh@6JNSYhe&w*8azi?hY2H#!D{gMsUa9p;Y|x5yUQe?Am4FX4O8*SR4B=Z60Na`>nMV0IPd; z1=dv45v1O#C>;VC$=^!8Q!yYn?!*x7j+3lW8j)+AUslYRbZs!@WMge+gtw&O6Ea@Ze$Vz+Vw~2)ZEr{cmk(}H>iaHk38xCn_Ns0DC?qh4Z)-bJ=A`|w2-7Mjj z=H>mW3bX1)=bk}{3Rc|nB5O6Pq)ymg$>)BtrL<`h67-i#38xo%*1}Iu;`Ra9I`ns? z`Ccp^B`>3bF}L!%`nN)9#?Vxf$qf8mVZUICDWb4}6jgTwu>DV}eOpkHxIN#MAOWRY9Bs zEi%l3?%Y|FD%n^#tWWDy<@*D#WP9b9+4)_HyUD<@Zted1QDC6F)sw#$OwRAdh*ey! zeP=7Lx#^k=HPxxut{g??b5}Tui9m7Cy)B+M24${boS%&6M%(3EJ+wvNE7NF^(1ZCg zJ-vX`O+N4A-3rUA9OBBcVh;0k2DBX>4e}tzthdlijrF-sFO&*w%U6t%E`S2NI-(@5 z@SJQ$AV+_tr^5D%aIulLAEXAnB{7N57bKmL`#Aemzu+kT0gFF>*s(CbVFW(d)67@1 zV1o6EyfNw5MEGYZj=&%FWquPZV#+N^1rydIigIpX&}W7e5Mjnb=|K_fpU)pX(|d=w z5$RCtUPOcEHX$}UVB?{n4iqe$|Kx;sx)aXO2lRq1K=7Bemlm;tdA3q_5i*D^a?4j+ z2juUV67$8`L%Rx_5j<5quXfO+jId}A+=`+?T>LN3*#?P3?MPbWRuhl4>SxGLcrHI3 ztn%6TH8i@6Yp33VC8i4XeKCRPIHn>_5aO2f`yx_K2}b_KKgd?aT-Vo6e$v#bpR^VaEtbTy?7_k?(H8md9j4DJYOcpTuM) zu4~ylgr-Ym9P5e2$JJJXbhg5Ex}de9PFo7rB_3_~ty@drc25eyNo=ZiLJvGcVVOHq z^)bU8q~rQ=^M=wfhte@(PRovtkn^DZ)FvN0=@UA+N;;{$(GThDF=t0OYb#IrnQ*Fv zj>X%Swidvkg5+j7d2t_I(Jp#zUvt*D<1okDar||u)Ka>{RC5wFK_wLB=tOBQYPqja)JV4vBW1~o#l>IL?w+_c$3%j?)r|U-SvTwvtX{Z_oG5a!$T#kX_}=wIK2_A1jV4yQfRq6qoE=hjNFv7ixF;+M#2`@ zaTE};>fpp@NWYZV$*lLfFQyx>g~KmWmBDk4gW+hpbKUlZ?PX)>IGmEPY{rPU`8QA_ z{)Uv7L(s<376u9O`0a}VNQ>-(C4S!)scML>bEDDM!E;-u1(Bh@0*IXR)z1;$(smwsEpJY;d!Lnrm1$YmBI>D-yIsQk= zWMUN57pCLqI->G0=CC`l!cl5ezcFHWVom^oM|Oo$Jmye4vU{K z){usd(&?Udg5v(UPj?Swm+p%j?{n)r*h{9qa<*`xRuUUNfa8x4h1}RVwBb&OC@>-t zFlZ4FoEG6gZH1&#TfsUD0}~vieM`>T1;-N6DM$df?i3n*P(T}VeE_2{-Hjrw+b|U{ zZmw?745?Zf#=Ft(RWsQnr4Fq4CfdQ@={`z6>GWwmhhv|5Thh|!TGP(&n=pD&QR9|; zbw&NL1$wKA#Vt&$@g#ZVVSZvu#REuaiKX=h<=aQMzlGlPU?RQN$-}_`v*sa^bBpJJcSacjO9Dpn^4h{i4N2oh0`TrJNIi7W#ZAi-5_7c9@`21wx~~aZUbDv zOl0NQ+E5s9Fd}eNs-vqI!qMeD-Q%?rLJ0-buyCbar4Y}u1wxE5X46WNdP!OOv!0eM zN%#BrBAkgMwqv-^Y(9jkriq)(t{?PDgS4Yk2BiXM#pA=eqFLzheh`I~wabN0rgYZ= zPS2Pxt|+rsl@42E$qahNa_QLpV8>$t{<IxP5Y}jV$u{EgV+n3K^ zoFt>0&qF7NT~jt=mtm1mA6fflm^@tew;J1C=?67-O&Zs4sl&7Nq5`)K#yOffB2*Nd z#(z&dyjKnawjp@w9Mi&7h+^mreRxRYg(iAGH^m`ZABDMe|blP8s`t ze%`Idc>F4z9d)!s_8P@Xfg4%svO-Fw^E1UR<;sK^TiUWBz)fp;jGGxa5z{yTDi{?I z6`dWupX%K^5HcMwtF?)tZJ_0*j2f}-aD2W}cQ*eyBy9U+r+RoHJhw*Kz!Q>|Oqo5X z6O`*Cojx|FRcS|)e^7&@a;S3zbDe|9C-JRQXnJb;a|HG15Q6!Pj6~^!_%0fsZ+p9| z`gX>s`O~~SYwcrBO7c%We2#VOP=9))FJ_3}JpghUcjx0lW_;ROXOT3`cDO6A@m#-N z?Boe){>(WbliB}sMCc26ee82hy+%O~)5ekQBbj@bt_7=;KbyI01!(_9`!5k&oq${^ zk47c_ubpMwo|D57z+?i=r_gsLP>!SBPU<;8C#;2;&)vTc>Bji6X4$M=*G!L(;FG0X zO5TeStnPACooXzKdbLBC`yy8Q_TJtiy$KY@68a5IX^n?BnFdxW+xxa+@vvbv1Gg~p zbv5g%r)tk^dfCxwoM#*5&B%G*)$Ml=3r2wR`8`2n(~PQmASY%$WmJk_Ew7v_P&8Yf zwE5&WX(kfBz};B-1sj=KmTP&GMXZj^wuhytS>dcZ#pVE!i4!Q`W5(XP?Yi~&%6;Cx zl+3E*G?Yxs^TPyZOh}!B9)iCV6Lh;d3kbkz_1w4?c z+P!mia|zQ4*NHR!vkl$@+|v|N)l5H>geMXxa0#{&jFjA#)@f zWEF%;rBRC33&N6=1QiZ{TFdi&#qSz?)CxZx4$AsIgG{cg0kStrNn(Zd!+)l~rk7IY zC59orBHynYxC|(87qQZ9H1aSHk`BFy@WGQeg6O2=#0|fbbwxm(FEJfsn=(qx4t($x)l6Ei% z@S`Io?X;kR2TXiH;?7tE0f*0^`EF8=&>6n|H*7q|V9fSJyHq$R1$Wbp`{+A%{hw$X zP!-GbJTN@4UEwDtL-DF=v>`0{(m$+Z)G%eua~0wgy29&nxCqI3ANz=t@KU(XoO$koNBYrFsXBlOcA)8(3M1 zTR!#nv;AD?6RYi;bMlzGhiO5bM=}76N*Y9FC#8Feo;#+%t-V^$czb%V%pKGGA)^AIG)?I|hf1t!>~<70JA+Q$_98d1%!(1V zOtjf_-M3*$B)MF2KLc3_LL`I~{|?176jF}mT7`^l%2^M-#}R&2N|auk=9Qt@*qeZE zrt<_>)uy}w6~BH8IgD2oXPtE^NOC9y<(opQ6*Eak8OgVZF?Y1!-RfO3DISZ+M_k6C zpwzfI;eQdBAE>BPUU0$0+UmugoN@o*>m7qDVWNNCXu?S{F(=8y6MM&;*tTukwryum zJh5$a2RpWHpZ8yN>YTdwR^8A2sjIqb_3HIJzuo{4pyv_`eD)C9hxr3P545REC$?+1 z<#4GlFZeugwwYwNp!*Zxp0>NgwI*O+#yP1tRW`x`cDR~35jnCslabWD6Q2!?%a(l| zE(13?6Ekc+i`I4zFVE9(_cL>_QGbLpY#!pGBqxODkr>(Ko8=S3YC6Cr&2Z4=iyd%g z*4j}@97=QXNiKLz=2kRkLFgcnpIKO_?EdKd4cEq>xf_Tlccd9LR6_uzdSLnOyCp|# z15|#tDT3K(j8eg^K+zT(w8gU(>+2e)nI!#5cv>Im+2k9o59rq(zd=-|tFmDhiT$dI zmOxzRRVBQ5N)EF}1_}20Rl>#(%C10uOqM=b0(+w0lN)SWevr~+$gK)ii{7(1(WOwI zeC<75KR+(?Ro`uOvi~i?S~Vsp+*L?CWT@r^>#O=$Im%}Q(CsH=`&xvW_xu{Wvm1(g zJe6i_-?Zs|phigJn={S`$GM`QMAJ>8Hg2^w?N(VSBF;WRA`^5~7eqG)f=nS1rfk&Q zza3XX-3}>rXAqaov4+Fk+d_`d2UPL<`P2WXHZC-zjbA+{U#3tF8a7Q@$gpXNiWoa` zn;<+O`AH-7d~so?pZ&K$VO=O5Ta1)?MI1W}!OdodmY;YiB*$YVIDlLdgNB4RNcvFn z5Vruk+vZS{HH7@!d@fpRtWND4+|W0Ow4R8`RR$9;T&tGRjBg;2hWv}bG0 zCmuY=2`}JqU@G-}AazEg@TG8;!A=)ulS8ql0Ur9M9X6X*kME5?f>QCrLG^5wQu@0R zCQq5+__d6L9BqwcvdI*{kdp36cBraoNwJeFNWT`djU&6v-z7U1BNr!Mox$Z`lcUgBTl${qjc|qN;yHgy!l&ahsRzBd+&A1H;`HEMt}wj^QNPTB7q* zSTfdf7Tt>CuFgZ%cNUj+b60YSbB*xPK4D@7JtSMf>2_8P$E7iZ^GfIqezenWd{6(s zZKp7|i>%5?U4#PBG(qQkT+zQVg+J8t-D~qWc5b`6M~cgo&S#MZhs|P)B>lBow$Jd4 zGrCwFFBeBPN7Qk>)oE2bTKUKIbh5nUU%8^3 zL$A_;-pZ{BXGRdZY`y(Ir4sf17o?Lz@u9xT#(?^@k04c*XWC3exh)>!90!%uL042V zA8S0|6GmM84SDD}^wZy1oa(7Pn3;x~H%kbZEVG*ZR7AOa3O7qm*%~}5xY#Eb3MQ%P zCA9Wn>`dvaQca1T=t8aS*;;A>O&Xyrs<)+ zt&z*Cc$iLxO3?go%_;ILGVZa*I0u!DocmXb#2sqJnUTdBBGzChmzcYj z2u0*0goUyuqv|T9HY>c1T6IL7t%^R>Uu!%Gv|B43I4(qG67=3 zdTfBHhE-*fQ~?WlRs8xM8K=&)X>M;mxC5mh!`J|JT&P%0-xZSXF%sQOmshK z9}D|&0+;*?mMt`d$XBmT7*wB2*3a=ua7tD2qCVoU9RRvk_@tqGIdO;Tq zOp|?F&2bxPg?FhHXT!daY5lYf+MQYcou3wM)Cw)}_>dOCtcO`< zK(2AYZ~%dGl_5kyT3Mi9;B^B7nrj)$>+ktQL@#W&?c5<_%A@+a!&t3u6Hlsu??s&vh65M-BEW*;;tkJZ0vPv(lF(k1Ie*Gx{lwC?;7$Kh>7{t`qm zuJ^_3_q6Bzt=Rv(Zp0C|LY23~GeYG3z31(Dfn|3mHCmrM>e{^QZ@KiAL{%>9_NC58 zNmDyfjnm+zgj)F?%nSKnT~LF&4#JqAq2`+jG3j_=pX_((`t`$2;}hMFWmjxzj_aao zUT~fHHaKpxNG!oH#Tq)bP-404f$E2?*yFESA&o~_Of!y{$*(twZj8>t42EouD&nj! zhWbMAB}z%Qh}Y9=rp=WE2RIqK#nY#}Z?v&}!&uum#)ZSty750~*RAT8jTKhQ49Bnn zw_V2%J67`;?Oa`Jw7T{z>q-q-#`rpyPO&EVvmDkTy6&u8OSG|DoHWgRHATL*NlGDK z;D5J^i!x{ZwVJdDl14vhrtsZ^#4x_|srLta)~M&OQ#@W4UNuNdK1!cZ-jo(O(T2!F$)5 z3)7_=C!*D<^SI_5WD)_bay{oj6O0j z;X_%=Wmep~sOBtZ8mT6fj0VzdIB~DR1JDSqLJ6~z;kQXE32u^U{4K3V*; z*CjXyMuut&9ew4Ia@xfKPOzX&l?E`LJkEHtbSqP;l~S!CMX%~PZV!KurRP_kw0~8F zEo96Ax47llfmUQii%w5&Owc8%Is-Hr*W%*` z$;R;A`pJUr`)_uQ0uVNY;)9xbs;^*=-F*{PtWV8v4gBf}tA9D##)@i?Fzb3(tn&Dt zcwxmCjSh=iaiD+|RbzU!wYWwN^ItUgyEATRw>j+oq{%ZBb74IzGTK_y@N2zTxpXPe9nPSLi6ssb50dhUvrypZF8l~hT z-OP-^3+#+AlK4)MDRCUs0b3L&5TbJ+A1b%5dK3K*?qum&sk>#^R_Kq&D)8VyXe1rS zUu%V;(aFcD*}<9`t|=zFt&n;V%7}KD1-^C!O^?+)O5HZ|uW89S@=%dlYm@MLutU4{ zfaM6Dvvq2{)C8`d(a^3Gtw#+e*-ow~Y0`uwZ<9B6Gd6pBXA>QB{UZFa7B)VlcXbN> z+xs?^jHoU6vGMI)u1TRSGSqLTs@dp;?Y{--N_rhV@QJv^ds3o}To%XPMyh$B@fb*F zT}$KXn&p=+612WXx3_S9ZbB^oTa(-Bvix8RKaC=f@RqPVpCVrKvh}cP{>0P_&@wR8|klaPy z!tak^(yqIjLADv1cUBXGhY!@!qTujr-N&g&nLI4i3;QVP`XS<@<~td8CSqWTJ6C>N zQI|$cEOJhsY~yzW28^hudNxkYHXN+0!>dKNJDeLAZ7iTN%UI2u9pU<^1vx<*te=5_ zYv6d*DCs1>Xq5XrEUEiaoy>rx(;~LcGzE%dykny`JFlNs%RKR^J0H6JAH2~;e0vF9 zH^i;?*z$o`U9zdog4f0RJntO&Hih>D4!1&fZ|Ghr)uRPnc&1eaYyN`+h<_dYD%JBo zI7KhdT9$gt^YgrYPs^?iY|`@kr?%z;8r^zzKNlST>&7vwIN~Wc=L9w zJq7F$g>mEWWkSVesTzXPLggu9?I!bbsc!kRjNYC~Tg=x~Vt4y#aoxu)WlVV8MSaD7 zDZ1*XW+yfW1+X2HH03aibuSHwysS#qrjlCvH$$X5F!lQ%YZK4NkM(Ur;uPqzudT_S z26S9qxSn+rag`P-(4Jcrarbxu8(39e@k=F@|ndIrXyxDsFobM^`%3w zXHsh`MQcvTOv|CXL$^>#{q*0Kx1XZ$R;&lywMFXP9yR=z5nq0%K?26*>%LFc2*Ik6 z%0$;YSVH&9Mi0c9KCA5SCU7{vs^@b0#+De$Qt6KA~%G&8nwAO7bonK7JmEPglSk$H*Tw zx@^TvK#ei4X(j}Z^Eu{%`5e1ixmBxuXMu^39d$~Iz!oKM_w_dyZuo3mM^{7mIEpm* zWfEMigW?_twM2xZNLS5zF|;FXdb9$bnLRXH?5qTUjw_%fiJrWJpO%*B??BQ<}1KcF@hxBNHV4>;BDd zF9TexHYJe>GH_Qg0Sx`q8}>EYF9Iyp@|4R4ME@TOKZBl#;LLLC;9s0{T6cS$v{Hel z-g^#xX_%9JS2M?&sdd9{rg=CD(MuMr7gFdDp~=o3I5%{`+{M~Z zHx&3l@O?{P`O5NyL*#mJyeS_J%|E_}<6W0IiHkPI?)&j{$nteNmJ#RV6?xPSQ%_4C zUXOa*k=D_>Gcsq(pfBfQ#J{^FyuExQ@Q42M9vdg?Ty`oz=nQpR*h)4>{klq}ZgXUmw` zDeDx0N*}y6Px(zRtRw8xj30cTc4*fu%Y>mAN=fApMx8nEMq}y*PKS(m)2qw?WfGH+ z`Ur*knxYbqAZ?(7Wz!rOtEHxwD#lZjFfBG+KaYvDAq(ivkX`u`=61F|+#UZbNwc{F zE!-r4v4<2%qq}-a{bQLLnCDN1T@E^yqky zvr+P&gn7yl7R5Sf&(Lsfi^JrDbP>TYL7IHyKi9wn;%n5T#!4ZhW=3y=<}D)@&?aQp zUL(5mYX#%NeClk=z9R^*tnl!lQ>p4(E6d?4!uGr{4%+I`b(3e?v`--~li@3yvR<`w zX|LHaODFsgFO6QI*)>sf-A8apF z6$sq^jtC05&AHU#r*kcAEplsE!Cz*U@4_OZTOr5#&Aihiji21(rtLA217((7q~}w! z@2;^^in1|!H^>3d%3DTM&p&UqF_3i?r_hP7A`8-`tO~>#izVL}xcCl0zILSxHYp1< zQwl&zzFWd1%ouA`Hd!|o_)pE}fXI>N#!tHeDFGD>n}62drI)bb`q~Soj7Gj%PN+2> zL*<50VvQRG!J~3Zf(Uv=uT>oPUqRo<04&GHyx*KTWBn6;L}Uvl=^3dD& zbTlSWvKJzDTiKOt;qW5m331N`!Lm`D$&PZAVU0I^VU0P(pA2eQe2aNX<&FExW<>6a zQ$km2-9)^=rn$o&gsXN^X|r5CA*WU4#%kys+GQqT9#qbCB9*j5S1-Sz#T zC5!tG(6E?Ol#~5Qbo?LKguPN9~9;?-DsfjZDN-%`w*Bd+AS?qb^2 zP5ltMFm8nz?ESCVILj)pt!cNVjZ`gOW9BN}B!*|))cUQ!A>l^O1R=?YybojQMM+oX zWkxpQ+i^7q=TG*1hn<|nTp{}dIp@12B8TLe%=R%EkvMoaR~RHmO|MP_xQEoNoUh(p z>Hh*+pdYQeBCV5?lyqU8m41kzRCr4MTYi}6YLP5kuJ!#Nn$7c7oc6`HTjKYhfs2GEr*MvRruBS{|B1eGt1gqeBehbtfJrAFm8zK1Q=3u~UqP1Y*X?F$Dz59ZdIOHyEbNZ1c5+*4 zrFvI#iZiM{xIn(JiE0O3u3sg<&@L03T=oNvwYh-{zq3Qd(yV<&MsBX?x0)1UV{Km# zX|wHj!og@6jND7u$!YYj!;dHK+ed?IG8e<|KB}-Os@~v6_lY!&4vol%GUJVV*$IN> z&j0YduO*1b52mE1LubjcZ+;z0wct`a#O{ASjGix|9>3>re%Y=~;9VPqVt2AT7Qg2= zDXl;;j-xJGle2jy@S7#_HxqEFH*{(U4aiNzdY`!*-B(#CM4h(X<(dcMjBD%N) z0N6=@XG3pGy-23Wovj_BdI9>ilhHkM9(6mD9*XXU<5{f<6)Q^N zNrlht7V&j)hOV==d@nBYX5CKVW)W};JbJvvfr+b1(i+)8|FicIF}%u+c(z^p#}wKj zc;28Bv><=CP`)e=elJ-5(E8vDb5n82y+3bS?nx_W*mqTiaFTl-9ccOX z%cr78Imnwe4(n|kmuU-4upYLZ9}_caYx}m;YMGETB%7DKw4J)YOf+e|C$?(ibC-L! zha(D#I=p}Q1M;ypsI`XyVNw@wd5_UzuMr^vH?VymEb5qT zMgHh3>p*L-ZPL2A+0T3r&8fFgtlH=~Ad=6IQLyoR)KJ()iuIwv#@U(1?W`4RlRMlUaJ6Yz507ruH`4FdOjfc`&KqvF7 zE2GUcmVQeP{6!0JGkwOd`*QO6Sn|$l#S5{q4%NaH-f0ihs+U2}1!4^~LqCRr3bij~o=^s-}om5V{XhS0z*fGf>k z_0lR=_6+sH>ofIG+_$J6ZJ)?{Qi67A9`(QXW~}#09qGlN+#^2qWn>**xqDH_tL!aY zyVF3L)4}+dn)CQ?cNclZExKQbVu>8nlgUHr=6}!LxX)|4{d;=PDm}jR~2N-Gx~q0)IYKPpnaq&>Fuk|uVt6c zaR|eJYik7ByZm+^{Uvia&zEr{%_FbFo1MnR`LX9`6bpUunl(a6k>ci? z&I{G2poc^vDfvPa|@-xHF&kFvI6M1H7 zw83TeWCpH_OU!6^nn%IqrQ8=8)Zu%Ww`sJ=%{}%+KH;xb{WT$cg-g^rNQTnn8Gg$! z^vfKKn+2nPOKE~4$fEG6yvd#qb%u{Y_963bT5_god)d`JE>oc5;(FCNFBYPY3nyl^ z0y=T8bCCHD0lLcZ#p4jb`dMi81h=Wc_5J@)vd&X;&y8D+D+y;Lo5d?%Jk-m5ojtov zhab04+nR@;`PL1g4P&pN()d;vFSpU#IMn6=y;G(pb}zDlJ>D!k+6^{>@h^tW9qJkbkKBYfq7tD4TlJlwG60+dTzXL<(*BBC*)AsS7q`A&Mc=}Q7F`Qrn7V0KT zy#KEz_pCthk4Jnsjtj=Uf@;{N`^vanF0Nm9;}x~MyDqVqi=Qt$Wk8$&QG=1#&xFPd zhjY|F8t4fo&dTew(*>LFd`l$Tg>&!ntyiDbYVY|qoAdv0$U14kg7g3j_Yyc)xDs0N7U*vk)rOG97phJpEhL4F>}j4C`P` zE$@7u0s!PRZjRJj!GBGiDRXZ7@b5nCtm}DBewPFbX_h3!Jc4(V@*24HtBYfs(QR<2fjMQeeHf63k+s78~BO4t##WHUt^xUJoUuyk+nbyB&>31UL zg5P|ky>r`X?%J5a~IL1HNp_mz47d35K5TD$?BT`;Tq zhXdXJOGs2S7!-!^NIkBM;s90VPYVI~Gs#{Vu4j2IJ(YOISd%rx8zjVQgVNOxlhj!h zIEgBjW@rh@9$M?$yTurP1vqbr)KN@f)-T?h!BmKHQ;ofJnXG%T4tQWdv+%2S=3izX zbX0DmbG`lh8&rV3)de^-9E591Ke&3$vHcI-KF0I^{;db0#J!(sl<%Xu&oMpWEs#8{nq~gq#Q5aNN~7<~ zFFAfkDY3vW%h8tm4nm-e$O=Z@s%+fU`qX-VXq@<&m`rDyX*x@bL&Kg3Fbfc#gnATD-RoZ0n_Kd($)xvVTjfKB2t$i_b z|B2WBqFW={q=}Cm+GQ^enAOe9c6OY)!=4E09koeiP(LnqZvGPA$cErlQ}x`t+I)L_ zN=o-Fmr_bCsZe^$lCs)rudM9w(m(MHx63M9B`6`VP!tkWQ#TmK!V^<582qLnknSj$ zi-(h=DD)$}XDueGxPUa(=V|jPNlLF&Nm4cH!VSz0wI!q+d8gY-G{ma8(chobme01KYz5`N%vgdj-LM~-IIRtV1|lpXXH#Cq?ni6AYI+QuA@ zxWiTVUzV)4GZnpTD#KEDX>1tIqW?$yixxTkhr zmPDWYg>us9$!Mt`3vqvQuBWXM z4E1Zm5g!IYt}(DBf_F32+-!5Tp{BUc^1yR$$v#TkU|#JxC^T6GiC1w0#~xjSh#K*| zF*Te{shai4(^{Ncn-FSU06=Ltc*^ojjWoGoHT~3nAQWm$=MIFfKxd z6xtbVR=aGxbD~zMugX+Q$1hOp5XCWf9Y9!t2xzakzH>_ASk)w`a?<|Li%YHw^my2~ zq&B}9KGoS#M2h?-dkAa4MoC4F9j&b#oFto6>ia;PNR~x*oWMEML#uWaD_J$!B&)J@ zVh7`bjWXGAfwixxmmY-3 zG?_jn`a55n>tt9{?}DS^BAsjN_MITwK+U_xvB@OtA^W17EDW`{o_FHTN+rPj%!#Z9 zhk5L9P#fr;}-lQ=v7dUb|S%ABOb^K{(im72?Z;}!cY2x`qO#T z^nsLOvf~@`IoFtDr|zA)Xb!vXuk=IH)PJSiyA_(Oq!FZ05_R8Ik*esdQRI8EFM|Nj zB5u{8IOGs+Gw;0@6;aDgusL)1AODO;G##+^HE8`2Uz-v-z&k&R+2r0yHsLC_MKmh~ zWTD0Wq>Ri&%`Gz_J{(QoLm#`M56^vxoA*5Y5NX@|L-Lk-33E+?3Itb*@EWaV!b+d5RuE+9B{_54#KRQhhoqG`Zh1o{us zw!W9x1PT-r#wdwXJKA#lQo{1#)9f!tZM*lvZjGd>OCo}+=8oPQT1^{Nw@n0pcN`{Y z^ePFe$Oy@|G%L<@D{QAC^g)#CiNSa^tI$yfvqc8>G=d) z=rNi`>UxfLPYP@hp6RuP;FC}#-YO^Lk=(?wN8dGo7+({{Pq=1#NbN1>oyhLXy4;&Q zZ+RWIUJz960uD5YL8#^|*>--=GqN2y^3(R_gXe0UId5gd{Zr_55=S|^Cf<_7b3A|- zgZ_tv?B#1R72oq1S(|DO+66el(}=N@GbafhE&ez=3*6W?NA;>!y~?zSi3~y1-uduc zCt!p@$k(+I>lf=jh>vtwQ5=2)&SepWLKnaoE7c)-QwXQ>U0nVj*u z(E`Bp@_7UZk?ga={Y(a~p}YcYR{Z{UsPTqiJwlQT)0OLa!x|*QNr`095Y_(;jBKJ+ zdvzll+YC8&TmcP1DOw=@nyh)XN{90Unx;r^IJkICfotXzFgs<@VCXuf70}W8aWF*# zE9G(}mjJKf!^4lkNLXWX6@cWcv2ThBRSTMU4w4@4+Dtui#(s$r)HE+6P8y~E3CRJt zWso2^7d?FA5%Tdtz9aR6r8^dvQW9o(KD(6@JFD+a5f{eFpe!59+4W*HYHEpp@{OWd zA+XWo<*vo_7Rdc~umNJk;nbXb3VI-JN;OYw!uz8Z`#eN-R9CoD=&76HsqWM4KM>#8 z>WcYx9rL$9M6T`l9OEnzS~h+}j#3IoU2FXSrd&|*J>RX z{hrR))^j(%r$f5|)B9TjR?rB)%$uTs@Y{1NLw&ryos}})#+%;ghK}^6tM0qVm#H&4@q}YK&LBn3;JvPw{b?sfq0L*Dl@{)J-RhBX z2FPCLS?5dz=1uj$^Y9n*m+r9*$n*+m9ZNhY$x#7+v~kqNX# zw9! zmus6oY~A&KIE}6`Dk%S)S_I?^g@)y0L1VTONU0q^krJw@aEmp(p3UH}v5U-hn-8;F z>`tjhhQBiyRO$06MI$8yiJxTE8blR>`cXQDjqlx<`V(jo<7b@nxs09Y9SQ_aeT=Mz zeA-zcv@^OM@7mT|ug4%BRFWlE)&8~C)_Tm5R+w6>2 z)7Yg_;=i&@y(N~}m|xWwyrr7&u5Pw_tb={6UK2Z%_zE#=WH@wZ_4Sahjn167S2{J? zOdL1}Ju?d0xVw*P$C}v~m|%6E61Jl#vrm;w(VqXlX_>Tl&sEhoGso5G7TINb>+5kK zzbEdj<8~msr>A_$EN-hHV94_qIco7QSEB`wN?<@ozmxYWM@4EG1TVS+r=gS*-GEDK z28Q=@%Buf_qW2PYdlI-o!f&!$6x=9PCWQxEOCVh!ZW;FPznu(nmCRnG94~nAwPv;D zwGAw?E7;%Jh)LvB6-y@kmdhDs)J}{rT@yG;sUE#xp~kGxMib@h?R3gvuTalP5$)4H z+6}+rvbG$DkV-q-RV|Qr=`fL0u-)$PWT$Vncv!i#BIq&N5R6X!fwy~+wZ~4a+GLAI zp-TUFFE?b}>7^Py)8A_h2zJ9rZTNuA#s13w9I!_Cg~*45?WtITtOl8-$gF*D(54dd(<-u97%Q>6ueISC5mo4?*wkn4zvyZZFi4iAUFfvv;fQ@$+<~!IkXf zp;WzhR^3dd2R9!L;x|XENewB8sde9`_~zY1QVXNFZKg=$CqKe>z4p1Ky4Tk!pVavL z44VsM`oxOC&DN$m(`L@FealF8O%Z*0U)$$)?JOVqH9->d@}dW3tx6srJ$}UD@DAM@ zQ1U{c72{E~`%-j`L-j6R&6S3A%D6%9PGobkzZ7pBKOryT+AR)4S{uv940Rwgo++4_ ze#xXNikU^l_xq|X4F<(wq$#irXV93$c$?KY3ZBxNGN%b;8&mXY+>b7fkPu8AQAKTk z+EL(o)SC@Pm*nx7%fq)`<@1o0*6+6t`i7+@#zYh-y{)k|T#lh3hqpnCKv;jSJIB*y z<}&akwr9It_QyI(la;-5=WScs`S9mNl}rx-hk@QQQ>;ePn1`2fyP*AdX2TdJ9zpKT zSC8+Ck^P;konBv?kAHjg-0GuROJBM_Y+bWO^{@c|TbB{^4sFq1Nk(siqz-K-=i7SN^G3?=pClAo2pVCI=M20u( zUFUeBvHnEpr4?A=;k}&$Zvn|0qYMqUQ1-~}1+bSG7whN1c>vmV6p561u2j>L8kSY2 zyT_!-)<1*1leghv(M$XxF`Z$t8wjUS`1ombmxVg+)>U~5+aKFI=S)~BcxA$mFkSKI zB8~a)5B7potGxK|p7)w%!}YSBv#pG%99U%pM)oT2B3|vtgWbs(>Gg`cauvA>_l>urcLMXxPx9G$k$PR+qJ{)pL#wd2*sA0&mFWoC~sRnP^HC_}1_&v*L%&aO$ zH1+d&S$@PENHxOf1mqM4F}~Q7d1a&R#K<&Fq=W7ck81FS(IC z1ezC&t}VUMSZ!jH`8Esr@GZvJra5g7fqLk*=igBN{3S0sApVM&q8lFHb+yJ?YxBo^ z6AOBfWPFIA3ru6fD+BJXSFf`xpsI=>rqvV1mN&XYL%V6de*U0g*sTe9iJPN|3c&ps zA?U=(%R&&NZSuEmK-HDwB@i^SUKo*YTt~BLbVQlm9>!OA(q41Zkoae?7Li2jpvraa zdLih`-8Vbn1u+A5EAWZ6&gPsCbQa4_Vd)_R!@t&o>~nN);tO}Z#=L!zMX~s^;kn|y z()*ig6YS|OXoKvZ*_}2)(B>&gWvk|s325_FVL)oNziyhQ)|R6CXy7?de%G5r$36u=76}T45jmqp!X2hm z4$r))YrruSbiv(~csumvp!h8=f6EJY^B~?NqmLoZS1b?<(qr3}a-M_Mf`QmnAgLr~ z8kx5>z!_k!$?-YLTR$vai;X!5z7SIPgSs8f-~(Pzm-<7O9oT-?yuqf%Ur;ZhV(x4* zP{e_!+h*6IT8nZeg~hB5`ar$If_Z*>L0=bY$9rf0pz!jA#S>0O)31XvWIp^Wx?5>d z$u=ZJ#}i>rCl_tjBeLYIvlP(70{Nkn{NG`#`u@eR#qnLKR&cb{b@DI`qj3sJ(@PF| zp?1MDaF_RE$O;*YlC}Gjc$sVc2@~N!RrTj!GB`%%riQ$5tHUZ(LpWQTC2}` z(!;C|>gogUqek?*{-NWJwzLw&z1137cC`o?lecSX6UB1ds zKs_1ld3}@f{T4IGi}*ID<=@G?@JeqB-S^q^`oCsqFmo6I*@qKWcPLOw#n1R1tjpAF zn{cvwzUyo{KCyxK2CuL8aAR8iIQr6dboD00MbeR~tjoWk%l~-!zYgDP)~CUHT8}+= zQj|EI>d+q&{(ZCG?os-hBJ~BBzL`t8_Z@S8a{3M9>zOiWS6wpzfO0Gq5O*gd!(5c z5azfqMQv)`Z4B7Y@rw(P&v6fMl{U{eZ4N4B$bL_rx`XI;PVqiL?c%*igXV=!JMDQK ztAIP@Se($+kWVfml%Uw(??+F#1o(ReE3tERhxm<%!ym*QHK`ievELMcRSj0DE*!1Jo}A#$ViEbrU{ ztZH=^lbTw}9OF2KQqy|a{;`*s1iUL0A37k#?VnS3+7)zs3RK9E>zrT!fjKuznMpy3 zUfxW54m03$2WufPGykO1wBA!zW%%!$_aCgMAQK}U^e)3(j;=*S`HIw#hr***VGes; zb{AV``;o`7{gax@ykEq1C2OsVUxGknJmTwbH{pcpqs<%Xw)rc!i`RL|*1#nn3J1Y` zPAo3LKcVQ89|rg5q}>4U$baAToJ&7c)1M1WY)f5@be(Us(yn-SkZv^pW`w=Y=|QUn zI=g|<0@N2etJTu~L=1#Qq0h>;XnPb7a2Ss8RD-m&C!p+UY%S}!s*1C~rGGm~I4kgf z8mJUu6$J@Kx9SdV9yYF3Z9kQl(X^?d>MDt%K~XP<@38p^3+1#8SpS*<1isu6Oy+kD z-yPhNNYU!-9K{qtb-jHzRui#%cgW2kT*y7psZomYq=+<#)%eocRh3)$= zoqfq=y%i#|raicw$?rnl;sWHaO_F7yafo;^AKlAqd%L z!Qb`gYONO2UP1Uf3c{ut6E7t-HUf#}8O0?!^8>G7L_3N>01?E)D)hf;QW~vl;z*s@ zprg#9yutGv&lmi0QMpb+|JWgEfeBh&{?Xs=TAr%#B$ZGqlg2ys@9~*gaG9=MA%5m} zueC=HiUblUT#IQJQE-_mVpl96s~*r+L~d@ch;o=|5xCX9bqyDk*w?)mam z*uIaSJq>+89&g+<_e@?R|K z1SVt+7t1Q%St7b)8!WbQxPFxeWb@yygCwLrj%d~MYzB|9Y#HnRhJ1+d3rtm2L^)vQO6}KAoarl&%Uy^XD03&;hK#Q+x4yDsmf8qfXd3&J zAc4RKax|juHr;*TZV3#wW*RjggJxpKDMLpRETfpEus^~yC{fy}Xp6U@p%g{WsyoqY zxcd8u{Vp39a1$mvdUEe?m-Z6_CQ02!MDsamrISP|$6l6-#wzJMov$JnYqQijKpLVB zc<_2nEza!}5pcd{Pkg)WosisFHrUyX?gw!l^s%l%bb_33sjWiX|1=}mD6)9Y-xZJY zN6KMT5_c3y&BZ=M-P)8!)?ii4NrS)!@Q2Td?1%iZvXv_)g}))|gC%fsN< zkI50xJ@DQ-2bvM1S>&b;Yeji_LY6(4tsHla!3C<{q6B!gJ}!)NG;|M~!IYLe+}V5u z_12pQHOfDme|K;gG>uBRj2OQ`YozK09NBR$aMN4x_VNhEyo^!l)R z$jGRw@u;g1Ksk5DFf(s3^x(zV*Xrp2?X<<=IS~sx^5;XD<8BQta8DFuw*&UiuPk$5P9E+v%S=WX}Ypy?7Uy z#=P60TCEq}`xWT&$aZ_X{6QD4sH3s`2d>)M+Tnjp`1fO|t~v1c*aua_D2sC);o*~9i1$+unnn*JKDMjH zXR7sEXgOC)SLlmKtezkq*z$f5Zao~Y>|^CFoM~}vHkQ`e>pqQhbk=a!s8$tnz$mI( z6rHWiKFH^Q**yn5N5UEfBTn}SS@Zm`yW#zN6Jib|JA;pNJG256-DtE{HcLA4*=LjO z-AFeX}+G9r3A{G!nUyTgtE}v80GSOy$@6;kTpQ@ZE;~)7w#3AHg&U@ zX(Rk8F}&_iiTa~+W)<6=?J3?H(pcpLz0~gSt;4Ln`CJdR=0|Y{7s}qvw4a$*3nP3iH^G@l&OSaR<`%@~A^C>fb=VDs3V+<@ z9jKB&!ocPmK6*aQmvy6wZ%$Jyv)MQa8#uUvRm*1>e{SFzwSToH1A_dBuKq9_C7@+@ zS=u(oY}t@J_S#p$PpBZKT?WFd;!XKX9sLsGXV*_!0fP(B(z*_CGI!9r&u4B;eH?v+ zUb=9{cP78~T@4r#xxNzDaM}S~0u$M?SUa(R9@K%|R%Ls;VFk+YPo5Uy0r)(g_NntE zz_8WuL4U(8cH`m`%WQQF{e?&y`|su5w$mYxjPdBz@f64jt8(Nnyu#&>?_hgAf^l&I z{DUdgIV_9+hSaODU?5yBdXgfNlV%+W>Sk(uG&_-heVMDp%Fan8)!brvRf|;olV!X| z7O{*RVZ`F^|Harj1!uxUTRXNUwr$(CZJQI@oH!FrY`(E=+k9i&PR{pV|5c~XO;>ea z?!MU7z1Mmc!vvLiq=F{#mNA&rxs+pTYopWaAgt#tZ>n53H=k1l18AIze73ilGE1IR z>phIOy<$?-1TkbVPp+lys&p?Ha$z$%8#Ha$Kkw*fYlv=tBo)jAa<5H&(M0zVt7*A9!d4dbhA*k9^vVud(v#a8 zP4{>;C=PGv)w5zIC~j4|3=!1m4)I6rL=;ct5+XKPoc?tGHHg$`UgAuFICFDw@omIC z-He85Z+CFFE7}$%wN;z7YFX7{*lBgPq+u4&**R?Ivsgv z$&@+uW))QglQMZr3Ye&xxkBTp?`W<`>2<70)Mg--=;Wb;=#y?`0HQ7W?J9+n56FG- zbP7zTlQ)%iFRsif7GV^&RGh7$+WcJTS9r3-3UeJ!Jcm&kiHc05r2aXP1)@yu{yH5) z+n?OU?%-fTvILvF>A1Ogz4z(;ee^8rJD}P+owSjDjP{{yc=ZX$kX+myM~H*F%JRn1 z2#=qhZn&q6k$JH8@j%{YuGCHf!#kp_!ibZ5WK)lueaqC^^KdlwJwV}V>a;M|>=9B| z?h!h>9R4_iuCfMVY#vSuC1yn}tG3oxm|02v=$ja&PnUNIO;;21?8&_lj!YqLO}vnQ z^W%YaP^a&wHrg?bH~x$8oO@>}xfRTt9ky`F7(93a_n7?TPYtCzcO5D&f_(EPXJ-0n zQa0CQpRoDMT-B=;uIS+7{~(XtG&}V!Vl~xeam{cZAZxUW_(zyo-v~5yZ97%ko?VU z^s3Jo5PH>~eBR(3sL8r|@&_tG4WU|(7Vf^}Vc3MUHqUh(Cb$s^a!mf9{)}gqk2d=u zo9zlK@9OhoDkJ^xS(l^cvu@-`W9~s$04&c8&!@h;$HQQz3Ik{3+oZSM5}7t|;lOyN z%FWQV2Ku)Q&a_^`u7SGW)WuIKnNiI!|I$N-6lUsZ_F%UvTZT|BlRm@W?(EvhtvsO9 zt(Df%TgpYWK5Ar7a%(w#yxfmb4FU02(PKcUlG&d_wHa9*+OmlISZoTH3uvB;0${?k zrX^8QY+54`pu)%FNN=HMQs1h})WGH?qtbTvx#TMwnA(x-Z}X61jJ5OcdH(Zpy~Mq& zd*ZL}2h{$EIdLt&1wuH`-`%_r-Q$IbL&ncT=yCr%bFres%NuJSqs+C~Eb78^Xe+Ol{xcT1>NCL!- zu#*oyjQVe1=KZK2dSd2&1It|!OOyk~l#edc`t{iveUP*)*lvnhBQ;f7xAT~;w$VW^ z1kBi8ipRrAcsKf@`%CfQ(~-Bysqfz_npLX^0sj`hZVTz4(hXe-Kfwm9u&Py5BpKc> z{af?LUXcDweWoJ{6-#6s@KC%B!}2oZ4!!4#Rdg83VgC z@QDm*wRj)7eSzOnC^e2_t(5%{T;zZvkUTYb+b=QH>%eeVMTo_SPh$Xul#nun+R3Bk zJYJ}j6VfnmgR1S-wlN&7f0kJMQ}--!Kbjz-Y9T&fDk z3{`9JU*GpW{BzW7Gxf)~*}ILmRwc}V-OFDl{P>C05!{>k)e-U~%+Gww#1I3L(@CT0 zm76j&-EIyFoC!GD&isI@K%bhRv>pn#65LS|RCS=)uJi9m1Qm zmjLU9D@6x*#Yg!Cl3n`92M;(y=xp9>#l}m2LlfMsxnf-JYA**LC#lwFNKT>O5+5H! zd;%nRXF8*NzySL0G^1{A7}t@M*A5%kapu%rfLqkPPQr^JLY`caIF5c`1q`CBlT)*8 z@E)GFmrZ*eie5vV%}9t<{e3A897`jNrE!6k_kX7$w{NO0xb$#vrF&M+&OO1QcC@jf zN9f|yC@QozdCUdJ1Qchdyz!3A&TbYGE=~NRXL1&c&MM_};eHYNwpN2sm`a+Me=x9= zr(Z8BqhfiXy&>T*&b9espb)DGYROk+8Fd*FgUKwVW1~?$%C|wzK@WFyea4isk)R zme!kWNeG!%R{5Vn2xnity64z_0y{Kga?yX+S28g8u7R)m=xuM}qWoFgQop1{Nsi;_ zF7p}ai~I2Q%vJbcc*b{q-$&fFYpKuT)#qCBZYCYrlh?=#0aDU(ueEvBZCQ>pMVZigKfW9$IvtkSbl&o(7*jF1)jkp+AwK^7AND4KG48mV5#WhhelR zb5}6Qq$`5nEm?&2e^_g#A`-Yjv?!ssS^I5kX77LF{;1LDXTr3i|F<+yYfsJK0T=ofjtR7Y4E~9(~2*CDH{Ujx!D#gAD-|X7G%66n|Sf>c}MwX8)VSezGpsYzE6+iyMu!$P-{5j`Q<(Vf$hqv%69C7yGx^6?DEy2Y zJl7nZa*X}x1(ac_mn71Vjc84%=Dg;~^ROv+ZxY_~|=b zM*k;;n?W~R%HShg}3!|)zMp*(g$rr>icP39M0n!s9> zHNP1dKc^mUEGsL{U+83OyLc>_cdFNHIq0Rv4&rrszjB}wFht4i<^(zrepe%UL~PVIE6gI~!y zM^sPt98>Qt_5*^d`6mzG)~u>W7Z-N1X%Nz+cwUwIkXbY$cd_-3Nk;@KseKdZ-EYwu zu+n5Tcf;Q-{xN<=D;fEBiv7wu-1jCoD2!uR#Cqp4Kfq2KIO0l?NF7=v6H_E0H#846 zk9uTwqMQ^IJ)D0mzqbZUK-Q`fd#Q^mi3wLngN8*!$N7E#YmXlLv~n{SDk;m<_(}C8 zMBk>uKH`UlF7EH__7aurl{A(ajxFM&N^XrVs?q~LrUmP5$EDZAvF5+=D!doOqWJqd z#;ir&3FDR}^L|aT%nZE~Fpj@3Uf0_K{`&mfWszel-c9aL3_yb>uY0ena!VP&3|($^ zht;Yt)0&DNyd!(7o#3)AAMfvNQ0FG)b@L*}-&kG??0?b{iO5Du{LB~d>VM74T6GjzYu!>BJ zdlaX?W9KYTynJrKupmUg_X*f8bVd))T0*}O@mh$^YcP;a1d)kzsR@x{~cnh*D*%)mONP68)R#B0*q+D*tJUxKRFoO%Z>`q3>^Sbo7fy z4WZp+$62i@;zr1WGxoGAPc{De3d~8Jji>ty2I2@CPA~*{i&Zyy)o$^>Pz_6GEG?Xa zTw%5UPg>-9)~4vGW>zj?Pn1=;VlEeYOc*CDA`;?X$gn`puC1;Jz;h6>OWagN6fQ~- zbL)K&_SU$_&^`jV%;ib_{@cFEr4(Kj*#Z6(eEd3ZY4oH(8U*%lZJzM1ZT7}Cf!6>L z&jLrBfm0}fo1**%+Yx?;oxIepyMBHdLO+fF>l$$=MdzgoJ2#hWwYr)-m-KC7A1{(B zg;a-xpI}mpnGTG`nbPQF%J|$yzNnSq0axQ4N}Hw4-)#?2pA6zxH{`E2dX(<|kBN+N za!s8sZ2cG#)({8#=Rez+Rh>Ze*=MQ6u*V=6@9*K;&r;Io#~9K#uf4}{dJm^Xn-0G< z1L?CG6vJ%^7+sv=ZvZg`EawL^=XORs7Cjy31NvYLC`&40as~CJVXu(V&qm18_@ees z8N04v^$|@L2H1+d?^aA7^;6ZB->4HaLMnJ`()E2YDPy=&=@pnbH4*CQ$rN}$u+ym?e6cEGAZNZr=4uro+HrzMkZM1FmXLFie8c+ zaLX?m(zk5Qe$Xy;f9H;vLG%rq$b>PJbiy|gpGhD5YY)a9K|%JMl5}0w#WksS{QtV_YT4PkB%jo zM-kes$<$Bi_WYYrY~8?~hso+?AcG#k@w z&ViB{;Z7aDYZrpI-x-RRt%-m0NA69Xm-WT(4G;=J zXvGoxvW;`WMl*z16>0mML2Ox-EI+zKz?B>PWs>TQ>1>TsCH8ERAo-cvDKHzw$Ul56 zYU&eY2C<#l!o+Hh$JTI0{2`P+- zwdziJOJCH?O*|V{3(nzO*q~WlfTlNAV)zZNa&I*GHtAS0;4%K*e+Yzd1lQ`HJw37d z?UkI#9pJ1nGybZ2d%m8M^S@LjXB^Rj)vUEj*Mj`9?&gYt-0=1vkY@Pj?~ zMEqM$v9lhe+^gy|Mwbq#x;X?pT1+@R>hSxaEOtNyRaw4ofE#Gm7UkCB1WWcb`DiXRZi* zu&VHl5JnJl&wswCA(DHs2;K)PD@PPAQ#Vj z=_c|Es5NB6=T}*z7S|GKKki;#HN(Kl{It7*!qXy|fO3;5E+X!S4RU=wm?-l7T8C0g zSXl{D^OUfwqBXOJf|ICI6IGk^ADAzEWUn}cV1#6hAacpUWbK24@m<`*EvLf`T)~fN zoWo{cv1!L7j8xc6LLjx9icUd zEjF155~fw3H#NJGvWugv1z`6o*BHZw(*w{}C4Hy1&B)GX29%xhcf9O93q{D#VZ|C)xwAU=nX3lTj%y!)*360>vqR_8zy0Dn+pb<*DB8y}1eZVk zliM+NX~Ni6PCx{o$k{<-kW(?RRe_FmtHjb zl{UFWBqlM7A+%7)Pd1d$AUdTB9btcX>nG6u63(Fry5@WnXRmITBu?}>W;rpE^)r+x13Aeq>4N=K>)uPinA$ab=$d(ewLX^VU5MWI zo5v5<`F$Z}Rd%7BjNY3w{*Cw`TnLP5jvC0#Rw>0hv_TTR62}o6g56WIss=u06!$n@ zyl37$c!W-u1+De`a-~jO;9uwcTGY z(m8!+bl7b`?zAKF({R|AXPK~~hXLaKoFvs9v;Q@P#`#AgVuCCoQ7JEA0MxP0x7*|M z#^}<}2r&C$^NAej7x@ia%VX4Ur-<+kd*GL2Xl8|(tJsBU=cYgYO1*2)xb4mAuO=0Y zRt5JCx*?VNJcOw)3IlVO5hN6X5f@OFS3V)+$))Mb*{7R^p$_t2^3Nt?3VT;{i?KrA z(Ms|a9zUXpR`Jm;h>^qIB@)OvZV^EiFTX}}QBTI~nNxX)ym?*Ggdki(Wp+$)&)kHz z^0E2N`LQtbbpu`ga8F?I3P$x4LASQ6cjC^7=%lpqTuP5tA7s!AOZR6u7dpM;fN9F}d%yD|9`FFE9{ z9T--1NW$R97)P)iaR;$V{Hy&2M^6CEz~}>0M66z)7w}2l2iZTsexU0O!7pR}^fSK- zz9&P)0LPi_hjhFU;;x0CnD%wrm&ftAI*f`MeuuQ;^>dybJ^&&x;R$U->{wGw-ia@o zNtE2Ka5VmOo7M)i&A8kTc#Q(Lz>H1zCjlaObn`)uE{|I&aibmzrHy=pp=qPwqmsoC zL!;TGM4^sTj=+TYH~;S%n_uQ#FCiYmJJg>8XbFug5wk8&3vTK$QUn?av98^N4Sk?} z?vl*~2^`dW9uQ;ZK@D4UyneO}A3VJ&cv^0_;tOD}q=ew(@EEMqg-~b2k}`o!5!%HB zh~NtW0bzqu5h7ypKQb)qmwT=h%nxK$h1sZgD z{0U`d8Xf;Ginhyc#i%08;a3f-5d2zJI6sC|MeFDcSzz)WH}1$LX$fme;t^U)r)?;} zDL>vZq52NI5CTe*WX^FAaPt!|Z2A%e;rd|kA-6@DY*1pfe3FBNz_QZ>lX!~VZm1oQKmrqBv%HL!xXeto zS8$;e_zfe@Nf*M`DXGKnmIhrhP1Jqf1$@n4w$svoJsT#h*6*D0i#D&%{)A58~i7GGdA4 zL;t0!(<-tvJeBvm1%~&_ZQ~vcBUIph#o}B1V@u^NSneFhO*}tn$HV^0okMeZ{oH#t z?jmaw{pqtmo)WYNS*{bs2`)TFm{JPM)(3i(sI3d*d9 z1zCxqZ*nQjWX5isM7T3+4A%=a;=+Y?BuTiv-E#B$1;nAZ;qkiItXrLQ&2JupjhGFV z>HRHpr@R%EDuBjQIw-sRW}%T4TASF5$DDRg^oGlH@3{Tu{niJHyQw z8`&?t#!k=_4$P-#7!{}P?-0`vVEcWaMp(nf)ACJz?ag-(aBSiC*M|+6+@vr2klwZM zYD)Uz?}C=Jh0rXx=jOkHJ^=w{a$H;3yFgf_%^9qI`1gx|njLG$w`c@-!?`SdC{-a! z1xAq|)$h?a0AughOnV$-p1*0!w8gpLK;qnB;NVZt&#O!20mXt)xc@C%Q|nN_XsUOL zTRKs(=88v=T9ZmS#Ux;TB^c}s5F5CIHc}~}@8oM^o^b|-c!(~gA?IF|fgO_o?4Tix zGVFf{YBn?xsfMm){_q}$3+}~8fwBRNdiC;ITkwo;%-B}oF|*Y{+*%s0Ap|YXa~(FV z>=TKv!Xm${zDN$xwImSw@_ToV*}1^)^Y~Fw3rk2!Q2_t(?z@jh9B_VDd8sUu*MT!p z=5u2I_m1o3xI~ELf_UscK7VxD#oA4mxfGN#e@4n5t))8S6vmnozxEX+VRfxwAUse* zejO>I;*-i)hzc0?MQo(`27p*llM%iqi;V+-P;yj}#F0amU6e6^_m7ri_faOC*e27K zac#mhi|XG^o(?)#$l#e~ptGpK*&N&s^>fp7o^PIkb&&fKUTGMpSN{ zCJ1`admqyS34nrhk5Sq?*L7Z7kQIZd8rqw$eIZXzg;H|#whj7^H`44pNw==(Oc=UX z5mF>y?e9@)=DsAfGM3AWMQu+GrF5Wvb6s)|l3c0@j(z zXeE>`7r_P(u(fDoq_!)-Xp1NaNZ{1@>%pN+qol+m_MTu_?~dhGhOBOFz^Tf~DYZ(q z7Y`1n<*2+F`>`x#Si?Bc&_TukQM_Xm{KGC_Cbz zx!EJg5U?#7VkPEXERr@tyNu?9lU!#U++y2?SlPY1^mJ2cPB?fkJXW0i&!RFBK_FM&Uo)n zA^gulO^alOK-kN)fkRbWjr1n^>hjpWMw!6Udg)2YwNh`4V?>^X`N;I8S{sMuXqT|B=OP?-iD49oG{?{ znJ?G-IDRwF#_#gdJwf1kqsR^k`LZffC9BZmofktTF@)2D?c3hB%QfHAT|Z^L)ZL#$ zlW(mc?`7)Bipck|`9DT-yVYW+xF#kB3}n)>#`hmd~)ID9;gQ$A&OYJ*T_8#~!K*Za9@Sb0PjDDk&l7@*g<^i|sa3Yh%j zhJ3q-BX*U(5DM5rfq?^9htIMG{^KB0TZ}4I_IP>~Zo{g991I9`l%3R<*f?Fq6TDl8 z7n>8d_?}A_?mqb1v`0F(&F4^&u^eT}=d~_75t5>ve=>!Nm`peRi;qGx|vV%N&luU^1kt1FLG+!_(sW(2t3|3G(yr?!+Ag3kiy}f=t8OK99ePko;q=Qr&}P zAtNVXQ4)oLfw1OKv5AhT|Mxv+>Mb|`oioG`%#6H@5L8bh?Cn-&cVx{Q!p_Qbk-Pab z91m?l4ZNcQV4U{vwFt?u<3$_MB)qAZ@;kr7UZ$eUTTX|W&KZs|r7d>}OVFPM0tzc1 zn$86EP>et$<2x%qUsqjmLES;VG0r;1z|q-ER?h&O!I97vbXi0Z$^}uFm>Sf-kj}QV z7IBfx+N!mCNG>WoAIX@pJZ9;1Kwp{f?~}Q}8u{-rWAIB9iQs?k+D^@}I z`!v8d@b;RQ~0a2E6R8w<+SYc?)IneREk&sy7s!v#B_f)}<|HVsSGX02`ADz?BRaK+#VGlRmVDbUT4)pS)-k;< zO~oQKBnWkB1Pwb@jPe37zEJ{#ZH#@~+MS!4JHCWvy64r3C6#52xotkT3e^YuGYTi}_CI4o z(^y8t;gMN7G9nk}NRLf9sDb`K)zlB)q|i*E#HT60*$gY1ipR3@gB(W;ZEn%h1$ zB%|ipQTk0@xM`{LSfp%tlo;u>gp!VsFd>kN1~?|5lW^H`qTpJC36PfjRq>uY6zgxt zsoKRS+MxBse#7W}DJY^6&QV$ph5S8y0ti}W{t?_=GLEXTL#-qGGRZF6`Fhw-2C>>~ zyAXC~1~-1$?i-+9elI$t5u!GuSIg2#1@7Ctzd|Cbt1TLQOBZ2-B2Ye`??5nn2vFT} zHfbmIX4~_NFA!Tz-YydFtqRcQYzIKXtrLPcJB1<5zdi>4cHIvzZ$Z>g@gHd zg4xA-12;cWHYWCTM&k8*bzt=iC{2ksBw~7ie`T#Z9_Hx6&-G?O4gO{9Fyu=kEp?mM ziq(P@nk8F@m1l8I4Bj(&$@vK~v?;PxuR<10TY;P^eHrDY3DZgQ8ZA%<6)S9pit-nS zf^{4kxj+UcOlRHfz{+5berB@ z^l2z_0VBG~-JY+0ogun-wGjl5N5{5R-7WomZr_PFD-30$%dL5vf>#gQ9dG9@M`?I1 zwO{Yb4&{UXrtT*zJxqIn2?*f>SJ!BCNI(~N!EhsW{i{J}B# zvYMZFsCTNgr|*uc)KPhha)(I0uKbbpG5Du07 z$|F8>I?^Varx{Def74>z+M#bQpz16AHPtl>U2S45iV9O)-&raCxXRt ze-qo`@t+@)$(3ijZ|9X3G!hrwxwMgWkiG8MRnxt>h8H|~;SlScx?XdeKMT}u4%L$Q z40wS3@;{IC)hs2%Z-ZY#+H(KCPQ^|!k~SMB{LZbuVV{VtZcuj^xu*@NajeafR1l7{ z&u~l_{PP0_tqfV+)9(|k)kn3uhhq_G4r=dblp+D5A7mCtLtR-M!slb(H5^}3;2H;G zoV}reg{#!fd0gKhEwNa!L)oqyJ%gyF3(|S(D(=nzJqxe6W0kz5yBzFuw#Y4DjKws# z3Yt*&IT1q(O$^N7pbEj98Ci~|@e}&H0jX}d=Y^3l4MzB|rVY3lRN`{Qgb1lvG&8Ri z_!|J}hjzo@V)OP-evAy@}KbB~6YlU2+jL_HiIv@rsDfZ=w!v-K&SI>ipb;;bCL z-mq2)WtgnXK3w?1N}?L&Zd3Q8qqB6Tt>{}>X@Hhfyn7E6h4*R6`@q1&n5eL0t_4wZ z=pmcb#4ScSbzYgO>h5x>s5@)%>P7xhfv;+3dt;F^;%p@`fJvGqkSxg@zkb!^7nV~5 zwY*WE7=2!cT-av8P$*z`7nA(%Hjz360P$R*6xw{4I!4HIM?T|ckg3EjUfs7SfTxpu z>vp{z`(r^SKXXZdx5-yp>vFLu6n^FPW4qc}IJ_TixcwK8YF23sDi?mSBjygXq09t~tNrYalC1}Us%#@(Kp#2AZr}nNuy`z_m zg=1Nuy;b4@^yEA-=S4(ndoCdbVKT~VwjK3`hom$mGk-a@9H~6LbZjH|_F2S&#ug)A zk1^E^R*C051*-BRX$hP3awzI+kq>M)^@Xq+9u5@3VOxN%Wh+2AZpGBE@pW-%B@M%X zgJh=PXP=?|^m(yj7TameWW^;FP=N6R-GL-zFG0xTW&F7d-T=jrjK3w6h7X2G9QwS{ zlcE6ZRK%;@d>Y&-uc#GRQ}{OaC=>b{z~o0rXl$>on_=oPCy!+NvQQ$~Ac2d?l)RO^ zbish;AJeuS#Z8Z0j{<8;FjyuEX&!6pHIzaB^k`OEj@`4_4lp%n)#~u*WarGd&s!&n zM{rqnOxbw9ax}-dEA}9Rl_DIVZe3f)MIZOlH%XP>J^;FgIK{QrTY#}8vzx;(`K>dX z(Pz(x;Ehwi#R-~4neLGA9x6+8NPDYt32Q0sFJMX zwU{4)?5}LoFX(6y?rQlZoo&46hoY~%JlA1cUfMEpR_AJ1cKZ0VB%T%ObTE`ZeWUE9 zTXmqvAdIN9UDU z0VG$d`&l{2HGZ`_eaHqOXlDS*tCNu^YlCn(la4r>eKi~MtaVT;Q{Z$_8)zyIx%Ygy z1R?x#T=H=6!F%h!x$BG5di{~Nsqw2NbAxPP^WlR)RgI=w5&7T9YDM0Stm_6(GsB$n zhF@8GcOl8GNow%p1mj7M0Q~k#aQL;{K8oM&zeg>Y?Y4n!>ROgn|h+@AcA?m$%z^RRxXpyK{w4$q0f zd;ZYP$>nE~r!1?_tU;kyx{74l?qP4goN}=5Y4>lstAQ@uj@{)gxOVXsvJ;6U$UH3O z93HESj8;>A0K$iD{lIOaHzcVpR!`W6_8$$;$ZyjZ&@*m%v+Rqe$%?&BmNI^(-&T`1 zaH0`VvL?70>3fCCn??+}#Rg@;Rm_M}FZG!1N`kO(TamFRs7oO?DcNEd3+WTTs|tH{ zn;>5Ox^-a{q`w|Y1ge_5tftG1qrXPoIvj&~hh{fSeOAKL(g!d`;WhI8+#TN>7AFc9 zW0x?Pet4=&G@-dv%I7BFGd@(Az&O4nY=uaw90Q)yH`X0ev~)Q1xOd}oZMT*~H8u*| zW#h=iSL`uAF+X7ir?jozT9Qul^1Z*q+8guwh>{E}Hz9zyZYJFig7Y)e0bsT8g>EYq z>b_1I1!>+=HB2F^eMY+{vTfgaNn(<(F=_c(hCBV!0U@f-?{E|PMkKrkwy##D$QSsJ z?=z&U|J^eGKD-g(!)tHftT9YYzBi*cXUP^lSzXV})#H+P_VtZ9urG+}y!q(U45fQT z7rk^fW;(E$=ZfptuzR?gA>-z``x6oe6oa#0;8c43MKHDf$Sji!{MeU>bNgen)OXRP zHY70rrH1bJ%~p4NU%uUghuuRI(fCd`j@cBSvXb{p4&QEk*T+ApkGi)W=58boX6QKu zUduUt&u!Kd!P6idR;Vjcc(W1x%3_eW{o@5POE|*}BU$k>qv?T+OxK%s@@lS~P5I%6 zha1EUqtLuy0_ScXE|kl$mANq+x}c*tWMkZlQ%+i^!%xF$%|h;sHy%k!!-pQrHJ+*2 zZ;po)FxOy=#2%EMe~K3#NNUr3E9yt$V@x_BH{D9_fK7e3PaRmoBOQy(z?&$g zM9?=)TBmQR+n_5_l@^%IYR2UNrx{9a@=gNwyfpSCEvevAHSyN z7Nh}9tvT9|2o8sW6%K$q3%CC2;cp2C!8~gA+NcPqr+x?3{isXP%)OORO`S-lfK zb>~c|?FQVPUpW9fxAy7c;kH*C;ESm<2O8-e{&4u2cJ8OXMg8%_tQuq-M$nlOa>&I3 z5&5bW7B$^(gI{l-$|ia+D}gG1+o8I1^aFz%C|1!`#O9_S^oi`jYfawyMdmLXND=SZ zFXOz1=BQbuhEe_!&*sICgVYXUnbfaPI#(io3(&cs9=2`&3O~4W;TxZFRwnu}?m+zh zPpK%+T9uiT8N=XZfN8C5>O@}|>0lv>Pu3lM2(xft)itv|x9x$lj8<32U(&~u8}_lC zh0D8N__QlH-6{I^W^30Nhgm7}9%T9=Y0FWk*iwOS9USkYb4ZY8-2EQ}iq~%cDchs> zMdmgLwnU4a3f27}RN8$-(91tnmq9p_ynq{Iw|d^TZG>dQ>e^XPnw>F7fS~!wpA_?< zPyHO$>pMG_JCX&lvc4xdA2_Cidsv!3SC_WcuhIXfi4(5WB9sy3DQ%I6jXiHzui;?DAqZ5))0-QvO z*hU2u<{6WmRGUFixFuSK1w!ymJjIAGLA_KoYyAFeHes}G2*c(TIVlt(Bcf|_Dx9SC z#EiQ5np7c3OYMJLZL0CNl+AvLT7%zCWcXrAbej#3m@Y4Xi=+39z}eE%1}+AjLWcC@ zLFR+qgyvb;mRFm%eM#8}I-|7l8w%`azhdwCR|B3KlEw+(s_L>jhLR??f{t{k2Lqiy ze4)xBZN2jAIr~~X2l~02V#KQNNs^Wh*|uO44H77(#&rhi&) zuD&DvB|;uujwCEfptrMoD>;u)h*9OqN!h89o2AdetsQ7#XEy1TWlV(={sPia(E4QG zq50BDv+K00(FN7O0RM;g!@lO;-jZ)yl{AWmL^Oy zeN!9Boc^+2=rQ=apu5p~Iy2HsqQ3mU&3GCKQ$&bZzjCYgM_w}W9a*E?l%^HPXhBII zQyxR!@<=vEvBJekE+;q8L;kr}-WbaJpbA;AH@Nx|<_w7EyI(%?_U7+*~6^kH`K@9c5gD+&KA9{p%9QHOf!# z%wkl=`Q~=z?aXRzK|V8AKbqu3d8=f;az?~Cy-8#?Q?AruW?{4__IRawwm&A=a=w_z2 zpS*Y{$y%ZF-{dNtiW``i%q>-IAv}7I5_-B`(9m33$HewNz!>KGZ`WivsqX0-k{qf_>VRfH!aRwNHg6zQ4+3-R1aITPrKux^nJbmt zSO~5?be-r@bZne1Wfgg0X--Ukig8b{2$Zgt@$<4!>4;u-yV2ar>S~XP=loh7!Asb>8COqBd zGJHVGdZb$?j?XlGo?*6Jh)ckowK=iC?zdz)rbUs+CQWE_z>Kb)YYsse)eRz{fny*IW;=jVMdQ9QK z-cK8WIefKn?3yy$EZ)*M4^XISlh`7tOXJ95Z~YayBG^FeV5TLJ*_VPNLA!iW7j{E= zwnC3ed;UPW7bO=!6(%m7V}KYz zM*5kenk3aj_Sn;%J1`0vh~FD8T`vzBrd-bcPMB_`HYin6_aID_Xr`0iQEU=-eyZ?I zne6VTkh9ea3Sg3f3MCzF^LWqJnYoYab>c;@9PVf{OGO$SQ(A!6D1EQMVuuQboZ6|R zE(qJhj3#0n2OXQX>ffP1h93RJcyHv4gRA+699YM#{mIfzGr5ROH%we58snT%2#T)=#39KLA=vx9Vr*?Sd~P z9wnL6VMRpAZP`k)-XX631;Pr39d78=<`|ypTPh&&IXw`Mv>RXIQgk0~T}Dwya-6pv zoNH_!ByUguLUJ0K72(B_ou$1Y#W7R`C*6>Cbch_SpJ2rLm+*9*Zmm;QO7)E7WA0K&!Eguo+J$C~HAo=Y@hzI7jzn92(5DBkJGr9M53RWYCeR^XC`Uq3YnB!X_r?Cv<`PkjB|WeABi%6g(a zn{{9Aae(@W2}2e&*0|2loO&yng@`kw3L6JLjD{q1OdSN8R8o<3$Vief_GcBVN{HvI zYD+?+&!ILwfsNH@i&I>)+d#Ce?SvFs3Nx6%Ll6kXo(yWX?4*$g{h~@Q=e#>NT8X!l z$mhcgitGs>n&TuQ=MJHihACm!stI^LHQVcFLUCAD5bo2*q3;j+Jq0Ut*Wa_Dj^`h+3;-}jui2$UHVAHRrt;+6&33%w}>KQ+jJJRC+~s*?&a z;x`jX63HuUI=nGjVg}(F)Sqp!1kW0@Tu+A=XqlPQGIPIo;drJ|K3x?|KJ)(J^ z%*1@M;OJk(Xck+FbEosfMwR5>l-Ws7Lj6+z*VgoG7PJR`h2a;j=du;Xz%9afrm4bSB_MlKQ{V?foG;i~55H0H<{p;I zl0wH+V-V;rdp66nDPu}**m&q>Q%ZF)){8y=7LWT?Bv?PY7^>&f(Y4&4YWS@o{D1$p z{0#n?D*AtDMK}qv|FE}VUJ7GxbY|ov>I|iOJH+j?Hih*y0D|36Hw3wiB3f`)o%SzZ z!?k+d8;lMzJ*U$9g|ZLW%ue2>{Fkx%rpH)$ZWYYO%P;!5;uX=uk$;d=_wC!8xkdNrkZa z#IGuo8ol6#Zt+|5t=Uwbk0*UOa~}U~s%BI8-c6jLy}s~IDsoo&gRhtp?Lm_PG30tF zbiX4g^f@1-)S?m()uUlm1<6Up3_aGFz;cGP(pv(-F8WPg5mRbW+sPL`V+K%ixa3Do zOmJBd{sc9U1e$k2`sH3mAgkS3+2k!U6wVU6)iA@+Es!hQ7hg&42BF=uasE$X?--of z6MYLOGr`2RCZ5=~ZQFTbn-e>k*tTukwr%5yar1lM|E+t!-BVrb?5fk>!{l}}XQ9e`EVX>_YI{|43Pm}mV)V21<`2&vV=C~k}LPn|0QXm#Aoybb* z=Kbkq8qNnpMn?Pb(m|UiN3fV&g^4HjHJc+_RnqM*;9AJET z_{Bm&%nxLJHf(M__bO`EqF;P=)4TmoxLZOcw8ilc?xQz0fvmB)WLV)}WC32D;*QM@ zUL!Wiq^_Z#ft&ELx7(La`n>?P*J$11CC|9nL z42oYqe?hcaqrjuYlM(^D=PdjdXjPj{yRqYM!^$9m#wX|>POvIe1Cn*JH%;M-{6u zaJZSftrIU2@ZJ3~^Ca=!o^0OmB>3H23?gw4(jzVAK#K~eR)^QlGIBS3G}PpCmX|A@ zM*a&Vi|1v8nEcx3AoeWcZ^!kPmQ5E%hXX;xi-jLZE!Fk#?AnRiRKuQLT-*t=0Mt=e_&n z8^&F3MiRLCi!nr(ww$CtnJ*QC!8)Mb{nU+k;0`y0DwQ8xSdt2Bcnx-XLFVorjwCdH zF?l#ik+{n>tPvN?!hJOTx{-I)BW(k{aVQER@QjLTws2AUjIVp^VW`xX`Fb#P;;!|D z>At#ODwJcrV3w)2sJhWKY)K(NMXn=(+>a2p=&@~fu*|Dy@cy^V)LE^nr|Zq&ep&*yt`i^iZKLe(2 zL13{>b8&t1`)wXqC@aK!-(9It(N?dK`1M#*rkQnQkXeVRj!SXMt@%72C3k-uE-`>& zNq1AZou{#3_;hXOQM-g9FY_9Kp?QCO7Pwj}y+%mLUly4y3fTIWl~}SiX%-OuyU-p? z_M@-m9dFf zj7Q%zqW|5;yacvr`(==qp1O5_aPUGKNoIa#g&+pTbMjs_mV4kHE}! zDluf4qFMYskXQaohfT|_^D^nkNF3Y8{|6(G-svp+UxCIikgg>LVK{Fhxj5l%QcjwG zb4J0kb_dC-Ck*XBSAY1M)jF>)v90-o4}r`xP$e~5=OOaOFFZwfG$4u}m6UP>fayDi zBfO3V0{fZw<={)ytFQU?S?NejFn(d3dHZSu4&sN#&+30^PaYXVZ?#=SlTp7Q5WmyZ80UtNy$uU4*ig*&|R_WbixmcJTRUhjd+I-sDu74zj6KM;j|JYu&JI z#!En=J)FI=URL;|%VYo0KYubpgF}N2u`0{^EBk zQk;D9B6bAXOW_7C7%Hm6q2udiKUf@~^mH|0cKI>?wA&3)lm`FX&*k*ERLuRfexYf! zf2sVkR|GOz93h&xVl#SglD^IgJUuFW*DNo9xtdYQJ)?O=Fae zFq&1v`P{8qDClB$TeWTMS(oT~@u}k}25{xmjJKRw;hbIRfPl$fMFByWIQd}&TUN5P zhhbKW^ug#j$#2bmDfTv*yMd@1lf}1tZ!4Ui>SngR^!kUjqCVK}jU?sd^5kOVNtFQS zV$`sV(fFsXKiu7u8C=*xeuQ04wUu zGrHh7N@ip_O`)nPez);Y!46kBRjH?Y{v8tU-&w9j9!m{y*H)>TOdb*#)(BOnktXAe zq9ED&;pO3{1N2h2R{BDH>qVUR*X!HMjyIWMBZCGZ5$MP+(`B*Yo^Bom-s`U$6Y9Jg zTf*!Ew@x%MJ_KocNRzggeH1lV+E}@X3|uf9@Tu?}NnVE1O9fHZIpU>C<$p1Fn~J#? zjnsaE(yz3YQ6a0N@S7#?y2%Zfm84IIk;j~8kw6hr)JV!j@VTug-33=NIc~S^Jf7}p z78}PP1@-aYiQ5tXrpO97&6>sBUi7HQ5P!x@CD9NJrMRtf)4y0N&F$Uq3gpOMct?Cj zN}G10(JD?dFdC^$YBJS+#{`5hnvWEMW16$BxKQ-$1PL%q#teBgX71Fe;D?ez;(;>% z``c68G)POW&>-|ElQ+@5WlQKDHX$#_{(C-yx@4zcAyf70@mIJMkvrR< zD8l1MF+qt-JMH0B2e7{UFTea4`Fbvi;+XHxA7QOO=tS>E`rjt30z};x0(7X69VvNC zrS*shoHVSQ!rc{a?}RWChu`p~axeyM58a%a29YSH;cptidZ-8gdD$2@^u=lW*t*V9 z3FH~+8=L*G8^+h!?(Q#TC)O{R(SY7g>C*QXQ~%+_`G<$UWEEBnWLFB0Yi6qhKdLcu ziPKtG9-^L5VLctLfWv)09W&V_QlilIt}4%7XkG78Hpz?e;)-gEJ%Q%gyDW2#C6}FR z7K0!~<_WS8C!ygi^jNi6dv}5PUknevH4f|wc{dMRdo2x;G&`v%u%NlB%hIx8=0$m2 zPgF_wdV$n4&!C=eR>v$-Y!(wTiiysuuY%byz`jN_%%qNC38hwhNZ(Ju*_PefEkSF( zv5MbbSH*>DaOnhea(*x2TY7d)YnDPF_ep+4#a(nb6I+B|U=)FhguEXiZZ0w8Yut51 zx4B;y!OQsLnLMb3ENpj_dmLrY6+Di-jmjp5ZD;F{DALhCz^sFVSr>1$eJhA343$-5 zKW}h-+^A^IK-V;sl~kbz%7|qiR+p#M^5F+XlTS^+<5okL>t%2%A!o^f)|)H1DTkmO z>4SOu-U@xANsZ&)$+k5QgO&?`ir7z;b%DWC=x}7qEmp$M%=0+-ssNCp&{z6-rq`D5 zVnXE6QSGST-AiY^I>;I@i)6Gba&ev_F~ROsMgiWmL@_VVxO9meh^J`VbXgWVB|z;m zSG)>2MD_Ewt1;FqBHn!`PdG--pLkc@jsIACbaX1U0Og&m?MbOWU|mxQl*D~)H|VyJ ziD30C3b6l}A0x5`@dIx(OL+n5P2KH$qOe`2y9+LA5$gK_uwGAdBAoek)S{Ze}cS{fmwY9#l^`4&c{71r2+N51_`|DMJWRPU<&W zVgAxG6EaF+T5*C~)n{pj{}(dShQ;=tOxm<&Zf^aPiLn=TQh%x@X$PUfTZh&~`yNak zs+Zk;fQgMY<3jEM57x7vNa_Z8B*Y78wY}Tc_{%EURY|N{>rk^wx+%e!zqY~wjIy;> zp!i5Qr(*7Wio}7qvUcJz>kl4~5j0I-DcX*=?Cr(_8s;r-<)hW_I`3gj$cI8SlPo6?#BEtW~IVir;;iOp!q#$v+XsG%kHtv>%0< zs^4;avHOC0OgZO-{s)(N--^B@ZOn6|4u8?Xxmr(o7)F?6CHVP?Z~gEtK|NJf=NkGC zj}>(Z`{!{dp_VE9(}A_o2xh2=?rD8^u==M`kf#MY;LvhGbZ=+BL1Pc_AHF61eE)*~ zh&rbKYuxu2P&+M|kv;u7q;;zi54f!RN7Dkm>c4e=udyTNYnt5%S$X_xF6{n6gV=Ty z;P>S?#cN5YIp1^-)S&x%@m9U|!tMxL0(yC&yWMYitKMaJqtiPyiS{I0r#g7~p2%tW81I(ANGyxPfotikxlDG-D3JtR!p*BCd0akvTasu7 z?tt}{w*?$>44G$jt~%c!ROV&w&^6%Kc>-j62iInbZ10>!#Wa_8h%t?nS6j4X)&& zD%uyL0L;q$Kr8X_{z1OWbWh+<5RknV-V{5?(?=PBtx2ITU=G!= z5vW=%PjUu0!@rSU&i(wWmMa`gh*y5KP0IA+HHIwk6P;~s9rZYg;~E8CuM*cDLtII`cMt9xL1L-iIeU z#I}drdYM^uaVaUzyhUA;IshA1J9rVBqG|Xh3uajv28zj9(RE|eY@{Iy+v}&2qNj@I zOsOL*U7aPOaC`y~B|6knh?W=AX@8q@KdU0%!DC2|IvIXhy~3Qle;_l?2>8!ljq5Bg zvs5J$Pw1K0XEZ+AiNWGoB)RO5cdk0<)?cU+6~0nYEy2~(0(Uv!PPAkma+rr@_uaj< znxD=hd3kpkifTP>%H;}l6*I4$J;^psxadu{>uKN`(J-&48zrXrpv8GBRONA#2ZH?m zP;?FLH9CzV@r$bxq>B((;Ot|aPqJm!y4?Ooi5oc!@FAz9*rP9s3dvE7j9S!o(S05}X?Zd@=xoK)>wd(B`yS}u`vY#_kN_cw0 zhjGvDRX8aviF$C=XJ#sgN&WNmF3@bMeyG!HzhXM*+RNTh9VMG%LofZ2!#)MYb&<@o zCalQI;0honE$dL6#ai5v;=K{aVUIldHD#LGpC)9c4w|np<{)KsStJ5&V4e2LxX=Xa z;VB@8SEMp%lcThei&_a-c63{ifB{ZWh2GDL$=CS`OQ;;0SW(nC~wIg z+#EhHSAyhQWr?z)`5_8ShLOUD_NZU)H4!a+zN+NXLO#*kKg4tR0v5)9F7-J5HY?Sm z^axt36@Qby`VgC4+cs=3HV0Z(Pn*?9=VmNvW1Wr)ZD^d8+pmjmC@rt_nmJfGm-#2p zmA1xrWu!a2it6D}E={IfM6_%7l`+VB_ z{|z9OmuhDoMYF@Jm^Gr5+5#`iLI;~foc}XJb~oTmRwxK$G}hp@$I$t_ zaaOd9x(qqq2giG;vq>hTx%nAO><0-FS9I~O$nbOb(ua$Ob$y%GsI>>#14ZQfWPVe= zmqLikgA9f+Mi}t~R4>qPx_i0cJ5CaayKW?KFF^16{UZ(x8*!F;!H*E_PiVGS8G?wp zp}dlN=c{BHLjTWK04}76|ENRSeT*nktm+wE5YkL{D7yCIHQ`3H>x z{6)DV{|uWL8(G7aJW);dN}ff^=67qL*5Uc~@|pFvQ)qN^Fwlx$kbFtlzvU3Ruc2RmYG|L5 zD1ZJJEhGIK^xkB&R&-8^{S`&_!T$DoDLw38d4+d+N>z0Uu z81EVONAo=&u!#_NfRoC2zWNHdH0}5sZ@`X*&KPxTpak-Azgjjq<4BS)ul@PJ8&v&-(GC?rbw@MO|gEuh{afmnSwl zwI^N<)(>O%r`)2sr!yIuu<^i3055_MoUe`J?@OI_`LLg7Dr~~U>Hg*u_Q)H8O%B%Q zYJX=dXD#yJ?Wymvn73Y6JQKzN)kA>fvWw}2Ia;pkt5b@o%#{V4-yM0`9lTQViV!R?!wbP|%4R>c4#%mb!Z)?cc zG4@p{0G=VZZlM-))KO1MA@B@^W?K#_rI_382DLBV3D$4CfncgW(JDfHp0b*uX0 zZm%opz74|PaKwW6cw$K@_Bg3yB0tLqde8hGxJv@T)lODtt&B(*8 zc-6YzXbi#W;)7FnqZ5%>dQm-Ve#FT;?eh>|B| z-t`BgU=Wx@4Ar?mkB_pbd9HLOg~8hnBSPVhqKTRAuQQnpr__v5%YHmaIxd_=$_&FW zXCqT|n|E`S?}b7SWZL~_ zu65BXx7$yjUKhX1x%o#jP1fY+igg4esnd2n^&(IOCBfU(2aH$WsE7tH$J|1!L4XaR zvbW=`B9;eu3|1r8`4nBVTWPGf<>|m80o&9&E^=r&fz_U5^QkywTP%Eo1`8q}ovkBM zc{+vi{Zr(Q)6vE*{x>YwEp$e3fHUus=+<(tNJljW>k`TD_P>_Zc8flhk7N8CqI@^# zEd@9%SHE=2Q+s53)6c7uS|qhSfj*%x8)kQ_>Eiy&IwLSenhiBj)f73_DO8fLDh(pQH*!o!>MM69j7ciieD9khW}+nquHYQ}7Qp zh75m1nDY-Wdy-iROv$IyZI*Qj*b2r8(pnItleQN)BdDB*UC-3bp_81aws{yWDk!rh zc!RgsC+VDRMmercC>*8-D7tyXt}o$YPlf9Dt`fPNCC4`s@eHeIpt^#6=T<&fX4`dM zMX!5=;M@xIBLdMk!uQ>9Qy}>X{uou<(Iroqz9Lg!XcH3dM`J`pxhGLt*pr-OAm(B* zZyn%Ly>Qz0hi@#O{UDdG0~a0b@Ap`w$oz}4SznS0H<0_f%80Yl>UmU@;zUPzms8XG)g(M7ODcJ%8~0#g!aK75Yd;oVY#^HCtgn47ongVQM3aK26Qz7w-rZ(9T*6fGgM7Sg_-*Us;FBld z95c!IrFAPH)%rsX5J3v*wxxh)v(0O#;%Phk+ZR)i;kO+F>ZuN!`!gP&yPgU8uzX;H zV+Fv&+2oQbQ4oDYbwb&?oP69eTVx+EyBmir*9K;FuBl4%SZe-Ev%fMEW*O2FmPMgv zDySESU~?7ep45$;q6TN}^S;W;i%07I;&sXSO4;5M0t@O2O|Gt?6Yn|NM<52(7|y!H zsP%L_YbNH7n*w)Iq(6Cs7jga*@lmxKnb)h`K&>0{3M3>l?eUOut@pJ7y1fp zIEwTAuGA54%@ZDJ124UO7B48XcpOEwi-{B#L{adKwqvgVk@tQnqzWKBQZHH5-&TDyl8=YbbIt6 z%9mB4Y&Z{fb^|3vI#$a+IhyE^%9X!{^LDN@ad+gyL*jncMn;rGY_UXDEuih9wWmUN@;f*hjz58Z7)gZa8Wh!slAPb=KQp@j8i&#f8-1a#dM3U7} z)37vctTq&()j&6H!PAf19gcIEBbB78oG1D|Z({5-nLqDhB_$15f$ zsfVXq9ObZcW5TJ&>+JUPZkc1KPW3aAI%v*4V{1JU0^_8N?RyfbEv=9Ur|GvnyRenF zUAt5#20OQ?mgmh~$A?;{!CmJzjjr!G7HW=fAP$IzOO6#^@GiekN;~rs46zl3(^6O| zdg?`7G29?eKws2j#O1R^Z_*Tkd4i?o1%H+^q0_WCRf^bHTG{Mp=wHVQqYD^8+bglCCrqE zR7iohqB$y(5k3)0SC=b-#vd}0J+gYuRp_TxxiuTW%R`U%4s|@)Gw#_>6c=TjRukwN zJ_!+!;S?TzvOm)Y?!L_}A+4oYSuVARMY>@SjIEOszhwBD67!vZbYao6w<@Ct=! z{{7__-d3!Yg=2n1%FGLc!?{wUM=7g2>pF!L=(N8(Y1lMtvI^IfPaS?Qe#)ku#uYLe zK0d6AzExEtMXF%_fwhkDSS5Na)cmtjf=iIw2u08$?lGeqVV!fc*m{DHr~f?SR`q@y z*A+K3;i~)qeEGen`kHxaK%5fqb-x9&Q?Gn`d+W8D3!hF#iC1UWFlk{<_~UZj!0VO& z{&OOg=Lrg9(lBD=R3odEwEZ4VAQ(XN-JeYOt1+W&{U&ic)G_uRdN%oHjfOi(%gA}pYuDB^UE0IB^TMsdCx_F{C!gelId9u-t;;Ioowu2H zCR@|3iH6frLMRh(Qo$fn+HlCHg@MQ;q4-Cj5fQJBwqV+S{WpBZm3X0ivW5<*AyWf#)LB+~|t?py6 zgJ068$p{8zN4g4OWB7I4uX2Ou1+(2hXa~m)jnLmn4`R-a!g#}8a`THDLara>hJ+hD zyXQAI{t3Ie2bWrj=l8neu zB|FNhkn?@!4G^}0C_AGKFt)+T9mr=SwqY7OsSW7nA!|FW4fy6^JUg)sDAys=4RE6y zl#W1}m2hizJiv~0Mo@h%p6lSIo$JQWr$E2gsjF8NgpiOoHj2*!i ze48GTl>iCt$TxjD+FoAi-L4y^_aE2&kS(ZB{U$BAPs2ZN__=?*-Rdjbfy}1}pg}T3 za4gt?Z-fbz?9(6Z3O)aVqwYCuL6)I5grd<7B5uJJvxmd?57{$tW5@Rs+f#63#t#_Y zvv=dp`b)ki;l`NdU%Y4T#+l^@*wb)h%?jx5-`;P1hI}Xfg!+o~?d{%kcjMLli?Ju- z#-Qt;yk~O5*zvQv_xuK;1FpJ9%Z=mn`$ct2*@68Py}Z-thNt_T%a3wH*@|r5sdU5H zivH^1x{>XGxelhf!SBHM3j6ANyYcY?As7g`G4KK-7#zN__xh1NP<&(V1wq>bxZQrn z<&F6cF`tpUBCB`GpRv25ZFkb2(YvCqciNxvyJ84-LY|R)BC~gjpRs(R^>&h<(R`w| zcYJTUpJ{wTw)ekoAm5RE`?7B_-qC#f8^HpF2TFo1DEI}+@)3h51c9i95EcbwY5XYW z$TCF~%!y0+Kyz$sew;bc1_o=v^f}W84r>9eIn@RhYoYZymj<3oey%y$1}2tyTGV`W zi$c~kStc{G$-LDC+4eb`2Chp1&pF)&wn1}v=NWCu8Sb+J+OwjUhQH`@L}$4qG)3Me z$w~4Ag=Tu1l5f3Hb|^FfTDz=nAU45TI|A(3R{C}Y9(W+}1##&@hVW%3(>;d6yf={Yl^ zbBIs>aHd7)8lAFpX36HDoRV;+%H}GaGIM55Tcc#yJsf3$rQGI#F$lvMKhR z!E++t6mgyBaiaT({}g$e{dfT35l5KqfB3~C88f^8Ew?8jH#`3T%OmwwbbR9O;ip@W zd}87eq+7Ur;_MNyRnUB5?Gf_*Pdo8`{mWYrb7J%w$Xhsl;`r*{RZx9m`5OEw^i^nk z%JrJ{DaJPw^MLXp-91zNfKzGG^AE|WB6zV51g4G?-OvEt07yNCtxjxdh_WKg8YW|^ zw4&UEuazxhgi`~zDr#la1ht-EJ;Vyos-am`wX$r2TTikcZUS)C;H}D90hT6Q3>cRB zG&OM2r%Y>7zg3nd*H){n*qXT311?7&4L$4G)B8DBw3GDj)ydDlw-Hp;gNC;$LdQ$V zqLD{=D)KUu^f;0nN-8?4XA`Lkwg=vQ&oyzZQC{+~%!mZ^iz*iUNF}?>FL=jS7@2yCJsLn73p`KN;vVzj6=$P?*<-= zI~&mN2Ifj)i;KsU{f?E@lh@Fb&&;Z>fH~)yAG)d8qt_rnIlX%sb#E!Mv^-#=`jp^&tM%(Qtjce*E#sGsjE!OX{FrdJ+&IK`zxl%1BYq z3>HS_sLETOvQ$&0URbhw{G+o@Pw8>}{CfG}X`Skgg;ay6GTdJ{>A6hh!A?g{bJDD> zY9SF9@g)9;RN_1Apyp7dAcq_uB_wsM0V|bB)#TN(Tq(O>z4@+^Z$sewabbO*bmB09cj!K;V zbYa3y^$L1*jY29mu`qPKFj?N-fIjP=vcjRVIJ$FTO8m-=-JdM18IfOvgCbBErL<=* zu}66Y5wIA*whZ2L-kS5NC$=rE7&iUMWT+a2}OZX@*r)VKATu!+;^kLuf{pQ8n1Oqg`TUFJe3KSD9taED`2ZnPZfGU~e&m8Gew^_^q_r{D)D`f*TYr_n%G+7~^ zDjr8VUw1SFy_L1!=7MJn;-9NQ$?)Ptds=5!(W&`kTUrs0n5_z~sS2)?c6wgKtG2R( z=f(Y*rh|cgY09%7l0?!N3pY`D-9qJcXhc$b*7Eb~pXMQ*)V)WXX{@1tjQTg}fA{)| zTAIVcr_vHE{N#kdj+YVrM|t6vZ;ef0bGMV(dEUDM_2cC7abHMGbTovw3J;O{U|Daj@*(Fj#mvwV~e1}B7n@P}3M_v?BEe8)f zD=S}Ak&9GucCIKRFDtK7RpVh0U0ifF*|ni#n+)=LqEi!aRG1S9w#t_li!9uT5DktsS;uAOcQygNitj##2`vD02Iio2!;$&1|u!JDzgzg5w%s08m$;Pl$Has`F{ME|}S%~xINZG*%Z5PrB z-kyM^(u-qdDiWKA;Nu8+5h*W7$!BRZrEGK$`l>_nVFf%d){z;cipmIl>yf;{&blpe zhjp-2q=}a3!<}d{3}1f}WvhX+C@N2?q`@X)wHnOINKtjqqDsA|7Z;co6!84d@~`yy z7os&6=~bVXoF^FzC`SuPYqhS5KCUPaPd)lBj-s@Mf(pyDQ2!lkWX!L2Cp0*_3o3-6 zTio0f6y={rP~)VNm^z(-g>Z){BQt3SD2I*rMP4D^q0-GChHkd1@%=+mq~WMZA)N7+ z6eVPM-!R)?C?HY(F{M+!UCd-MkyIkLmR7QJ#KH4T*kfy@-CfH7TpwPv&EAP`3U$jz ziwmtbk>0l6f3<-LJ8!Qx#bdR(amjMdXo}WiZyvN?cNZfmH$qmM{>y%PasHkig}Avc>ye?;2@xiX>wG3#pIA6<>#(lhk1h zl^!I5lEaA_s+9sCL#qo%zd$b?nTyvdDp{zjOALA4XSUeKXddzR*eK*OPMZufKL{IT zUvKTD@T$|i?PF_JCYQr#&~GPE0PBCyw9Yb@8D6?_pHpW3lHun$HCBx@8*(`S^ma|B zu=h%6tx-iUFT)DOf{EYtDDDhKN=#FXwh~npA71aCimc`PkClhg)wLBF_}0%*K0}oW z`jZ~$!i?|VFBoMS6P5+5ruCN;rAez*;L&V<9gD(ns3h?0_4u2k3L@Xv&2n@FWl(()^HcNpvC)e^$!cfaMR-h2mx=l2O(w@0Cf@4Y>oby@}a~eg}Ux5 z$OIySx?Rey73JRhXl?NJy^S6fMDjjN|2<*CKX+j&hr*#?+xlf1lf26dk$A>Bvy`aM zJE-ZBX_Od=OwsY>;FIpo#(7i+7;uZy_p(J*RCO#k&kHjL?MC(96bd0sk_ro?36unk zgyoTW_a9oK3t6U&?efnW{f%9$Ug)&VtM)#&k7Gd0qX=H&$yRV9^49wJ?F2gb1FL^Qc6$J?bp zSfG|kd6$9GwF4OH`5$|i$Cvot?_jI7a&siF)rczw&)zGu&)y?B@5or`+hM2G)<4apACi+=WC0a64#7(00${}95hrWQC(QhobzXg-oc=mi-86mublvX$4f6jzpkH4gKit;vCK=%U z^$~@4pQbgT;Ly-Le*FXKT`z$|gyxia7^7rCsdFDyn~eLrF)52mQgwBN;+pjs(fqM% z`SrXWK^bR}8H?@gQr+8iVi1FUN%2@PLL?L`W=^nYmYoNjvUwYV4mX1J|K3+eDRWtA zt03~Y9=R{Sb+m6?4AVv9)iWXq{-TBvM@1A7^oKIQ(@H`Up{S_n@B$h_j{gpzwnGHZ z3)L1z{Us7g1fG|d7php8Cz(i;Cn@4r^1ErdJA>=m0Bc-e)?#`hlM8S)k=cp>&mT(T zisIo>zz}x!Q0&MMynpl#i~Xx5;?nxy4=z|ZH4sr}Ju zkj+E>I1S^meT-&ii}5oUqPJ8RW;=63?`MxjVGpp^?jh}np;GTb?izCN*z?akvLn`L zcR`lA8`YdDzAIM#OclqIes`iwlq&6r9n&i9$R7D!Nw01^1Q|Qe{S_OrX~MQ|n(7$w z#?k&ws$?hED)7{B#9}B4P#<)cp4}HR{enWjCB5$sK7WSi+adEAqI*VbwukKArM=p9 z9=rN&gl|9HYT!E2d88X+A%H)|he%@~evyP`D4`k#X&j}V#9B*q zK2Tjg*N~OP_}f=LK(=~oNcq9$B34QFsWDHb(mY1A;i?gB{^|Ibb2z)6Gw^;! zFpI4&$X_LXs7Ne`e6b~ZB}(^bL+p$C9Nt7k)kJ2kjuxj*!gRa~f|t@b{b|qYsLc#d z(3x+@M`K=Vt{uqG7br8ngMkS>Yhunc+7qA>3ZS#V@U7g^pv>s*lx+8pJUyhHBTL& z@V$h(+2~!ndn9U1BR?&8D(XBbm4 zfBm-jLD<_stfZH(UBj&yyykA+&Zz|1zV^v5-`JK^j$m0lefq4q3`x7rS)y+4Ypr5Y zyi~S$wwoK!r$9{kS#x%~vQ{cGvp#*jp|>|l8U|$sMSNZ*=hKQX!r|%rXX&^OzlIfm9~EzZu0qL^HsOEA?NcN&$x?1@UeiMdUxvkc_C)v@fc~$ z$MPn19JcG%oy^kVQxZ9qrGhoiquLp;U2Yr-JD%mPes-WwE9U zAt0L|YX0qCse0;v=;nz*4si{mZwL2x;4gVz60~u24h|(-C99GwjVraOTt_z@syE2HlORpvtzttCd=HUUX=}M@@#>Va zl63YCINWu06Os>1UOL|eKL1pY^1270Fl&|yIk%{u{=us>a_*3wk zZ_ovtQD^2m0>P{o+}A2#8dT)wp3X;Km)o?}&M0*%k59Hw`A+yw-Cr3#T0SGPsx2SL zIyG#+uSG3t0bM>yKc#)EzHIAcw*ysCexm-Z=%|hAi`D37n zw}jr(_USbc;Z@9A=IIniG?F3Fv;@nhY~l~oS|rQU16P} zs>`k1=4-rZO6#~gLs6qckIhL_<)dv1V9AA@q-axl4!(B8EBs&UR;&pOp*6rJiQ zI%)Z-y1uKYV=ViYKB2Q+Q*eL%x()?{L(5^m8Fr3+FVp{Fd0h1r<21K0dq3Ij@mslh zdi~UPQYhUr4F{uvTD@+kBWWl}g@(ZLL;75#QmsK(uU>(2^1Uyp2um}k(Joh5Q7_aO zow<)&UA|_nZ%uP#T+nR)lVXDpO*UYD7nU7mijrMJ`};n$)U!9m`6~CPz;_0=wcFpB zw4pylAMKr{BG_?<_y(;7j5cU|$D!rNogJw*xLQ9;P11gAG?42?=NVrc;S2732ciYZ z#vf?M6a5Ot%}^3w<6>nC4`UJuT&M9|qUxM|Y5#0Yc2Bi8mf}kUEvV zIN_WD{SQ&}jA%n1XwiO5OqhLr?rFS9P9(B^270ujUe8^+oGN{wx@toXlRgy*eM}qpP%`;pF2vot1mK*pdf^HB`4TdhAaM6#@ zbV8fpmeg4O3D{f4fiXmKp$u4i#(~iZYIPyp3F;*ddJNXs0FP2);>sFCS%21NZZFWT zUwZx#JL7f?6+!JgHZ94K-|jLC{>9p_>IT*%1REm+c@bD_fWhttpA~`u6HV-do4J32 zD|UP$>AYaY(|!Ag<+8+F>_gLHNijL8tzjX*5T3@_jN^=3Ec3YJ9@jZ5f68&eJ$g+s zIlAq@_kX)lhY*S-MLyvFhsNZ_)q zrep~fJEmltTp<$Dagrs6@&u~N@D`o>=r}?t=TPz zb6Ig4W94yJ7lI0DksGpe-oPfwPk02*p*Wj87_0RL+A_Wu+x=S^wU#@x#W!bmre@ooC<$P(;6ApHlkIW8XBFv&4?FXA0a ziJY&!EiaKfTlE2!1OyvvT#c@$iXm6ynVrO*Y(rs87!Su@x~v2V(-4P}H>rB|e>>Zn ztrATSKmSFQ5NNnD3V)>4S1b8Cqif^g5d#n>;dO`0k%`tfkDcKY6r zQ@(KTvyDFQKPGq4;hXIYJ{@}*wC!Nkjxgbe?bw51?z4=x99x3Efj#&?>px%*`Oo?f z*h9ZNoR=jU>P6OdsXHNIXb(6qPJyom?jLhu9R1gxT$baIaq{IaA{0*kA1yYuj}y&8 zUz0V+xq4KpKJ7ti+1$<}rE4m%UaF|(&;Z$zE_GV==g z5!Rr@M6kq!X}tQZJ4!-U&K+1HdCA5jdn^x>Trbm3apqjugq)ENGL@`>Ztf;|t)ZOM ziFs1i(wi}BcH_!4@~cLij5(G1N1*D3k;t?s^Yd2pj&}_BmnV_iPJAz9Hni@u^i4k? zfcy%+>p6WVYLH^`(NgjrB`tgE-T0`OkuUbed_vC92l^f*CA;hWK!wqH))9p%9mvkU zWmyo?>hrag4)eLT#i9db=`a36Au2&3DXE|s9yf=4Ev=v!8K;<#ke7#wHdyjUM1+D; zQpi?TK|*sc!v1EE`PmTLi$R3J{ALh!u;33FTvrN&o^$|RQ7^_kt)5ro`0?D1qxns1 z!}xJg9b4AW#~yP+UNok2K?yM&Z(`Dm@8NI@I{W6zV}^3Nk6$g?3`8XljnS1Ie?^AP-mLQP*ZQK{dy3 z(biQ5b8xS#3JS^wl zpSW02qb1v^gT@QjQ-+LZu4c7Qfm5#Rk{!JOcC!9x*kR+9*xeM^c6_fs@NBzYa$KFu z#EN{DKv@^<6Md0tyIv|>i<6Dn!9RW1XL~i}5$#_4KYaKWxH^75!6$br*}tqm`|y9# zbQHKICQ&*4KD+qV=Dmh`DCpYSLOFRr!tfdZCpgtG>7+Q@{Osz9{76_#793+z&B|0D z#4!>=_j2MQZl{!X{)~KIeP+4^>`wO(bn-d)E_{Z5%sLd@g;h~pL^5VR@#p~chCh~T z;INwn`UlO+ASX@Ja2-K3A2_Y}yo&KOwvM@=0zPyC?_A;FZ`;8bg_9)XFM^hHf6Hk0 zw)yL{^B^BLGfb%{N@$A-&}2}{nDR0U!8G)_tn@na5_2%d5&sV;%tsz$=5hv}{ey&D z1+u%QXva8)Ja}l|cBT+7@4*i?wm6GX9*GZj5TiLFF3uIIR?m!HFDx4R0@bh)&V6*} z#vye`30|lrzbH98mX+_|0)%rjJugag1$ab(PN`N)(qvB*&|>I&~;JC zu79s?zWa7RZVx;A66{iwZ9dv*NPF4Fgh^xofn7#>@Bt~$?!;GVY;D5>RPsP$9r;#@{wk$LcYrXq@1BanEZ~P0}R~@$H zUe}%Wo6ZVeYJe4o(KwdY)QZDU64N7#K`9iDMNi)To=Q(Y5PApk$datLFGvxGOt`4O zPE4!6qorhcwMyA&>s+mS&=qs(ai`waS*6W!dS*cLm1881YEj2CM5ba8t1lR<5B`DD zr8Q|nkjRe9YTQsryDqhjl)J6-eD(;ja$RU3%@~#sQfc88hk?KkJVWF(HRfL}&xH;~ zUsD{9Rgin*zclZllDx7RLIO-Dq-V#oB5wc0x6>jAzDebjH)uRlxn21B=6~U;kyOvu z%UgXmm9nPdLXR?kkn_U8)Wf^3}<5(?QiZ40T@rQMas^iCP-qnxmon8Tt5UmO} zaX^Rq9oIXfm#_`W=26kd_(9vHEjol>p4zKKT8D)0>P@5hbwc>@qU+E=X`{lmjs<=Z(g@q}CI>(;tm>3aA=&mdy&%o$767 zD_`Ypx5HS5lv5;`24GqGC+wK|lv86(6F9~qS&b&tnWiGy#L}Tm^HPn;(k&dNm)Q)C z(@pDp8d6m2XEtoEnARs;>iaLsQ{)vs$o*~Ou* diff --git a/marginalia_nu/src/main/resources/static/fonts/LM-italic.woff2 b/marginalia_nu/src/main/resources/static/fonts/LM-italic.woff2 deleted file mode 100644 index 166d6e60b8ff0bb412a7010cefa255ffcff2523b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39148 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�Cv-G`xDidpv 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 Ju)ADy{~xO_x*Y%j diff --git a/marginalia_nu/src/main/resources/static/fonts/LM-regular.woff b/marginalia_nu/src/main/resources/static/fonts/LM-regular.woff deleted file mode 100644 index eb9fec0a84425a3ffa3bc0e73e1db1c9e97ecfde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53760 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

    oYy2k%lR;3 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 - - - - Error - - - - - -

    - -
    -
    -

    An error has occurred!

    -

    - Either the page you attempted to access does not exist, - or the automatic cleaning has process failed. -

    -

    - Please use this link as a back-up:
    - {{.}} -

    -
    - \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/templates/encyclopedia/wiki-search.hdb b/marginalia_nu/src/main/resources/templates/encyclopedia/wiki-search.hdb deleted file mode 100644 index bc5d5a12..00000000 --- a/marginalia_nu/src/main/resources/templates/encyclopedia/wiki-search.hdb +++ /dev/null @@ -1,35 +0,0 @@ - - - - - Encyclopedia Search: {{query}} - - - - - -
    - -
    -
    -

    Search the Encyclopedia

    - -

    Search results

    - {{#if error}} -
    Failed to find exact article match
    - {{/if}} -
    - {{#each results}} -
    {{name}}
    - {{#if refName}}
    {{refName}}
    {{/if}} - {{/each}} -
    -
    - \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/templates/smhi/index.hdb b/marginalia_nu/src/main/resources/templates/smhi/index.hdb deleted file mode 100644 index 556d8e7e..00000000 --- a/marginalia_nu/src/main/resources/templates/smhi/index.hdb +++ /dev/null @@ -1,44 +0,0 @@ - - - - {{title}} - - - - - - - - -
    - -
    - - - - diff --git a/marginalia_nu/src/main/resources/templates/smhi/prognos.hdb b/marginalia_nu/src/main/resources/templates/smhi/prognos.hdb deleted file mode 100644 index 274a6fce..00000000 --- a/marginalia_nu/src/main/resources/templates/smhi/prognos.hdb +++ /dev/null @@ -1,73 +0,0 @@ - - - - Väderprognos för {{plats.namn}} - - - - - - - - - - -
    - -
    -
    -

    {{plats.namn}}

    - - {{#each dygn}} - - - - - - - - - - - {{#each data}} - - - - - - - - {{/each}} - - {{/each}} -
    {{date}}{{{veckodag}}}
    TidTempVindNeder.Moln
    {{time}}{{temp}}{{vind}} ({{byvind}}){{nederbord}} {{nederbordTyp}}{{moln}}
    - - -

    Förklaring

    -

    Molntäcke (Moln.) visas på en skala 0-8, där höga värden indikerar - tjockt molntäcke, och låga värden indikerar blåare skyar.

    - -

    Nederbörd (Neder.) indikeras med förkortningar: - - - - - - - -
    SSnö
    SBSnöblandat regn
    RRegn
    DDimma
    UKRUnderkylt regn
    UKDUnderkyld dimma
    -

    - -

    Källa SMHI

    -

    - All prognosdata hämtas från SMHI:s öppna API:er, under licensen - Creative Commons Erkännande 2.5. - Bäst före {{bastFore}}. -

    -
    - - - diff --git a/marginalia_nu/src/main/resources/templates/status/server-status.hdb b/marginalia_nu/src/main/resources/templates/status/server-status.hdb deleted file mode 100644 index e8217101..00000000 --- a/marginalia_nu/src/main/resources/templates/status/server-status.hdb +++ /dev/null @@ -1,25 +0,0 @@ - - - - Server Status - - - - - -
    - -
    -
    -

    Server Status

    - - {{#each status}} -

    - {{server}} - {{status}} -

    - {{/each}} - -
    - - - diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/SeekDictionaryTest.java b/marginalia_nu/src/test/java/nu/marginalia/util/SeekDictionaryTest.java deleted file mode 100644 index 1987da6f..00000000 --- a/marginalia_nu/src/test/java/nu/marginalia/util/SeekDictionaryTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package nu.marginalia.util; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class SeekDictionaryTest { - - @Test - public void testSeek() { - var dict = SeekDictionary.of((int[] x) -> x.length); - - for (int i = 0; i < 10000;) { - int j = (int)(1 + 9 * Math.random()); - int[] block = new int[j]; - for (int k = 0; k < j; k++) { - block[k] = i+k; - } - dict.add(block); - i+=j; - } - - o: for (int i = 0; i < 10000; i++) { - int[] vals = dict.bankForOffset(i); - for (var v : vals) { - if (v == i) continue o; - } - Assertions.fail("Could not find " + i); - } - } - -} \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/configuration/HostsFileTest.java b/marginalia_nu/src/test/java/nu/marginalia/wmsa/configuration/HostsFileTest.java deleted file mode 100644 index cd6c24f1..00000000 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/configuration/HostsFileTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package nu.marginalia.wmsa.configuration; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -class HostsFileTest { - Path tempFile; - - @BeforeEach - public void setUp() throws IOException { - tempFile = Files.createTempFile(getClass().getSimpleName(), ".tmp"); - } - - @AfterEach - public void tearDown() throws IOException { - tempFile = Files.createTempFile(getClass().getSimpleName(), ".tmp"); - } - - @Test - public void testParseSunnyDay() throws IOException { - Files.writeString(tempFile, """ - # Comment - edge-index 192.168.0.1 - edge-search 192.168.1.1 - - auth 127.0.0.55 - - - """); - var hf = new HostsFile(tempFile); - - Assertions.assertEquals("192.168.0.1", hf.getHost(ServiceDescriptor.EDGE_INDEX)); - } - - @Test - public void testTooLong() throws IOException { - Files.writeString(tempFile, """ - edge-index 192.168.0.1 this is where my homie lives - """); - - assertThrows(IllegalArgumentException.class, () -> new HostsFile(tempFile)); - } - - @Test - public void testTooShort() throws IOException { - Files.writeString(tempFile, """ - edge-index - """); - - assertThrows(IllegalArgumentException.class, () -> new HostsFile(tempFile)); - } - - @Test - public void testBadName() throws IOException { - Files.writeString(tempFile, """ - garum-factory 127.0.0.1 - """); - - new HostsFile(tempFile); - } -} \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/suggest/SuggestionsTest.java b/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/suggest/SuggestionsTest.java deleted file mode 100644 index 7184c8b9..00000000 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/suggest/SuggestionsTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package nu.marginalia.wmsa.edge.assistant.suggest; - -import nu.marginalia.util.TestLanguageModels; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.wmsa.edge.assistant.dict.SpellChecker; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.nio.file.Path; -import java.util.List; - -class SuggestionsTest { - private static Suggestions suggestions; - - @BeforeAll - public static void setUp() { - LanguageModels lm = TestLanguageModels.getLanguageModels(); - suggestions = new Suggestions(Path.of("/home/vlofgren/Work/sql-titles-clean"), - new SpellChecker(), new TermFrequencyDict(lm)); - } - - @Test - @Disabled - void getSuggestions() { - System.out.println(tryGetSuggestions("neop")); - System.out.println(tryGetSuggestions("neopla")); - System.out.println(tryGetSuggestions("middle p")); - System.out.println(tryGetSuggestions("new public mana")); - System.out.println(tryGetSuggestions("euse")); - } - - List tryGetSuggestions(String s) { - long start = System.currentTimeMillis(); - try { - return suggestions.getSuggestions(10, s); - } - finally { - System.out.println(System.currentTimeMillis() - start); - } - } -} \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/PrimeUtilTest.java b/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/PrimeUtilTest.java deleted file mode 100644 index 703ed8cd..00000000 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/PrimeUtilTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package nu.marginalia.wmsa.edge.index.service.util; - -import nu.marginalia.util.PrimeUtil; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class PrimeUtilTest { - - @Test - void isPrime() { - assertTrue(PrimeUtil.isPrime(1)); - assertTrue(PrimeUtil.isPrime(2)); - assertTrue(PrimeUtil.isPrime(3)); - assertFalse(PrimeUtil.isPrime(4)); - assertTrue(PrimeUtil.isPrime(5)); - assertFalse(PrimeUtil.isPrime(6)); - assertTrue(PrimeUtil.isPrime(7)); - assertFalse(PrimeUtil.isPrime(8)); - assertFalse(PrimeUtil.isPrime(9)); - assertFalse(PrimeUtil.isPrime(10)); - assertTrue(PrimeUtil.isPrime(11)); - } - - @Test - void nextPrime() { - System.out.println(PrimeUtil.nextPrime(1L<<31, -1)); - System.out.println(PrimeUtil.nextPrime(1L<<31, 1)); - - } -} \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/arxiv/ArxivParserTest.java b/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/arxiv/ArxivParserTest.java deleted file mode 100644 index e6e146ee..00000000 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/arxiv/ArxivParserTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.arxiv; - -import nu.marginalia.util.TestLanguageModels; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.util.language.processing.DocumentKeywordExtractor; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.util.language.processing.model.KeywordMetadata; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.integration.arxiv.model.ArxivMetadata; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.io.IOException; - -@Disabled // this isn't used and the test is hella slow -class ArxivParserTest { - final LanguageModels lm = TestLanguageModels.getLanguageModels(); - - @Test - void parse() throws IOException { - var parser = new ArxivParser(); - var data = parser.parse(new File("/home/vlofgren/Work/arxiv/arxiv-metadata-oai-snapshot.json")); - - data.stream().map(ArxivMetadata::getAbstract).limit(100).forEach(System.out::println); - } - - @Test - void extractKeywords() throws IOException { - var dict = new TermFrequencyDict(lm); - - DocumentKeywordExtractor documentKeywordExtractor = new DocumentKeywordExtractor(dict); - - var parser = new ArxivParser(); - var data = parser.parse(new File("/home/vlofgren/Work/arxiv/arxiv-metadata-oai-snapshot.json")); - - var se = new SentenceExtractor(lm); - - data.stream().map(meta -> documentKeywordExtractor.extractKeywords(se.extractSentences(meta.getAbstract(), meta.getTitle()), new KeywordMetadata())).limit(100).forEach(System.out::println); - } -} \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostsTest.java b/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostsTest.java deleted file mode 100644 index 6971d9b7..00000000 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/stackoverflow/StackOverflowPostsTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.stackoverflow; - -import nu.marginalia.util.ParallelPipe; -import nu.marginalia.util.TestLanguageModels; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.util.language.processing.DocumentKeywordExtractor; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.integration.model.BasicDocumentData; -import nu.marginalia.wmsa.edge.integration.stackoverflow.model.StackOverflowPost; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; - -public class StackOverflowPostsTest { - final LanguageModels lm = TestLanguageModels.getLanguageModels(); - - @Test @Disabled("this is stupidly slow") - public void test() throws ParserConfigurationException, SAXException, InterruptedException { - var documentKeywordExtractor = new DocumentKeywordExtractor(new TermFrequencyDict(lm)); - - ThreadLocal processor = ThreadLocal.withInitial(() -> { - return new StackOverflowPostProcessor(new SentenceExtractor(lm), documentKeywordExtractor); - }); - - var pipe = new ParallelPipe("pipe", 10, 5, 2) { - @Override - public BasicDocumentData onProcess(StackOverflowPost stackOverflowPost) { - return processor.get().process(stackOverflowPost); - } - - @Override - public void onReceive(BasicDocumentData stackOverflowIndexData) { - System.out.println(stackOverflowIndexData.url); - } - }; - - var reader = new StackOverflowPostsReader("/mnt/storage/downloads.new/stackexchange/sites/philosophy/Posts.xml", new EdgeDomain("philosophy.stackexchange.com"), - pipe::accept); - reader.join(); - System.out.println("Waiting for pipe"); - pipe.join(); - } -} diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaTest.java b/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaTest.java deleted file mode 100644 index 40a58e93..00000000 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/integration/wikipedia/WikipediaTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package nu.marginalia.wmsa.edge.integration.wikipedia; - -import lombok.SneakyThrows; -import nu.marginalia.util.ParallelPipe; -import nu.marginalia.util.TestLanguageModels; -import nu.marginalia.util.language.DocumentDebugger; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.util.language.processing.DocumentKeywordExtractor; -import nu.marginalia.util.language.processing.sentence.SentenceExtractor; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.integration.model.BasicDocumentData; -import nu.marginalia.wmsa.edge.integration.wikipedia.model.WikipediaArticle; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import org.jsoup.Jsoup; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -@Tag("slow") -public class WikipediaTest { - final LanguageModels lm = TestLanguageModels.getLanguageModels(); - - @Test @SneakyThrows - public void test() { - var documentKeywordExtractor = new DocumentKeywordExtractor(new TermFrequencyDict(lm)); - ThreadLocal processor = ThreadLocal.withInitial(() -> { - return new WikipediaProcessor(new SentenceExtractor(lm), documentKeywordExtractor); - }); - - var pipe = new ParallelPipe("pipe", 10, 5, 2) { - @Override - public BasicDocumentData onProcess(WikipediaArticle stackOverflowPost) { - return processor.get().process(stackOverflowPost); - } - - @Override - public void onReceive(BasicDocumentData indexData) { - System.out.println(indexData.url); - System.out.println(indexData.title); - System.out.println(indexData.description); - } - }; - - var reader = new WikipediaReader("/home/vlofgren/Work/wikipedia_en_100_nopic_2021-06.zim", new EdgeDomain("encyclopedia.marginalia.nu"), - pipe::accept); - reader.join(); - } - - - @Test @SneakyThrows - public void test2() { - var documentKeywordExtractor = new DocumentKeywordExtractor(new TermFrequencyDict(lm)); - var debugger = new DocumentDebugger(lm); - - ThreadLocal processor = ThreadLocal.withInitial(() -> { - return new WikipediaProcessor(new SentenceExtractor(lm), documentKeywordExtractor); - }); - - var reader = new WikipediaReader("/home/vlofgren/Work/wikipedia_en_100_nopic_2021-06.zim", new EdgeDomain("encyclopedia.marginalia.nu"), - article -> { - try { - debugger.debugDocument(article.url.getPath(), Jsoup.parse(article.body)); - - } catch (IOException e) { - e.printStackTrace(); - } - }); - - reader.join(); - debugger.writeIndex(); - } -} diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/podcasts/PodcastFetcherTest.java b/marginalia_nu/src/test/java/nu/marginalia/wmsa/podcasts/PodcastFetcherTest.java deleted file mode 100644 index 0033464e..00000000 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/podcasts/PodcastFetcherTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package nu.marginalia.wmsa.podcasts; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -class PodcastFetcherTest { - - @Test - void fetchPodcast() { - var result = new PodcastFetcher().fetchPodcast("hopwag", "https://rss.acast.com/readmeapoem"); - assertTrue(result.isPresent()); - System.out.println(result); - } -} \ No newline at end of file diff --git a/marginalia_nu/src/test/resources/log4j2.properties b/marginalia_nu/src/test/resources/log4j2.properties deleted file mode 100644 index 9c2dbefd..00000000 --- a/marginalia_nu/src/test/resources/log4j2.properties +++ /dev/null @@ -1,15 +0,0 @@ - -status = info - -appender.console.type = Console -appender.console.name = LogToConsole -appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %highlight{%-5level}{FATAL=red, ERROR=red, WARN=yellow} %c{1}- %msg%n - -logger.console.name = nu.marginalia -logger.console.level = debug -logger.console.additivity = false -logger.console.appenderRef.rolling.ref = LogToConsole - -rootLogger.level = info -rootLogger.appenderRef.console.ref = LogToConsole diff --git a/marginalia_nu/src/test/resources/model-data.json b/marginalia_nu/src/test/resources/model-data.json deleted file mode 100644 index 063b93e7..00000000 --- a/marginalia_nu/src/test/resources/model-data.json +++ /dev/null @@ -1 +0,0 @@ -{"comments":[{"id":{"value":"t1_gku7btj"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-26 17:35:16","author":"TheEmpathyBox","body":"Hello,\n\nHow would you translate in latin \" *unreliable narrator*\" ?","sequenceNumber":2,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":596601000}}},{"id":{"value":"t1_gkudywj"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gku7btj"},"distinguished":false,"created_utc":"21-01-26 18:23:03","author":"kc_kennylau","body":"Literally it would be \"narr?tor ?nfid?lis\", but obviously this is a recent term ([Wiktionary](https://en.wiktionary.org/wiki/unreliable_narrator) says coined in 1961) that does not have an immediate equivalence to Roman literature that comes to mind.","sequenceNumber":3,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597256000}}},{"id":{"value":"t1_gkxfa9w"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkudywj"},"distinguished":false,"created_utc":"21-01-27 09:46:25","author":"BobTheSCV","body":"Seems strange there wouldn\u0027t be a term for this. Unreliable narrators go back as far as Homer with the Odyssey.\n\nEvery part of the story Odysseus narrates is full of fantastical monsters and extremely unlikely events; a complete break from the story as narrated by the voice of Homer which is more in line with the style of the Illiad, a lot more down to earth except for minor interventions by the gods.\n\nAs to make a point, the guy is shown compulsively deceiving every single person he meets: Gods, beasts, enemies, allies alike. Why *wouldn\u0027t* he deceive the audience as well?","sequenceNumber":4,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597341000}}},{"id":{"value":"t1_gkuf0n7"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gku7btj"},"distinguished":false,"created_utc":"21-01-26 18:30:35","author":"BaconJudge","body":"I\u0027d convey that idea with an adjective meaning \"untrustworthy,\" either *narrator infidus* or *narrator infidelis*, the former possibly helping to avoid the secondary religious sense of the latter.\n\nThe noun *narrator* traditionally existed only in masculine form, but the explicitly feminine version *narratrix* appears in newer references like the Vatican\u0027s *Lexicon Recentis Latinitatis* and Rene Hoeven\u0027s *Lexique de la Prose Latine de la Renaissance,* so for a female character you\u0027d have the option of *narratrix infida* (note the change at the end of the adjective) or *narratrix infidelis*, if you wanted.","sequenceNumber":5,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597415000}}},{"id":{"value":"t1_gkug4j7"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gku7btj"},"distinguished":false,"created_utc":"21-01-26 18:38:25","author":"lutetiensis","body":"*incredibilis narrator.*\n\n[*incredibilis*](https://logeion.uchicago.edu/incredibilis) \u003d *in* \\+ [*credibilis*](https://logeion.uchicago.edu/credibilis), from [*credere*](https://logeion.uchicago.edu/credo), to believe, to intrust.","sequenceNumber":6,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597483000}}},{"id":{"value":"t1_gkue6uo"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gku7btj"},"distinguished":false,"created_utc":"21-01-26 18:24:37","author":"EgoSumInHorto","body":"\"*Narr?tor viti?sissimus*\"","sequenceNumber":7,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597550000}}},{"id":{"value":"t1_gkv6xf8"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkue6uo"},"distinguished":false,"created_utc":"21-01-26 21:39:47","author":"Tharadin1970","body":"Wouldnt that be more \"a very vice-ful narrator\"?","sequenceNumber":8,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597617000}}},{"id":{"value":"t1_gkv76xq"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkv6xf8"},"distinguished":false,"created_utc":"21-01-26 21:41:21","author":"EgoSumInHorto","body":"I couldn\u0027t find a word for \"unreliable\"; \"viti?sus\" means \"full of faults, corrupt, vicious, morally faulty, defective\", hence \"unreliable\"","sequenceNumber":9,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597684000}}},{"id":{"value":"t1_gkv7rzf"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkv76xq"},"distinguished":false,"created_utc":"21-01-26 21:45:06","author":"lutetiensis","body":"\u003e \"unreliable\"\n\nIncredibilis (see my comment).","sequenceNumber":10,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597750000}}},{"id":{"value":"t1_gkv848d"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkv7rzf"},"distinguished":false,"created_utc":"21-01-26 21:47:13","author":"EgoSumInHorto","body":"That should have been a pretty obvious derivation to make... Doh!\nThanks :)","sequenceNumber":11,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597823000}}},{"id":{"value":"t1_gkup1zh"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-26 19:39:00","author":"Gopnikcykablyat","body":"How do you say \"Hope never dies, because hope is the killer\" ?","sequenceNumber":12,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597887000}}},{"id":{"value":"t1_gkuvwow"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkup1zh"},"distinguished":false,"created_utc":"21-01-26 20:26:06","author":"lutetiensis","body":"For stylistic reasons, I would render it as:\n\n*spes non decedit sed caedit.*\n\nHope doesn\u0027t die, but kills. You can replace *non* with *numquam* (\"never\") *sed* with *quia* (\"because \\[it\\]\").","sequenceNumber":13,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":597954000}}},{"id":{"value":"t1_gkuqvlo"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkup1zh"},"distinguished":false,"created_utc":"21-01-26 19:51:09","author":"BluuDuud","body":"Id say, \"sp?s numquam moritur, nam sp?s nec?tor est\"","sequenceNumber":14,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598026000}}},{"id":{"value":"t1_gkvrzwi"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkuqvlo"},"distinguished":false,"created_utc":"21-01-27 00:08:45","author":"magistramegaera","body":"Should it be necatrix instead of necator, since spes is feminine? Or does that not really matter with an abstract concept like this?","sequenceNumber":15,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598092000}}},{"id":{"value":"t1_gl60nlx"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkvrzwi"},"distinguished":false,"created_utc":"21-01-29 01:01:50","author":"Sochamelet","body":"I wouldn\u0027t say it\u0027s wrong per se to use *necator*, but in my experience, Roman authors were generally inclined to preserve correspondences between the gender of words, if possible.","sequenceNumber":16,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598158000}}},{"id":{"value":"t1_gkwj57n"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkvrzwi"},"distinguished":false,"created_utc":"21-01-27 03:48:35","author":"BluuDuud","body":"I forgot that, I think you\u0027re correct","sequenceNumber":17,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598223000}}},{"id":{"value":"t1_gkzsh08"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkwj57n"},"distinguished":false,"created_utc":"21-01-27 21:15:26","author":"glaraaaaaaah","body":"No it doesn?t, _necator_ is a masculine noun not an adjective, so it doesn?t need to agree. Unless you want to specify that hope is female, male-gendered words are more common for abstract concepts I think","sequenceNumber":18,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598287000}}},{"id":{"value":"t1_gkuthtv"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-26 20:09:24","author":"Youngerthandumb","body":"There was a quote from a Scandanavian bishop who, on his deathbed suffering from intense pains, cried out \"Do not pray me out of god\u0027s battle!\" when his colleagues gathered round to pray for his recovery. I thought that was kind of metal. How would that translate to Latin?","sequenceNumber":19,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598351000}}},{"id":{"value":"t1_gkv2ymf"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkuthtv"},"distinguished":false,"created_utc":"21-01-26 21:13:38","author":"nimbleping","body":"*M? (mihi (?)) ? pugn? De? n?n d?prec?re/d?prec?min?* (sg./pl. addressees).\n\n(Do not intercede by prayer on behalf of me away from the battle of God.)\n\nEDIT: I\u0027m not 100% sure if the accusative *m?* or the dative *mihi* should be used here. I\u0027d who knows to offer an opinion. I figure the ablative of motion away from would be appropriate here.","sequenceNumber":20,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598419000}}},{"id":{"value":"t1_gkv8czl"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkv2ymf"},"distinguished":false,"created_utc":"21-01-26 21:48:49","author":"Youngerthandumb","body":"Thank you I appreciate it! I took 2 years of high school latin but I don\u0027t trust myself to translate anything beyond \"Caecilius ad venit\".","sequenceNumber":21,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598484000}}},{"id":{"value":"t1_gkv9lku"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-26 21:57:03","author":"XENO-BLAZE","body":"How would you translate:\n\nAllow me to impress upon you the severe mistake you have made. For years my conduct has been largely benign. And yet, without provocation, you have severed our détente and forced me to unleash upon you the vengeful flames of a thousand suns. You shall curse your mother for the day of your birth. So, go now, go, and begin your life of fear, knowing that when you least expect it, the looming sword of Damocles will crash upon you, cleaving you in twain and as you gaze upon the smoking wreckage that was once your life, you will regret the day you crossed me","sequenceNumber":22,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598545000}}},{"id":{"value":"t1_gkve8fd"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-26 22:28:44","author":"iillltt","body":"Hello! I\u0027m not sure if this fits here but I was wondering if anyone would be able to identify/translate what the chant in the beginning of [this song](https://youtu.be/3oUUG7Mfoc4) is. I think it\u0027s in Latin so forgive me if it\u0027s not","sequenceNumber":23,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598610000}}},{"id":{"value":"t1_gkvkja8"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkve8fd"},"distinguished":false,"created_utc":"21-01-26 23:12:05","author":"lutetiensis","body":"That\u0027s hard... *regum satus*? *sanctus?* Do you know what they sampled?","sequenceNumber":24,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598686000}}},{"id":{"value":"t1_gkvm21e"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkvkja8"},"distinguished":false,"created_utc":"21-01-26 23:23:51","author":"iillltt","body":"unfortunately not, i\u0027ve heard other people say it\u0027s \u0027Spiritus Sanctus\u0027 but i haven\u0027t been able to find the original sample- same with regum satus which doesn\u0027t have any search results. thank you so much as well","sequenceNumber":25,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598753000}}},{"id":{"value":"t1_gkvmwyy"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkvm21e"},"distinguished":false,"created_utc":"21-01-26 23:30:26","author":"lutetiensis","body":"Ok. Sorry. I don\u0027t think I can do more on this one.","sequenceNumber":26,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598819000}}},{"id":{"value":"t1_gkvnbr3"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkvmwyy"},"distinguished":false,"created_utc":"21-01-26 23:33:31","author":"iillltt","body":"thank you so much for trying!!! i\u0027ll be questioning producers where they get samples from next haha","sequenceNumber":27,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598880000}}},{"id":{"value":"t1_gkvm2ak"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkve8fd"},"distinguished":false,"created_utc":"21-01-26 23:23:54","author":"ragnrikr","body":"Can\u0027t really help (to my ears it sounds like (sectum/secum) (satu/sato) I.e. gibberish), just wanted to point out this post https://www.reddit.com/r/kpophelp/comments/g49xob/latin_in_gottasadae/\n\nNo source sadly :/","sequenceNumber":28,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":598944000}}},{"id":{"value":"t1_gkvn3fo"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkvm2ak"},"distinguished":false,"created_utc":"21-01-26 23:31:47","author":"iillltt","body":"Yep :( \nI was reminded of this question when someone asked a similar question as to your linked post so it still hasn\u0027t been answered. well on the bright side I got to share a nice song and the mystery will remain unsolved ...","sequenceNumber":29,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599016000}}},{"id":{"value":"t1_gkw3c5e"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-27 01:38:08","author":"luisafonsoteixeira","body":"I\u0027ve recently came across the US Navy Academy saying \"Ex Scientia Tridens\", ?Through Knowledge, Sea Power?. How could one correctly say \"through knowledge, power\"?","sequenceNumber":30,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599082000}}},{"id":{"value":"t1_gkwzw4j"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkw3c5e"},"distinguished":false,"created_utc":"21-01-27 06:24:34","author":"lutetiensis","body":"\u003epower\n\n[Potentia](https://logeion.uchicago.edu/potentia), [imperium](https://logeion.uchicago.edu/imperium), [fortitudo](https://logeion.uchicago.edu/fortitudo)...\n\nNote the original motto is poetic. It doesn\u0027t say \"sea power\", but instead \"\\[Neptune\u0027s\\] trident\". You could find an artifact that would represent power for you (a sword? a crown?).","sequenceNumber":31,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599146000}}},{"id":{"value":"t1_gkw4obz"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-27 01:48:56","author":"Eldonith","body":"Help me fix a cringe tattoo!\n\nBackground: I got a tattoo back when the emo craze was big and Hot Topic was one of the most popular stores in the mall about 12 years ago. It reads \"Nascentes Morimur\" which roughly translated to \"When we are born we begin to die.\" It seemed cool at the time in my addled 18 year old mind, but I\u0027m a family man now and not only does it no longer represent my mindset. but it\u0027s even embarrassing to explain.\n\nWhat line (in Latin) would you suggest I add to brighten it up? Please include a translation, as my highschool Latin days are far behind me. Thanks!","sequenceNumber":32,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599207000}}},{"id":{"value":"t1_gkxz2rq"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkw4obz"},"distinguished":false,"created_utc":"21-01-27 14:11:03","author":"BaconJudge","body":"Given the constraint that it\u0027ll contain a reference to dying, is there any particular sentiment you want to convey? Because you\u0027re a family man, maybe you could expand it to something like *Amati Nascentes, Morimur Amati\" (inserting the optional comma if there\u0027s room) to imply roughly \"Born loved, we die loved.\"","sequenceNumber":33,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599267000}}},{"id":{"value":"t1_gl1ij4p"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxz2rq"},"distinguished":false,"created_utc":"21-01-28 04:12:22","author":"Eldonith","body":"Wow that\u0027s beautiful and exactly the kinda sentiment I\u0027d like it changed to! I love how you transformed it with a word added to the beginning and end instead of a whole 2nd line. I may very well end up going with this unless anybody can top that suggestion.","sequenceNumber":34,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599334000}}},{"id":{"value":"t1_gl83w6w"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl1ij4p"},"distinguished":false,"created_utc":"21-01-29 12:52:05","author":"BaconJudge","body":"Happy to help, and I hope the tattoo change works out for you.","sequenceNumber":35,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599401000}}},{"id":{"value":"t1_gkwk2sa"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-27 03:56:29","author":"reds3232","body":" mater servum vituperavit","sequenceNumber":36,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599464000}}},{"id":{"value":"t1_gkwlsx3"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-27 04:11:09","author":"megtheedemon","body":"How do you say ?I would rather be studying latin?","sequenceNumber":37,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599524000}}},{"id":{"value":"t1_gkxgp1b"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkwlsx3"},"distinguished":false,"created_utc":"21-01-27 10:07:50","author":"kc_kennylau","body":"Lat?nae linguae stud?re m?l?","sequenceNumber":38,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599586000}}},{"id":{"value":"t1_gkx6rax"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-27 07:45:53","author":"ResidentGift","body":"In a game I\u0027m playing, there are characters named Alatus, Bosacius, Indarias, Bonanus, and Menogias. I\u0027m pretty sure Alatus is the Latin word for \"*winged*\" (or at least related to \"*wing*\") and it also suits the character\u0027s motif. The other four names sound Latin-ish, but I can\u0027t find anything on them. Can anyone confirm if the other four names are Latin or rooted in Latin? If yes, how would they be translated?","sequenceNumber":39,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599646000}}},{"id":{"value":"t1_gkxwqf0"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkx6rax"},"distinguished":false,"created_utc":"21-01-27 13:45:42","author":"BaconJudge","body":"You\u0027re right about Alatus, but I don\u0027t think the others are Latin-derived. Bosacius could have been loosely inspired by words like *boscis* (\"waterfowl\") or Medieval Latin *boscus* (\"woods\") if either of those makes sense for the character. If Bonanus is the good guy, the name might be inspired by the very common Latin adjective *bonus* (\"good\"). Words or names ending in -as are likely to be Greek rather than Latin, and I don\u0027t recognize any promising Latin roots for those two unless Indarias is from India, which is the same in Latin.","sequenceNumber":40,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599714000}}},{"id":{"value":"t1_gl0mrhe"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxwqf0"},"distinguished":false,"created_utc":"21-01-28 00:36:11","author":"ResidentGift","body":"Thank you for the help!","sequenceNumber":41,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599777000}}},{"id":{"value":"t1_gkxpfxd"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-27 12:17:05","author":"stickybeak7","body":"Hi there! Hoping to get a translation similar to \"memento mori\" but for \"remember you are loved\" for a valentines gift :o) thank you so much!","sequenceNumber":42,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599838000}}},{"id":{"value":"t1_gkxsyty"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxpfxd"},"distinguished":false,"created_utc":"21-01-27 13:02:11","author":"aveCaecilius","body":"memento amari","sequenceNumber":43,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599899000}}},{"id":{"value":"t1_gkzyg27"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxsyty"},"distinguished":false,"created_utc":"21-01-27 21:54:16","author":"stickybeak7","body":"thank you so much! ?","sequenceNumber":44,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":599965000}}},{"id":{"value":"t1_gkxpgd0"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-27 12:17:16","author":"pierro_la_place","body":"Hi there!\n\nI am building a mediaval-ish world in which the people of kingdom A really don\u0027t like kingdom B they are at war with, to the point that they refuse to pronounce its name. Instead they say someting along the lines of \"land of the rapists\", but in Latin to give it an almost religious tone. After a bit of research, the translation I was thinking about was \"terra stuparotes\", but I am not an expert in Latin so I would like to know what you think.\n\nThanks!","sequenceNumber":45,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600039000}}},{"id":{"value":"t1_gkxvjvf"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxpgd0"},"distinguished":false,"created_utc":"21-01-27 13:32:32","author":"BaconJudge","body":"That\u0027s probably a typo for *stupratores*, which is the plural form of the word [*stuprator*](http://www.perseus.tufts.edu/hopper/text?doc\u003dPerseus%3Atext%3A1999.04.0059%3Aentry%3Dstuprator) when used as the subject of a sentence. Because you want it as a possessive plural (\"of the rapists\"), the phrase would be *terra stupratorum*.","sequenceNumber":46,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600100000}}},{"id":{"value":"t1_gkxypyz"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxvjvf"},"distinguished":false,"created_utc":"21-01-27 14:07:19","author":"pierro_la_place","body":"Yup, I mixed up accusative and genitive. Good thing I asked! Thx.","sequenceNumber":47,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600161000}}},{"id":{"value":"t1_gkxzjnm"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxypyz"},"distinguished":false,"created_utc":"21-01-27 14:15:50","author":"BaconJudge","body":"You\u0027re welcome, anytime.","sequenceNumber":48,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600231000}}},{"id":{"value":"t1_gkylxmm"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-27 16:55:07","author":"Koa1121","body":"How would you say ?Where easy becomes hard? or ?Where easy is hard?","sequenceNumber":49,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600292000}}},{"id":{"value":"t1_gl38i9x"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkylxmm"},"distinguished":false,"created_utc":"21-01-28 15:09:01","author":"quintus_sub_rosa","body":"in quo facile fit difficile.","sequenceNumber":50,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600351000}}},{"id":{"value":"t1_gl0zg1x"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-28 01:53:37","author":"GIGABIT","body":"What\u0027s up my dudes. WSB \"investor\" here. \n\nIn light of the ongoing GameStop insanity you might have heard about I thought it could be cool to get a tattoo celebrating the gains. Naturally it has to be the \"Power to the players\" slogan, but from what I understand, the word \"player\" isn\u0027t really a thing in Latin.\n\nI thought \"Power to the Gambler\" might be a more appropriate choice considering the nature of both parties involved, and because there actually might be a proper word for \"gambler\".\n\nSo I thought I\u0027d ask you guys for some advice about the sentence so I don\u0027t go printing myself with something stupid like \"short the market\".\n\nThanks in advance!","sequenceNumber":51,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600408000}}},{"id":{"value":"t1_gl29fog"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl0zg1x"},"distinguished":false,"created_utc":"21-01-28 08:38:37","author":"kc_kennylau","body":"potentia ad aleatores\n\nPS: don\u0027t trust internet strangers (such as me!) for tattoo","sequenceNumber":52,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600486000}}},{"id":{"value":"t1_gl33bxa"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl29fog"},"distinguished":false,"created_utc":"21-01-28 14:37:06","author":"GIGABIT","body":"Thanks a lot!\n\nI SHOULD know better than to trust internet strangers... Then again, you know what everyone is over at WSB, so..","sequenceNumber":53,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600548000}}},{"id":{"value":"t1_gl1qbxz"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-28 05:16:51","author":"dpm5150","body":"I want to mount a motto carved in wood in my library. I?m trying to figure out a good noun to express ?usefulness?. I?m want to express to my son that he should strive for contributing to society in ways that return tangible value. Usefulness, itself, is a dull word, so I?m not sure if Latin has something with a little more zing.","sequenceNumber":54,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600612000}}},{"id":{"value":"t1_gl29810"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl1qbxz"},"distinguished":false,"created_utc":"21-01-28 08:35:56","author":"kc_kennylau","body":"utilitas / ?tilit?s (whence English \"utility\")","sequenceNumber":55,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600672000}}},{"id":{"value":"t1_gl3020g"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl29810"},"distinguished":false,"created_utc":"21-01-28 14:13:35","author":"dpm5150","body":"Yes, this definitely can work. Now I have to decide how inspirational it is for a motto. Thank you so much.","sequenceNumber":56,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600732000}}},{"id":{"value":"t1_gl4ppso"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-28 20:03:46","author":"Rashed8StringVi","body":"From the popular phrase ?In Vino Veritas?, how would one correctly substitute ?blood? instead of ?wine? such that the phrase becomes ?in blood is the truth??","sequenceNumber":57,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600790000}}},{"id":{"value":"t1_gl54pgr"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl4ppso"},"distinguished":false,"created_utc":"21-01-28 21:34:34","author":"kc_kennylau","body":"in sanguine veritas","sequenceNumber":58,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600847000}}},{"id":{"value":"t1_gl55c0i"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-28 21:38:27","author":"coralcakes","body":"How would An Appeal to Heaven be translated into latin?","sequenceNumber":59,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600903000}}},{"id":{"value":"t1_gl5nhd2"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl55c0i"},"distinguished":false,"created_utc":"21-01-28 23:28:44","author":"jayzwasinnirvana","body":"*Obsecratio ad caelum* is one way. I\u0027m not sure if there is an existing phrase. I did not use *appellatio* because I think it\u0027s meaning in Latin is more strictly legal.","sequenceNumber":60,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":600978000}}},{"id":{"value":"t1_gl5quql"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl55c0i"},"distinguished":false,"created_utc":"21-01-28 23:52:04","author":"kc_kennylau","body":"Quite literally \"appellatio ad caelum\".","sequenceNumber":61,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601035000}}},{"id":{"value":"t1_gl57uz9"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-28 21:53:32","author":"RandyDautzenberg","body":"Hey all, \n\n\nCurrently I\u0027m looking for a translation of \u0027to the top\u0027 / \u0027the top\u0027 in Latin. Some internet translators are giving me different translations. Most of the time they translate it as \u0027ad summitatem\u0027 or \u0027ad verticem\u0027. \n\n\nI really like the word \u0027verticem\u0027, haha. So I was wondering if you can also use \u0027verticem\u0027 without the preposition \u0027ad\u0027. Or would that be grammatically incorrect? \n\n\nMany thanks! :)","sequenceNumber":62,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601091000}}},{"id":{"value":"t1_gl5khcv"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl57uz9"},"distinguished":false,"created_utc":"21-01-28 23:08:37","author":"kc_kennylau","body":"\"the top\" \u003d apex / vertex\n\n\"to the top\" \u003d ad apicem / ad verticem\n\nIt would be better to provide context (e.g. whole sentence).","sequenceNumber":63,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601147000}}},{"id":{"value":"t1_gl5lazb"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl5khcv"},"distinguished":false,"created_utc":"21-01-28 23:14:06","author":"RandyDautzenberg","body":"Many thanks for your quick reply! Well, I would like to use it as a brand name, haha. So that means that using verticem without the preposition is not grammatically correct, right?","sequenceNumber":64,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601203000}}},{"id":{"value":"t1_gl5avpy"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-28 22:11:03","author":"ContuberniumSPQR","body":"Hello \n\nI have to determine the word Casus from Casus,Casus I think it is a nominative but that doesn\u0027t fit with its function in the phrase could it be an accusative?","sequenceNumber":65,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601268000}}},{"id":{"value":"t1_gl5kal7"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl5avpy"},"distinguished":false,"created_utc":"21-01-28 23:07:22","author":"kc_kennylau","body":"Perfacilis inventu ex [Wiktionary](https://en.wiktionary.org/wiki/casus#Latin)","sequenceNumber":66,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601343000}}},{"id":{"value":"t1_gl7qke4"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl5kal7"},"distinguished":false,"created_utc":"21-01-29 09:59:22","author":"ContuberniumSPQR","body":" Gratias tibi ago","sequenceNumber":67,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601398000}}},{"id":{"value":"t1_gl6kjfb"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-29 03:30:49","author":"Havatra","body":"Hello!\n\nI need some translation help with this sentence:\n\n\"Remember you must die, so [do] thrive(vigorously), while you are [still] able to.\" \n\"Memento mori; ita vigemusque, dum es possunt.\"\n\nDoes this sound correct? Or is there perhaps a different way you\u0027d rather put it?\n\nI appreciate all help! :-)","sequenceNumber":68,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601454000}}},{"id":{"value":"t1_gl7b1cb"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-29 07:03:49","author":"Ghost_1243","body":"Hello, I\u0027m trying to translate the following the phrases:\n\n1. A Love of One\u0027s Fate\n2. The Will to Power\n3. Strive for a Higher Purpose\n4. Embrace the Ordinary","sequenceNumber":69,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601510000}}},{"id":{"value":"t1_gl7bfpk"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-29 07:07:53","author":"DV5161","body":"I?m having trouble translating ?live and die? so when I put that into google translate I get back ?vivere et mori? but when I switch it and go from Latin to English I get back ?live and? but when I put in ?vivamus, moriendum est.? I get back ?live and die? if someone could help me with which is right and wrong it would be greatly appreciated!","sequenceNumber":70,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601564000}}},{"id":{"value":"t1_gl7hq77"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl7bfpk"},"distinguished":false,"created_utc":"21-01-29 08:13:05","author":"kc_kennylau","body":"Anything google translate says is wrong.\n\n\"live and die\" \u003d vive et morere (command to 1 person) / vivete et morimini (command to multiple people)\n\nYou can also replace the \"et\" to \"atque\".","sequenceNumber":71,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601618000}}},{"id":{"value":"t1_gl7hzbb"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gl7hq77"},"distinguished":false,"created_utc":"21-01-29 08:15:50","author":"DV5161","body":"Lmao I had no idea but ok nice, glad to have got that cleared up thank you so much!","sequenceNumber":72,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601680000}}},{"id":{"value":"t1_gkuwp93"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t3_l5ejhj"},"distinguished":false,"created_utc":"21-01-26 20:31:29","author":"ki4clz","body":"Romanes eunt domis...?","sequenceNumber":73,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601737000}}},{"id":{"value":"t1_gkuzut8"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkuwp93"},"distinguished":false,"created_utc":"21-01-26 20:52:42","author":"kc_kennylau","body":"People called Romanes, they go, the house?","sequenceNumber":74,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601792000}}},{"id":{"value":"t1_gkvxdoo"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkuzut8"},"distinguished":false,"created_utc":"21-01-27 00:50:38","author":"ki4clz","body":"It says *Romans Go Home!*...","sequenceNumber":75,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601848000}}},{"id":{"value":"t1_gkx8lf8"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkvxdoo"},"distinguished":false,"created_utc":"21-01-27 08:10:02","author":"rsotnik","body":"It doesn\u0027t :)\n\nhttps://en.m.wikipedia.org/wiki/Romani_ite_domum","sequenceNumber":76,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601904000}}},{"id":{"value":"t1_gkxml3q"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkx8lf8"},"distinguished":false,"created_utc":"21-01-27 11:36:21","author":"ki4clz","body":"but latin for Roman is, Romanus...?","sequenceNumber":77,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":601957000}}},{"id":{"value":"t1_gkxn1hb"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxml3q"},"distinguished":false,"created_utc":"21-01-27 11:42:53","author":"rsotnik","body":"Sorry, you lost me :) Do you want to know what \" Romanes eunt domis\" means or how one says \"Romans, go home!\"?","sequenceNumber":78,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":602016000}}},{"id":{"value":"t1_gkxo45g"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxn1hb"},"distinguished":false,"created_utc":"21-01-27 11:58:33","author":"ki4clz","body":"Some kind soul was patient with me in this thread, and showed me the way: *(you may need to check their work?)*\n\nhttps://www.reddit.com/r/dankchristianmemes/comments/kp0ua2/a_catholic_a_protestant_and_an_orthodox_walk_into/ghv5c44?utm_medium\u003dandroid_app\u0026utm_source\u003dshare\u0026context\u003d3","sequenceNumber":79,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":602068000}}},{"id":{"value":"t1_gkxoeil"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxo45g"},"distinguished":false,"created_utc":"21-01-27 12:02:38","author":"rsotnik","body":"Haha, you played me all along :)","sequenceNumber":80,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":602120000}}},{"id":{"value":"t1_gkxok0c"},"submission_id":{"id":"t3_l5ejhj"},"parent_id":{"id":"t1_gkxoeil"},"distinguished":false,"created_utc":"21-01-27 12:04:44","author":"ki4clz","body":"I\u0027ve only ever had one person take the bait... was hoping for a rematch...\n\n***Happy Cake Day!***\n-","sequenceNumber":81,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":602170000}}},{"id":{"value":"t1_gl5au93"},"submission_id":{"id":"t3_l765yl"},"parent_id":{"id":"t3_l765yl"},"distinguished":false,"created_utc":"21-01-28 22:10:49","author":"CabezadeVaca_","body":"Russian priests singing in Latin?? Very beautiful but also very odd","sequenceNumber":83,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":53,"nano":161325000}}},{"id":{"value":"t1_gl5h3k4"},"submission_id":{"id":"t3_l765yl"},"parent_id":{"id":"t1_gl5au93"},"distinguished":false,"created_utc":"21-01-28 22:48:23","author":"HanSo1oCup","body":"*Russian Orthodox* but the monastery is located in WV, USA","sequenceNumber":84,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":53,"nano":161442000}}},{"id":{"value":"t1_gl5i3sq"},"submission_id":{"id":"t3_l765yl"},"parent_id":{"id":"t1_gl5h3k4"},"distinguished":false,"created_utc":"21-01-28 22:54:09","author":"CabezadeVaca_","body":"Well that seems especially odd to me if they?re not even Eastern Catholics. I?ve never understood the Russian or the Greek churches to have much respect for Latin as a liturgical language, but of course that?s just based on my own experiences","sequenceNumber":85,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":53,"nano":161497000}}},{"id":{"value":"t1_gl5pu5j"},"submission_id":{"id":"t3_l765yl"},"parent_id":{"id":"t1_gl5i3sq"},"distinguished":false,"created_utc":"21-01-28 23:45:00","author":"greetings_traveler2","body":"yeah, it\u0027s pretty uncommon for Orthodox priests, I love their chants in ancient Greek though","sequenceNumber":86,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":53,"nano":161547000}}},{"id":{"value":"t1_gl4um7f"},"submission_id":{"id":"t3_l765yl"},"parent_id":{"id":"t3_l765yl"},"distinguished":false,"created_utc":"21-01-28 20:34:13","author":"HanSo1oCup","body":"Correction *It was adapted by the monks in honor of the Holy Prophet David*","sequenceNumber":87,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":53,"nano":161627000}}},{"id":{"value":"t1_gl5n4sd"},"submission_id":{"id":"t3_l765yl"},"parent_id":{"id":"t3_l765yl"},"distinguished":false,"created_utc":"21-01-28 23:26:23","author":"marktwainbrain","body":"Is this a group that has a ?Western Rite? orthodox parish?","sequenceNumber":88,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":53,"nano":161677000}}},{"id":{"value":"t1_gl64il6"},"submission_id":{"id":"t3_l765yl"},"parent_id":{"id":"t1_gl5n4sd"},"distinguished":false,"created_utc":"21-01-29 01:30:08","author":"greetings_traveler2","body":"Is that a thing?","sequenceNumber":89,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":53,"nano":161724000}}},{"id":{"value":"t1_gl64vsg"},"submission_id":{"id":"t3_l765yl"},"parent_id":{"id":"t1_gl64il6"},"distinguished":false,"created_utc":"21-01-29 01:32:52","author":"marktwainbrain","body":"Yes, not at all common. An overture to some Latin traditionalists. I think OCA has Western Rite parishes.\n\nETA: https://en.m.wikipedia.org/wiki/Western_Rite_Orthodoxy","sequenceNumber":90,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":53,"nano":161770000}}},{"id":{"value":"t1_gl5m4eg"},"submission_id":{"id":"t3_l7aez5"},"parent_id":{"id":"t3_l7aez5"},"distinguished":false,"created_utc":"21-01-28 23:19:33","author":"qed1","body":"There is only one appropriate manuscript to share on such an occasion as this: https://digi.vatlib.it/view/MSS_Vat.lat.9850/0016.\n\n^^^^^^^^(Not ^^^^^^^sure ^^^^^^^why ^^^^^^^there ^^^^^^^are ^^^^^^^two ^^^^^^^threads, ^^^^^^^only ^^^^^^^one ^^^^^^^of ^^^^^^^which ^^^^^^^I\u0027m ^^^^^^^seeing ^^^^^^^at ^^^^^^^a ^^^^^^^given ^^^^^^^time... ^^^^^^^but ^^^^^^^this ^^^^^^^should ^^^^^^^obviously ^^^^^^^be ^^^^^^^seen ^^^^^^^in ^^^^^^^both!)","sequenceNumber":92,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":55,"nano":144469000}}},{"id":{"value":"t1_gl730gk"},"submission_id":{"id":"t3_l7aez5"},"parent_id":{"id":"t1_gl5m4eg"},"distinguished":false,"created_utc":"21-01-29 05:52:40","author":"SheepExplosion","body":"Things that make me long for Merovingian chancery hands. Good thing he had 4 scribes or no one would have ever known what he wrote.","sequenceNumber":93,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":55,"nano":144676000}}},{"id":{"value":"t1_gl5ns9x"},"submission_id":{"id":"t3_l7aez5"},"parent_id":{"id":"t1_gl5m4eg"},"distinguished":false,"created_utc":"21-01-28 23:30:47","author":"Kingshorsey","body":"Are you sure that link goes where you want? The title page is nice, but your link goes to a random page in the middle.","sequenceNumber":94,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":55,"nano":144774000}}},{"id":{"value":"t1_gl5oqvi"},"submission_id":{"id":"t3_l7aez5"},"parent_id":{"id":"t1_gl5ns9x"},"distinguished":false,"created_utc":"21-01-28 23:37:26","author":"qed1","body":"That was the point, yes. I was aiming to land at a section of Aquinas\u0027s near incomprehensible handwriting. ;)","sequenceNumber":95,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":55,"nano":144866000}}},{"id":{"value":"t1_gl5ocsz"},"submission_id":{"id":"t3_l7aez5"},"parent_id":{"id":"t1_gl5m4eg"},"distinguished":false,"created_utc":"21-01-28 23:34:45","author":"EmergencySufficient6","body":"... What is *that?* \n\nI thought for sure it\u0027d be his musings upon [the effects of stars on demons](http://www.logicmuseum.com/wiki/Authors/Thomas_Aquinas/Summa_Theologiae/Part_I/Q115#q115a5arg1) or something similar.","sequenceNumber":96,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":55,"nano":144970000}}},{"id":{"value":"t1_gl5ovhj"},"submission_id":{"id":"t3_l7aez5"},"parent_id":{"id":"t1_gl5ocsz"},"distinguished":false,"created_utc":"21-01-28 23:38:18","author":"qed1","body":"It\u0027s Aquinas\u0027s totally incomprehensible handwriting.","sequenceNumber":97,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":55,"nano":145068000}}},{"id":{"value":"t1_gl5rrc7"},"submission_id":{"id":"t3_l7aez5"},"parent_id":{"id":"t1_gl5ovhj"},"distinguished":false,"created_utc":"21-01-28 23:58:24","author":"EmergencySufficient6","body":"How sure are we that there\u0027s a teleological argument against gays in there?\n\nEdit: Forgive me, downvote brigade, for I have sinned.","sequenceNumber":98,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":55,"nano":145174000}}},{"id":{"value":"t1_gl5kbyc"},"submission_id":{"id":"t3_l7aez5"},"parent_id":{"id":"t3_l7aez5"},"distinguished":false,"created_utc":"21-01-28 23:07:37","author":"Kingshorsey","body":"Source: [https://digi.vatlib.it/mss/detail/Urb.lat.136](https://digi.vatlib.it/mss/detail/Urb.lat.136)","sequenceNumber":99,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":55,"nano":145265000}}},{"id":{"value":"t1_gl315u1"},"submission_id":{"id":"t3_l6s45e"},"parent_id":{"id":"t3_l6s45e"},"distinguished":false,"created_utc":"21-01-28 14:21:53","author":"joaojcorreia","body":"Hi u/Irene_SaturaLanx, I was able to see part of the session live, really good. Congratulations. I was really happy, because I was able to follow it. Gratias.","sequenceNumber":103,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":59,"nano":155093000}}},{"id":{"value":"t1_gl3oyaf"},"submission_id":{"id":"t3_l6s45e"},"parent_id":{"id":"t1_gl315u1"},"distinguished":false,"created_utc":"21-01-28 16:28:46","author":"Irene_SaturaLanx","body":"Gratias tibi!","sequenceNumber":104,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":59,"nano":155276000}}},{"id":{"value":"t1_gl31nts"},"submission_id":{"id":"t3_l6s45e"},"parent_id":{"id":"t3_l6s45e"},"distinguished":false,"created_utc":"21-01-28 14:25:30","author":"ironicsadboy","body":"Congratulations on your work!","sequenceNumber":105,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":59,"nano":155374000}}},{"id":{"value":"t1_gl3oz4e"},"submission_id":{"id":"t3_l6s45e"},"parent_id":{"id":"t1_gl31nts"},"distinguished":false,"created_utc":"21-01-28 16:28:53","author":"Irene_SaturaLanx","body":"Thanks!","sequenceNumber":106,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":59,"nano":155466000}}},{"id":{"value":"t1_gl45js7"},"submission_id":{"id":"t3_l6s45e"},"parent_id":{"id":"t3_l6s45e"},"distinguished":false,"created_utc":"21-01-28 17:53:49","author":"logatwork","body":"I\u0027m reading/studying this book! Spoilers ahead!\n\nThank you","sequenceNumber":107,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":59,"nano":155556000}}},{"id":{"value":"t1_gl4w10f"},"submission_id":{"id":"t3_l6s45e"},"parent_id":{"id":"t3_l6s45e"},"distinguished":false,"created_utc":"21-01-28 20:42:52","author":"Redbubbles55","body":"hunc librem nunc perlego, sperans posthac linguam latinam modo eius docere. gaudeo multum sessionem tuam spectauisse, gratias ueras tibi ago !","sequenceNumber":108,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":59,"nano":155643000}}},{"id":{"value":"t1_gl510zz"},"submission_id":{"id":"t3_l6s45e"},"parent_id":{"id":"t3_l6s45e"},"distinguished":false,"created_utc":"21-01-28 21:12:53","author":"Monsieurantipyrine","body":"Truly one (or two) of the best texts out there for Latin students! Very glad I learned from this back in University.","sequenceNumber":109,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":59,"nano":155732000}}},{"id":{"value":"t1_gl58d7w"},"submission_id":{"id":"t3_l6s45e"},"parent_id":{"id":"t3_l6s45e"},"distinguished":false,"created_utc":"21-01-28 21:56:31","author":"scriptapuella","body":"My university is grammar focussed, but I teach with this book (alongside the companion) to aid comprehension and get students used to continuous Latin passages early. Evaluations indicate they like it more than Wheelock, at any rate.","sequenceNumber":110,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":59,"nano":155832000}}},{"id":{"value":"t1_gl7scfq"},"submission_id":{"id":"t3_l7iwsm"},"parent_id":{"id":"t3_l7iwsm"},"distinguished":false,"created_utc":"21-01-29 10:22:23","author":"kc_kennylau","body":"* You dare to drink wine in my presence?\n\n\"you dare to drink wine\" is indeed \"vinum bibere audes\", but \"ante\" takes the accusative instead of the dative, so it would be \"ante me\" instead of \"ante mi\". Other possible translations of \"in my presence\" include \"coram me\" and \"prae me\".\n\nE.g. Exodus 23:3 Non habebis deos alienos **coram me**. \"Thou shalt have no other gods **before me**.\"\n\n\u0026#x200B;\n\n* Because of covid, two million people are dead.\n\n[Latin Wikipedia](https://la.wikipedia.org/wiki/COVID-19) translates COVID-19 as \"morbus coronarii viri anni 2019\" and notes that this is their internal translation only, not found outside Wikipedia. To be safe, I would use COVID-19, but this does not admit cases. I guess in the end I would prefer \"morbus coronarii viri\" which does admit cases.\n\nNote that \"quia\" needs to be followed by a phrase, or put simply, \"quia\" \u003d \"because\" not \"because of\". I would use \"propter\" for \"because of\", or I would just use the ablative.\n\n\"concident\" is the future tense. The perfect tense \"conciderunt\" is more appropriate.\n\nIn conclusion: \"propter morbum coronarii viri, duo milliones homines conciderunt.\" or \"morbo coronarii viri, duo milliones homines conciderunt.\"","sequenceNumber":112,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":1,"nano":89793000}}},{"id":{"value":"t1_gl6k27a"},"submission_id":{"id":"t3_l7carp"},"parent_id":{"id":"t3_l7carp"},"distinguished":false,"created_utc":"21-01-29 03:27:06","author":"jayzwasinnirvana","body":"You\u0027ve almost got it - the verb is right, but consul should be in the nominative. It\u0027s not a direct object, but a subject (nominative? maybe someone can chime in with the grammatical term) complement. Here\u0027s Livy:\n\n\n\u003eDecembri mense summo patrum studio L.\tQuinctius Cincinnatus, pater Caesonis, **consul creatur**","sequenceNumber":114,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":3,"nano":83507000}}},{"id":{"value":"t1_gl6wvd8"},"submission_id":{"id":"t3_l7carp"},"parent_id":{"id":"t1_gl6k27a"},"distinguished":false,"created_utc":"21-01-29 05:03:53","author":"ogorangeduck","body":"It\u0027s an attributive, not a subject.","sequenceNumber":115,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":3,"nano":83662000}}},{"id":{"value":"t1_gl6u4mq"},"submission_id":{"id":"t3_l7gja1"},"parent_id":{"id":"t3_l7gja1"},"distinguished":false,"created_utc":"21-01-29 04:42:50","author":"isolde100","body":"Quis ut Deus refers to St. Michael the Archangel - it means ?who is like God?. It?s the literal translation of the Hebrew Michael or Mika?el.","sequenceNumber":117,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":5,"nano":86997000}}},{"id":{"value":"t1_gl7941t"},"submission_id":{"id":"t3_l7gja1"},"parent_id":{"id":"t1_gl6u4mq"},"distinguished":false,"created_utc":"21-01-29 06:45:24","author":"LurkinOG","body":"You are right..i looked up old latin versions of the I and one is shaped like what i thought what was a 3 but its a capital that looks like this only L only reversed £..what is that quote significant to..i know when translated to todays english its hard to know the meaning or significance without context..and thank you for sharing your knowledge in figuring this out","sequenceNumber":118,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":5,"nano":87152000}}},{"id":{"value":"t1_gl6lb05"},"submission_id":{"id":"t3_l7gja1"},"parent_id":{"id":"t3_l7gja1"},"distinguished":false,"created_utc":"21-01-29 03:36:43","author":"TWFM","body":"Qu3s is apparently an internet personality.","sequenceNumber":119,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":5,"nano":87249000}}},{"id":{"value":"t1_gl6mzss"},"submission_id":{"id":"t3_l7gja1"},"parent_id":{"id":"t1_gl6lb05"},"distinguished":false,"created_utc":"21-01-29 03:49:30","author":"LurkinOG","body":"I thought so to but old latin Ques can be translated to mean seek/ask its where you end up with english words like ques-tion, ques-t..3 could signify both holy trinity and a internet personality","sequenceNumber":120,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":5,"nano":87345000}}},{"id":{"value":"t1_gl88ce7"},"submission_id":{"id":"t3_l7gja1"},"parent_id":{"id":"t3_l7gja1"},"distinguished":false,"created_utc":"21-01-29 13:40:59","author":"BaconJudge","body":"There\u0027s a common Latin scribal abbreviation that resembles a 3, and it can stand in for various letter combinations, such as *-et*. For example, the modern-day abbreviation *viz.* for *videlicet* originated from misinterpreting *vi?* as *viz.* However, it\u0027s normally used at the end of words, and I can\u0027t see what it would represent here, so I mention it mainly to rule it out.","sequenceNumber":121,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":5,"nano":87445000}}},{"id":{"value":"t1_gl1hr7e"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t3_l6mdp8"},"distinguished":false,"created_utc":"21-01-28 04:06:11","author":"antinousrex","body":"feel free to point out errors, please!","sequenceNumber":123,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177467000}}},{"id":{"value":"t1_gl1tfor"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t1_gl1hr7e"},"distinguished":false,"created_utc":"21-01-28 05:44:51","author":"Thalionwen20","body":"In section 4, it should be \"undecimum,\" accusative. In the last line of section 5, it should be \"et,\" not \"and.\" In 9, you might want to say \"inter se\" instead of \"eorum.\" In 10, it should be \"annulum\" and \"esse\" for indirect speech, or \"annulus delendus\" for a direct quote. In 11, you only need one \"est\" and a \"qui\" before \"nunc.\" In 12, it should be \"mortuos.\" In 15, \"Galadriela\" and \"principe\" are misspelled. In 16, you probably just want \"interficit,\" and I would put another verb such as \"incipit\" with \"iter facere.\"\n\nDespite that, I really enjoyed this and think you did a good job on it! :)","sequenceNumber":124,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177562000}}},{"id":{"value":"t1_gl2y8de"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t1_gl1tfor"},"distinguished":false,"created_utc":"21-01-28 13:57:47","author":"eglwufdeo","body":"Some more I found: \n\nIn section 1 \n\"ignotum eis\" seems sketchy, I would expect an ablative absolute \n\"eius\" should be \"suae\" \n\nIn section 2 \n\"foedum\" should be \"foedus\", having it as the subject seems a bit weird but I\u0027m not sure \n\"miletes\" should be \"milites\" \nI think the second sentence misses a verb \n\"saeculum\" not \"saecula\" \n\nIn section 3 \n\"pro se\" not \"pro sibi\" \n\nIn section 4 \n\"centesimus\" not \"centisimus\" \ndon\u0027t think \"discedere\" takes an accusative like that \n\"faciat\" not \"faceat\" \n\"suo\" not \"eius\" \n\nIn section 5 \n\"certior\" not \"certiorem\" \nYou need either indirect speech or some subjunction, but as it stands the sentence about Gollum\u0027s torture doesn\u0027t work \n\"aperiens verba\" doesn\u0027t work (match number), should be an ablative absolute \n\"discedat\" not \"discedeat\", also see above \n\nIn section 6 \nsame thing with \"certior\" \nthe relative clause needs a verb \n\nIn Section 7 \n\"Sarumano\" should be \"a Sarumano\" \n\"adiuvantur\" not \"adiuntur\" \n\"a venatore\" not \"venatori\" \n\nIn section 8 \nDon\u0027t know if \"do\" is the best verb here , maybe \"parare\"? \n\nIn section 9 \n\"curatur\" should probably be plural \n\"suum\" not \"eorum\" \n\nIn section 10 \n\"ambo\" doesn\u0027t work \n\nIn section 11 \nI don\u0027t think \"se voluntare\" is a thing \n\"comitatus\" not \"commitatus\" \n\nIn section 12 \n\"quae\" not \"qui\" \n\nIn section 13 \nThink there\u0027s a verb missing after \"vastum antrum\" \n\"cum se\" not \"cum sibi\" \n\nIn section 14 \n\"devastata\" not \"devastatus\" \n\"capturum\" not \"capturus\" \n\nIn section 15 \nI think \"per\" is the wrong preposition \n\nIn section 16 \n\"suos\" not \"eius\" \ndon\u0027t think \"iurandum\" works like that","sequenceNumber":125,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177599000}}},{"id":{"value":"t1_gl3du83"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t1_gl2y8de"},"distinguished":false,"created_utc":"21-01-28 15:37:05","author":"antinousrex","body":"Thank you so much!","sequenceNumber":126,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177643000}}},{"id":{"value":"t1_gl3dsb3"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t1_gl1tfor"},"distinguished":false,"created_utc":"21-01-28 15:36:49","author":"antinousrex","body":"Thank you so much! I do these off the seat of my pants and rely on people like you to catch the errors I miss after staring at my own text for 3 hrs","sequenceNumber":127,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177681000}}},{"id":{"value":"t1_gl27bkf"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t1_gl1hr7e"},"distinguished":false,"created_utc":"21-01-28 08:11:56","author":"Julius_The_Caesar","body":"Happy cake day","sequenceNumber":128,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177721000}}},{"id":{"value":"t1_gl212je"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t3_l6mdp8"},"distinguished":false,"created_utc":"21-01-28 06:59:30","author":"PinkyPiePerson","body":"You wouldn\u0027t happen to have the link to the Shrek one.\n\nAlso Bee Movie next???","sequenceNumber":129,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177763000}}},{"id":{"value":"t1_gl3hymr"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t1_gl212je"},"distinguished":false,"created_utc":"21-01-28 15:56:22","author":"antinousrex","body":"here\u0027s shrek. bee movie is in progress [https://docs.google.com/document/d/1-0GY-JqbusyDbOp7aaRJy1q8rY4LSTMQbccTek7673Q/edit?usp\u003dsharing](https://docs.google.com/document/d/1-0GY-JqbusyDbOp7aaRJy1q8rY4LSTMQbccTek7673Q/edit?usp\u003dsharing)","sequenceNumber":130,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177822000}}},{"id":{"value":"t1_gl2ad9g"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t3_l6mdp8"},"distinguished":false,"created_utc":"21-01-28 08:50:55","author":"-Frind-","body":"Is it classical?","sequenceNumber":131,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177859000}}},{"id":{"value":"t1_gl2tdy1"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t1_gl2ad9g"},"distinguished":false,"created_utc":"21-01-28 13:07:27","author":"OperaRotas","body":"Of course Lord of the Rings is a classical\n\n_[jocor]_","sequenceNumber":132,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177893000}}},{"id":{"value":"t1_gl2bz5e"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t3_l6mdp8"},"distinguished":false,"created_utc":"21-01-28 09:12:40","author":"MadScientist2854","body":"happy cake day!","sequenceNumber":133,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177926000}}},{"id":{"value":"t1_gl42d9n"},"submission_id":{"id":"t3_l6mdp8"},"parent_id":{"id":"t3_l6mdp8"},"distinguished":false,"created_utc":"21-01-28 17:37:36","author":"OperaRotas","body":"\u003eSed potestas Annuli Isildurem corrumpit, qui Annulum pro se capit\n\nThis \"pro se\" sounds a bit weird to me, kinda \"he takes the ring for his own sake\". Maybe \"sibi\" would work better?\n\nI\u0027m also not sure if this \"capit\" means \"takes\", as in right after cutting Sauron\u0027s finger (in which case it\u0027s fine) or \"keeps\", as in \"keeps the ring longer than he should\", in which case \"tenet\" could be better. Any way I don\u0027t know if \"sibi tenet\" works well.","sequenceNumber":134,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":177969000}}},{"id":{"value":"t1_gl2mk6t"},"submission_id":{"id":"t3_l6thod"},"parent_id":{"id":"t3_l6thod"},"distinguished":false,"created_utc":"21-01-28 11:41:58","author":"bandzugfeder","body":"I\u0027ll check with the reference grammars. My guess beforehand (to record my failure for posterity) is that the two genitives would be on either side of the noun,or otherwise separated (eg by a determiner or a case-marked attribute). But I would also guess that it is a comparatively rare occurrence.","sequenceNumber":136,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":9,"nano":96392000}}},{"id":{"value":"t1_gl4dsl6"},"submission_id":{"id":"t3_l6thod"},"parent_id":{"id":"t1_gl2mk6t"},"distinguished":false,"created_utc":"21-01-28 18:38:18","author":"j1bb3r1sh","body":"One example I can recall that may apply here, if I understand the question correctly, is from Sallust?s *Bellum Catilinae* chapter 2.3, ?*Quod si regum atque imperatorum animi virtus in pace ita ut in bello valeret*...? if that would help in your research. \n\nI?m not certain if the situation warrants defining it as its own construction, but it was unique enough that I remembered it six months after reading it. I?d be interested to hear if you are able to find anything else on this. \n\nAlso apologies for formatting if it is messed up, I am on mobile","sequenceNumber":137,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":9,"nano":96530000}}}],"threads":[{"id":{"value":"t3_l5ejhj"},"parent_id":{"value":"t5_2qloa"},"title":"English to Latin translation requests go here!","body":" \n\n1. Ask and answer questions about mottos, tattoos, book titles, lines for your poem, slogans for your bowling club?s t-shirt, etc. in the comments of this thread. **Separate posts for these types of requests will be removed.**\n2. Here are some examples of what types of requests this thread is for: [Example #1](https://www.reddit.com/r/latin/comments/dyqs8p/would_the_correct_translation_of_satans_sister_be/?utm_source\u003dshare\u0026utm_medium\u003dweb2x), [Example #2](https://www.reddit.com/r/latin/comments/dyp18o/translation_from_english/?utm_source\u003dshare\u0026utm_medium\u003dweb2x), [Example #3](https://www.reddit.com/r/latin/comments/dy4o7b/i_need_help_in_translating_correctly_these_2_words/?utm_source\u003dshare\u0026utm_medium\u003dweb2x), [Example #4](https://www.reddit.com/r/latin/comments/dxdzpb/are_there_any_words_that_convey_the_idea_of_a/?utm_source\u003dshare\u0026utm_medium\u003dweb2x), [Example #5](https://www.reddit.com/r/latin/comments/dx5xzc/motto_in_latin/?utm_source\u003dshare\u0026utm_medium\u003dweb2x).\n3. This thread is **not for correcting longer translations and student assignments**. If you have some facility with the Latin language and have made an honest attempt to translate that is **NOT from Google Translate**, Yandex, or any other machine translator, create a separate thread requesting to check and correct your translation: [Separate thread example](https://www.reddit.com/r/latin/comments/dyjz4m/motto_idea_for_motorbike/). Make sure to take a look at Rule 4.\n4. [Previous iterations of this thread](https://www.reddit.com/r/latin/search/?q\u003dLatin%20translation%20requests%20here\u0026restrict_sr\u003d1).","url":"https://www.reddit.com/r/latin/comments/l5ejhj/english_to_latin_translation_requests_go_here/","sub":"latin","author":"NasusSyrae","num_comments":80,"created_utc":"21-01-26 15:00:22","sequenceNumber":82,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":51,"nano":602455000}}},{"id":{"value":"t3_l765yl"},"parent_id":{"value":"t5_2qloa"},"title":"A hymn dedicated to Saint Nicholas, performed by two monks of Holy Cross Monastery.","body":"","url":"https://v.redd.it/4n18nwz2k4e61","sub":"latin","author":"HanSo1oCup","num_comments":8,"created_utc":"21-01-28 20:30:17","sequenceNumber":91,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":53,"nano":161815000}}},{"id":{"value":"t3_l7aez5"},"parent_id":{"value":"t5_2qloa"},"title":"Hodie est Festum S. Thomae de Aquino - Ecce Pagina Illustrata","body":"","url":"https://i.redd.it/nar1o78fc5e61.jpg","sub":"latin","author":"Kingshorsey","num_comments":8,"created_utc":"21-01-28 23:07:28","sequenceNumber":100,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":55,"nano":145355000}}},{"id":{"value":"t3_l7isan"},"parent_id":{"value":"t5_2qloa"},"title":"Verbum Diei, die Jovis, A D V KAL FEB, anni AUC MMDCCLXXIV: excaeco","body":"Verbum diei hodie est:\n\nexcaeco, excaecare, excaecvi, excaecatum: to blind, to make blind\n\n1st conjugation verb\n\n*Frequens curatio est uenas in temporibus adurere, quae fere quidem in eiusmodi malo tument: sed tamen, ut inflentur magisque se ostendant, ceruix ante modice deliganda est, tenuibusque ferramentis et retussis uenae adurendae, donec in oculis pituitae cursus conquiescat. Id enim signum est quasi excaecatorum itinerum, per quae umor ferebatur*\n\nCelsus, *de Medicina*, 7.7","url":"https://www.reddit.com/r/latin/comments/l7isan/verbum_diei_die_jovis_a_d_v_kal_feb_anni_auc/","sub":"latin","author":"Glofkill","num_comments":0,"created_utc":"21-01-29 05:10:12","sequenceNumber":101,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":56,"nano":63285000}}},{"id":{"value":"t3_l7sduu"},"parent_id":{"value":"t5_2qloa"},"title":"I can?t understand this sub sometimes.","body":"I?m feeling lost when it comes to the natural method vs grammar method as laid out on this sub as the best way to learn and understand Latin. I am using LLPSI to learn Latin. Everyone posts about the subjunctive and the perfect and all the other what I believe are grammar rules or modes etc, and I?m over here thinking that none of that is In LLPSI. I feel like there is a whole world of information that I?m not getting because while I know that est is for singular and sunt is for plural, I have no idea what anything else is. I can parse our meaning from reading and context clues, but when or how should I learn to get into the grammar? Right now it?s all story and vocab- I know what I?m reading but I have no idea why it goes its changing in spelling etc. is this normal for reading LLPSI?","url":"https://www.reddit.com/r/latin/comments/l7sduu/i_cant_understand_this_sub_sometimes/","sub":"latin","author":"Squeeks0","num_comments":0,"created_utc":"21-01-29 14:22:11","sequenceNumber":102,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":57,"nano":85717000}}},{"id":{"value":"t3_l6s45e"},"parent_id":{"value":"t5_2qloa"},"title":"If you want to see how a lesson with \"Familia Romana\" works, you can now watch the first lesson on my YouTube channel! ? I can\u0027t recommend that book more to anyone who decides to learn Latin, be it with a teacher or alone.","body":"","url":"https://www.youtube.com/watch?v\u003dwCO_McKXEzA\u0026lc\u003dUgwFAsByoLpr_pnXNqZ4AaABAg.9IykSUZhPwo9J0N2_fqeL2\u0026ab_channel\u003dSaturaLanx","sub":"latin","author":"Irene_SaturaLanx","num_comments":8,"created_utc":"21-01-28 09:50:50","sequenceNumber":111,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":59,"nano":155917000}}},{"id":{"value":"t3_l7iwsm"},"parent_id":{"value":"t5_2qloa"},"title":"I like to do random translations; could someone check these and point out the errors?","body":"Vinum bibere audes ante mi?\n\nYou dare to drink wine in my presence?\n\nQuia coronavirus-morbus, duo milliones homines concident.\n\nBecause of covid, two million people are dead.\n\nI know these will probably be full of errors, can someone check them pwease.","url":"https://www.reddit.com/r/latin/comments/l7iwsm/i_like_to_do_random_translations_could_someone/","sub":"latin","author":"seaweedWorkers","num_comments":1,"created_utc":"21-01-29 05:16:07","sequenceNumber":113,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":1,"nano":90014000}}},{"id":{"value":"t3_l7carp"},"parent_id":{"value":"t5_2qloa"},"title":"Passive translation","body":"How would you say this in latin: The man is elected consul. \n\nIs elected in the passive? but it doesn?t take a direct object\n\nVir consulem creatur?","url":"https://www.reddit.com/r/latin/comments/l7carp/passive_translation/","sub":"latin","author":"SnooDoggos8723","num_comments":2,"created_utc":"21-01-29 00:23:35","sequenceNumber":116,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":3,"nano":83765000}}},{"id":{"value":"t3_l7gja1"},"parent_id":{"value":"t5_2qloa"},"title":"can someone translate. \"Qu3s Ut Deus\" i saw it tattooed on a stranger and made me wonder if 3 was just the way some use 3 in place of E and that was a personal choice or am i missing hidden meaning. Ques Ut Deus, would that not translate roughly to who is god. Am i missing something?","body":"","url":"https://www.reddit.com/r/latin/comments/l7gja1/can_someone_translate_qu3s_ut_deus_i_saw_it/","sub":"latin","author":"LurkinOG","num_comments":5,"created_utc":"21-01-29 03:27:25","sequenceNumber":122,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":5,"nano":87535000}}},{"id":{"value":"t3_l6mdp8"},"parent_id":{"value":"t5_2qloa"},"title":"From the guy who brought you Phineas and Ferb \u0026 Shrek, here\u0027s the story of Lord of the Rings, The Fellowship of the Ring, in Latin!","body":"[https://docs.google.com/document/d/1gZ2lLzlrOuzxNWyNHczDNonVRlMAvwfIT--W3xLMTzk/edit?usp\u003dsharing](https://docs.google.com/document/d/1gZ2lLzlrOuzxNWyNHczDNonVRlMAvwfIT--W3xLMTzk/edit?usp\u003dsharing)","url":"https://www.reddit.com/r/latin/comments/l6mdp8/from_the_guy_who_brought_you_phineas_and_ferb/","sub":"latin","author":"antinousrex","num_comments":12,"created_utc":"21-01-28 04:05:36","sequenceNumber":135,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":7,"nano":178008000}}},{"id":{"value":"t3_l6thod"},"parent_id":{"value":"t5_2qloa"},"title":"Is \"double genitive\" possible in Latin?","body":"There are verbs that take double accusative (e.g. doce?) and there is also double dative (e.g. cu? bon?). These duplicated cases take on different meanings, e.g. in the double dative, [one dative is the dative of purpose and the other dative is the dative of reference](http://dcc.dickinson.edu/grammar/latin/dative-purpose).\n\nIs it possible to have double genitive for a noun that can take on two genitives out of (a) the objective genitive, (b) the partitive genitive, and (c) the genitive of possession?\n\nFor example, odium *barbar?rum* **civilizati?nis**, where *barbar?rum* is the genitive of possession and **civilizati?nis** is the objective genitive.\n\nAre such formations attested?","url":"https://www.reddit.com/r/latin/comments/l6thod/is_double_genitive_possible_in_latin/","sub":"latin","author":"kc_kennylau","num_comments":2,"created_utc":"21-01-28 11:25:02","sequenceNumber":138,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":9,"nano":96574000}}}],"subreddits":[{"name":"latin","title":"The Latin Language","id":{"value":"t5_2qloa"},"description":"This is a community for discussions related to the Latin language.","sequenceNumber":139,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":36,"second":10,"nano":75346000}}}],"top":[{"sequenceNumber":1,"crawlTime":{"date":{"year":2021,"month":1,"day":29},"time":{"hour":14,"minute":35,"second":47,"nano":893685000}}}]} diff --git a/marginalia_nu/build.gradle b/other/memex/build.gradle similarity index 94% rename from marginalia_nu/build.gradle rename to other/memex/build.gradle index 638c1e30..65460946 100644 --- a/marginalia_nu/build.gradle +++ b/other/memex/build.gradle @@ -59,10 +59,16 @@ jmhJar { zip64 true } dependencies { - implementation project(':third_party') + implementation project(':third-party') implementation project(':protocol') + implementation project(':common:service') + implementation project(':common:config') + implementation project(':libraries:misc') + implementation project(':common:service-discovery') + implementation project(':common:service-client') implementation 'org.projectlombok:lombok:1.18.24' + implementation 'org.jetbrains:annotations:20.1.0' annotationProcessor 'org.projectlombok:lombok:1.18.24' implementation 'com.github.jknack:handlebars:4.3.1' @@ -85,12 +91,10 @@ dependencies { implementation 'com.github.jnr:jnr-ffi:2.2.12' implementation 'org.apache.httpcomponents:httpcore:4.4.15' implementation 'org.apache.httpcomponents:httpclient:4.5.13' - implementation 'com.github.ThatJavaNerd:JRAW:1.1.0' implementation group: 'com.h2database', name: 'h2', version: '2.1.210' implementation 'org.jsoup:jsoup:1.15.3' - implementation group: 'com.github.crawler-commons', name: 'crawler-commons', version: '1.2' implementation 'org.mariadb.jdbc:mariadb-java-client:3.0.6' implementation group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3' @@ -120,15 +124,9 @@ dependencies { implementation 'org.eclipse.jgit:org.eclipse.jgit.ssh.jsch:5.12.0.202106070339-r' implementation 'com.jcraft:jsch:0.1.55' - implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.21' - implementation 'edu.stanford.nlp:stanford-corenlp:4.4.0' - implementation group: 'it.unimi.dsi', name: 'fastutil', version: '8.5.8' implementation 'org.roaringbitmap:RoaringBitmap:0.9.32' - implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.29' - implementation 'com.github.Marcono1234:gson-record-type-adapter-factory:0.2.0' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' testImplementation 'org.mockito:mockito-junit-jupiter:4.5.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' diff --git a/marginalia_nu/lombok.config b/other/memex/lombok.config similarity index 100% rename from marginalia_nu/lombok.config rename to other/memex/lombok.config diff --git a/other/memex/src/main/java/nu/marginalia/memex/MemexServiceDescriptors.java b/other/memex/src/main/java/nu/marginalia/memex/MemexServiceDescriptors.java new file mode 100644 index 00000000..25ef5662 --- /dev/null +++ b/other/memex/src/main/java/nu/marginalia/memex/MemexServiceDescriptors.java @@ -0,0 +1,15 @@ +package nu.marginalia.memex; + +import nu.marginalia.memex.auth.AuthMain; +import nu.marginalia.service.descriptor.ServiceDescriptor; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; + +import java.util.List; + +public class MemexServiceDescriptors { + public static ServiceDescriptors descriptors = new ServiceDescriptors( + List.of( + new ServiceDescriptor(ServiceId.Other_Memex, 5030), + new ServiceDescriptor (ServiceId.Other_Auth, 5003))); +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/AuthConfigurationModule.java b/other/memex/src/main/java/nu/marginalia/memex/auth/AuthConfigurationModule.java similarity index 69% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/AuthConfigurationModule.java rename to other/memex/src/main/java/nu/marginalia/memex/auth/AuthConfigurationModule.java index 3a6772ae..e0ad33f5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/AuthConfigurationModule.java +++ b/other/memex/src/main/java/nu/marginalia/memex/auth/AuthConfigurationModule.java @@ -1,12 +1,14 @@ -package nu.marginalia.wmsa.auth; +package nu.marginalia.memex.auth; import com.google.inject.AbstractModule; import com.google.inject.name.Names; +import nu.marginalia.service.descriptor.HostsFile; import java.nio.file.Path; public class AuthConfigurationModule extends AbstractModule { public void configure() { bind(Path.class).annotatedWith(Names.named("password-file")).toInstance(Path.of("/var/lib/wmsa/password.dat")); + bind(HostsFile.class).toInstance(new HostsFile()); } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/AuthMain.java b/other/memex/src/main/java/nu/marginalia/memex/auth/AuthMain.java similarity index 52% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/AuthMain.java rename to other/memex/src/main/java/nu/marginalia/memex/auth/AuthMain.java index bb408581..e997d777 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/AuthMain.java +++ b/other/memex/src/main/java/nu/marginalia/memex/auth/AuthMain.java @@ -1,14 +1,13 @@ -package nu.marginalia.wmsa.auth; +package nu.marginalia.memex.auth; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.server.Initialization; - -import java.io.IOException; +import nu.marginalia.memex.MemexServiceDescriptors; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.server.Initialization; public class AuthMain extends MainClass { @@ -17,11 +16,11 @@ public class AuthMain extends MainClass { } public static void main(String... args) { - init(ServiceDescriptor.AUTH, args); + MainClass.init(ServiceId.Other_Auth, args); Injector injector = Guice.createInjector( new AuthConfigurationModule(), - new ConfigurationModule()); + new ConfigurationModule(MemexServiceDescriptors.descriptors, ServiceId.Other_Auth)); injector.getInstance(AuthMain.class); injector.getInstance(Initialization.class).setReady(); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/AuthService.java b/other/memex/src/main/java/nu/marginalia/memex/auth/AuthService.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/AuthService.java rename to other/memex/src/main/java/nu/marginalia/memex/auth/AuthService.java index 4c93db95..6f9ef59d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/AuthService.java +++ b/other/memex/src/main/java/nu/marginalia/memex/auth/AuthService.java @@ -1,11 +1,15 @@ -package nu.marginalia.wmsa.auth; +package nu.marginalia.memex.auth; import com.google.inject.Inject; import com.google.inject.name.Named; -import nu.marginalia.wmsa.auth.model.LoginFormModel; -import nu.marginalia.wmsa.configuration.server.*; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; +import nu.marginalia.client.Context; +import nu.marginalia.memex.auth.model.LoginFormModel; +import nu.marginalia.memex.renderer.MustacheRenderer; +import nu.marginalia.memex.renderer.RendererFactory; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.RateLimiter; +import nu.marginalia.service.server.Service; import org.apache.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/client/AuthClient.java b/other/memex/src/main/java/nu/marginalia/memex/auth/client/AuthClient.java similarity index 65% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/client/AuthClient.java rename to other/memex/src/main/java/nu/marginalia/memex/auth/client/AuthClient.java index 81cc95ba..f2d68667 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/client/AuthClient.java +++ b/other/memex/src/main/java/nu/marginalia/memex/auth/client/AuthClient.java @@ -1,24 +1,27 @@ -package nu.marginalia.wmsa.auth.client; +package nu.marginalia.memex.auth.client; +import com.google.gson.GsonBuilder; import com.google.inject.Inject; import io.reactivex.rxjava3.core.Observable; -import kotlin.text.Charsets; -import nu.marginalia.wmsa.client.AbstractDynamicClient; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.server.Context; +import nu.marginalia.WmsaHome; +import nu.marginalia.client.AbstractDynamicClient; +import nu.marginalia.client.Context; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; import org.apache.http.HttpStatus; import spark.Request; import spark.Response; import spark.Spark; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; public class AuthClient extends AbstractDynamicClient { @Inject - public AuthClient() { - super(ServiceDescriptor.AUTH); + public AuthClient(ServiceDescriptors descriptors) { + super(descriptors.forId(ServiceId.Other_Auth), WmsaHome.getHostsFile(), new GsonBuilder()::create); } public Observable isLoggedIn(Context ctx) { @@ -28,7 +31,7 @@ public class AuthClient extends AbstractDynamicClient { public void redirectToLoginIfUnauthenticated(String domain, Request req, Response rsp) { if (!isLoggedIn(Context.fromRequest(req)).timeout(1, TimeUnit.SECONDS).blockingFirst()) { rsp.redirect(req.headers("X-Extern-Domain") + "/auth/login?service="+domain - +"&redirect="+ URLEncoder.encode(req.headers("X-Extern-Url"), Charsets.UTF_8)); + +"&redirect="+ URLEncoder.encode(req.headers("X-Extern-Url"), StandardCharsets.UTF_8)); Spark.halt(); } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/model/LoginFormModel.java b/other/memex/src/main/java/nu/marginalia/memex/auth/model/LoginFormModel.java similarity index 82% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/model/LoginFormModel.java rename to other/memex/src/main/java/nu/marginalia/memex/auth/model/LoginFormModel.java index 09029161..f31876e0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/auth/model/LoginFormModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/auth/model/LoginFormModel.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.auth.model; +package nu.marginalia.memex.auth.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/BadBotList.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/BadBotList.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/BadBotList.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/BadBotList.java index 6f759cff..2b879a10 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/BadBotList.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/BadBotList.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini; +package nu.marginalia.memex.gemini; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiConfigurationModule.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiConfigurationModule.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiConfigurationModule.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiConfigurationModule.java index 2e6cda6b..2d269332 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiConfigurationModule.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiConfigurationModule.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini; +package nu.marginalia.memex.gemini; import com.google.inject.AbstractModule; import com.google.inject.name.Names; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiService.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiService.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiService.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiService.java index 7650c1a2..d5c6db9c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiService.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiService.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini; +package nu.marginalia.memex.gemini; public interface GeminiService { String DEFAULT_FILENAME = "index.gmi"; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiServiceDummy.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiServiceDummy.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiServiceDummy.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiServiceDummy.java index 81586f31..33fcffb2 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiServiceDummy.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiServiceDummy.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini; +package nu.marginalia.memex.gemini; import com.google.inject.Singleton; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiServiceImpl.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiServiceImpl.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiServiceImpl.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiServiceImpl.java index 0381be48..27b956d9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/GeminiServiceImpl.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/GeminiServiceImpl.java @@ -1,15 +1,15 @@ -package nu.marginalia.gemini; +package nu.marginalia.memex.gemini; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; -import nu.marginalia.gemini.io.GeminiConnection; -import nu.marginalia.gemini.io.GeminiSSLSetUp; -import nu.marginalia.gemini.io.GeminiStatusCode; -import nu.marginalia.gemini.io.GeminiUserException; -import nu.marginalia.gemini.plugins.BareStaticPagePlugin; -import nu.marginalia.gemini.plugins.Plugin; -import nu.marginalia.gemini.plugins.SearchPlugin; +import nu.marginalia.memex.gemini.io.GeminiConnection; +import nu.marginalia.memex.gemini.io.GeminiSSLSetUp; +import nu.marginalia.memex.gemini.io.GeminiStatusCode; +import nu.marginalia.memex.gemini.io.GeminiUserException; +import nu.marginalia.memex.gemini.plugins.BareStaticPagePlugin; +import nu.marginalia.memex.gemini.plugins.Plugin; +import nu.marginalia.memex.gemini.plugins.SearchPlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/client/GeminiClient.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/client/GeminiClient.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/client/GeminiClient.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/client/GeminiClient.java index e306e88f..27d2a2a9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/client/GeminiClient.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/client/GeminiClient.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini.client; +package nu.marginalia.memex.gemini.client; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/Gemtext.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/Gemtext.java similarity index 78% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/Gemtext.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/Gemtext.java index 3b07f4cc..692b65ce 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/Gemtext.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/Gemtext.java @@ -1,11 +1,11 @@ -package nu.marginalia.gemini.gmi; +package nu.marginalia.memex.gemini.gmi; import lombok.Getter; -import nu.marginalia.gemini.gmi.line.AbstractGemtextLine; -import nu.marginalia.gemini.gmi.parser.GemtextParser; -import nu.marginalia.gemini.gmi.renderer.GemtextRenderer; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.gemini.gmi.line.AbstractGemtextLine; +import nu.marginalia.memex.gemini.gmi.parser.GemtextParser; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRenderer; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.io.IOException; import java.io.Writer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/GemtextDatabase.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/GemtextDatabase.java similarity index 87% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/GemtextDatabase.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/GemtextDatabase.java index 33d33dd9..2beb1772 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/GemtextDatabase.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/GemtextDatabase.java @@ -1,10 +1,10 @@ -package nu.marginalia.gemini.gmi; +package nu.marginalia.memex.gemini.gmi; import com.google.common.collect.Sets; -import nu.marginalia.gemini.gmi.line.GemtextLineVisitorAdapter; -import nu.marginalia.gemini.gmi.line.GemtextLink; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.model.MemexUrl; +import nu.marginalia.memex.gemini.gmi.line.GemtextLineVisitorAdapter; +import nu.marginalia.memex.gemini.gmi.line.GemtextLink; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexUrl; import java.io.IOException; import java.nio.file.Files; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/GemtextDocument.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/GemtextDocument.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/GemtextDocument.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/GemtextDocument.java index 1eeed2c8..8e347c6f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/GemtextDocument.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/GemtextDocument.java @@ -1,13 +1,13 @@ -package nu.marginalia.gemini.gmi; +package nu.marginalia.memex.gemini.gmi; import lombok.Getter; -import nu.marginalia.gemini.gmi.line.*; -import nu.marginalia.gemini.gmi.renderer.GemtextRenderer; -import nu.marginalia.gemini.gmi.renderer.GemtextRendererFactory; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeTaskId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.model.MemexTaskState; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRenderer; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRendererFactory; +import nu.marginalia.memex.gemini.gmi.line.*; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeTaskId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexTaskState; import org.apache.commons.lang3.tuple.Pair; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/AbstractGemtextLine.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/AbstractGemtextLine.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/AbstractGemtextLine.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/AbstractGemtextLine.java index f1307b9b..21e0bf4d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/AbstractGemtextLine.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/AbstractGemtextLine.java @@ -1,6 +1,6 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import java.util.Optional; import java.util.function.Function; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextAside.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextAside.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextAside.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextAside.java index ef73accc..9c0b5632 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextAside.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextAside.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; @AllArgsConstructor @Getter @ToString public class GemtextAside extends AbstractGemtextLine { diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextHeading.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextHeading.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextHeading.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextHeading.java index a2c9f309..d969bc95 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextHeading.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextHeading.java @@ -1,10 +1,10 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import java.util.Optional; import java.util.function.Function; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextLineVisitor.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextLineVisitor.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextLineVisitor.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextLineVisitor.java index 219267ca..ef3cb97a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextLineVisitor.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextLineVisitor.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; public interface GemtextLineVisitor { default T take(AbstractGemtextLine line) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextLineVisitorAdapter.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextLineVisitorAdapter.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextLineVisitorAdapter.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextLineVisitorAdapter.java index cb0a7544..8e00aae3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextLineVisitorAdapter.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextLineVisitorAdapter.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; public class GemtextLineVisitorAdapter implements GemtextLineVisitor { @Override diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextLink.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextLink.java similarity index 82% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextLink.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextLink.java index 27aa1a5c..f4359946 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextLink.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextLink.java @@ -1,10 +1,10 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexUrl; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexUrl; import javax.annotation.Nullable; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextList.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextList.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextList.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextList.java index c06c1e6a..903b4f63 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextList.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextList.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextPragma.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextPragma.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextPragma.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextPragma.java index 082cef26..1baf6658 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextPragma.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextPragma.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; @AllArgsConstructor @Getter @ToString public class GemtextPragma extends AbstractGemtextLine { diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextPreformat.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextPreformat.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextPreformat.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextPreformat.java index 56a1f196..a83e70bc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextPreformat.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextPreformat.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextQuote.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextQuote.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextQuote.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextQuote.java index ad9f2e9b..5eb318a2 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextQuote.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextQuote.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextTask.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextTask.java similarity index 76% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextTask.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextTask.java index d2360afc..4d371785 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextTask.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextTask.java @@ -1,12 +1,12 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeTaskId; -import nu.marginalia.wmsa.memex.model.MemexTaskState; -import nu.marginalia.wmsa.memex.model.MemexTaskTags; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeTaskId; +import nu.marginalia.memex.memex.model.MemexTaskState; +import nu.marginalia.memex.memex.model.MemexTaskTags; import java.util.Optional; import java.util.function.Function; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextText.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextText.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextText.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextText.java index 15394533..80a84e7e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextText.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextText.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; @AllArgsConstructor @Getter @ToString public class GemtextText extends AbstractGemtextLine { diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextTextLiteral.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextTextLiteral.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextTextLiteral.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextTextLiteral.java index 7e44702f..d2a6698e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/line/GemtextTextLiteral.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/line/GemtextTextLiteral.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.gmi.line; +package nu.marginalia.memex.gemini.gmi.line; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextAsideParser.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextAsideParser.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextAsideParser.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextAsideParser.java index 541ada0c..3038111d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextAsideParser.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextAsideParser.java @@ -1,7 +1,7 @@ -package nu.marginalia.gemini.gmi.parser; +package nu.marginalia.memex.gemini.gmi.parser; -import nu.marginalia.gemini.gmi.line.GemtextAside; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.gemini.gmi.line.GemtextAside; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import java.util.regex.Pattern; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextHeadingParser.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextHeadingParser.java similarity index 66% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextHeadingParser.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextHeadingParser.java index c91d2a45..9a0c5329 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextHeadingParser.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextHeadingParser.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.gmi.parser; +package nu.marginalia.memex.gemini.gmi.parser; -import nu.marginalia.gemini.gmi.line.AbstractGemtextLine; -import nu.marginalia.gemini.gmi.line.GemtextHeading; -import nu.marginalia.gemini.gmi.line.GemtextText; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.gemini.gmi.line.AbstractGemtextLine; +import nu.marginalia.memex.gemini.gmi.line.GemtextHeading; +import nu.marginalia.memex.gemini.gmi.line.GemtextText; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import java.util.regex.Pattern; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextLinkParser.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextLinkParser.java similarity index 67% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextLinkParser.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextLinkParser.java index 16ca2ec6..04b5353a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextLinkParser.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextLinkParser.java @@ -1,12 +1,12 @@ -package nu.marginalia.gemini.gmi.parser; +package nu.marginalia.memex.gemini.gmi.parser; -import nu.marginalia.gemini.gmi.line.AbstractGemtextLine; -import nu.marginalia.gemini.gmi.line.GemtextLink; -import nu.marginalia.gemini.gmi.line.GemtextText; -import nu.marginalia.wmsa.memex.model.MemexExternalUrl; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.model.MemexUrl; +import nu.marginalia.memex.gemini.gmi.line.AbstractGemtextLine; +import nu.marginalia.memex.gemini.gmi.line.GemtextLink; +import nu.marginalia.memex.gemini.gmi.line.GemtextText; +import nu.marginalia.memex.memex.model.MemexExternalUrl; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexUrl; import javax.annotation.Nullable; import java.util.regex.Pattern; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextListParser.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextListParser.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextListParser.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextListParser.java index 8416895e..8a93de29 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextListParser.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextListParser.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini.gmi.parser; +package nu.marginalia.memex.gemini.gmi.parser; import java.util.regex.Pattern; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextParser.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextParser.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextParser.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextParser.java index ec15be17..d1b63a69 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextParser.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextParser.java @@ -1,8 +1,8 @@ -package nu.marginalia.gemini.gmi.parser; +package nu.marginalia.memex.gemini.gmi.parser; -import nu.marginalia.gemini.gmi.line.*; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeTaskId; +import nu.marginalia.memex.gemini.gmi.line.*; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeTaskId; import java.util.*; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextPragmaParser.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextPragmaParser.java similarity index 62% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextPragmaParser.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextPragmaParser.java index 192c4ba6..f96581cc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextPragmaParser.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextPragmaParser.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.gmi.parser; +package nu.marginalia.memex.gemini.gmi.parser; -import nu.marginalia.gemini.gmi.line.AbstractGemtextLine; -import nu.marginalia.gemini.gmi.line.GemtextPragma; -import nu.marginalia.gemini.gmi.line.GemtextText; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.gemini.gmi.line.AbstractGemtextLine; +import nu.marginalia.memex.gemini.gmi.line.GemtextPragma; +import nu.marginalia.memex.gemini.gmi.line.GemtextText; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import java.util.regex.Pattern; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextQuoteParser.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextQuoteParser.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextQuoteParser.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextQuoteParser.java index af72b3c9..b4468ea3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextQuoteParser.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextQuoteParser.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini.gmi.parser; +package nu.marginalia.memex.gemini.gmi.parser; import java.util.regex.Pattern; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextTaskParser.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextTaskParser.java similarity index 62% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextTaskParser.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextTaskParser.java index d9b95f2e..08b1b803 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/parser/GemtextTaskParser.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/parser/GemtextTaskParser.java @@ -1,11 +1,11 @@ -package nu.marginalia.gemini.gmi.parser; +package nu.marginalia.memex.gemini.gmi.parser; -import nu.marginalia.gemini.gmi.line.AbstractGemtextLine; -import nu.marginalia.gemini.gmi.line.GemtextTask; -import nu.marginalia.gemini.gmi.line.GemtextText; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeTaskId; -import nu.marginalia.wmsa.memex.model.MemexTaskTags; +import nu.marginalia.memex.gemini.gmi.line.AbstractGemtextLine; +import nu.marginalia.memex.gemini.gmi.line.GemtextTask; +import nu.marginalia.memex.gemini.gmi.line.GemtextText; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeTaskId; +import nu.marginalia.memex.memex.model.MemexTaskTags; import java.util.regex.Pattern; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/renderer/GemtextRenderer.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/renderer/GemtextRenderer.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/renderer/GemtextRenderer.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/renderer/GemtextRenderer.java index 1697c8df..ccfa1fd7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/renderer/GemtextRenderer.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/renderer/GemtextRenderer.java @@ -1,6 +1,6 @@ -package nu.marginalia.gemini.gmi.renderer; +package nu.marginalia.memex.gemini.gmi.renderer; -import nu.marginalia.gemini.gmi.line.*; +import nu.marginalia.memex.gemini.gmi.line.*; import java.util.function.Function; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/renderer/GemtextRendererFactory.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/renderer/GemtextRendererFactory.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/renderer/GemtextRendererFactory.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/renderer/GemtextRendererFactory.java index 257cfc1c..0e75d047 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/gmi/renderer/GemtextRendererFactory.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/gmi/renderer/GemtextRendererFactory.java @@ -1,8 +1,8 @@ -package nu.marginalia.gemini.gmi.renderer; +package nu.marginalia.memex.gemini.gmi.renderer; -import nu.marginalia.gemini.gmi.line.*; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.model.MemexUrl; +import nu.marginalia.memex.gemini.gmi.line.*; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexUrl; import org.apache.logging.log4j.util.Strings; import java.util.Objects; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiConnection.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiConnection.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiConnection.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiConnection.java index 6d032a2e..3278ec2f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiConnection.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiConnection.java @@ -1,7 +1,7 @@ -package nu.marginalia.gemini.io; +package nu.marginalia.memex.gemini.io; -import nu.marginalia.gemini.BadBotList; -import nu.marginalia.gemini.plugins.FileType; +import nu.marginalia.memex.gemini.BadBotList; +import nu.marginalia.memex.gemini.plugins.FileType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiSSLSetUp.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiSSLSetUp.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiSSLSetUp.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiSSLSetUp.java index 525515f3..cd8afa3e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiSSLSetUp.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiSSLSetUp.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini.io; +package nu.marginalia.memex.gemini.io; import com.google.inject.Inject; import com.google.inject.name.Named; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiStatusCode.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiStatusCode.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiStatusCode.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiStatusCode.java index f201e331..b3b205eb 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiStatusCode.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiStatusCode.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini.io; +package nu.marginalia.memex.gemini.io; public class GeminiStatusCode { public static final int INPUT = 10; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiUserException.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiUserException.java similarity index 82% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiUserException.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiUserException.java index 937da4fe..12022d56 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/io/GeminiUserException.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/io/GeminiUserException.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini.io; +package nu.marginalia.memex.gemini.io; /** Throw to report message to user */ public class GeminiUserException extends RuntimeException { diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/BareStaticPagePlugin.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/BareStaticPagePlugin.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/BareStaticPagePlugin.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/BareStaticPagePlugin.java index 46bdfb7d..d3a210a3 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/BareStaticPagePlugin.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/BareStaticPagePlugin.java @@ -1,9 +1,9 @@ -package nu.marginalia.gemini.plugins; +package nu.marginalia.memex.gemini.plugins; import com.google.inject.Inject; import com.google.inject.name.Named; -import nu.marginalia.gemini.GeminiService; -import nu.marginalia.gemini.io.GeminiConnection; +import nu.marginalia.memex.gemini.GeminiService; +import nu.marginalia.memex.gemini.io.GeminiConnection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/FileType.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/FileType.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/FileType.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/FileType.java index 587a9894..f8472f9d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/FileType.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/FileType.java @@ -1,4 +1,4 @@ -package nu.marginalia.gemini.plugins; +package nu.marginalia.memex.gemini.plugins; import java.nio.file.Path; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/Plugin.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/Plugin.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/Plugin.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/Plugin.java index 3765e1ca..b0ae7f0f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/Plugin.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/Plugin.java @@ -1,7 +1,7 @@ -package nu.marginalia.gemini.plugins; +package nu.marginalia.memex.gemini.plugins; -import nu.marginalia.gemini.io.GeminiConnection; -import nu.marginalia.gemini.io.GeminiUserException; +import nu.marginalia.memex.gemini.io.GeminiConnection; +import nu.marginalia.memex.gemini.io.GeminiUserException; import java.io.IOException; import java.net.URI; diff --git a/marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/SearchPlugin.java b/other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/SearchPlugin.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/SearchPlugin.java rename to other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/SearchPlugin.java index e0122d75..95f52bd5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/gemini/plugins/SearchPlugin.java +++ b/other/memex/src/main/java/nu/marginalia/memex/gemini/plugins/SearchPlugin.java @@ -1,8 +1,8 @@ -package nu.marginalia.gemini.plugins; +package nu.marginalia.memex.gemini.plugins; import com.google.inject.Inject; -import nu.marginalia.gemini.io.GeminiConnection; -import nu.marginalia.gemini.io.GeminiStatusCode; +import nu.marginalia.memex.gemini.io.GeminiConnection; +import nu.marginalia.memex.gemini.io.GeminiStatusCode; import org.apache.http.HttpHost; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.routing.HttpRoute; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/Memex.java b/other/memex/src/main/java/nu/marginalia/memex/memex/Memex.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/Memex.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/Memex.java index febdc5af..5376f86c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/Memex.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/Memex.java @@ -1,22 +1,22 @@ -package nu.marginalia.wmsa.memex; +package nu.marginalia.memex.memex; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; import io.reactivex.rxjava3.schedulers.Schedulers; -import nu.marginalia.gemini.GeminiService; -import nu.marginalia.gemini.gmi.GemtextDatabase; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.util.graphics.dithering.FloydSteinbergDither; -import nu.marginalia.util.graphics.dithering.Palettes; -import nu.marginalia.wmsa.memex.change.GemtextTombstoneUpdateCaclulator; -import nu.marginalia.wmsa.memex.model.MemexImage; -import nu.marginalia.wmsa.memex.model.MemexNode; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.renderer.MemexRendererers; -import nu.marginalia.wmsa.memex.system.MemexFileSystemMonitor; -import nu.marginalia.wmsa.memex.system.MemexFileWriter; -import nu.marginalia.wmsa.memex.system.git.MemexGitRepo; +import nu.marginalia.memex.gemini.GeminiService; +import nu.marginalia.memex.gemini.gmi.GemtextDatabase; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.util.dithering.FloydSteinbergDither; +import nu.marginalia.memex.util.dithering.Palettes; +import nu.marginalia.memex.memex.change.GemtextTombstoneUpdateCaclulator; +import nu.marginalia.memex.memex.model.MemexImage; +import nu.marginalia.memex.memex.model.MemexNode; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.renderer.MemexRendererers; +import nu.marginalia.memex.memex.system.MemexFileSystemMonitor; +import nu.marginalia.memex.memex.system.MemexFileWriter; +import nu.marginalia.memex.memex.system.git.MemexGitRepo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexConfigurationModule.java b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexConfigurationModule.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexConfigurationModule.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/MemexConfigurationModule.java index 2533a9d1..f0cb1bac 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexConfigurationModule.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexConfigurationModule.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex; +package nu.marginalia.memex.memex; import com.google.inject.AbstractModule; import com.google.inject.Inject; @@ -6,13 +6,13 @@ import com.google.inject.Provider; import com.google.inject.name.Named; import com.google.inject.name.Names; import lombok.SneakyThrows; -import nu.marginalia.gemini.GeminiService; -import nu.marginalia.gemini.GeminiServiceDummy; -import nu.marginalia.gemini.GeminiServiceImpl; -import nu.marginalia.wmsa.memex.system.MemexFileWriter; -import nu.marginalia.wmsa.memex.system.git.MemexGitRepo; -import nu.marginalia.wmsa.memex.system.git.MemexGitRepoDummy; -import nu.marginalia.wmsa.memex.system.git.MemexGitRepoImpl; +import nu.marginalia.memex.gemini.GeminiService; +import nu.marginalia.memex.gemini.GeminiServiceDummy; +import nu.marginalia.memex.gemini.GeminiServiceImpl; +import nu.marginalia.memex.memex.system.MemexFileWriter; +import nu.marginalia.memex.memex.system.git.MemexGitRepo; +import nu.marginalia.memex.memex.system.git.MemexGitRepoDummy; +import nu.marginalia.memex.memex.system.git.MemexGitRepoImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexData.java b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexData.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexData.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/MemexData.java index 22c20f8f..42bb42ba 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexData.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexData.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.memex; +package nu.marginalia.memex.memex; import com.google.inject.Singleton; -import nu.marginalia.gemini.gmi.GemtextDatabase; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.wmsa.memex.model.MemexLink; -import nu.marginalia.wmsa.memex.model.MemexImage; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.model.fs.MemexFileSystem; +import nu.marginalia.memex.gemini.gmi.GemtextDatabase; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.memex.model.MemexImage; +import nu.marginalia.memex.memex.model.MemexLink; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.fs.MemexFileSystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexLinks.java b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexLinks.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexLinks.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/MemexLinks.java index 8d491494..68168baa 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexLinks.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexLinks.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.memex; +package nu.marginalia.memex.memex; -import nu.marginalia.wmsa.memex.model.MemexLink; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexLink; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.util.*; import java.util.stream.Collectors; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexLoader.java b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexLoader.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexLoader.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/MemexLoader.java index f5f6b29b..8945cb77 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexLoader.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexLoader.java @@ -1,15 +1,15 @@ -package nu.marginalia.wmsa.memex; +package nu.marginalia.memex.memex; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.name.Named; -import nu.marginalia.gemini.gmi.GemtextDatabase; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.wmsa.memex.model.MemexImage; -import nu.marginalia.wmsa.memex.model.MemexNode; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.system.MemexFileSystemModifiedTimes; -import nu.marginalia.wmsa.memex.system.MemexSourceFileSystem; +import nu.marginalia.memex.gemini.gmi.GemtextDatabase; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.memex.model.MemexImage; +import nu.marginalia.memex.memex.model.MemexNode; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.system.MemexFileSystemModifiedTimes; +import nu.marginalia.memex.memex.system.MemexSourceFileSystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexMain.java b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexMain.java similarity index 54% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexMain.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/MemexMain.java index f46ce4d1..c5601380 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexMain.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexMain.java @@ -1,13 +1,14 @@ -package nu.marginalia.wmsa.memex; +package nu.marginalia.memex.memex; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; -import nu.marginalia.gemini.GeminiConfigurationModule; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.server.Initialization; +import nu.marginalia.memex.MemexServiceDescriptors; +import nu.marginalia.memex.gemini.GeminiConfigurationModule; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.server.Initialization; public class MemexMain extends MainClass { private final MemexService service; @@ -18,12 +19,12 @@ public class MemexMain extends MainClass { } public static void main(String... args) { - init(ServiceDescriptor.MEMEX, args); + MainClass.init(ServiceId.Other_Memex, args); Injector injector = Guice.createInjector( new MemexConfigurationModule(), new GeminiConfigurationModule(), - new ConfigurationModule()); + new ConfigurationModule(MemexServiceDescriptors.descriptors, ServiceId.Other_Memex)); injector.getInstance(MemexMain.class); injector.getInstance(Initialization.class).setReady(); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexService.java b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexService.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexService.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/MemexService.java index 96c553da..5e799298 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/MemexService.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/MemexService.java @@ -1,21 +1,21 @@ -package nu.marginalia.wmsa.memex; +package nu.marginalia.memex.memex; import com.google.inject.Inject; import com.google.inject.name.Named; import lombok.SneakyThrows; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.gemini.gmi.renderer.GemtextRendererFactory; -import nu.marginalia.wmsa.auth.client.AuthClient; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; -import nu.marginalia.wmsa.memex.change.GemtextMutation; -import nu.marginalia.wmsa.memex.change.update.GemtextDocumentUpdateCalculator; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.model.render.*; -import nu.marginalia.wmsa.memex.renderer.MemexHtmlRenderer; +import nu.marginalia.client.Context; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRendererFactory; +import nu.marginalia.memex.auth.client.AuthClient; +import nu.marginalia.memex.memex.model.render.*; +import nu.marginalia.memex.memex.change.GemtextMutation; +import nu.marginalia.memex.memex.change.update.GemtextDocumentUpdateCalculator; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.renderer.MemexHtmlRenderer; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.Service; import org.apache.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextAppend.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextAppend.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextAppend.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextAppend.java index be9c34dd..8f4e0b6e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextAppend.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextAppend.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; import lombok.AllArgsConstructor; import lombok.ToString; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.gemini.gmi.renderer.GemtextRendererFactory; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRendererFactory; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextCreate.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextCreate.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextCreate.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextCreate.java index 764d54ce..f44c3875 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextCreate.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextCreate.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; import lombok.AllArgsConstructor; import lombok.ToString; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextCreateOrMutate.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextCreateOrMutate.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextCreateOrMutate.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextCreateOrMutate.java index f115849f..7f60a759 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextCreateOrMutate.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextCreateOrMutate.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; import lombok.AllArgsConstructor; import lombok.ToString; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextMutation.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextMutation.java similarity index 74% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextMutation.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextMutation.java index c9d73526..7612b1a0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextMutation.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextMutation.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.io.IOException; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextPrepend.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextPrepend.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextPrepend.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextPrepend.java index d9415e4b..84f70ca8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextPrepend.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextPrepend.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; import lombok.AllArgsConstructor; import lombok.ToString; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.gemini.gmi.renderer.GemtextRendererFactory; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRendererFactory; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextReplace.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextReplace.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextReplace.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextReplace.java index 3f5d7890..6e437fa2 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextReplace.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextReplace.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; import lombok.AllArgsConstructor; import lombok.ToString; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.gemini.gmi.renderer.GemtextRendererFactory; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRendererFactory; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextTombstoneUpdateCaclulator.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextTombstoneUpdateCaclulator.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextTombstoneUpdateCaclulator.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextTombstoneUpdateCaclulator.java index 711e1f55..83e03b31 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/GemtextTombstoneUpdateCaclulator.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/GemtextTombstoneUpdateCaclulator.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import com.google.inject.name.Named; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/update/GemtextDocumentUpdateCalculator.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/update/GemtextDocumentUpdateCalculator.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/update/GemtextDocumentUpdateCalculator.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/update/GemtextDocumentUpdateCalculator.java index 51142ed0..237ad448 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/update/GemtextDocumentUpdateCalculator.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/update/GemtextDocumentUpdateCalculator.java @@ -1,17 +1,17 @@ -package nu.marginalia.wmsa.memex.change.update; +package nu.marginalia.memex.memex.change.update; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.gemini.gmi.line.GemtextText; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.gemini.gmi.line.AbstractGemtextLine; -import nu.marginalia.gemini.gmi.line.GemtextHeading; -import nu.marginalia.gemini.gmi.renderer.GemtextRenderer; -import nu.marginalia.gemini.gmi.renderer.GemtextRendererFactory; -import nu.marginalia.wmsa.memex.change.*; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.gemini.gmi.line.GemtextText; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.gmi.line.AbstractGemtextLine; +import nu.marginalia.memex.gemini.gmi.line.GemtextHeading; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRenderer; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRendererFactory; +import nu.marginalia.memex.memex.change.*; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/update/GemtextTaskExtractor.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/update/GemtextTaskExtractor.java similarity index 81% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/update/GemtextTaskExtractor.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/update/GemtextTaskExtractor.java index 4bdc1ce4..f85902a0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/update/GemtextTaskExtractor.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/update/GemtextTaskExtractor.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.memex.change.update; +package nu.marginalia.memex.memex.change.update; -import nu.marginalia.gemini.gmi.line.AbstractGemtextLine; -import nu.marginalia.gemini.gmi.line.GemtextTask; +import nu.marginalia.memex.gemini.gmi.line.AbstractGemtextLine; +import nu.marginalia.memex.gemini.gmi.line.GemtextTask; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/update/GemtextTasksRewrite.java b/other/memex/src/main/java/nu/marginalia/memex/memex/change/update/GemtextTasksRewrite.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/update/GemtextTasksRewrite.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/change/update/GemtextTasksRewrite.java index c2266f86..0db13d26 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/change/update/GemtextTasksRewrite.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/change/update/GemtextTasksRewrite.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.memex.change.update; +package nu.marginalia.memex.memex.change.update; import lombok.Getter; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.gemini.gmi.line.AbstractGemtextLine; -import nu.marginalia.gemini.gmi.line.GemtextTask; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.gmi.line.AbstractGemtextLine; +import nu.marginalia.memex.gemini.gmi.line.GemtextTask; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; import org.jetbrains.annotations.NotNull; import java.time.LocalDate; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/GemtextSection.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/GemtextSection.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/GemtextSection.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/GemtextSection.java index 5fc3cc73..c2237841 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/GemtextSection.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/GemtextSection.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/GemtextSectionAction.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/GemtextSectionAction.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/GemtextSectionAction.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/GemtextSectionAction.java index 16cb8158..a55e4546 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/GemtextSectionAction.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/GemtextSectionAction.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; public enum GemtextSectionAction { REPLACE, diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexExternalUrl.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexExternalUrl.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexExternalUrl.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexExternalUrl.java index 38775753..28784a10 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexExternalUrl.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexExternalUrl.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexImage.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexImage.java similarity index 83% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexImage.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexImage.java index e0184d03..d27a5e65 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexImage.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexImage.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexIndexTask.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexIndexTask.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexIndexTask.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexIndexTask.java index 38c79410..893b5854 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexIndexTask.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexIndexTask.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexLink.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexLink.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexLink.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexLink.java index c44eaa26..c03822ac 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexLink.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexLink.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNode.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNode.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNode.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNode.java index ddd01f82..0730ebfe 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNode.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNode.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeHeadingId.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeHeadingId.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeHeadingId.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeHeadingId.java index 084e22fe..d5db6ffa 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeHeadingId.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeHeadingId.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.EqualsAndHashCode; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeTaskId.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeTaskId.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeTaskId.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeTaskId.java index e40dccbf..4fd45946 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeTaskId.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeTaskId.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.EqualsAndHashCode; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeType.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeType.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeType.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeType.java index 6c645921..33bdbb5f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeType.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeType.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.AllArgsConstructor; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeUrl.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeUrl.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeUrl.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeUrl.java index 96d91bd0..c919b511 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexNodeUrl.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexNodeUrl.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.EqualsAndHashCode; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexTaskState.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexTaskState.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexTaskState.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexTaskState.java index 085ac7aa..128bb918 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexTaskState.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexTaskState.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; public enum MemexTaskState { DONE('/', true,"done"), diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexTaskTags.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexTaskTags.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexTaskTags.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexTaskTags.java index e19819bc..b3906241 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexTaskTags.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexTaskTags.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import lombok.Getter; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexUrl.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexUrl.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexUrl.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexUrl.java index 14cff995..9597a997 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/MemexUrl.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/MemexUrl.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import java.util.Optional; import java.util.function.Consumer; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/fs/MemexDirectory.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/fs/MemexDirectory.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/fs/MemexDirectory.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/fs/MemexDirectory.java index deacc639..a198faf8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/fs/MemexDirectory.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/fs/MemexDirectory.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.model.fs; +package nu.marginalia.memex.memex.model.fs; import lombok.Getter; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.wmsa.memex.model.MemexImage; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.memex.model.MemexImage; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.util.HashMap; import java.util.Map; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/fs/MemexFileSystem.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/fs/MemexFileSystem.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/fs/MemexFileSystem.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/fs/MemexFileSystem.java index ffd31508..b5452f29 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/fs/MemexFileSystem.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/fs/MemexFileSystem.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.memex.model.fs; +package nu.marginalia.memex.memex.model.fs; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.wmsa.memex.model.MemexImage; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.memex.model.MemexImage; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.util.*; import java.util.concurrent.ConcurrentHashMap; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRenderCreateFormModel.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRenderCreateFormModel.java similarity index 76% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRenderCreateFormModel.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRenderCreateFormModel.java index eb59bcc1..c317ac82 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRenderCreateFormModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRenderCreateFormModel.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.memex.model.render; +package nu.marginalia.memex.memex.model.render; import lombok.Getter; import lombok.RequiredArgsConstructor; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.wmsa.memex.renderer.MemexHtmlRenderer; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.memex.renderer.MemexHtmlRenderer; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.util.Comparator; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRenderUpdateFormModel.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRenderUpdateFormModel.java similarity index 71% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRenderUpdateFormModel.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRenderUpdateFormModel.java index 9e32ed95..12ffefff 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRenderUpdateFormModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRenderUpdateFormModel.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.model.render; +package nu.marginalia.memex.memex.model.render; import lombok.AllArgsConstructor; import lombok.Getter; -import nu.marginalia.wmsa.memex.renderer.MemexHtmlRenderer; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.renderer.MemexHtmlRenderer; +import nu.marginalia.memex.memex.model.MemexNodeUrl; @AllArgsConstructor @Getter public class MemexRenderUpdateFormModel implements MemexRendererableDirect { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRenderUploadFormModel.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRenderUploadFormModel.java similarity index 76% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRenderUploadFormModel.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRenderUploadFormModel.java index e098af52..99cd3d36 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRenderUploadFormModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRenderUploadFormModel.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.memex.model.render; +package nu.marginalia.memex.memex.model.render; import lombok.Getter; import lombok.RequiredArgsConstructor; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.wmsa.memex.renderer.MemexHtmlRenderer; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.memex.renderer.MemexHtmlRenderer; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.util.Comparator; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererDeleteFormModel.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererDeleteFormModel.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererDeleteFormModel.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererDeleteFormModel.java index 84760fc2..c0b71441 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererDeleteFormModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererDeleteFormModel.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.model.render; +package nu.marginalia.memex.memex.model.render; import lombok.AllArgsConstructor; import lombok.Getter; -import nu.marginalia.wmsa.memex.renderer.MemexHtmlRenderer; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.renderer.MemexHtmlRenderer; +import nu.marginalia.memex.memex.model.MemexNodeUrl; @AllArgsConstructor @Getter public class MemexRendererDeleteFormModel implements MemexRendererableDirect { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererImageModel.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererImageModel.java similarity index 77% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererImageModel.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererImageModel.java index d5534117..79880110 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererImageModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererImageModel.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.memex.model.render; +package nu.marginalia.memex.memex.model.render; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.SneakyThrows; -import nu.marginalia.wmsa.memex.model.MemexLink; -import nu.marginalia.wmsa.memex.model.MemexImage; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexLink; +import nu.marginalia.memex.memex.model.MemexImage; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.nio.file.Files; import java.util.Base64; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererIndexModel.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererIndexModel.java similarity index 82% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererIndexModel.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererIndexModel.java index 2fed0510..13630727 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererIndexModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererIndexModel.java @@ -1,10 +1,13 @@ -package nu.marginalia.wmsa.memex.model.render; +package nu.marginalia.memex.memex.model.render; import lombok.Getter; import lombok.RequiredArgsConstructor; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.gemini.gmi.renderer.GemtextRendererFactory; -import nu.marginalia.wmsa.memex.model.*; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRendererFactory; +import nu.marginalia.memex.memex.model.MemexImage; +import nu.marginalia.memex.memex.model.MemexIndexTask; +import nu.marginalia.memex.memex.model.MemexLink; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.util.Comparator; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererRenameFormModel.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererRenameFormModel.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererRenameFormModel.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererRenameFormModel.java index 6dcb62c4..464bca88 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererRenameFormModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererRenameFormModel.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.model.render; +package nu.marginalia.memex.memex.model.render; import lombok.AllArgsConstructor; import lombok.Getter; -import nu.marginalia.wmsa.memex.renderer.MemexHtmlRenderer; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.renderer.MemexHtmlRenderer; +import nu.marginalia.memex.memex.model.MemexNodeUrl; @AllArgsConstructor @Getter public class MemexRendererRenameFormModel implements MemexRendererableDirect { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererTombstoneModel.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererTombstoneModel.java similarity index 66% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererTombstoneModel.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererTombstoneModel.java index 60907952..aebcb9b1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererTombstoneModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererTombstoneModel.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.model.render; +package nu.marginalia.memex.memex.model.render; import lombok.AllArgsConstructor; import lombok.Getter; -import nu.marginalia.wmsa.memex.model.MemexLink; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexLink; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererViewModel.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererViewModel.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererViewModel.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererViewModel.java index f77e4bfd..8aac05e8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/model/render/MemexRendererViewModel.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererViewModel.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.model.render; +package nu.marginalia.memex.memex.model.render; import lombok.AllArgsConstructor; import lombok.Getter; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.wmsa.memex.model.MemexLink; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.memex.model.MemexLink; import java.util.List; diff --git a/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererableDirect.java b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererableDirect.java new file mode 100644 index 00000000..b0631dfa --- /dev/null +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/model/render/MemexRendererableDirect.java @@ -0,0 +1,7 @@ +package nu.marginalia.memex.memex.model.render; + +import nu.marginalia.memex.memex.renderer.MemexHtmlRenderer; + +public interface MemexRendererableDirect { + String render(MemexHtmlRenderer renderer); +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/renderer/MemexGmiRenderer.java b/other/memex/src/main/java/nu/marginalia/memex/memex/renderer/MemexGmiRenderer.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/renderer/MemexGmiRenderer.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/renderer/MemexGmiRenderer.java index e613d746..c74b498b 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/renderer/MemexGmiRenderer.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/renderer/MemexGmiRenderer.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.memex.renderer; +package nu.marginalia.memex.memex.renderer; import com.google.inject.Inject; import com.google.inject.name.Named; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.gemini.gmi.renderer.GemtextRendererFactory; -import nu.marginalia.wmsa.memex.MemexData; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.system.MemexFileWriter; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRendererFactory; +import nu.marginalia.memex.memex.MemexData; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.system.MemexFileWriter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/renderer/MemexHtmlRenderer.java b/other/memex/src/main/java/nu/marginalia/memex/memex/renderer/MemexHtmlRenderer.java similarity index 82% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/renderer/MemexHtmlRenderer.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/renderer/MemexHtmlRenderer.java index a39e5763..6cfaada1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/renderer/MemexHtmlRenderer.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/renderer/MemexHtmlRenderer.java @@ -1,15 +1,17 @@ -package nu.marginalia.wmsa.memex.renderer; +package nu.marginalia.memex.memex.renderer; import com.google.inject.Inject; import com.google.inject.name.Named; import lombok.SneakyThrows; -import nu.marginalia.gemini.gmi.renderer.GemtextRendererFactory; -import nu.marginalia.wmsa.memex.MemexData; -import nu.marginalia.wmsa.memex.model.*; -import nu.marginalia.wmsa.memex.model.render.*; -import nu.marginalia.wmsa.memex.system.MemexFileWriter; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; +import nu.marginalia.memex.gemini.gmi.renderer.GemtextRendererFactory; +import nu.marginalia.memex.memex.MemexData; +import nu.marginalia.memex.memex.model.MemexIndexTask; +import nu.marginalia.memex.memex.model.MemexLink; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.render.*; +import nu.marginalia.memex.memex.system.MemexFileWriter; +import nu.marginalia.memex.renderer.MustacheRenderer; +import nu.marginalia.memex.renderer.RendererFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,18 +57,18 @@ public class MemexHtmlRenderer { final var rendererFactory = new RendererFactory(); - viewRenderer = rendererFactory.renderer("memex/memex-view"); - indexRenderer = rendererFactory.renderer("memex/memex-index"); - indexFeedRenderer = rendererFactory.renderer("memex/memex-index-feed"); - imageRenderer = rendererFactory.renderer("memex/memex-image"); + viewRenderer = rendererFactory.renderer("static/memex/memex-view"); + indexRenderer = rendererFactory.renderer("static/memex/memex-index"); + indexFeedRenderer = rendererFactory.renderer("static/memex/memex-index-feed"); + imageRenderer = rendererFactory.renderer("static/memex/memex-image"); - tombstoneRenderer = rendererFactory.renderer("memex/memex-tombstone"); + tombstoneRenderer = rendererFactory.renderer("static/memex/memex-tombstone"); - updateFormRenderer = rendererFactory.renderer("memex/memex-update-form"); - uploadFormRenderer = rendererFactory.renderer("memex/memex-upload-form"); - deleteFormRenderer = rendererFactory.renderer("memex/memex-delete-form"); - renameFormRenderer = rendererFactory.renderer("memex/memex-rename-form"); - createFormRenderer = rendererFactory.renderer("memex/memex-create-form"); + updateFormRenderer = rendererFactory.renderer("static/memex/memex-update-form"); + uploadFormRenderer = rendererFactory.renderer("static/memex/memex-upload-form"); + deleteFormRenderer = rendererFactory.renderer("static/memex/memex-delete-form"); + renameFormRenderer = rendererFactory.renderer("static/memex/memex-rename-form"); + createFormRenderer = rendererFactory.renderer("static/memex/memex-create-form"); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/renderer/MemexRendererers.java b/other/memex/src/main/java/nu/marginalia/memex/memex/renderer/MemexRendererers.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/renderer/MemexRendererers.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/renderer/MemexRendererers.java index b033eabf..bffaaf26 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/renderer/MemexRendererers.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/renderer/MemexRendererers.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.memex.renderer; +package nu.marginalia.memex.memex.renderer; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexNodeUrl; @Singleton public class MemexRendererers { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexFileSystemModifiedTimes.java b/other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexFileSystemModifiedTimes.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexFileSystemModifiedTimes.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexFileSystemModifiedTimes.java index e0f3428e..ceb5f38f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexFileSystemModifiedTimes.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexFileSystemModifiedTimes.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.system; +package nu.marginalia.memex.memex.system; import com.google.inject.Singleton; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexFileSystemMonitor.java b/other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexFileSystemMonitor.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexFileSystemMonitor.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexFileSystemMonitor.java index bbcc2b4b..e5c1a94a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexFileSystemMonitor.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexFileSystemMonitor.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.memex.system; +package nu.marginalia.memex.memex.system; import com.google.inject.Inject; import com.google.inject.name.Named; import lombok.SneakyThrows; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,6 +69,7 @@ public class MemexFileSystemMonitor { @SneakyThrows + @SuppressWarnings("unchecked") private void monitorWatch() { for (;;) { var key = watchService.take(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexFileWriter.java b/other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexFileWriter.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexFileWriter.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexFileWriter.java index 577da3d9..285d149e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexFileWriter.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexFileWriter.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.memex.system; +package nu.marginalia.memex.memex.system; import nu.marginalia.util.FileSizeUtil; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexSourceFileSystem.java b/other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexSourceFileSystem.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexSourceFileSystem.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexSourceFileSystem.java index 9d165272..305a4c42 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/MemexSourceFileSystem.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/system/MemexSourceFileSystem.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.memex.system; +package nu.marginalia.memex.memex.system; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.system.git.MemexGitRepo; +import nu.marginalia.memex.memex.system.git.MemexGitRepo; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/git/MemexGitRepo.java b/other/memex/src/main/java/nu/marginalia/memex/memex/system/git/MemexGitRepo.java similarity index 68% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/git/MemexGitRepo.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/system/git/MemexGitRepo.java index d4e55491..a26375c9 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/git/MemexGitRepo.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/system/git/MemexGitRepo.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.memex.system.git; +package nu.marginalia.memex.memex.system.git; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexNodeUrl; public interface MemexGitRepo { void pull(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/git/MemexGitRepoDummy.java b/other/memex/src/main/java/nu/marginalia/memex/memex/system/git/MemexGitRepoDummy.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/git/MemexGitRepoDummy.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/system/git/MemexGitRepoDummy.java index 4d5116ff..84cc3406 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/git/MemexGitRepoDummy.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/system/git/MemexGitRepoDummy.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.memex.system.git; +package nu.marginalia.memex.memex.system.git; import com.google.inject.Singleton; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/git/MemexGitRepoImpl.java b/other/memex/src/main/java/nu/marginalia/memex/memex/system/git/MemexGitRepoImpl.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/git/MemexGitRepoImpl.java rename to other/memex/src/main/java/nu/marginalia/memex/memex/system/git/MemexGitRepoImpl.java index 10c72060..e60a9db0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/memex/system/git/MemexGitRepoImpl.java +++ b/other/memex/src/main/java/nu/marginalia/memex/memex/system/git/MemexGitRepoImpl.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.memex.system.git; +package nu.marginalia.memex.memex.system.git; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Repository; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/mustache/MustacheRenderer.java b/other/memex/src/main/java/nu/marginalia/memex/renderer/MustacheRenderer.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/mustache/MustacheRenderer.java rename to other/memex/src/main/java/nu/marginalia/memex/renderer/MustacheRenderer.java index 2910ca74..1c6e7e92 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/mustache/MustacheRenderer.java +++ b/other/memex/src/main/java/nu/marginalia/memex/renderer/MustacheRenderer.java @@ -1,14 +1,14 @@ -package nu.marginalia.wmsa.renderer.mustache; +package nu.marginalia.memex.renderer; import com.github.jknack.handlebars.*; import com.github.jknack.handlebars.helper.ConditionalHelpers; import com.github.jknack.handlebars.io.ClassPathTemplateLoader; import com.github.jknack.handlebars.io.TemplateLoader; import lombok.SneakyThrows; -import nu.marginalia.gemini.gmi.GemtextDocument; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.model.render.MemexRendererIndexModel; -import nu.marginalia.wmsa.memex.model.render.MemexRendererViewModel; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.render.MemexRendererIndexModel; +import nu.marginalia.memex.memex.model.render.MemexRendererViewModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/other/memex/src/main/java/nu/marginalia/memex/renderer/RendererFactory.java b/other/memex/src/main/java/nu/marginalia/memex/renderer/RendererFactory.java new file mode 100644 index 00000000..96e61038 --- /dev/null +++ b/other/memex/src/main/java/nu/marginalia/memex/renderer/RendererFactory.java @@ -0,0 +1,13 @@ +package nu.marginalia.memex.renderer; + +import java.io.IOException; + +public class RendererFactory { + + public RendererFactory() { + } + + public MustacheRenderer renderer(String template) throws IOException { + return new MustacheRenderer<>(template); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/graphics/dithering/FloydSteinbergDither.java b/other/memex/src/main/java/nu/marginalia/memex/util/dithering/FloydSteinbergDither.java similarity index 99% rename from marginalia_nu/src/main/java/nu/marginalia/util/graphics/dithering/FloydSteinbergDither.java rename to other/memex/src/main/java/nu/marginalia/memex/util/dithering/FloydSteinbergDither.java index 59d0f848..942d65b0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/graphics/dithering/FloydSteinbergDither.java +++ b/other/memex/src/main/java/nu/marginalia/memex/util/dithering/FloydSteinbergDither.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.graphics.dithering; +package nu.marginalia.memex.util.dithering; import lombok.AllArgsConstructor; import net.sf.image4j.util.ConvertUtil; diff --git a/marginalia_nu/src/main/java/nu/marginalia/util/graphics/dithering/Palettes.java b/other/memex/src/main/java/nu/marginalia/memex/util/dithering/Palettes.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/util/graphics/dithering/Palettes.java rename to other/memex/src/main/java/nu/marginalia/memex/util/dithering/Palettes.java index 1318993e..a8137c9a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/util/graphics/dithering/Palettes.java +++ b/other/memex/src/main/java/nu/marginalia/memex/util/dithering/Palettes.java @@ -1,4 +1,4 @@ -package nu.marginalia.util.graphics.dithering; +package nu.marginalia.memex.util.dithering; public class Palettes { diff --git a/marginalia_nu/src/main/resources/static/memex/ico/dir.png b/other/memex/src/main/resources/static/memex/ico/dir.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/dir.png rename to other/memex/src/main/resources/static/memex/ico/dir.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/doc32.png b/other/memex/src/main/resources/static/memex/ico/doc32.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/doc32.png rename to other/memex/src/main/resources/static/memex/ico/doc32.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/file.png b/other/memex/src/main/resources/static/memex/ico/file.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/file.png rename to other/memex/src/main/resources/static/memex/ico/file.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/folder32.png b/other/memex/src/main/resources/static/memex/ico/folder32.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/folder32.png rename to other/memex/src/main/resources/static/memex/ico/folder32.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/folderup16.png b/other/memex/src/main/resources/static/memex/ico/folderup16.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/folderup16.png rename to other/memex/src/main/resources/static/memex/ico/folderup16.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/nav16.png b/other/memex/src/main/resources/static/memex/ico/nav16.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/nav16.png rename to other/memex/src/main/resources/static/memex/ico/nav16.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/pic16.png b/other/memex/src/main/resources/static/memex/ico/pic16.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/pic16.png rename to other/memex/src/main/resources/static/memex/ico/pic16.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/pic32.png b/other/memex/src/main/resources/static/memex/ico/pic32.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/pic32.png rename to other/memex/src/main/resources/static/memex/ico/pic32.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/root.png b/other/memex/src/main/resources/static/memex/ico/root.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/root.png rename to other/memex/src/main/resources/static/memex/ico/root.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/shiba16.png b/other/memex/src/main/resources/static/memex/ico/shiba16.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/shiba16.png rename to other/memex/src/main/resources/static/memex/ico/shiba16.png diff --git a/marginalia_nu/src/main/resources/static/memex/ico/world16.png b/other/memex/src/main/resources/static/memex/ico/world16.png similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/ico/world16.png rename to other/memex/src/main/resources/static/memex/ico/world16.png diff --git a/marginalia_nu/src/main/resources/static/memex/style-new.css b/other/memex/src/main/resources/static/memex/style-new.css similarity index 100% rename from marginalia_nu/src/main/resources/static/memex/style-new.css rename to other/memex/src/main/resources/static/memex/style-new.css diff --git a/marginalia_nu/src/main/resources/templates/auth/login.hdb b/other/memex/src/main/resources/templates/auth/login.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/auth/login.hdb rename to other/memex/src/main/resources/templates/auth/login.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-create-form.hdb b/other/memex/src/main/resources/templates/memex/memex-create-form.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-create-form.hdb rename to other/memex/src/main/resources/templates/memex/memex-create-form.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-delete-form.hdb b/other/memex/src/main/resources/templates/memex/memex-delete-form.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-delete-form.hdb rename to other/memex/src/main/resources/templates/memex/memex-delete-form.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-image.hdb b/other/memex/src/main/resources/templates/memex/memex-image.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-image.hdb rename to other/memex/src/main/resources/templates/memex/memex-image.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-index-feed.hdb b/other/memex/src/main/resources/templates/memex/memex-index-feed.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-index-feed.hdb rename to other/memex/src/main/resources/templates/memex/memex-index-feed.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-index.hdb b/other/memex/src/main/resources/templates/memex/memex-index.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-index.hdb rename to other/memex/src/main/resources/templates/memex/memex-index.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-rename-form.hdb b/other/memex/src/main/resources/templates/memex/memex-rename-form.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-rename-form.hdb rename to other/memex/src/main/resources/templates/memex/memex-rename-form.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-tombstone.hdb b/other/memex/src/main/resources/templates/memex/memex-tombstone.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-tombstone.hdb rename to other/memex/src/main/resources/templates/memex/memex-tombstone.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-update-form.hdb b/other/memex/src/main/resources/templates/memex/memex-update-form.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-update-form.hdb rename to other/memex/src/main/resources/templates/memex/memex-update-form.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-upload-form.hdb b/other/memex/src/main/resources/templates/memex/memex-upload-form.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-upload-form.hdb rename to other/memex/src/main/resources/templates/memex/memex-upload-form.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/memex-view.hdb b/other/memex/src/main/resources/templates/memex/memex-view.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/memex-view.hdb rename to other/memex/src/main/resources/templates/memex/memex-view.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-backlinks-inline.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-backlinks-inline.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-backlinks-inline.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-backlinks-inline.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-backlinks.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-backlinks.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-backlinks.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-backlinks.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-directories.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-directories.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-directories.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-directories.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-documents-inline.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-documents-inline.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-documents-inline.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-documents-inline.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-documents.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-documents.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-documents.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-documents.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-footer.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-footer.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-footer.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-footer.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-head.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-head.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-head.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-head.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-images.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-images.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-images.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-images.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-task-listing.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-task-listing.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-task-listing.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-task-listing.hdb diff --git a/marginalia_nu/src/main/resources/templates/memex/partial/memex-topbar.hdb b/other/memex/src/main/resources/templates/memex/partial/memex-topbar.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/memex/partial/memex-topbar.hdb rename to other/memex/src/main/resources/templates/memex/partial/memex-topbar.hdb diff --git a/marginalia_nu/src/test/java/nu/marginalia/gemini/gmi/GemtextDatabaseTest.java b/other/memex/src/test/java/nu/marginalia/gmi/GemtextDatabaseTest.java similarity index 88% rename from marginalia_nu/src/test/java/nu/marginalia/gemini/gmi/GemtextDatabaseTest.java rename to other/memex/src/test/java/nu/marginalia/gmi/GemtextDatabaseTest.java index ba4587ed..3a9cf4dd 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/gemini/gmi/GemtextDatabaseTest.java +++ b/other/memex/src/test/java/nu/marginalia/gmi/GemtextDatabaseTest.java @@ -1,6 +1,7 @@ -package nu.marginalia.gemini.gmi; +package nu.marginalia.gmi; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; +import nu.marginalia.memex.gemini.gmi.GemtextDatabase; +import nu.marginalia.memex.memex.model.MemexNodeUrl; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/gemini/gmi/GemtextDocumentTest.java b/other/memex/src/test/java/nu/marginalia/gmi/GemtextDocumentTest.java similarity index 90% rename from marginalia_nu/src/test/java/nu/marginalia/gemini/gmi/GemtextDocumentTest.java rename to other/memex/src/test/java/nu/marginalia/gmi/GemtextDocumentTest.java index 5dd8f252..1edc5832 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/gemini/gmi/GemtextDocumentTest.java +++ b/other/memex/src/test/java/nu/marginalia/gmi/GemtextDocumentTest.java @@ -1,8 +1,10 @@ -package nu.marginalia.gemini.gmi; +package nu.marginalia.gmi; -import nu.marginalia.gemini.gmi.line.GemtextLink; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.model.MemexUrl; +import nu.marginalia.memex.gemini.gmi.GemtextDatabase; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.gmi.line.GemtextLink; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.model.MemexUrl; import org.junit.jupiter.api.Test; import java.util.Arrays; diff --git a/marginalia_nu/src/test/java/nu/marginalia/gemini/gmi/parser/GemtextTaskParserTest.java b/other/memex/src/test/java/nu/marginalia/gmi/parser/GemtextTaskParserTest.java similarity index 78% rename from marginalia_nu/src/test/java/nu/marginalia/gemini/gmi/parser/GemtextTaskParserTest.java rename to other/memex/src/test/java/nu/marginalia/gmi/parser/GemtextTaskParserTest.java index 62a62082..d63d5f4c 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/gemini/gmi/parser/GemtextTaskParserTest.java +++ b/other/memex/src/test/java/nu/marginalia/gmi/parser/GemtextTaskParserTest.java @@ -1,7 +1,8 @@ -package nu.marginalia.gemini.gmi.parser; +package nu.marginalia.gmi.parser; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeTaskId; +import nu.marginalia.memex.gemini.gmi.parser.GemtextTaskParser; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeTaskId; import org.junit.jupiter.api.Test; class GemtextTaskParserTest { diff --git a/marginalia_nu/src/test/java/nu/marginalia/util/graphics/dithering/FloydSteinbergDitherTest.java b/other/memex/src/test/java/nu/marginalia/memex/dithering/FloydSteinbergDitherTest.java similarity index 89% rename from marginalia_nu/src/test/java/nu/marginalia/util/graphics/dithering/FloydSteinbergDitherTest.java rename to other/memex/src/test/java/nu/marginalia/memex/dithering/FloydSteinbergDitherTest.java index 632603bd..7d18977e 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/util/graphics/dithering/FloydSteinbergDitherTest.java +++ b/other/memex/src/test/java/nu/marginalia/memex/dithering/FloydSteinbergDitherTest.java @@ -1,5 +1,7 @@ -package nu.marginalia.util.graphics.dithering; +package nu.marginalia.memex.dithering; +import nu.marginalia.memex.util.dithering.FloydSteinbergDither; +import nu.marginalia.memex.util.dithering.Palettes; import org.junit.jupiter.api.Test; import javax.imageio.ImageIO; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/MemexFileWriterTest.java b/other/memex/src/test/java/nu/marginalia/memex/memex/MemexFileWriterTest.java similarity index 88% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/MemexFileWriterTest.java rename to other/memex/src/test/java/nu/marginalia/memex/memex/MemexFileWriterTest.java index fb240c25..32833242 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/MemexFileWriterTest.java +++ b/other/memex/src/test/java/nu/marginalia/memex/memex/MemexFileWriterTest.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.memex; +package nu.marginalia.memex.memex; import nu.marginalia.util.test.TestUtil; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.system.MemexFileWriter; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.system.MemexFileWriter; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/MemexTest.java b/other/memex/src/test/java/nu/marginalia/memex/memex/MemexTest.java similarity index 78% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/MemexTest.java rename to other/memex/src/test/java/nu/marginalia/memex/memex/MemexTest.java index 9ae437a2..54d80069 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/MemexTest.java +++ b/other/memex/src/test/java/nu/marginalia/memex/memex/MemexTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex; +package nu.marginalia.memex.memex; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/change/GemtextChangeTest.java b/other/memex/src/test/java/nu/marginalia/memex/memex/change/GemtextChangeTest.java similarity index 93% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/change/GemtextChangeTest.java rename to other/memex/src/test/java/nu/marginalia/memex/memex/change/GemtextChangeTest.java index e3e670c7..d6548042 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/change/GemtextChangeTest.java +++ b/other/memex/src/test/java/nu/marginalia/memex/memex/change/GemtextChangeTest.java @@ -1,19 +1,19 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; import io.reactivex.rxjava3.plugins.RxJavaPlugins; import lombok.SneakyThrows; -import nu.marginalia.gemini.GeminiServiceImpl; +import nu.marginalia.memex.gemini.GeminiServiceImpl; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.memex.MemexData; +import nu.marginalia.memex.memex.MemexLoader; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.renderer.MemexRendererers; +import nu.marginalia.memex.memex.system.MemexFileSystemModifiedTimes; +import nu.marginalia.memex.memex.system.MemexFileWriter; +import nu.marginalia.memex.memex.system.MemexSourceFileSystem; +import nu.marginalia.memex.memex.system.git.MemexGitRepoImpl; import nu.marginalia.util.test.TestUtil; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.wmsa.memex.MemexData; -import nu.marginalia.wmsa.memex.MemexLoader; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.renderer.MemexRendererers; -import nu.marginalia.wmsa.memex.system.MemexFileSystemModifiedTimes; -import nu.marginalia.wmsa.memex.system.MemexFileWriter; -import nu.marginalia.wmsa.memex.system.MemexSourceFileSystem; -import nu.marginalia.wmsa.memex.system.git.MemexGitRepoImpl; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/change/GemtextTaskUpdateTest.java b/other/memex/src/test/java/nu/marginalia/memex/memex/change/GemtextTaskUpdateTest.java similarity index 90% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/change/GemtextTaskUpdateTest.java rename to other/memex/src/test/java/nu/marginalia/memex/memex/change/GemtextTaskUpdateTest.java index d80d32eb..06d74be4 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/change/GemtextTaskUpdateTest.java +++ b/other/memex/src/test/java/nu/marginalia/memex/memex/change/GemtextTaskUpdateTest.java @@ -1,21 +1,21 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; import io.reactivex.rxjava3.plugins.RxJavaPlugins; import lombok.SneakyThrows; -import nu.marginalia.gemini.GeminiServiceImpl; -import nu.marginalia.gemini.gmi.GemtextDocument; +import nu.marginalia.memex.gemini.GeminiServiceImpl; +import nu.marginalia.memex.gemini.gmi.GemtextDocument; import nu.marginalia.util.test.TestUtil; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.wmsa.memex.MemexData; -import nu.marginalia.wmsa.memex.MemexLoader; -import nu.marginalia.wmsa.memex.change.update.GemtextDocumentUpdateCalculator; -import nu.marginalia.wmsa.memex.model.MemexNodeHeadingId; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.renderer.MemexRendererers; -import nu.marginalia.wmsa.memex.system.MemexFileSystemModifiedTimes; -import nu.marginalia.wmsa.memex.system.MemexFileWriter; -import nu.marginalia.wmsa.memex.system.MemexSourceFileSystem; -import nu.marginalia.wmsa.memex.system.git.MemexGitRepoImpl; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.memex.MemexData; +import nu.marginalia.memex.memex.MemexLoader; +import nu.marginalia.memex.memex.change.update.GemtextDocumentUpdateCalculator; +import nu.marginalia.memex.memex.model.MemexNodeHeadingId; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.renderer.MemexRendererers; +import nu.marginalia.memex.memex.system.MemexFileSystemModifiedTimes; +import nu.marginalia.memex.memex.system.MemexFileWriter; +import nu.marginalia.memex.memex.system.MemexSourceFileSystem; +import nu.marginalia.memex.memex.system.git.MemexGitRepoImpl; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/change/GemtextTombstoneUpdateCaclulatorTest.java b/other/memex/src/test/java/nu/marginalia/memex/memex/change/GemtextTombstoneUpdateCaclulatorTest.java similarity index 84% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/change/GemtextTombstoneUpdateCaclulatorTest.java rename to other/memex/src/test/java/nu/marginalia/memex/memex/change/GemtextTombstoneUpdateCaclulatorTest.java index 51120654..80e0b627 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/change/GemtextTombstoneUpdateCaclulatorTest.java +++ b/other/memex/src/test/java/nu/marginalia/memex/memex/change/GemtextTombstoneUpdateCaclulatorTest.java @@ -1,18 +1,18 @@ -package nu.marginalia.wmsa.memex.change; +package nu.marginalia.memex.memex.change; import io.reactivex.rxjava3.plugins.RxJavaPlugins; import lombok.SneakyThrows; -import nu.marginalia.gemini.GeminiServiceImpl; +import nu.marginalia.memex.gemini.GeminiServiceImpl; import nu.marginalia.util.test.TestUtil; -import nu.marginalia.wmsa.memex.Memex; -import nu.marginalia.wmsa.memex.MemexData; -import nu.marginalia.wmsa.memex.MemexLoader; -import nu.marginalia.wmsa.memex.model.MemexNodeUrl; -import nu.marginalia.wmsa.memex.renderer.MemexRendererers; -import nu.marginalia.wmsa.memex.system.MemexFileSystemModifiedTimes; -import nu.marginalia.wmsa.memex.system.MemexFileWriter; -import nu.marginalia.wmsa.memex.system.MemexSourceFileSystem; -import nu.marginalia.wmsa.memex.system.git.MemexGitRepoImpl; +import nu.marginalia.memex.memex.Memex; +import nu.marginalia.memex.memex.MemexData; +import nu.marginalia.memex.memex.MemexLoader; +import nu.marginalia.memex.memex.model.MemexNodeUrl; +import nu.marginalia.memex.memex.renderer.MemexRendererers; +import nu.marginalia.memex.memex.system.MemexFileSystemModifiedTimes; +import nu.marginalia.memex.memex.system.MemexFileWriter; +import nu.marginalia.memex.memex.system.MemexSourceFileSystem; +import nu.marginalia.memex.memex.system.git.MemexGitRepoImpl; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/model/MemexNodeHeadingIdTest.java b/other/memex/src/test/java/nu/marginalia/memex/memex/model/MemexNodeHeadingIdTest.java similarity index 96% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/model/MemexNodeHeadingIdTest.java rename to other/memex/src/test/java/nu/marginalia/memex/memex/model/MemexNodeHeadingIdTest.java index f0163b3b..73506861 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/memex/model/MemexNodeHeadingIdTest.java +++ b/other/memex/src/test/java/nu/marginalia/memex/memex/model/MemexNodeHeadingIdTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.memex.model; +package nu.marginalia.memex.memex.model; import org.junit.jupiter.api.Test; diff --git a/other/memex/src/test/java/nu/marginalia/util/test/TestUtil.java b/other/memex/src/test/java/nu/marginalia/util/test/TestUtil.java new file mode 100644 index 00000000..c8f3735a --- /dev/null +++ b/other/memex/src/test/java/nu/marginalia/util/test/TestUtil.java @@ -0,0 +1,50 @@ +package nu.marginalia.util.test; + + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +public class TestUtil { + private static boolean isTempDir(Path dir) { + return dir.startsWith("/tmp") || dir.toString().contains("tmp"); + } + + public static void clearTempDir(Path dir) { + if (!isTempDir(dir)) { + throw new IllegalArgumentException("Refusing to recursively delete directory with that name"); + } + if (Files.isDirectory(dir)) { + for (File f : dir.toFile().listFiles()) { + File[] files = f.listFiles(); + if (files != null) { + Arrays.stream(files).map(File::toPath).forEach(TestUtil::clearTempDir); + } + System.out.println("Deleting " + f + " (" + fileSize(f.toPath()) + ")"); + f.delete(); + } + } + System.out.println("Deleting " + dir); + dir.toFile().delete(); + } + + private static String fileSize(Path path) { + try { + long sizeBytes = Files.size(path); + + if (sizeBytes > 1024 * 1024 * 1024) return round(sizeBytes / 1073741824.) + "Gb"; + if (sizeBytes > 1024 * 1024) return round(sizeBytes / 1048576.) + "Mb"; + if (sizeBytes > 1024) return round(sizeBytes / 1024.) + "Kb"; + return sizeBytes + "b"; + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static String round(double d) { + return String.format("%.2f", d); + } +} diff --git a/third_party/build.gradle b/other/wmsa_old/build.gradle similarity index 53% rename from third_party/build.gradle rename to other/wmsa_old/build.gradle index cc0cb57d..0c2bfb7c 100644 --- a/third_party/build.gradle +++ b/other/wmsa_old/build.gradle @@ -1,5 +1,8 @@ plugins { id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' } repositories { @@ -26,92 +29,86 @@ java { languageVersion.set(JavaLanguageVersion.of(17)) } } - dependencies { - implementation 'junit:junit:4.13.2' - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:service') + implementation project(':common:service-discovery') + implementation project(':common:service-client') - implementation 'org.projectlombok:lombok:1.18.22' - annotationProcessor 'org.projectlombok:lombok:1.18.22' + implementation 'org.jetbrains:annotations:24.0.0' - testCompileOnly 'org.projectlombok:lombok:1.18.22' - testImplementation 'org.projectlombok:lombok:1.18.22' - testAnnotationProcessor 'org.projectlombok:lombok:1.18.22' - - implementation 'com.github.jknack:handlebars:4.3.0' + implementation 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' + implementation 'com.github.jknack:handlebars:4.3.1' implementation 'com.github.jknack:handlebars-markdown:4.2.1' implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' - implementation 'io.reactivex.rxjava3:rxjava:3.1.4' + implementation 'io.reactivex.rxjava3:rxjava:3.1.5' implementation "com.sparkjava:spark-core:2.9.3" - implementation 'com.opencsv:opencsv:5.6' - implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.1' - implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.1' - implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.17.1' - implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.1' - implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.1' - implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.17.1' + implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.2' + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.2' + implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.17.2' implementation 'org.slf4j:slf4j-api:1.7.36' + testImplementation 'org.slf4j:slf4j-jdk14:2.0.3' implementation 'com.google.guava:guava:31.1-jre' implementation 'com.google.inject:guice:5.1.0' - implementation 'com.github.jnr:jnr-ffi:2.1.1' + implementation 'com.github.jnr:jnr-ffi:2.2.12' implementation 'org.apache.httpcomponents:httpcore:4.4.15' implementation 'org.apache.httpcomponents:httpclient:4.5.13' - implementation 'com.github.ThatJavaNerd:JRAW:1.1.0' - implementation group: 'com.h2database', name: 'h2', version: '2.1.210' - testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.3.1' - - implementation 'org.jsoup:jsoup:1.14.3' + implementation 'org.jsoup:jsoup:1.15.3' implementation group: 'com.github.crawler-commons', name: 'crawler-commons', version: '1.2' - implementation 'org.mariadb.jdbc:mariadb-java-client:3.0.3' + implementation 'org.mariadb.jdbc:mariadb-java-client:3.0.6' implementation group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3' implementation 'com.zaxxer:HikariCP:5.0.1' implementation 'org.apache.opennlp:opennlp-tools:1.9.4' - implementation 'io.prometheus:simpleclient:0.15.0' - implementation 'io.prometheus:simpleclient_servlet:0.15.0' - implementation 'io.prometheus:simpleclient_httpserver:0.15.0' - implementation 'io.prometheus:simpleclient_hotspot:0.15.0' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.1' - implementation 'org.apache.opennlp:opennlp-tools:1.9.4' - implementation 'io.prometheus:simpleclient:0.15.0' - implementation 'io.prometheus:simpleclient_servlet:0.15.0' - implementation 'io.prometheus:simpleclient_httpserver:0.15.0' - implementation 'io.prometheus:simpleclient_hotspot:0.15.0' - implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.1' + implementation 'io.prometheus:simpleclient:0.16.0' + implementation 'io.prometheus:simpleclient_servlet:0.16.0' + implementation 'io.prometheus:simpleclient_httpserver:0.16.0' + implementation 'io.prometheus:simpleclient_hotspot:0.16.0' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3' implementation group: 'org.yaml', name: 'snakeyaml', version: '1.30' - implementation 'com.syncthemall:boilerpipe:1.2.2' - implementation 'com.github.luben:zstd-jni:1.5.2-2' - implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.3.0' - implementation 'de.rototor.jeuclid:jeuclid-core:3.1.14' - - implementation 'org.imgscalr:imgscalr-lib:4.2' - implementation 'org.jclarion:image4j:0.7' - - implementation 'commons-net:commons-net:3.6' + implementation 'commons-net:commons-net:3.8.0' implementation 'org.eclipse.jgit:org.eclipse.jgit:5.12.0.202106070339-r' implementation 'org.eclipse.jgit:org.eclipse.jgit.ssh.jsch:5.12.0.202106070339-r' - implementation 'com.jcraft:jsch:0.1.55' implementation group: 'org.apache.commons', name: 'commons-compress', version: '1.21' implementation 'edu.stanford.nlp:stanford-corenlp:4.4.0' implementation group: 'it.unimi.dsi', name: 'fastutil', version: '8.5.8' - implementation 'org.roaringbitmap:RoaringBitmap:[0.6,)' - implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.29' + implementation 'org.roaringbitmap:RoaringBitmap:0.9.32' + implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.29' implementation 'com.github.Marcono1234:gson-record-type-adapter-factory:0.2.0' + + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'org.mockito:mockito-junit-jupiter:4.5.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testCompileOnly 'org.projectlombok:lombok:1.18.24' + testImplementation 'org.projectlombok:lombok:1.18.24' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.24' + + testImplementation group: 'org.mockito', name: 'mockito-core', version: '4.5.1' + + implementation 'net.agkn:hll:1.6.0' + } test { useJUnitPlatform() } + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/other/wmsa_old/src/main/java/nu/marginalia/wmsa/WmsaHome.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/WmsaHome.java new file mode 100644 index 00000000..acf53e8f --- /dev/null +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/WmsaHome.java @@ -0,0 +1,56 @@ +package nu.marginalia.wmsa; + + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.Properties; + +public class WmsaHome { + private static final String DEFAULT = "/var/lib/wmsa"; + + public static Path getHomePath() { + var retStr = Optional.ofNullable(System.getenv("WMSA_HOME")).orElse(DEFAULT); + + var ret = Path.of(retStr); + if (!Files.isDirectory(ret)) { + throw new IllegalStateException("Could not find WMSA_HOME, either set environment variable or ensure " + DEFAULT + " exists"); + } + return ret; + } + + public static Path getDisk(String name) { + var pathStr = getDiskProperties().getProperty(name); + if (null == pathStr) { + throw new RuntimeException("Disk " + name + " was not configured"); + } + Path p = Path.of(pathStr); + if (!Files.isDirectory(p)) { + throw new RuntimeException("Disk " + name + " does not exist or is not a directory!"); + } + return p; + } + + public static Properties getDiskProperties() { + Path settingsFile = getHomePath().resolve("conf/disks.properties"); + + if (!Files.isRegularFile(settingsFile)) { + throw new RuntimeException("Could not find disk settings " + settingsFile); + } + + try (var is = Files.newInputStream(settingsFile)) { + var props = new Properties(); + props.load(is); + return props; + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static final boolean debugMode = Boolean.getBoolean("wmsa-debug"); + public static boolean isDebug() { + return debugMode; + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/PodcastFetcher.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/PodcastFetcher.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/PodcastFetcher.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/PodcastFetcher.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperMain.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperMain.java similarity index 58% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperMain.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperMain.java index 22dfe1f3..8e006d96 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperMain.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperMain.java @@ -3,12 +3,11 @@ package nu.marginalia.wmsa.podcasts; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.server.Initialization; - -import java.io.IOException; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.wmsa.renderer.WmsaServiceDescriptors; public class PodcastScraperMain extends MainClass { @@ -20,10 +19,11 @@ public class PodcastScraperMain extends MainClass { } public static void main(String... args) { - init(ServiceDescriptor.PODCST_SCRAPER, args); + + init(ServiceId.Other_PodcastScraper, args); Injector injector = Guice.createInjector( - new ConfigurationModule()); + new ConfigurationModule(WmsaServiceDescriptors.descriptors, ServiceId.Other_PodcastScraper)); injector.getInstance(PodcastScraperMain.class); injector.getInstance(Initialization.class).setReady(); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperService.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperService.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperService.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperService.java index a36ec3ce..8c60c8d8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperService.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/PodcastScraperService.java @@ -3,10 +3,10 @@ package nu.marginalia.wmsa.podcasts; import com.google.inject.Inject; import com.google.inject.name.Named; import io.reactivex.rxjava3.schedulers.Schedulers; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; +import nu.marginalia.client.Context; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.Service; import nu.marginalia.wmsa.podcasts.model.Podcast; import nu.marginalia.wmsa.podcasts.model.PodcastEpisode; import nu.marginalia.wmsa.renderer.client.RendererClient; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/Podcast.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/Podcast.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/Podcast.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/Podcast.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastEpisode.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastEpisode.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastEpisode.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastEpisode.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastListing.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastListing.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastListing.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastListing.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastMetadata.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastMetadata.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastMetadata.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastMetadata.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastNewEpisodes.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastNewEpisodes.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastNewEpisodes.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/podcasts/model/PodcastNewEpisodes.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/PodcastRendererService.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/PodcastRendererService.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/PodcastRendererService.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/PodcastRendererService.java index cad1ad3e..3398a2ff 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/PodcastRendererService.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/PodcastRendererService.java @@ -3,12 +3,11 @@ package nu.marginalia.wmsa.renderer; import com.google.gson.Gson; import com.google.inject.Inject; import lombok.SneakyThrows; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.configuration.server.Context; +import nu.marginalia.client.Context; import nu.marginalia.wmsa.podcasts.model.Podcast; -import nu.marginalia.wmsa.podcasts.model.PodcastEpisode; import nu.marginalia.wmsa.podcasts.model.PodcastListing; import nu.marginalia.wmsa.podcasts.model.PodcastNewEpisodes; +import nu.marginalia.wmsa.podcasts.model.PodcastEpisode; import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; import nu.marginalia.wmsa.renderer.mustache.RendererFactory; import nu.marginalia.wmsa.resource_store.ResourceStoreClient; @@ -25,7 +24,7 @@ import java.util.concurrent.TimeUnit; public class PodcastRendererService { private final Logger logger = LoggerFactory.getLogger(getClass()); - private final Gson gson = GsonFactory.get(); + private final Gson gson = new Gson(); private final RendererFactory rendererFactory = new RendererFactory(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/RendererMain.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/RendererMain.java similarity index 63% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/RendererMain.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/RendererMain.java index f3802a4f..98d7fd83 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/RendererMain.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/RendererMain.java @@ -3,12 +3,10 @@ package nu.marginalia.wmsa.renderer; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.server.Initialization; - -import java.io.IOException; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.server.Initialization; public class RendererMain extends MainClass { private final RendererService service; @@ -20,11 +18,11 @@ public class RendererMain extends MainClass { } public static void main(String... args) { - init(ServiceDescriptor.RENDERER, args); + init(ServiceId.Other_Renderer, args); Injector injector = Guice.createInjector( new RendererModule(), - new ConfigurationModule()); + new ConfigurationModule(WmsaServiceDescriptors.descriptors, ServiceId.Other_Renderer)); injector.getInstance(RendererMain.class); injector.getInstance(Initialization.class).setReady(); } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/RendererModule.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/RendererModule.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/RendererModule.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/RendererModule.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/RendererService.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/RendererService.java similarity index 76% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/RendererService.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/RendererService.java index fb3d0e9e..86dfcc9a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/RendererService.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/RendererService.java @@ -3,9 +3,9 @@ package nu.marginalia.wmsa.renderer; import com.google.inject.Inject; import com.google.inject.name.Named; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.Service; import nu.marginalia.wmsa.resource_store.ResourceStoreClient; @@ -18,7 +18,6 @@ public class RendererService extends Service { @Named("service-host") String ip, @Named("service-port") Integer port, PodcastRendererService podcastRendererService, - StatusRendererService statusRendererService, Initialization initialization, MetricsServer metricsServer ) { @@ -27,7 +26,6 @@ public class RendererService extends Service { this.resourceStoreClient = resourceStoreClient; podcastRendererService.start(); - statusRendererService.start(); } public boolean isReady() { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/ServerStatusModel.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/ServerStatusModel.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/ServerStatusModel.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/ServerStatusModel.java diff --git a/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/WmsaServiceDescriptors.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/WmsaServiceDescriptors.java new file mode 100644 index 00000000..ca25440f --- /dev/null +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/WmsaServiceDescriptors.java @@ -0,0 +1,17 @@ +package nu.marginalia.wmsa.renderer; + +import nu.marginalia.service.descriptor.ServiceDescriptor; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; + +import java.util.List; + +public class WmsaServiceDescriptors { + public static ServiceDescriptors descriptors = new ServiceDescriptors( + List.of( + new ServiceDescriptor(ServiceId.Other_ResourceStore, 5000), + new ServiceDescriptor(ServiceId.Other_Renderer, 5002), + new ServiceDescriptor(ServiceId.Other_PodcastScraper, 5013))); + + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/client/RendererClient.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/client/RendererClient.java similarity index 73% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/client/RendererClient.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/client/RendererClient.java index 63537e3b..809a7cfa 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/client/RendererClient.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/client/RendererClient.java @@ -1,25 +1,28 @@ package nu.marginalia.wmsa.renderer.client; +import com.google.gson.Gson; import io.reactivex.rxjava3.core.Observable; import lombok.SneakyThrows; -import nu.marginalia.wmsa.client.AbstractDynamicClient; -import nu.marginalia.wmsa.client.HttpStatusCode; -import nu.marginalia.wmsa.client.exception.TimeoutException; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.server.Context; +import nu.marginalia.client.AbstractDynamicClient; +import nu.marginalia.client.Context; +import nu.marginalia.client.HttpStatusCode; +import nu.marginalia.service.descriptor.HostsFile; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; import nu.marginalia.wmsa.podcasts.model.Podcast; import nu.marginalia.wmsa.podcasts.model.PodcastEpisode; import nu.marginalia.wmsa.podcasts.model.PodcastListing; import nu.marginalia.wmsa.podcasts.model.PodcastNewEpisodes; +import nu.marginalia.client.exception.TimeoutException; import javax.inject.Inject; import java.util.concurrent.TimeUnit; -public class RendererClient extends AbstractDynamicClient{ +public class RendererClient extends AbstractDynamicClient { @Inject - public RendererClient() { - super(ServiceDescriptor.RENDERER); + public RendererClient(ServiceDescriptors descriptors) { + super(descriptors.forId(ServiceId.Index), new HostsFile(), Gson::new); } @SneakyThrows diff --git a/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/mustache/MustacheRenderer.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/mustache/MustacheRenderer.java new file mode 100644 index 00000000..90e50754 --- /dev/null +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/mustache/MustacheRenderer.java @@ -0,0 +1,61 @@ +package nu.marginalia.wmsa.renderer.mustache; + +import com.github.jknack.handlebars.*; +import com.github.jknack.handlebars.helper.ConditionalHelpers; +import com.github.jknack.handlebars.io.ClassPathTemplateLoader; +import com.github.jknack.handlebars.io.TemplateLoader; +import lombok.SneakyThrows; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class MustacheRenderer { + Template template; + private final Logger logger = LoggerFactory.getLogger(getClass()); + + MustacheRenderer(String templateFile) throws IOException { + + TemplateLoader loader = new ClassPathTemplateLoader(); + loader.setPrefix("/templates"); + loader.setSuffix(".hdb"); + + var handlebars = new Handlebars(loader); + handlebars.registerHelpers(ConditionalHelpers.class); + handlebars.registerHelper("md", new MarkdownHelper()); + + try { + template = handlebars.compile(templateFile); + } + catch (FileNotFoundException ex) { + logger.error("Kunde inte ladda template " + templateFile, ex); + System.exit(2); + } + catch (HandlebarsException ex) { + logger.error("Kunde inte instantiera mall " + templateFile, ex); + System.exit(2); + } + } + + @SneakyThrows + public String render(T model) { + return template.apply(model); + } + + @SneakyThrows + public String render(T model, String name, List children) { + Context ctx = Context.newBuilder(model).combine(name, children).build(); + + return template.apply(ctx); + } + + @SneakyThrows + public String render(T model, Map children) { + Context ctx = Context.newBuilder(model).combine(children).build(); + return template.apply(ctx); + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/mustache/RendererFactory.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/mustache/RendererFactory.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/renderer/mustache/RendererFactory.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/renderer/mustache/RendererFactory.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceEntityStore.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceEntityStore.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceEntityStore.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceEntityStore.java index 5ca3d60b..ebfda218 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceEntityStore.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceEntityStore.java @@ -4,7 +4,6 @@ import com.google.gson.Gson; import com.google.inject.name.Named; import io.prometheus.client.Counter; import io.reactivex.rxjava3.schedulers.Schedulers; -import nu.marginalia.wmsa.client.GsonFactory; import nu.marginalia.wmsa.resource_store.model.RenderedResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +32,7 @@ public class ResourceEntityStore { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Path dataPath; - private final Gson gson = GsonFactory.get(); + private final Gson gson = new Gson(); private final Base64.Encoder b64encoder = Base64.getEncoder(); private final static Counter wmsa_resource_store_count diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreClient.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreClient.java similarity index 71% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreClient.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreClient.java index b057d450..d9333638 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreClient.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreClient.java @@ -1,13 +1,16 @@ package nu.marginalia.wmsa.resource_store; +import com.google.gson.Gson; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.schedulers.Schedulers; -import nu.marginalia.wmsa.client.AbstractDynamicClient; -import nu.marginalia.wmsa.client.HttpStatusCode; -import nu.marginalia.wmsa.client.exception.TimeoutException; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.server.Context; +import nu.marginalia.client.AbstractDynamicClient; +import nu.marginalia.client.Context; +import nu.marginalia.client.HttpStatusCode; +import nu.marginalia.service.descriptor.HostsFile; +import nu.marginalia.service.descriptor.ServiceDescriptors; +import nu.marginalia.service.id.ServiceId; import nu.marginalia.wmsa.resource_store.model.RenderedResource; +import nu.marginalia.client.exception.TimeoutException; import javax.inject.Inject; import javax.inject.Singleton; @@ -16,11 +19,11 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @Singleton -public class ResourceStoreClient extends AbstractDynamicClient{ +public class ResourceStoreClient extends AbstractDynamicClient { @Inject - public ResourceStoreClient() { - super(ServiceDescriptor.RESOURCE_STORE); + public ResourceStoreClient(ServiceDescriptors descriptors) { + super(descriptors.forId(ServiceId.Other_ResourceStore), new HostsFile(), Gson::new); } public Observable getResource(Context ctx, String domain, String resource) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreMain.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreMain.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreMain.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreMain.java index ad903fc5..f3e84dee 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreMain.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreMain.java @@ -3,12 +3,11 @@ package nu.marginalia.wmsa.resource_store; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.server.Initialization; - -import java.io.IOException; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.wmsa.renderer.WmsaServiceDescriptors; public class ResourceStoreMain extends MainClass { private final ResourceStoreService service; @@ -16,15 +15,14 @@ public class ResourceStoreMain extends MainClass { @Inject public ResourceStoreMain(ResourceStoreService service) { this.service = service; - } public static void main(String... args) { - init(ServiceDescriptor.RESOURCE_STORE, args); + init(ServiceId.Other_ResourceStore, args); Injector injector = Guice.createInjector( new ResourceStoreModule(), - new ConfigurationModule() + new ConfigurationModule(WmsaServiceDescriptors.descriptors, ServiceId.Other_ResourceStore) ); injector.getInstance(ResourceStoreMain.class); injector.getInstance(Initialization.class).setReady(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreModule.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreModule.java similarity index 87% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreModule.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreModule.java index 06443dc8..70db9271 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreModule.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreModule.java @@ -2,7 +2,7 @@ package nu.marginalia.wmsa.resource_store; import com.google.inject.AbstractModule; import com.google.inject.name.Names; -import nu.marginalia.wmsa.configuration.WmsaHome; +import nu.marginalia.wmsa.WmsaHome; import java.nio.file.Path; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreService.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreService.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreService.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreService.java index 7db4ffe7..45415275 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreService.java +++ b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/ResourceStoreService.java @@ -4,14 +4,12 @@ import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.name.Named; import io.reactivex.rxjava3.schedulers.Schedulers; -import kotlin.text.Charsets; import lombok.SneakyThrows; -import nu.marginalia.wmsa.auth.client.AuthClient; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; +import nu.marginalia.client.Context; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.Service; +import nu.marginalia.service.server.StaticResources; import nu.marginalia.wmsa.resource_store.model.RenderedResource; import org.apache.http.HttpStatus; import org.slf4j.Logger; @@ -22,31 +20,27 @@ import spark.Spark; import spark.resource.ClassPathResource; import spark.staticfiles.MimeType; -import java.net.URLEncoder; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.concurrent.TimeUnit; public class ResourceStoreService extends Service { - private final Gson gson = GsonFactory.get(); + private final Gson gson = new Gson(); private final Logger logger = LoggerFactory.getLogger(getClass()); private final long startTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC); - private final AuthClient authClient; private final ResourceEntityStore resourceStore; private StaticResources staticResources; @Inject public ResourceStoreService(@Named("service-host") String ip, @Named("service-port") Integer port, - AuthClient authClient, ResourceEntityStore resourceStore, Initialization initialization, MetricsServer metricsServer, StaticResources staticResources ) { super(ip, port, initialization, metricsServer); - this.authClient = authClient; this.resourceStore = resourceStore; this.staticResources = staticResources; @@ -107,7 +101,6 @@ public class ResourceStoreService extends Service { if (data != null) { logger.info("getResource({}/{}, {})", domain, resource, data.etag()); - validatePermission(Context.fromRequest(request), request, response, domain, data); return serveDynamic(data, request, response); } @@ -118,19 +111,6 @@ public class ResourceStoreService extends Service { return ""; } - - private void validatePermission(Context ctx, Request req, Response rsp, String domain, RenderedResource resource) { - if ("memex".equals(domain)) { - if (resource.requireLogin && !memexIsLoggedIn(ctx)) { - rsp.redirect("https://www.marginalia.nu/auth/login?service=MEMEX&redirect="+ URLEncoder.encode(req.headers("X-Extern-Url"), Charsets.UTF_8)); - Spark.halt(); - } - } - } - - private boolean memexIsLoggedIn(Context ctx) { - return authClient.isLoggedIn(ctx).timeout(1, TimeUnit.SECONDS).blockingFirst(); - } private String serveDynamic(RenderedResource data, Request request, Response response) { handleEtag(data, request, response); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/model/RenderedResource.java b/other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/model/RenderedResource.java similarity index 100% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/resource_store/model/RenderedResource.java rename to other/wmsa_old/src/main/java/nu/marginalia/wmsa/resource_store/model/RenderedResource.java diff --git a/marginalia_nu/src/main/resources/static/podcast/style.css b/other/wmsa_old/src/main/resources/static/podcast/style.css similarity index 100% rename from marginalia_nu/src/main/resources/static/podcast/style.css rename to other/wmsa_old/src/main/resources/static/podcast/style.css diff --git a/marginalia_nu/src/main/resources/templates/podcast/episode.hdb b/other/wmsa_old/src/main/resources/templates/podcast/episode.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/podcast/episode.hdb rename to other/wmsa_old/src/main/resources/templates/podcast/episode.hdb diff --git a/marginalia_nu/src/main/resources/templates/podcast/listing.hdb b/other/wmsa_old/src/main/resources/templates/podcast/listing.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/podcast/listing.hdb rename to other/wmsa_old/src/main/resources/templates/podcast/listing.hdb diff --git a/marginalia_nu/src/main/resources/templates/podcast/new.hdb b/other/wmsa_old/src/main/resources/templates/podcast/new.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/podcast/new.hdb rename to other/wmsa_old/src/main/resources/templates/podcast/new.hdb diff --git a/marginalia_nu/src/main/resources/templates/podcast/podcast.hdb b/other/wmsa_old/src/main/resources/templates/podcast/podcast.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/podcast/podcast.hdb rename to other/wmsa_old/src/main/resources/templates/podcast/podcast.hdb diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/resource_store/ResourceStoreServiceTest.java b/other/wmsa_old/src/test/java/nu/marginalia/resource_store/ResourceStoreServiceTest.java similarity index 87% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/resource_store/ResourceStoreServiceTest.java rename to other/wmsa_old/src/test/java/nu/marginalia/resource_store/ResourceStoreServiceTest.java index 4325cff6..b1922bc1 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/resource_store/ResourceStoreServiceTest.java +++ b/other/wmsa_old/src/test/java/nu/marginalia/resource_store/ResourceStoreServiceTest.java @@ -1,9 +1,13 @@ -package nu.marginalia.wmsa.resource_store; +package nu.marginalia.resource_store; import lombok.SneakyThrows; -import nu.marginalia.util.TestUtil; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.configuration.server.Initialization; +import nu.marginalia.client.Context; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.StaticResources; +import nu.marginalia.wmsa.renderer.WmsaServiceDescriptors; +import nu.marginalia.wmsa.resource_store.ResourceEntityStore; +import nu.marginalia.wmsa.resource_store.ResourceStoreClient; +import nu.marginalia.wmsa.resource_store.ResourceStoreService; import nu.marginalia.wmsa.resource_store.model.RenderedResource; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; @@ -17,6 +21,7 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.time.LocalDateTime; +import java.util.Random; import static org.junit.jupiter.api.Assertions.*; @@ -24,7 +29,7 @@ class ResourceStoreServiceTest { static ResourceStoreService service; static ResourceStoreClient client; - static final int testPort = TestUtil.getPort(); + static final int testPort = new Random().nextInt(4000, 10000); static ResourceEntityStore resourceStore; static Path tempDir; private static final Logger logger = LoggerFactory.getLogger(ResourceStoreServiceTest.class); @@ -35,11 +40,11 @@ class ResourceStoreServiceTest { Spark.port(testPort); System.setProperty("service-name", "renderer"); - client = new ResourceStoreClient(); + client = new ResourceStoreClient(WmsaServiceDescriptors.descriptors); client.setServiceRoute("127.0.0.1", testPort); tempDir = Files.createTempDirectory("ResourceStoreServiceTest"); resourceStore = new ResourceEntityStore(tempDir); - service = new ResourceStoreService("127.0.0.1", testPort, null, + service = new ResourceStoreService("127.0.0.1", testPort, resourceStore, new Initialization(), null, new StaticResources()); Spark.awaitInitialization(); diff --git a/run/.gitignore b/run/.gitignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/run/.gitignore @@ -0,0 +1 @@ +* diff --git a/run/reconvert.sh b/run/reconvert.sh new file mode 100755 index 00000000..7f17f447 --- /dev/null +++ b/run/reconvert.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +## Configuration + +SAMPLE_DIR="samples/crawl-l/" + +CONVERTER_PROCESS_OPTS=" +-Xmx16G +-XX:-CompactStrings +-XX:+UseParallelGC +-XX:GCTimeRatio=14 +-XX:ParallelGCThreads=15 +" + +LOADER_PROCESS_OPTS=" +-Dsmall-ram=TRUE +-Dlocal-index-path=vol/iw +" + +JAVA_OPTS=" +-Dcrawl.rootDirRewrite=/crawl:${SAMPLE_DIR} +-Ddb.overrideJdbc=jdbc:mariadb://localhost:3306/WMSA_prod?rewriteBatchedStatements=true +" + +## Configuration ends + +pushd $(dirname $0) + +## Wipe the old index data + +rm -f ${SAMPLE_DIR}/process/process.log +rm -f vol/iw/dictionary.dat +rm -f vol/iw/index.dat + +## Upgrade the tools + +rm -rf install/loader-process install/converter-process +tar xf ../crawl/loading-process/build/distributions/loader-process.tar -C install/ +tar xf ../crawl/converting-process/build/distributions/converter-process.tar -C install/ + +PATH+=":install/converter-process/bin" +PATH+=":install/loader-process/bin" + +export WMSA_HOME=. +export PATH + +export JAVA_OPTS +export CONVERTER_PROCESS_OPTS +export LOADER_PROCESS_OPTS + +converter-process ${SAMPLE_DIR}/plan.yaml +loader-process ${SAMPLE_DIR}/plan.yaml + +mv vol/iw/index.dat vol/iw/0/page-index.dat + +popd diff --git a/services-core/assistant-service/build.gradle b/services-core/assistant-service/build.gradle new file mode 100644 index 00000000..a3aafc2e --- /dev/null +++ b/services-core/assistant-service/build.gradle @@ -0,0 +1,67 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'application' + id 'jvm-test-suite' + id 'com.palantir.docker' version '0.34.0' +} + +application { + mainClass = 'nu.marginalia.assistant.AssistantMain' + applicationName = 'assistant-service' +} + +apply from: "$rootProject.projectDir/docker-service.gradle" + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':api:assistant-api') + implementation project(':common:config') + implementation project(':common:service') + implementation project(':common:model') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + + implementation project(':features:screenshots') + implementation project(':libraries:language-processing') + + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.guice + implementation libs.rxjava + implementation libs.spark + implementation libs.opencsv + implementation libs.trove + implementation libs.fastutil + implementation libs.bundles.gson + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + + +} + + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantMain.java b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantMain.java new file mode 100644 index 00000000..4ecb82c0 --- /dev/null +++ b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantMain.java @@ -0,0 +1,34 @@ +package nu.marginalia.assistant; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.SearchServiceDescriptors; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.module.DatabaseModule; +import nu.marginalia.service.server.Initialization; + +public class AssistantMain extends MainClass { + private final AssistantService service; + + @Inject + public AssistantMain(AssistantService service) { + this.service = service; + } + + public static void main(String... args) { + init(ServiceId.Assistant, args); + + Injector injector = Guice.createInjector( + new AssistantModule(), + new ConfigurationModule(SearchServiceDescriptors.descriptors, ServiceId.Assistant), + new DatabaseModule() + ); + + injector.getInstance(AssistantMain.class); + injector.getInstance(Initialization.class).setReady(); + + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantModule.java b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantModule.java similarity index 63% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantModule.java rename to services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantModule.java index dcc8d90d..0670b103 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantModule.java +++ b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantModule.java @@ -1,14 +1,14 @@ -package nu.marginalia.wmsa.edge.assistant; +package nu.marginalia.assistant; import com.google.inject.AbstractModule; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.wmsa.configuration.WmsaHome; +import nu.marginalia.LanguageModels; +import nu.marginalia.WmsaHome; import java.nio.file.Path; import static com.google.inject.name.Names.named; -public class EdgeAssistantModule extends AbstractModule { +public class AssistantModule extends AbstractModule { public void configure() { bind(Path.class).annotatedWith(named("suggestions-file")).toInstance(WmsaHome.getHomePath().resolve("suggestions.txt")); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantService.java b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantService.java similarity index 70% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantService.java rename to services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantService.java index 975f11cb..c0d908fd 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/EdgeAssistantService.java +++ b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/AssistantService.java @@ -1,26 +1,25 @@ -package nu.marginalia.wmsa.edge.assistant; +package nu.marginalia.assistant; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.name.Named; import lombok.SneakyThrows; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; -import nu.marginalia.wmsa.edge.assistant.dict.DictionaryService; -import nu.marginalia.wmsa.edge.assistant.eval.MathParser; -import nu.marginalia.wmsa.edge.assistant.eval.Units; -import nu.marginalia.wmsa.edge.assistant.screenshot.ScreenshotService; -import nu.marginalia.wmsa.edge.assistant.suggest.Suggestions; +import nu.marginalia.assistant.eval.Units; +import nu.marginalia.assistant.suggest.Suggestions; +import nu.marginalia.assistant.eval.MathParser; +import nu.marginalia.model.gson.GsonFactory; +import nu.marginalia.screenshot.ScreenshotService; +import nu.marginalia.assistant.dict.DictionaryService; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import spark.Request; import spark.Response; import spark.Spark; -public class EdgeAssistantService extends Service { - +public class AssistantService extends Service { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Gson gson = GsonFactory.get(); private final Units units; @@ -29,15 +28,15 @@ public class EdgeAssistantService extends Service { @SneakyThrows @Inject - public EdgeAssistantService(@Named("service-host") String ip, - @Named("service-port") Integer port, - Initialization initialization, - MetricsServer metricsServer, - DictionaryService dictionaryService, - MathParser mathParser, - Units units, - ScreenshotService screenshotService, - Suggestions suggestions + public AssistantService(@Named("service-host") String ip, + @Named("service-port") Integer port, + Initialization initialization, + MetricsServer metricsServer, + DictionaryService dictionaryService, + MathParser mathParser, + Units units, + ScreenshotService screenshotService, + Suggestions suggestions ) { super(ip, port, initialization, metricsServer); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/DictionaryService.java b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/dict/DictionaryService.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/DictionaryService.java rename to services-core/assistant-service/src/main/java/nu/marginalia/assistant/dict/DictionaryService.java index 572c96fa..40686f74 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/DictionaryService.java +++ b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/dict/DictionaryService.java @@ -1,13 +1,14 @@ -package nu.marginalia.wmsa.edge.assistant.dict; +package nu.marginalia.assistant.dict; import com.google.inject.Inject; import com.google.inject.Singleton; import com.zaxxer.hikari.HikariDataSource; +import nu.marginalia.assistant.client.model.DictionaryEntry; +import nu.marginalia.assistant.client.model.DictionaryResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; -import java.util.stream.Collectors; @Singleton public class DictionaryService { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/SpellChecker.java b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/dict/SpellChecker.java similarity index 87% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/SpellChecker.java rename to services-core/assistant-service/src/main/java/nu/marginalia/assistant/dict/SpellChecker.java index 700a15ec..d1710122 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/SpellChecker.java +++ b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/dict/SpellChecker.java @@ -1,6 +1,7 @@ -package nu.marginalia.wmsa.edge.assistant.dict; +package nu.marginalia.assistant.dict; import com.google.inject.Singleton; +import symspell.SymSpell; import java.util.Comparator; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/eval/MathParser.java b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/eval/MathParser.java similarity index 99% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/eval/MathParser.java rename to services-core/assistant-service/src/main/java/nu/marginalia/assistant/eval/MathParser.java index 37388a8f..247963c8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/eval/MathParser.java +++ b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/eval/MathParser.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.assistant.eval; +package nu.marginalia.assistant.eval; import lombok.AllArgsConstructor; import lombok.SneakyThrows; @@ -46,6 +46,8 @@ public class MathParser { List tokens = tokenize(inputExpression); + // recursive descent + tokens = parenthesize(tokens); tokens = negate(tokens); tokens = functions(tokens); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/eval/Unit.java b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/eval/Unit.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/eval/Unit.java rename to services-core/assistant-service/src/main/java/nu/marginalia/assistant/eval/Unit.java index e8da905e..cdc352c8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/eval/Unit.java +++ b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/eval/Unit.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.assistant.eval; +package nu.marginalia.assistant.eval; public class Unit { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/eval/Units.java b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/eval/Units.java similarity index 98% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/eval/Units.java rename to services-core/assistant-service/src/main/java/nu/marginalia/assistant/eval/Units.java index 6a0d4be8..b73eb7ac 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/eval/Units.java +++ b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/eval/Units.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.assistant.eval; +package nu.marginalia.assistant.eval; import com.opencsv.CSVReader; import lombok.SneakyThrows; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/suggest/Suggestions.java b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/suggest/Suggestions.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/suggest/Suggestions.java rename to services-core/assistant-service/src/main/java/nu/marginalia/assistant/suggest/Suggestions.java index ff793015..cb59ffd4 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/suggest/Suggestions.java +++ b/services-core/assistant-service/src/main/java/nu/marginalia/assistant/suggest/Suggestions.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.assistant.suggest; +package nu.marginalia.assistant.suggest; import com.google.inject.Inject; import com.google.inject.name.Named; -import nu.marginalia.wmsa.edge.assistant.dict.SpellChecker; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.converting.processor.logic.HtmlFeature; +import nu.marginalia.language.statistics.TermFrequencyDict; +import nu.marginalia.model.crawl.HtmlFeature; +import nu.marginalia.assistant.dict.SpellChecker; import org.apache.commons.collections4.trie.PatriciaTrie; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,6 +42,10 @@ public class Suggestions { } private static PatriciaTrie loadSuggestions(Path file) { + if (!Files.exists(file)) { + logger.error("Suggestions file {} absent, loading empty suggestions db", file); + return new PatriciaTrie<>(); + } try (var lines = Files.lines(file)) { var ret = new PatriciaTrie(); diff --git a/marginalia_nu/src/main/resources/units.csv b/services-core/assistant-service/src/main/resources/units.csv similarity index 100% rename from marginalia_nu/src/main/resources/units.csv rename to services-core/assistant-service/src/main/resources/units.csv diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/dict/WikiCleanerTest.java b/services-core/assistant-service/src/test/java/nu/marginalia/assistant/dict/WikiCleanerTest.java similarity index 98% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/dict/WikiCleanerTest.java rename to services-core/assistant-service/src/test/java/nu/marginalia/assistant/dict/WikiCleanerTest.java index 2535f206..3c2bda7a 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/dict/WikiCleanerTest.java +++ b/services-core/assistant-service/src/test/java/nu/marginalia/assistant/dict/WikiCleanerTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.assistant.dict; +package nu.marginalia.assistant.dict; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/eval/MathParserTest.java b/services-core/assistant-service/src/test/java/nu/marginalia/assistant/eval/MathParserTest.java similarity index 96% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/eval/MathParserTest.java rename to services-core/assistant-service/src/test/java/nu/marginalia/assistant/eval/MathParserTest.java index 45827fad..4fdfdc71 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/eval/MathParserTest.java +++ b/services-core/assistant-service/src/test/java/nu/marginalia/assistant/eval/MathParserTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.assistant.eval; +package nu.marginalia.assistant.eval; import org.junit.jupiter.api.Test; import org.slf4j.Logger; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/eval/UnitsTest.java b/services-core/assistant-service/src/test/java/nu/marginalia/assistant/eval/UnitsTest.java similarity index 96% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/eval/UnitsTest.java rename to services-core/assistant-service/src/test/java/nu/marginalia/assistant/eval/UnitsTest.java index 93d1efd2..de2b709b 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/assistant/eval/UnitsTest.java +++ b/services-core/assistant-service/src/test/java/nu/marginalia/assistant/eval/UnitsTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.assistant.eval; +package nu.marginalia.assistant.eval; import org.junit.jupiter.api.Test; diff --git a/services-core/index-service/build.gradle b/services-core/index-service/build.gradle new file mode 100644 index 00000000..a1e04b73 --- /dev/null +++ b/services-core/index-service/build.gradle @@ -0,0 +1,76 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'com.palantir.docker' version '0.34.0' + id 'application' + id 'jvm-test-suite' +} + +application { + mainClass = 'nu.marginalia.index.IndexMain' + applicationName = 'index-service' +} + +apply from: "$rootProject.projectDir/docker-service.gradle" + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:config') + implementation project(':common:model') + implementation project(':common:service') + implementation project(':api:index-api') + implementation project(':common:service-discovery') + + implementation project(':libraries:array') + implementation project(':libraries:btree') + implementation project(':libraries:misc') + + implementation project(':index:index-journal') + implementation project(':index:index-query') + implementation project(':index:index-forward') + implementation project(':index:index-reverse') + implementation project(':index:lexicon') + + implementation project(':features:domain-ranking') + + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.guice + implementation libs.protobuf + implementation libs.bundles.httpcomponents + implementation libs.roaringbitmap + implementation libs.snakeyaml + implementation libs.rxjava + implementation libs.spark + implementation libs.opencsv + implementation libs.trove + implementation libs.fastutil + implementation libs.bundles.gson + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/IndexMain.java b/services-core/index-service/src/main/java/nu/marginalia/index/IndexMain.java new file mode 100644 index 00000000..35e3ae5e --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/IndexMain.java @@ -0,0 +1,35 @@ +package nu.marginalia.index; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.SearchServiceDescriptors; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.module.DatabaseModule; +import nu.marginalia.service.server.Initialization; + +public class IndexMain extends MainClass { + private final IndexService service; + + @Inject + public IndexMain(IndexService service) { + this.service = service; + } + + public static void main(String... args) { + init(ServiceId.Index, args); + + Injector injector = Guice.createInjector( + new IndexTablesModule(), + new IndexModule(), + new DatabaseModule(), + new ConfigurationModule(SearchServiceDescriptors.descriptors, ServiceId.Index) + ); + + injector.getInstance(IndexMain.class); + injector.getInstance(Initialization.class).setReady(); + + } +} diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/IndexModule.java b/services-core/index-service/src/main/java/nu/marginalia/index/IndexModule.java new file mode 100644 index 00000000..1e674d01 --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/IndexModule.java @@ -0,0 +1,38 @@ +package nu.marginalia.index; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import lombok.SneakyThrows; +import nu.marginalia.index.config.RankingSettings; +import nu.marginalia.WmsaHome; +import nu.marginalia.lexicon.KeywordLexicon; +import nu.marginalia.lexicon.KeywordLexiconReadOnlyView; +import nu.marginalia.lexicon.journal.KeywordLexiconJournal; + +import java.nio.file.Path; + +public class IndexModule extends AbstractModule { + + + + public void configure() { + } + + @Provides + @SneakyThrows + private KeywordLexiconReadOnlyView createLexicon() { + return new KeywordLexiconReadOnlyView( + new KeywordLexicon( + new KeywordLexiconJournal(WmsaHome.getDisk("index-write").resolve("dictionary.dat").toFile() + ) + ) + ); + } + + @Provides + public RankingSettings rankingSettings() { + Path dir = WmsaHome.getHomePath().resolve("conf/ranking-settings.yaml"); + return RankingSettings.from(dir); + } + +} diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/IndexService.java b/services-core/index-service/src/main/java/nu/marginalia/index/IndexService.java new file mode 100644 index 00000000..c6552c18 --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/IndexService.java @@ -0,0 +1,76 @@ +package nu.marginalia.index; + +import com.google.gson.Gson; +import com.google.inject.Inject; +import com.google.inject.name.Named; +import io.reactivex.rxjava3.schedulers.Schedulers; +import nu.marginalia.index.index.SearchIndex; +import nu.marginalia.index.svc.IndexOpsService; +import nu.marginalia.index.svc.IndexQueryService; +import nu.marginalia.model.gson.GsonFactory; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.Service; +import org.jetbrains.annotations.NotNull; +import spark.Request; +import spark.Response; +import spark.Spark; + +import java.util.concurrent.TimeUnit; + +import static spark.Spark.get; + +public class IndexService extends Service { + + @NotNull + private final Initialization init; + private final IndexOpsService opsService; + private final SearchIndex searchIndex; + + + @Inject + public IndexService(@Named("service-host") String ip, + @Named("service-port") Integer port, + Initialization init, + MetricsServer metricsServer, + IndexOpsService opsService, + IndexQueryService indexQueryService, + SearchIndex searchIndex + ) + { + super(ip, port, init, metricsServer); + this.opsService = opsService; + this.searchIndex = searchIndex; + + final Gson gson = GsonFactory.get(); + + this.init = init; + + Spark.post("/search/", indexQueryService::search, gson::toJson); + + Spark.post("/ops/repartition", opsService::repartitionEndpoint); + Spark.post("/ops/reindex", opsService::reindexEndpoint); + + get("/is-blocked", this::isBlocked, gson::toJson); + + Schedulers.newThread().scheduleDirect(this::initialize, 1, TimeUnit.MICROSECONDS); + } + + private Object isBlocked(Request request, Response response) { + return !initialized || opsService.isBusy(); + } + + volatile boolean initialized = false; + public void initialize() { + if (!initialized) { + init.waitReady(); + searchIndex.init(); + initialized = true; + } + + } + + +} + + diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/IndexServicesFactory.java b/services-core/index-service/src/main/java/nu/marginalia/index/IndexServicesFactory.java similarity index 68% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/IndexServicesFactory.java rename to services-core/index-service/src/main/java/nu/marginalia/index/IndexServicesFactory.java index dcc00e34..9b33cb8a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/IndexServicesFactory.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/IndexServicesFactory.java @@ -1,26 +1,17 @@ -package nu.marginalia.wmsa.edge.index; +package nu.marginalia.index; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.inject.name.Named; -import lombok.SneakyThrows; -import nu.marginalia.util.array.LongArray; -import nu.marginalia.util.dict.DictionaryMap; -import nu.marginalia.wmsa.edge.index.lexicon.KeywordLexicon; -import nu.marginalia.wmsa.edge.index.lexicon.KeywordLexiconReadOnlyView; -import nu.marginalia.wmsa.edge.index.lexicon.journal.KeywordLexiconJournal; -import nu.marginalia.wmsa.edge.index.postings.DomainRankings; -import nu.marginalia.wmsa.edge.index.postings.SearchIndex; -import nu.marginalia.wmsa.edge.index.postings.SearchIndexReader; -import nu.marginalia.wmsa.edge.index.postings.forward.ForwardIndexConverter; -import nu.marginalia.wmsa.edge.index.postings.forward.ForwardIndexReader; -import nu.marginalia.wmsa.edge.index.postings.journal.reader.SearchIndexJournalReaderSingleFile; -import nu.marginalia.wmsa.edge.index.postings.journal.writer.SearchIndexJournalWriterImpl; -import nu.marginalia.wmsa.edge.index.postings.reverse.ReverseIndexConverter; -import nu.marginalia.wmsa.edge.index.postings.reverse.ReverseIndexPrioReader; -import nu.marginalia.wmsa.edge.index.postings.reverse.ReverseIndexPriorityParameters; -import nu.marginalia.wmsa.edge.index.postings.reverse.ReverseIndexReader; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexSearchSetsService; +import nu.marginalia.index.forward.ForwardIndexConverter; +import nu.marginalia.index.forward.ForwardIndexReader; +import nu.marginalia.index.journal.reader.IndexJournalReaderSingleCompressedFile; +import nu.marginalia.index.reverse.ReverseIndexConverter; +import nu.marginalia.index.reverse.ReverseIndexPrioReader; +import nu.marginalia.index.reverse.ReverseIndexPriorityParameters; +import nu.marginalia.index.reverse.ReverseIndexReader; +import nu.marginalia.ranking.DomainRankings; +import nu.marginalia.index.index.SearchIndexReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,7 +29,6 @@ public class IndexServicesFactory { private final Logger logger = LoggerFactory.getLogger(getClass()); private final PartitionedDataFile writerIndexFile; - private final RootDataFile keywordLexiconFile; private final PartitionedDataFile fwdIndexDocId; private final PartitionedDataFile fwdIndexDocData; private final PartitionedDataFile revIndexDoc; @@ -47,8 +37,6 @@ public class IndexServicesFactory { private final PartitionedDataFile revPrioIndexDoc; private final PartitionedDataFile revPrioIndexWords; - private volatile static KeywordLexicon keywordLexicon; - private final Path searchSetsBase; int LIVE_PART = 0; @@ -64,7 +52,6 @@ public class IndexServicesFactory { this.tmpFileDir = tmpFileDir; this.writerIndexFile = new PartitionedDataFile(partitionRootSlow, "page-index.dat"); - this.keywordLexiconFile = new RootDataFile(partitionRootSlow, "dictionary.dat"); fwdIndexDocId = new PartitionedDataFile(partitionRootFast, "fwd-doc-id.dat"); fwdIndexDocData = new PartitionedDataFile(partitionRootFast, "fwd-doc-data.dat"); @@ -85,39 +72,19 @@ public class IndexServicesFactory { return searchSetsBase; } - public SearchIndexJournalWriterImpl getIndexWriter(int idx) { - return new SearchIndexJournalWriterImpl(getKeywordLexicon(), writerIndexFile.get(idx)); - } - - @SneakyThrows - public KeywordLexicon getKeywordLexicon() { - if (keywordLexicon == null) { - final var journal = new KeywordLexiconJournal(keywordLexiconFile.get()); - keywordLexicon = new KeywordLexicon(journal, DictionaryMap.create()); - } - return keywordLexicon; - } - - @SneakyThrows - public KeywordLexiconReadOnlyView getDictionaryReader() { - return new KeywordLexiconReadOnlyView(getKeywordLexicon()); - - } - public void convertIndex(DomainRankings domainRankings) throws IOException { convertForwardIndex(domainRankings); convertFullReverseIndex(domainRankings); convertPriorityReverseIndex(domainRankings); - - } private void convertFullReverseIndex(DomainRankings domainRankings) throws IOException { - logger.info("Converting full reverse index"); + var source = writerIndexFile.get(0).toPath(); - var longArray = LongArray.mmapRead(writerIndexFile.get(0).toPath()); - var journalReader = new SearchIndexJournalReaderSingleFile(longArray); + logger.info("Converting full reverse index {}", source); + + var journalReader = new IndexJournalReaderSingleCompressedFile(source); var converter = new ReverseIndexConverter(tmpFileDir, journalReader, domainRankings, @@ -131,11 +98,11 @@ public class IndexServicesFactory { private void convertPriorityReverseIndex(DomainRankings domainRankings) throws IOException { - logger.info("Converting priority reverse index"); + var source = writerIndexFile.get(0).toPath(); - var longArray = LongArray.mmapRead(writerIndexFile.get(0).toPath()); + logger.info("Converting priority reverse index {}", source); - var journalReader = new SearchIndexJournalReaderSingleFile(longArray, null, ReverseIndexPriorityParameters::filterPriorityRecord); + var journalReader = new IndexJournalReaderSingleCompressedFile(source, null, ReverseIndexPriorityParameters::filterPriorityRecord); var converter = new ReverseIndexConverter(tmpFileDir, journalReader, @@ -149,10 +116,12 @@ public class IndexServicesFactory { } private void convertForwardIndex(DomainRankings domainRankings) throws IOException { - logger.info("Converting forward index data"); - new ForwardIndexConverter( - writerIndexFile.get(0), + var source = writerIndexFile.get(0); + + logger.info("Converting forward index data {}", source); + + new ForwardIndexConverter(source, fwdIndexDocId.get(NEXT_PART).toPath(), fwdIndexDocData.get(NEXT_PART).toPath(), domainRankings) @@ -213,10 +182,6 @@ public class IndexServicesFactory { } } - public SearchIndex createIndexBucket(EdgeIndexSearchSetsService searchSetsService) { - return new SearchIndex(this, new EdgeIndexControl(this, searchSetsService)); - } - public SearchIndexReader getSearchIndexReader() throws IOException { return new SearchIndexReader( getForwardIndexReader(), diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/IndexTablesModule.java b/services-core/index-service/src/main/java/nu/marginalia/index/IndexTablesModule.java new file mode 100644 index 00000000..c26ca5e3 --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/IndexTablesModule.java @@ -0,0 +1,20 @@ +package nu.marginalia.index; + +import com.google.inject.AbstractModule; +import com.google.inject.name.Names; +import nu.marginalia.WmsaHome; + +import java.nio.file.Path; + +public class IndexTablesModule extends AbstractModule { + + public void configure() { + bind(Path.class).annotatedWith(Names.named("partition-root-slow")).toInstance(WmsaHome.getDisk("index-write")); + bind(Path.class).annotatedWith(Names.named("partition-root-fast")).toInstance(WmsaHome.getDisk("index-read")); + + bind(Path.class).annotatedWith(Names.named("partition-root-slow-tmp")).toInstance(WmsaHome.getDisk("tmp-slow")); + bind(Path.class).annotatedWith(Names.named("tmp-file-dir")).toInstance(WmsaHome.getDisk("tmp-fast")); + + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/config/RankingSettings.java b/services-core/index-service/src/main/java/nu/marginalia/index/config/RankingSettings.java similarity index 90% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/config/RankingSettings.java rename to services-core/index-service/src/main/java/nu/marginalia/index/config/RankingSettings.java index 1c71e544..a755a480 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/config/RankingSettings.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/config/RankingSettings.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.config; +package nu.marginalia.index.config; import lombok.ToString; import org.yaml.snakeyaml.Yaml; @@ -6,7 +6,6 @@ import org.yaml.snakeyaml.Yaml; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; @ToString public class RankingSettings { diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/config/RankingSettingsEntry.java b/services-core/index-service/src/main/java/nu/marginalia/index/config/RankingSettingsEntry.java new file mode 100644 index 00000000..7723e3ff --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/config/RankingSettingsEntry.java @@ -0,0 +1,11 @@ +package nu.marginalia.index.config; + +import java.util.List; + +public class RankingSettingsEntry { + /** Bias the ranking toward these domains */ + public List domains; + + /** Number of domains to include in ranking */ + public int max; +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndex.java b/services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndex.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndex.java rename to services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndex.java index 72c0e13f..b5b4677c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndex.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndex.java @@ -1,11 +1,14 @@ -package nu.marginalia.wmsa.edge.index.postings; +package nu.marginalia.index.index; -import nu.marginalia.wmsa.edge.index.EdgeIndexControl; -import nu.marginalia.wmsa.edge.index.IndexServicesFactory; -import nu.marginalia.wmsa.edge.index.query.IndexQuery; -import nu.marginalia.wmsa.edge.index.query.IndexQueryParams; -import nu.marginalia.wmsa.edge.index.query.IndexResultDomainDeduplicator; -import nu.marginalia.wmsa.edge.index.query.filter.QueryFilterStepFromPredicate; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import nu.marginalia.index.IndexServicesFactory; +import nu.marginalia.index.query.IndexQuery; +import nu.marginalia.index.query.IndexQueryBuilder; +import nu.marginalia.index.results.IndexResultDomainDeduplicator; +import nu.marginalia.index.query.IndexQueryParams; +import nu.marginalia.index.query.filter.QueryFilterStepFromPredicate; +import nu.marginalia.index.svc.IndexSearchSetsService; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,6 +20,7 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.LongPredicate; +@Singleton public class SearchIndex { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -27,11 +31,12 @@ public class SearchIndex { @NotNull private final IndexServicesFactory servicesFactory; - private final EdgeIndexControl indexControl; + private IndexSearchSetsService searchSetsService; - public SearchIndex(@NotNull IndexServicesFactory servicesFactory, EdgeIndexControl indexControl) { + @Inject + public SearchIndex(@NotNull IndexServicesFactory servicesFactory, IndexSearchSetsService searchSetsService) { this.servicesFactory = servicesFactory; - this.indexControl = indexControl; + this.searchSetsService = searchSetsService; } public void init() { @@ -39,7 +44,7 @@ public class SearchIndex { try { lock.lock(); - logger.info("Initializing bucket"); + logger.info("Initializing index"); if (indexReader == null) { indexReader = servicesFactory.getSearchIndexReader(); @@ -55,13 +60,14 @@ public class SearchIndex { public boolean switchIndex() throws IOException { - indexControl.regenerateIndex(); + servicesFactory.convertIndex(searchSetsService.getDomainRankings()); + System.gc(); Lock lock = indexReplacementLock.writeLock(); try { lock.lock(); - indexControl.switchIndexFiles(); + servicesFactory.switchFilesJob().call(); indexReader = servicesFactory.getSearchIndexReader(); } @@ -80,7 +86,7 @@ public class SearchIndex { return indexReader != null; } - public IndexQuery getQuery(EdgeIndexQuerySearchTerms terms, IndexQueryParams params, LongPredicate includePred) { + public IndexQuery createQuery(SearchIndexSearchTerms terms, IndexQueryParams params, LongPredicate includePred) { if (null == indexReader) { logger.warn("Index reader not ready"); @@ -89,7 +95,7 @@ public class SearchIndex { final int[] orderedIncludes = terms.sortedDistinctIncludes(this::compareKeywords); - SearchIndexReader.IndexQueryBuilder query = + IndexQueryBuilder query = switch(params.queryStrategy()) { case SENTENCE -> indexReader.findWordAsSentence(orderedIncludes); case TOPIC, REQUIRE_FIELD_SITE, REQUIRE_FIELD_TITLE, REQUIRE_FIELD_SUBJECT @@ -101,7 +107,7 @@ public class SearchIndex { return new IndexQuery(Collections.emptyList()); } - query.addInclusionFilter(new QueryFilterStepFromPredicate(includePred)); + query = query.addInclusionFilter(new QueryFilterStepFromPredicate(includePred)); for (int i = 0; i < orderedIncludes.length; i++) { query = query.also(orderedIncludes[i]); @@ -113,9 +119,9 @@ public class SearchIndex { // Run these last, as they'll worst-case cause as many page faults as there are // items in the buffer - query.addInclusionFilter(indexReader.filterForParams(params)); - - return query.build(); + return query + .addInclusionFilter(indexReader.filterForParams(params)) + .build(); } private int compareKeywords(int a, int b) { @@ -128,12 +134,6 @@ public class SearchIndex { public IndexQuery getDomainQuery(int wordId, IndexResultDomainDeduplicator localFilter) { throw new UnsupportedOperationException(""); // TBI - /* - var query = indexReader.findDomain(wordId); - - query.addInclusionFilter(new QueryFilterStepFromPredicate(localFilter::filterRawValue)); - - return query;*/ } /** Replaces the values of ids with their associated metadata, or 0L if absent */ diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexQueryBuilder.java b/services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexQueryBuilder.java new file mode 100644 index 00000000..89160aae --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexQueryBuilder.java @@ -0,0 +1,42 @@ +package nu.marginalia.index.index; + +import nu.marginalia.index.query.IndexQuery; +import nu.marginalia.index.query.IndexQueryBuilder; +import nu.marginalia.index.query.filter.QueryFilterStepIf; +import nu.marginalia.index.reverse.ReverseIndexReader; + +public class SearchIndexQueryBuilder implements IndexQueryBuilder { + private final IndexQuery query; + private final ReverseIndexReader reverseIndexReader; + + SearchIndexQueryBuilder(ReverseIndexReader reverseIndexReader, IndexQuery query) { + this.query = query; + this.reverseIndexReader = reverseIndexReader; + } + + public IndexQueryBuilder also(int termId) { + + query.addInclusionFilter(reverseIndexReader.also(termId)); + + return this; + } + + public IndexQueryBuilder not(int termId) { + + query.addInclusionFilter(reverseIndexReader.not(termId)); + + return this; + } + + public IndexQueryBuilder addInclusionFilter(QueryFilterStepIf filterStep) { + + query.addInclusionFilter(filterStep); + + return this; + } + + public IndexQuery build() { + return query; + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndexReader.java b/services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexReader.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndexReader.java rename to services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexReader.java index f19063ca..7836e92c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/SearchIndexReader.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexReader.java @@ -1,15 +1,16 @@ -package nu.marginalia.wmsa.edge.index.postings; +package nu.marginalia.index.index; import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.index.postings.forward.ForwardIndexReader; -import nu.marginalia.wmsa.edge.index.postings.forward.ParamMatchingQueryFilter; -import nu.marginalia.wmsa.edge.index.postings.reverse.ReverseIndexPrioReader; -import nu.marginalia.wmsa.edge.index.postings.reverse.ReverseIndexReader; -import nu.marginalia.wmsa.edge.index.postings.reverse.query.ReverseIndexEntrySourceBehavior; -import nu.marginalia.wmsa.edge.index.query.EntrySource; -import nu.marginalia.wmsa.edge.index.query.IndexQuery; -import nu.marginalia.wmsa.edge.index.query.IndexQueryParams; -import nu.marginalia.wmsa.edge.index.query.filter.QueryFilterStepIf; +import nu.marginalia.index.forward.ForwardIndexReader; +import nu.marginalia.index.forward.ParamMatchingQueryFilter; +import nu.marginalia.index.query.EntrySource; +import nu.marginalia.index.query.IndexQuery; +import nu.marginalia.index.query.IndexQueryBuilder; +import nu.marginalia.index.query.IndexQueryParams; +import nu.marginalia.index.query.filter.QueryFilterStepIf; +import nu.marginalia.index.reverse.ReverseIndexPrioReader; +import nu.marginalia.index.reverse.ReverseIndexReader; +import nu.marginalia.index.reverse.query.ReverseIndexEntrySourceBehavior; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,7 +37,7 @@ public class SearchIndexReader { entrySources.add(reverseIndexReader.documents(wordIdsByFrequency[0], ReverseIndexEntrySourceBehavior.DO_PREFER)); - return new IndexQueryBuilder(new IndexQuery(entrySources)); + return new SearchIndexQueryBuilder(reverseIndexReader, new IndexQuery(entrySources)); } public IndexQueryBuilder findWordAsTopic(int[] wordIdsByFrequency) { @@ -46,7 +47,7 @@ public class SearchIndexReader { entrySources.add(reverseIndexPrioReader.priorityDocuments(wordId)); } - return new IndexQueryBuilder(new IndexQuery(entrySources)); + return new SearchIndexQueryBuilder(reverseIndexReader, new IndexQuery(entrySources)); } public IndexQueryBuilder findWordTopicDynamicMode(int[] wordIdsByFrequency) { @@ -62,13 +63,13 @@ public class SearchIndexReader { entrySources.add(reverseIndexReader.documents(wordIdsByFrequency[0], ReverseIndexEntrySourceBehavior.DO_NOT_PREFER)); - return new IndexQueryBuilder(new IndexQuery(entrySources)); + return new SearchIndexQueryBuilder(reverseIndexReader, new IndexQuery(entrySources)); } QueryFilterStepIf filterForParams(IndexQueryParams params) { return new ParamMatchingQueryFilter(params, forwardIndexReader); } - @SneakyThrows + public long numHits(int word) { return reverseIndexReader.numDocuments(word); } @@ -85,37 +86,4 @@ public class SearchIndexReader { return forwardIndexReader.getDomainId(docId); } - public class IndexQueryBuilder { - private final IndexQuery query; - - IndexQueryBuilder(IndexQuery query) { - this.query = query; - } - - public IndexQueryBuilder also(int termId) { - - query.addInclusionFilter(reverseIndexReader.also(termId)); - - return this; - } - - public IndexQueryBuilder not(int termId) { - - query.addInclusionFilter(reverseIndexReader.not(termId)); - - return this; - } - - public IndexQueryBuilder addInclusionFilter(QueryFilterStepIf filterStep) { - - query.addInclusionFilter(filterStep); - - return this; - } - - public IndexQuery build() { - return query; - } - - } } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/EdgeIndexQuerySearchTerms.java b/services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexSearchTerms.java similarity index 76% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/EdgeIndexQuerySearchTerms.java rename to services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexSearchTerms.java index df4ff2c7..26fe36f5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/EdgeIndexQuerySearchTerms.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/index/SearchIndexSearchTerms.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.edge.index.postings; +package nu.marginalia.index.index; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntComparator; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -public record EdgeIndexQuerySearchTerms(IntList includes, IntList excludes, IntList priority) { - public EdgeIndexQuerySearchTerms() { +public record SearchIndexSearchTerms(IntList includes, IntList excludes, IntList priority) { + public SearchIndexSearchTerms() { this(IntList.of(), IntList.of(), IntList.of()); } diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/results/IndexMetadataService.java b/services-core/index-service/src/main/java/nu/marginalia/index/results/IndexMetadataService.java new file mode 100644 index 00000000..af6f100d --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/results/IndexMetadataService.java @@ -0,0 +1,26 @@ +package nu.marginalia.index.results; + +import com.google.inject.Inject; +import nu.marginalia.index.index.SearchIndex; + +public class IndexMetadataService { + private final SearchIndex index; + + @Inject + public IndexMetadataService(SearchIndex index) { + this.index = index; + } + + public long getDocumentMetadata(long urlId) { + return index.getDocumentMetadata(urlId); + } + + public int getDomainId(long urlId) { + return index.getDomainId(urlId); + } + + public long[] getTermMetadata(int termId, long[] docIdsAll) { + return index.getTermMetadata(termId, docIdsAll); + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexResultDomainDeduplicator.java b/services-core/index-service/src/main/java/nu/marginalia/index/results/IndexResultDomainDeduplicator.java similarity index 70% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexResultDomainDeduplicator.java rename to services-core/index-service/src/main/java/nu/marginalia/index/results/IndexResultDomainDeduplicator.java index 40ed46fc..7f2b69b6 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/query/IndexResultDomainDeduplicator.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/results/IndexResultDomainDeduplicator.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.index.query; +package nu.marginalia.index.results; import gnu.trove.map.TLongIntMap; import gnu.trove.map.hash.TLongIntHashMap; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultItem; +import nu.marginalia.index.client.model.results.EdgeSearchResultItem; public class IndexResultDomainDeduplicator { final TLongIntMap resultsByRankingId = new TLongIntHashMap(2048, 0.5f, -1, 0); @@ -12,20 +12,6 @@ public class IndexResultDomainDeduplicator { this.limitByDomain = limitByDomain; } - public boolean filterRawValue(long value) { - int rankingId = (int) (value >>> 32); - - if (rankingId == Integer.MAX_VALUE) { - return true; - } - - return resultsByRankingId.get(getKey(rankingId)) <= limitByDomain; - } - - long getKey(int rankingId) { - return rankingId; - } - public boolean test(long value) { int ranking = (int) (value >>> 32); if (ranking == Integer.MAX_VALUE) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/IndexResultValuator.java b/services-core/index-service/src/main/java/nu/marginalia/index/results/IndexResultValuator.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/IndexResultValuator.java rename to services-core/index-service/src/main/java/nu/marginalia/index/results/IndexResultValuator.java index da2e92f8..a6eb43bc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/postings/IndexResultValuator.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/results/IndexResultValuator.java @@ -1,23 +1,25 @@ -package nu.marginalia.wmsa.edge.index.postings; +package nu.marginalia.index.results; import gnu.trove.list.TLongList; import gnu.trove.map.hash.TObjectIntHashMap; import gnu.trove.set.hash.TLongHashSet; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordMetadata; -import nu.marginalia.wmsa.edge.index.model.QueryStrategy; -import nu.marginalia.wmsa.edge.index.query.IndexQueryParams; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultItem; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultKeywordScore; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSubquery; +import nu.marginalia.index.svc.SearchTermsService; +import nu.marginalia.model.crawl.EdgePageWordFlags; +import nu.marginalia.model.idx.EdgePageWordMetadata; +import nu.marginalia.index.query.limit.QueryStrategy; +import nu.marginalia.index.client.model.results.EdgeSearchResultItem; +import nu.marginalia.index.client.model.results.EdgeSearchResultKeywordScore; +import nu.marginalia.index.client.model.query.EdgeSearchSubquery; +import nu.marginalia.index.query.IndexQueryParams; import java.util.List; -import java.util.Objects; +import java.util.OptionalInt; public class IndexResultValuator { private final IndexMetadataService metadataService; + private final SearchTermsService searchTermsSvc; private final List> searchTermVariants; private final IndexQueryParams queryParams; private final int[] termIdsAll; @@ -27,21 +29,23 @@ public class IndexResultValuator { private final TObjectIntHashMap termToId = new TObjectIntHashMap<>(10, 0.75f, -1); private final TermMetadata termMetadata; - public IndexResultValuator(SearchIndexControl indexes, TLongList results, List subqueries, IndexQueryParams queryParams) { - this.metadataService = new IndexMetadataService(indexes); + public IndexResultValuator(SearchTermsService searchTermsSvc, + IndexMetadataService metadataService, + TLongList results, + List subqueries, + IndexQueryParams queryParams) { + this.searchTermsSvc = searchTermsSvc; this.searchTermVariants = subqueries.stream().map(sq -> sq.searchTermsInclude).distinct().toList(); this.queryParams = queryParams; + this.metadataService = metadataService; - var lexiconReader = Objects.requireNonNull(indexes.getLexiconReader()); IntArrayList termIdsList = new IntArrayList(); searchTermVariants.stream().flatMap(List::stream).distinct().forEach(term -> { - int id = lexiconReader.get(term); - - if (id >= 0) { + searchTermsSvc.lookUpWord(term).ifPresent(id -> { termIdsList.add(id); termToId.put(term, id); - } + }); }); final long[] resultsArray = results.toArray(); @@ -53,8 +57,9 @@ public class IndexResultValuator { subqueries.stream() .flatMap(sq -> sq.searchTermsPriority.stream()) .distinct() - .mapToInt(lexiconReader::get) - .filter(id -> id >= 0) + .map(searchTermsSvc::lookUpWord) + .filter(OptionalInt::isPresent) + .mapToInt(OptionalInt::getAsInt) .toArray(); resultsWithPriorityTerms = new TLongHashSet(results.size()); diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexOpsService.java b/services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexOpsService.java new file mode 100644 index 00000000..36561dc7 --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexOpsService.java @@ -0,0 +1,77 @@ +package nu.marginalia.index.svc; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import nu.marginalia.index.index.SearchIndex; +import spark.Request; +import spark.Response; +import spark.Spark; + +import javax.annotation.CheckReturnValue; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.locks.ReentrantLock; + +@Singleton +public class IndexOpsService { + private final ReentrantLock opsLock = new ReentrantLock(); + + private final SearchIndex index; + private final IndexSearchSetsService searchSetService; + + @Inject + public IndexOpsService(SearchIndex index, + IndexSearchSetsService searchSetService) { + this.index = index; + this.searchSetService = searchSetService; + } + + public boolean isBusy() { + return opsLock.isLocked(); + } + + public Object repartitionEndpoint(Request request, Response response) throws Exception { + + if (!run(searchSetService::recalculateAll)) { + Spark.halt(503, "Operations busy"); + } + return "OK"; + } + + public Object reindexEndpoint(Request request, Response response) throws Exception { + if (!run(index::switchIndex).isPresent()) { + Spark.halt(503, "Operations busy"); + } + return "OK"; + } + + + + @CheckReturnValue + private Optional run(Callable c) throws Exception { + if (!opsLock.tryLock()) + return Optional.empty(); + try { + return Optional.of(c.call()); + } + finally { + opsLock.unlock(); + } + } + + + @CheckReturnValue + private boolean run(Runnable r) { + if (!opsLock.tryLock()) + return false; + try { + r.run(); + return true; + } + finally { + opsLock.unlock(); + } + } + +} + diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexQueryService.java b/services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexQueryService.java new file mode 100644 index 00000000..6a46554c --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexQueryService.java @@ -0,0 +1,294 @@ +package nu.marginalia.index.svc; + +import com.google.gson.Gson; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import gnu.trove.list.TLongList; +import gnu.trove.list.array.TLongArrayList; +import gnu.trove.set.hash.TLongHashSet; +import io.prometheus.client.Counter; +import io.prometheus.client.Gauge; +import io.prometheus.client.Histogram; +import nu.marginalia.index.client.model.results.EdgeSearchResultItem; +import nu.marginalia.index.client.model.results.EdgeSearchResultSet; +import nu.marginalia.index.client.model.query.EdgeSearchSpecification; +import nu.marginalia.index.client.model.query.EdgeSearchSubquery; +import nu.marginalia.array.buffer.LongQueryBuffer; +import nu.marginalia.index.index.SearchIndex; +import nu.marginalia.index.index.SearchIndexSearchTerms; +import nu.marginalia.index.results.IndexMetadataService; +import nu.marginalia.index.searchset.SearchSet; +import nu.marginalia.index.results.IndexResultValuator; +import nu.marginalia.index.query.IndexQuery; +import nu.marginalia.index.results.IndexResultDomainDeduplicator; +import nu.marginalia.index.query.IndexQueryParams; +import nu.marginalia.index.query.IndexSearchBudget; +import nu.marginalia.index.svc.searchset.SmallSearchSet; +import nu.marginalia.model.gson.GsonFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; +import spark.HaltException; +import spark.Request; +import spark.Response; +import spark.Spark; + +import java.util.ArrayList; +import java.util.List; + +import static java.util.Comparator.comparingDouble; + +@Singleton +public class IndexQueryService { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final Marker queryMarker = MarkerFactory.getMarker("QUERY"); + + + private static final Counter wmsa_edge_index_query_timeouts = Counter.build().name("wmsa_edge_index_query_timeouts").help("-").register(); + private static final Gauge wmsa_edge_index_query_cost = Gauge.build().name("wmsa_edge_index_query_cost").help("-").register(); + private static final Histogram wmsa_edge_index_query_time = Histogram.build().name("wmsa_edge_index_query_time").linearBuckets(25/1000., 25/1000., 15).help("-").register(); + + private final Gson gson = GsonFactory.get(); + + private final SearchIndex index; + private final IndexSearchSetsService searchSetsService; + + private final IndexMetadataService metadataService; + private final SearchTermsService searchTermsSvc; + + + @Inject + public IndexQueryService(SearchIndex index, + IndexSearchSetsService searchSetsService, + IndexMetadataService metadataService, + SearchTermsService searchTerms) { + this.index = index; + this.searchSetsService = searchSetsService; + this.metadataService = metadataService; + this.searchTermsSvc = searchTerms; + } + + public Object search(Request request, Response response) { + String json = request.body(); + EdgeSearchSpecification specsSet = gson.fromJson(json, EdgeSearchSpecification.class); + + try { + return wmsa_edge_index_query_time.time(() -> { + var params = new SearchParameters(specsSet, getSearchSet(specsSet)); + + List results = executeSearch(params); + logger.info(queryMarker, "Index Result Count: {}", results.size()); + + wmsa_edge_index_query_cost.set(params.getDataCost()); + if (!params.hasTimeLeft()) { + wmsa_edge_index_query_timeouts.inc(); + } + + return new EdgeSearchResultSet(results); + }); + } + catch (HaltException ex) { + logger.warn("Halt", ex); + throw ex; + } + catch (Exception ex) { + logger.info("Error during search {}({}) (query: {})", ex.getClass().getSimpleName(), ex.getMessage(), json); + logger.info("Error", ex); + Spark.halt(500, "Error"); + return null; + } + } + + private SearchSet getSearchSet(EdgeSearchSpecification specsSet) { + if (specsSet.domains != null && !specsSet.domains.isEmpty()) { + return new SmallSearchSet(specsSet.domains); + } + + return searchSetsService.getSearchSetByName(specsSet.searchSetIdentifier); + } + + private List executeSearch(SearchParameters params) { + var resultIds = evaluateSubqueries(params); + + var resultItems = calculateResultScores(params, resultIds); + + return selectBestResults(params, resultItems); + } + + private TLongList evaluateSubqueries(SearchParameters params) { + final TLongList results = new TLongArrayList(params.fetchSize); + + for (var sq : params.subqueries) { + final SearchIndexSearchTerms searchTerms = searchTermsSvc.getSearchTerms(sq); + + if (searchTerms.isEmpty()) { + continue; + } + + results.addAll( + executeSubquery(searchTerms, params) + ); + + if (!params.hasTimeLeft()) { + logger.info("Query timed out {}, ({}), -{}", + sq.searchTermsInclude, sq.searchTermsAdvice, sq.searchTermsExclude); + break; + } + } + + return results; + } + + private TLongArrayList executeSubquery(SearchIndexSearchTerms terms, SearchParameters params) + { + final TLongArrayList results = new TLongArrayList(params.fetchSize); + final LongQueryBuffer buffer = new LongQueryBuffer(params.fetchSize); + + IndexQuery query = params.createIndexQuery(index, terms); + + while (query.hasMore() + && results.size() < params.fetchSize + && params.budget.hasTimeLeft()) + { + buffer.reset(); + query.getMoreResults(buffer); + + for (int i = 0; i < buffer.size() && results.size() < params.fetchSize; i++) { + results.add(buffer.data[i]); + } + } + + params.dataCost += query.dataCost(); + + return results; + } + + private ArrayList calculateResultScores(SearchParameters params, TLongList results) { + + final var evaluator = new IndexResultValuator( + searchTermsSvc, + metadataService, + results, + params.subqueries, + params.queryParams); + + ArrayList items = new ArrayList<>(results.size()); + ArrayList refusedItems = new ArrayList<>(results.size()); + + // Sorting the result ids results in better paging characteristics + results.sort(); + + results.forEach(id -> { + var item = evaluator.evaluateResult(id); + + // Score value is zero when the best params variant consists of low-value terms that are just scattered + // throughout the document, with no indicators of importance associated with them. + if (item.getScoreValue() < 0) { + items.add(item); + } + else { + refusedItems.add(item); + } + + return true; + }); + + if (items.isEmpty()) { + items.addAll(refusedItems); + } + + return items; + } + + private List selectBestResults(SearchParameters params, List results) { + + var domainCountFilter = new IndexResultDomainDeduplicator(params.limitByDomain); + + results.sort(comparingDouble(EdgeSearchResultItem::getScore) + .thenComparingInt(EdgeSearchResultItem::getRanking) + .thenComparingInt(EdgeSearchResultItem::getUrlIdInt)); + + List resultsList = new ArrayList<>(results.size()); + + for (var item : results) { + if (domainCountFilter.test(item)) { + resultsList.add(item); + } + } + + if (resultsList.size() > params.limitTotal) { + // This can't be made a stream limit() operation because we need domainCountFilter + // to run over the entire list to provide accurate statistics + + resultsList.subList(params.limitTotal, resultsList.size()).clear(); + } + + // populate results with the total number of results encountered from + // the same domain so this information can be presented to the user + for (var result : resultsList) { + result.resultsFromDomain = domainCountFilter.getCount(result); + } + + return resultsList; + } + +} + +class SearchParameters { + /** This is how many results matching the keywords we'll try to get + before evaluating them for the best result. */ + final int fetchSize; + final IndexSearchBudget budget; + final List subqueries; + final IndexQueryParams queryParams; + + final int limitByDomain; + final int limitTotal; + + // mutable: + + /** An estimate of how much data has been read */ + long dataCost = 0; + + /** A set of id:s considered during each subquery, + * for deduplication + */ + final TLongHashSet consideredUrlIds; + + public SearchParameters(EdgeSearchSpecification specsSet, SearchSet searchSet) { + var limits = specsSet.queryLimits; + + this.fetchSize = limits.fetchSize(); + this.budget = new IndexSearchBudget(limits.timeoutMs()); + this.subqueries = specsSet.subqueries; + this.limitByDomain = limits.resultsByDomain(); + this.limitTotal = limits.resultsTotal(); + + this.consideredUrlIds = new TLongHashSet(fetchSize * 4); + + queryParams = new IndexQueryParams( + specsSet.quality, + specsSet.year, + specsSet.size, + specsSet.rank, + searchSet, + specsSet.queryStrategy); + } + + IndexQuery createIndexQuery(SearchIndex index, SearchIndexSearchTerms terms) { + return index.createQuery(terms, queryParams, consideredUrlIds::add); + } + + boolean hasTimeLeft() { + return budget.hasTimeLeft(); + } + + long getDataCost() { + return dataCost; + } + +} + diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexSearchSetsService.java b/services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexSearchSetsService.java similarity index 76% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexSearchSetsService.java rename to services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexSearchSetsService.java index 5e9a3114..68012330 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/EdgeIndexSearchSetsService.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/svc/IndexSearchSetsService.java @@ -1,28 +1,28 @@ -package nu.marginalia.wmsa.edge.index.svc; +package nu.marginalia.index.svc; import com.google.inject.Inject; import com.google.inject.Singleton; import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.index.ranking.ReversePageRank; -import nu.marginalia.wmsa.edge.index.ranking.StandardPageRank; -import nu.marginalia.wmsa.edge.index.ranking.accumulator.RankingResultHashMapAccumulator; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcher; -import nu.marginalia.wmsa.edge.index.ranking.accumulator.RankingResultBitSetAccumulator; -import nu.marginalia.wmsa.edge.index.IndexServicesFactory; -import nu.marginalia.wmsa.edge.index.config.RankingSettings; -import nu.marginalia.wmsa.edge.index.postings.DomainRankings; -import nu.marginalia.wmsa.edge.index.ranking.data.RankingDomainFetcherForSimilarityData; -import nu.marginalia.wmsa.edge.index.svc.searchset.RankingSearchSet; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSet; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSetAny; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSetIdentifier; +import nu.marginalia.index.IndexServicesFactory; +import nu.marginalia.index.searchset.SearchSet; +import nu.marginalia.ranking.ReversePageRank; +import nu.marginalia.ranking.StandardPageRank; +import nu.marginalia.ranking.accumulator.RankingResultBitSetAccumulator; +import nu.marginalia.ranking.accumulator.RankingResultHashMapAccumulator; +import nu.marginalia.ranking.data.RankingDomainFetcher; +import nu.marginalia.ranking.data.RankingDomainFetcherForSimilarityData; +import nu.marginalia.index.svc.searchset.RankingSearchSet; +import nu.marginalia.index.svc.searchset.SearchSetAny; +import nu.marginalia.index.config.RankingSettings; +import nu.marginalia.ranking.DomainRankings; +import nu.marginalia.index.client.model.query.SearchSetIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; @Singleton -public class EdgeIndexSearchSetsService { +public class IndexSearchSetsService { private final Logger logger = LoggerFactory.getLogger(getClass()); private final RankingDomainFetcher rankingDomains; private final RankingDomainFetcher similarityDomains; @@ -39,10 +39,10 @@ public class EdgeIndexSearchSetsService { private volatile DomainRankings domainRankings = new DomainRankings(); @Inject - public EdgeIndexSearchSetsService(RankingDomainFetcher rankingDomains, - RankingDomainFetcherForSimilarityData similarityDomains, - RankingSettings rankingSettings, - IndexServicesFactory servicesFactory) throws IOException { + public IndexSearchSetsService(RankingDomainFetcher rankingDomains, + RankingDomainFetcherForSimilarityData similarityDomains, + RankingSettings rankingSettings, + IndexServicesFactory servicesFactory) throws IOException { this.rankingDomains = rankingDomains; diff --git a/services-core/index-service/src/main/java/nu/marginalia/index/svc/SearchTermsService.java b/services-core/index-service/src/main/java/nu/marginalia/index/svc/SearchTermsService.java new file mode 100644 index 00000000..0ac7cced --- /dev/null +++ b/services-core/index-service/src/main/java/nu/marginalia/index/svc/SearchTermsService.java @@ -0,0 +1,67 @@ +package nu.marginalia.index.svc; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import nu.marginalia.dict.OffHeapDictionaryHashMap; +import nu.marginalia.index.client.model.query.EdgeSearchSubquery; +import nu.marginalia.index.index.SearchIndexSearchTerms; +import nu.marginalia.lexicon.KeywordLexiconReadOnlyView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.OptionalInt; + +@Singleton +public class SearchTermsService { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final KeywordLexiconReadOnlyView lexicon; + + @Inject + public SearchTermsService(KeywordLexiconReadOnlyView lexicon) { + this.lexicon = lexicon; + } + + public SearchIndexSearchTerms getSearchTerms(EdgeSearchSubquery request) { + final IntList excludes = new IntArrayList(); + final IntList includes = new IntArrayList(); + final IntList priority = new IntArrayList(); + + for (var include : request.searchTermsInclude) { + var word = lookUpWord(include); + if (word.isEmpty()) { + logger.info("Unknown search term: " + include); + return new SearchIndexSearchTerms(); + } + includes.add(word.getAsInt()); + } + + for (var advice : request.searchTermsAdvice) { + var word = lookUpWord(advice); + if (word.isEmpty()) { + logger.info("Unknown search term: " + advice); + return new SearchIndexSearchTerms(); + } + includes.add(word.getAsInt()); + } + + for (var exclude : request.searchTermsExclude) { + lookUpWord(exclude).ifPresent(excludes::add); + } + for (var exclude : request.searchTermsPriority) { + lookUpWord(exclude).ifPresent(priority::add); + } + + return new SearchIndexSearchTerms(includes, excludes, priority); + } + + + public OptionalInt lookUpWord(String s) { + int ret = lexicon.get(s); + if (ret == OffHeapDictionaryHashMap.NO_VALUE) { + return OptionalInt.empty(); + } + return OptionalInt.of(ret); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/RankingSearchSet.java b/services-core/index-service/src/main/java/nu/marginalia/index/svc/searchset/RankingSearchSet.java similarity index 79% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/RankingSearchSet.java rename to services-core/index-service/src/main/java/nu/marginalia/index/svc/searchset/RankingSearchSet.java index 7ce90c73..22ceab15 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/RankingSearchSet.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/svc/searchset/RankingSearchSet.java @@ -1,6 +1,10 @@ -package nu.marginalia.wmsa.edge.index.svc.searchset; +package nu.marginalia.index.svc.searchset; +import nu.marginalia.index.client.model.query.SearchSetIdentifier; +import nu.marginalia.index.searchset.SearchSet; import org.roaringbitmap.RoaringBitmap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.DataInputStream; import java.io.DataOutputStream; @@ -16,6 +20,7 @@ import java.nio.file.StandardOpenOption; * */ public class RankingSearchSet implements SearchSet { + private final Logger logger = LoggerFactory.getLogger(getClass()); private final RoaringBitmap set; public final SearchSetIdentifier identifier; public final Path source; @@ -36,6 +41,10 @@ public class RankingSearchSet implements SearchSet { else { set = load(source); } + + if (set.isEmpty()) { + logger.warn("Search set {} is empty", identifier); + } } private static RoaringBitmap load(Path source) throws IOException { @@ -53,7 +62,9 @@ public class RankingSearchSet implements SearchSet { @Override public boolean contains(int urlId) { - return set.contains(urlId); + // Fallback on allow-all if no items are in set + + return set.contains(urlId) || set.isEmpty(); } public void write() throws IOException { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SearchSetAny.java b/services-core/index-service/src/main/java/nu/marginalia/index/svc/searchset/SearchSetAny.java similarity index 71% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SearchSetAny.java rename to services-core/index-service/src/main/java/nu/marginalia/index/svc/searchset/SearchSetAny.java index dabebb8a..63b433ac 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SearchSetAny.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/svc/searchset/SearchSetAny.java @@ -1,4 +1,6 @@ -package nu.marginalia.wmsa.edge.index.svc.searchset; +package nu.marginalia.index.svc.searchset; + +import nu.marginalia.index.searchset.SearchSet; public class SearchSetAny implements SearchSet { @Override diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SmallSearchSet.java b/services-core/index-service/src/main/java/nu/marginalia/index/svc/searchset/SmallSearchSet.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SmallSearchSet.java rename to services-core/index-service/src/main/java/nu/marginalia/index/svc/searchset/SmallSearchSet.java index 8f1e8e9a..8d261df8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/index/svc/searchset/SmallSearchSet.java +++ b/services-core/index-service/src/main/java/nu/marginalia/index/svc/searchset/SmallSearchSet.java @@ -1,6 +1,7 @@ -package nu.marginalia.wmsa.edge.index.svc.searchset; +package nu.marginalia.index.svc.searchset; import gnu.trove.set.hash.TIntHashSet; +import nu.marginalia.index.searchset.SearchSet; import java.util.Arrays; import java.util.Collection; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/model/EdgePageDocumentsMetadataTest.java b/services-core/index-service/src/test/java/nu/marginalia/index/model/EdgePageDocumentsMetadataTest.java similarity index 94% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/model/EdgePageDocumentsMetadataTest.java rename to services-core/index-service/src/test/java/nu/marginalia/index/model/EdgePageDocumentsMetadataTest.java index a3552a85..0bfe142f 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/model/EdgePageDocumentsMetadataTest.java +++ b/services-core/index-service/src/test/java/nu/marginalia/index/model/EdgePageDocumentsMetadataTest.java @@ -1,6 +1,8 @@ -package nu.marginalia.wmsa.edge.index.model; +package nu.marginalia.index.model; +import nu.marginalia.model.crawl.EdgePageDocumentFlags; +import nu.marginalia.model.idx.EdgePageDocumentsMetadata; import org.junit.jupiter.api.Test; import java.util.EnumSet; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/model/RankingSettingsTest.java b/services-core/index-service/src/test/java/nu/marginalia/index/model/RankingSettingsTest.java similarity index 93% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/model/RankingSettingsTest.java rename to services-core/index-service/src/test/java/nu/marginalia/index/model/RankingSettingsTest.java index a8a0c17f..f49d13d8 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/model/RankingSettingsTest.java +++ b/services-core/index-service/src/test/java/nu/marginalia/index/model/RankingSettingsTest.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.index.model; +package nu.marginalia.index.model; -import nu.marginalia.wmsa.edge.index.config.RankingSettings; +import nu.marginalia.index.config.RankingSettings; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -10,7 +10,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; class RankingSettingsTest { diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/EdgeIndexIntegrationTest.java b/services-core/index-service/src/test/java/nu/marginalia/index/service/EdgeIndexIntegrationTest.java similarity index 76% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/EdgeIndexIntegrationTest.java rename to services-core/index-service/src/test/java/nu/marginalia/index/service/EdgeIndexIntegrationTest.java index 0ef29e64..01af6363 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/EdgeIndexIntegrationTest.java +++ b/services-core/index-service/src/test/java/nu/marginalia/index/service/EdgeIndexIntegrationTest.java @@ -1,38 +1,12 @@ -package nu.marginalia.wmsa.edge.index.service; +package nu.marginalia.index.service; -import com.google.inject.Guice; -import com.google.inject.Inject; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.edge.index.model.*; -import nu.marginalia.wmsa.edge.index.postings.SearchIndexControl; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntry; -import nu.marginalia.wmsa.edge.index.postings.journal.model.SearchIndexJournalEntryHeader; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexLexiconService; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexOpsService; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexQueryService; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSetIdentifier; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultItem; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSpecification; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSubquery; -import nu.marginalia.wmsa.edge.model.search.domain.SpecificationLimit; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; -import spark.Spark; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumSet; -import java.util.List; -import java.util.stream.IntStream; import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; @Execution(SAME_THREAD) public class EdgeIndexIntegrationTest { +/* FIXME @Inject Initialization initialization; @@ -164,7 +138,7 @@ public class EdgeIndexIntegrationTest { long fullId = id | ((long) (32 - (id % 32)) << 32); - var header = new SearchIndexJournalEntryHeader(factors.length, fullId, new EdgePageDocumentsMetadata(0, 0, 0, id % 5, id, id % 20, (byte) 0).encode()); + var header = new IndexJournalEntryHeader(factors.length, fullId, new EdgePageDocumentsMetadata(0, 0, 0, id % 5, id, id % 20, (byte) 0).encode()); long[] data = new long[factors.length*2]; for (int i = 0; i < factors.length; i++) { @@ -172,12 +146,12 @@ public class EdgeIndexIntegrationTest { data[2*i + 1] = new EdgePageWordMetadata(i, i, i, EnumSet.of(EdgePageWordFlags.Title)).encode(); } - lexiconService.putWords(0, header, new SearchIndexJournalEntry(data)); + lexiconService.putWords(0, header, new IndexJournalEntryData(data)); } public void loadDataWithDomain(int domain, int id) { int[] factors = IntStream.rangeClosed(1, id).filter(v -> (id % v) == 0).toArray(); - var header = new SearchIndexJournalEntryHeader(factors.length, id | ((long) domain << 32), EdgePageDocumentsMetadata.defaultValue()); + var header = new IndexJournalEntryHeader(factors.length, id | ((long) domain << 32), EdgePageDocumentsMetadata.defaultValue()); long[] data = new long[factors.length*2]; for (int i = 0; i < factors.length; i++) { @@ -185,7 +159,7 @@ public class EdgeIndexIntegrationTest { data[2*i + 1] = new EdgePageWordMetadata(i % 20, i, i, EnumSet.of(EdgePageWordFlags.Title)).encode(); } - lexiconService.putWords(0, header, new SearchIndexJournalEntry(data)); + lexiconService.putWords(0, header, new IndexJournalEntryData(data)); } - +*/ } diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/EdgeIndexIntegrationTestModule.java b/services-core/index-service/src/test/java/nu/marginalia/index/service/EdgeIndexIntegrationTestModule.java similarity index 73% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/EdgeIndexIntegrationTestModule.java rename to services-core/index-service/src/test/java/nu/marginalia/index/service/EdgeIndexIntegrationTestModule.java index aaa44c35..bf816da3 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/EdgeIndexIntegrationTestModule.java +++ b/services-core/index-service/src/test/java/nu/marginalia/index/service/EdgeIndexIntegrationTestModule.java @@ -1,13 +1,13 @@ -package nu.marginalia.wmsa.edge.index.service; +package nu.marginalia.index.service; import com.google.inject.AbstractModule; import com.google.inject.name.Names; -import nu.marginalia.util.test.TestUtil; -import nu.marginalia.wmsa.edge.index.IndexServicesFactory; -import nu.marginalia.wmsa.edge.index.postings.DomainRankings; -import nu.marginalia.wmsa.edge.index.svc.EdgeIndexSearchSetsService; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSetAny; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSetIdentifier; +import nu.marginalia.index.IndexServicesFactory; +import nu.marginalia.ranking.DomainRankings; +import nu.marginalia.index.svc.IndexSearchSetsService; +import nu.marginalia.index.svc.searchset.SearchSetAny; +import nu.marginalia.index.util.TestUtil; +import nu.marginalia.index.client.model.query.SearchSetIdentifier; import org.mockito.Mockito; import java.io.IOException; @@ -46,11 +46,11 @@ public class EdgeIndexIntegrationTestModule extends AbstractModule { slowDir, fastDir )); - EdgeIndexSearchSetsService setsServiceMock = Mockito.mock(EdgeIndexSearchSetsService.class); + IndexSearchSetsService setsServiceMock = Mockito.mock(IndexSearchSetsService.class); when(setsServiceMock.getSearchSetByName(SearchSetIdentifier.NONE)).thenReturn(new SearchSetAny()); when(setsServiceMock.getDomainRankings()).thenReturn(new DomainRankings()); - bind(EdgeIndexSearchSetsService.class).toInstance(setsServiceMock); + bind(IndexSearchSetsService.class).toInstance(setsServiceMock); bind(String.class).annotatedWith(Names.named("service-host")).toInstance("127.0.0.1"); bind(Integer.class).annotatedWith(Names.named("service-port")).toProvider(this::randomPort); diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/DictionaryDataTest.java b/services-core/index-service/src/test/java/nu/marginalia/index/service/util/DictionaryDataTest.java similarity index 91% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/DictionaryDataTest.java rename to services-core/index-service/src/test/java/nu/marginalia/index/service/util/DictionaryDataTest.java index a88715a2..92f134cf 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/DictionaryDataTest.java +++ b/services-core/index-service/src/test/java/nu/marginalia/index/service/util/DictionaryDataTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.service.util; +package nu.marginalia.index.service.util; class DictionaryDataTest { diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/DictionaryHashMapTest.java b/services-core/index-service/src/test/java/nu/marginalia/index/service/util/DictionaryHashMapTest.java similarity index 96% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/DictionaryHashMapTest.java rename to services-core/index-service/src/test/java/nu/marginalia/index/service/util/DictionaryHashMapTest.java index c39d5c03..a290f33f 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/DictionaryHashMapTest.java +++ b/services-core/index-service/src/test/java/nu/marginalia/index/service/util/DictionaryHashMapTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.service.util; +package nu.marginalia.index.service.util; class DictionaryHashMapTest { // diff --git a/services-core/index-service/src/test/java/nu/marginalia/index/service/util/PrimeUtilTest.java b/services-core/index-service/src/test/java/nu/marginalia/index/service/util/PrimeUtilTest.java new file mode 100644 index 00000000..c11d9719 --- /dev/null +++ b/services-core/index-service/src/test/java/nu/marginalia/index/service/util/PrimeUtilTest.java @@ -0,0 +1,30 @@ +package nu.marginalia.index.service.util; + +import nu.marginalia.util.PrimeUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class PrimeUtilTest { + + @Test + void isPrime() { + Assertions.assertTrue(PrimeUtil.isPrime(1)); + Assertions.assertTrue(PrimeUtil.isPrime(2)); + Assertions.assertTrue(PrimeUtil.isPrime(3)); + Assertions.assertFalse(PrimeUtil.isPrime(4)); + Assertions.assertTrue(PrimeUtil.isPrime(5)); + Assertions.assertFalse(PrimeUtil.isPrime(6)); + Assertions.assertTrue(PrimeUtil.isPrime(7)); + Assertions.assertFalse(PrimeUtil.isPrime(8)); + Assertions.assertFalse(PrimeUtil.isPrime(9)); + Assertions.assertFalse(PrimeUtil.isPrime(10)); + Assertions.assertTrue(PrimeUtil.isPrime(11)); + } + + @Test + void nextPrime() { + System.out.println(PrimeUtil.nextPrime(1L<<31, -1)); + System.out.println(PrimeUtil.nextPrime(1L<<31, 1)); + + } +} \ No newline at end of file diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/RandomWriteFunnelTest.java b/services-core/index-service/src/test/java/nu/marginalia/index/service/util/RandomWriteFunnelTest.java similarity index 98% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/RandomWriteFunnelTest.java rename to services-core/index-service/src/test/java/nu/marginalia/index/service/util/RandomWriteFunnelTest.java index deb5c992..c2f907bb 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/service/util/RandomWriteFunnelTest.java +++ b/services-core/index-service/src/test/java/nu/marginalia/index/service/util/RandomWriteFunnelTest.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.index.service.util; +package nu.marginalia.index.service.util; import nu.marginalia.util.RandomWriteFunnel; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/svc/searchset/RankingSearchSetTest.java b/services-core/index-service/src/test/java/nu/marginalia/index/svc/searchset/RankingSearchSetTest.java similarity index 83% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/svc/searchset/RankingSearchSetTest.java rename to services-core/index-service/src/test/java/nu/marginalia/index/svc/searchset/RankingSearchSetTest.java index effa7a1f..214596d3 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/index/svc/searchset/RankingSearchSetTest.java +++ b/services-core/index-service/src/test/java/nu/marginalia/index/svc/searchset/RankingSearchSetTest.java @@ -1,5 +1,6 @@ -package nu.marginalia.wmsa.edge.index.svc.searchset; +package nu.marginalia.index.svc.searchset; +import nu.marginalia.index.client.model.query.SearchSetIdentifier; import org.junit.jupiter.api.Test; import org.roaringbitmap.RoaringBitmap; @@ -7,7 +8,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; class RankingSearchSetTest { diff --git a/services-core/index-service/src/test/java/nu/marginalia/index/util/TestUtil.java b/services-core/index-service/src/test/java/nu/marginalia/index/util/TestUtil.java new file mode 100644 index 00000000..ef80181e --- /dev/null +++ b/services-core/index-service/src/test/java/nu/marginalia/index/util/TestUtil.java @@ -0,0 +1,50 @@ +package nu.marginalia.index.util; + + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; + +public class TestUtil { + private static boolean isTempDir(Path dir) { + return dir.startsWith("/tmp") || dir.toString().contains("tmp"); + } + + public static void clearTempDir(Path dir) { + if (!isTempDir(dir)) { + throw new IllegalArgumentException("Refusing to recursively delete directory with that name"); + } + if (Files.isDirectory(dir)) { + for (File f : dir.toFile().listFiles()) { + File[] files = f.listFiles(); + if (files != null) { + Arrays.stream(files).map(File::toPath).forEach(TestUtil::clearTempDir); + } + System.out.println("Deleting " + f + " (" + fileSize(f.toPath()) + ")"); + f.delete(); + } + } + System.out.println("Deleting " + dir); + dir.toFile().delete(); + } + + private static String fileSize(Path path) { + try { + long sizeBytes = Files.size(path); + + if (sizeBytes > 1024 * 1024 * 1024) return round(sizeBytes / 1073741824.) + "Gb"; + if (sizeBytes > 1024 * 1024) return round(sizeBytes / 1048576.) + "Mb"; + if (sizeBytes > 1024) return round(sizeBytes / 1024.) + "Kb"; + return sizeBytes + "b"; + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + private static String round(double d) { + return String.format("%.2f", d); + } +} diff --git a/services-core/readme.md b/services-core/readme.md new file mode 100644 index 00000000..1af591d3 --- /dev/null +++ b/services-core/readme.md @@ -0,0 +1,12 @@ +# Core Services + +The cores services constitute the main functionality of the search engine. + +* The [search-service](search-service/) parses queries, interrogates the index-service, + and decorates search results with metadata from the database. + +* The [index-service](index-service/) contains the indexes, it answers questions about + which documents contain which terms. + +* The [assistant-service](assistant-service/) helps the search service with spelling + suggestions other peripheral functionality. \ No newline at end of file diff --git a/services-core/search-service/build.gradle b/services-core/search-service/build.gradle new file mode 100644 index 00000000..816f2066 --- /dev/null +++ b/services-core/search-service/build.gradle @@ -0,0 +1,76 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'com.palantir.docker' version '0.34.0' + id 'application' + id 'jvm-test-suite' +} + +application { + mainClass = 'nu.marginalia.search.SearchMain' + applicationName = 'search-service' +} + +apply from: "$rootProject.projectDir/docker-service.gradle" + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:service') + implementation project(':common:config') + implementation project(':index:index-query') + + implementation project(':libraries:misc') + implementation project(':libraries:language-processing') + + implementation project(':api:assistant-api') + implementation project(':api:index-api') + implementation project(':api:search-api') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + + implementation project(':features:renderer') + implementation project(':features:screenshots') + implementation project(':features:random-websites') + implementation project(':features:query-parser') + + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.guice + implementation libs.rxjava + implementation libs.spark + implementation libs.opencsv + implementation libs.trove + implementation libs.fastutil + implementation libs.bundles.gson + implementation libs.bundles.mariadb + implementation libs.bundles.nlp + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + +} +test { + maxHeapSize = "8G" + useJUnitPlatform() +} + +task fastTests(type: Test) { + maxHeapSize = "8G" + useJUnitPlatform { + excludeTags "slow" + } +} + diff --git a/services-core/search-service/src/main/java/nu/marginalia/search/SearchMain.java b/services-core/search-service/src/main/java/nu/marginalia/search/SearchMain.java new file mode 100644 index 00000000..f652352a --- /dev/null +++ b/services-core/search-service/src/main/java/nu/marginalia/search/SearchMain.java @@ -0,0 +1,38 @@ +package nu.marginalia.search; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.SearchServiceDescriptors; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.module.DatabaseModule; +import nu.marginalia.service.server.Initialization; +import spark.Spark; + +public class SearchMain extends MainClass { + private final SearchService service; + + @Inject + public SearchMain(SearchService service) { + this.service = service; + } + + public static void main(String... args) { + + init(ServiceId.Search, args); + + Spark.staticFileLocation("/static/search/"); + + Injector injector = Guice.createInjector( + new SearchModule(), + new ConfigurationModule(SearchServiceDescriptors.descriptors, ServiceId.Search), + new DatabaseModule() + ); + + injector.getInstance(SearchMain.class); + injector.getInstance(Initialization.class).setReady(); + + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchModule.java b/services-core/search-service/src/main/java/nu/marginalia/search/SearchModule.java similarity index 53% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchModule.java rename to services-core/search-service/src/main/java/nu/marginalia/search/SearchModule.java index 9db18272..1492c99f 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchModule.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/SearchModule.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.search; +package nu.marginalia.search; import com.google.inject.AbstractModule; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.wmsa.configuration.WebsiteUrl; -import nu.marginalia.wmsa.configuration.WmsaHome; +import nu.marginalia.LanguageModels; +import nu.marginalia.WebsiteUrl; +import nu.marginalia.WmsaHome; -public class EdgeSearchModule extends AbstractModule { +public class SearchModule extends AbstractModule { public void configure() { bind(LanguageModels.class).toInstance(WmsaHome.getLanguageModels()); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchOperator.java b/services-core/search-service/src/main/java/nu/marginalia/search/SearchOperator.java similarity index 53% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchOperator.java rename to services-core/search-service/src/main/java/nu/marginalia/search/SearchOperator.java index 64c8346a..982264e8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchOperator.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/SearchOperator.java @@ -1,27 +1,25 @@ -package nu.marginalia.wmsa.edge.search; +package nu.marginalia.search; import com.google.inject.Inject; import com.google.inject.Singleton; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.schedulers.Schedulers; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.assistant.client.AssistantClient; -import nu.marginalia.wmsa.edge.assistant.dict.WikiArticles; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.search.EdgeUrlDetails; -import nu.marginalia.wmsa.edge.search.model.BrowseResult; -import nu.marginalia.wmsa.edge.search.model.DecoratedSearchResults; -import nu.marginalia.wmsa.edge.search.query.QueryFactory; -import nu.marginalia.wmsa.edge.search.query.model.EdgeSearchQuery; -import nu.marginalia.wmsa.edge.search.query.model.EdgeUserSearchParameters; -import nu.marginalia.wmsa.edge.search.svc.EdgeSearchDomainSearchService; -import nu.marginalia.wmsa.edge.search.svc.EdgeSearchQueryIndexService; -import nu.marginalia.wmsa.edge.search.svc.EdgeSearchUnitConversionService; -import nu.marginalia.wmsa.edge.search.svc.EdgeSearchWikiArticlesService; +import nu.marginalia.assistant.client.AssistantClient; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.dbcommon.DbDomainQueries; +import nu.marginalia.search.model.UrlDetails; +import nu.marginalia.client.Context; +import nu.marginalia.search.model.DecoratedSearchResults; +import nu.marginalia.search.query.QueryFactory; +import nu.marginalia.search.query.model.SearchQuery; +import nu.marginalia.search.query.model.UserSearchParameters; +import nu.marginalia.search.svc.SearchQueryIndexService; +import nu.marginalia.search.svc.SearchUnitConversionService; import org.apache.logging.log4j.util.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; import javax.annotation.Nullable; import java.util.*; @@ -30,71 +28,65 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Singleton -public class EdgeSearchOperator { +public class SearchOperator { + + private static final Logger logger = LoggerFactory.getLogger(SearchOperator.class); + + // Marker for filtering out sensitive content from the persistent logs + private final Marker queryMarker = MarkerFactory.getMarker("QUERY"); - private static final Logger logger = LoggerFactory.getLogger(EdgeSearchOperator.class); private final AssistantClient assistantClient; - private final EdgeDataStoreDao edgeDataStoreDao; + private final DbDomainQueries domainQueries; private final QueryFactory queryFactory; - private final EdgeSearchQueryIndexService searchQueryService; - private final EdgeSearchDomainSearchService domainSearchService; - private final EdgeSearchWikiArticlesService wikiArticlesService; - private final EdgeSearchUnitConversionService edgeSearchUnitConversionService; + private final SearchQueryIndexService searchQueryService; + private final SearchUnitConversionService searchUnitConversionService; @Inject - public EdgeSearchOperator(AssistantClient assistantClient, - EdgeDataStoreDao edgeDataStoreDao, - QueryFactory queryFactory, - - EdgeSearchQueryIndexService searchQueryService, - EdgeSearchDomainSearchService domainSearchService, - EdgeSearchWikiArticlesService wikiArticlesService, - EdgeSearchUnitConversionService edgeSearchUnitConversionService) { + public SearchOperator(AssistantClient assistantClient, + DbDomainQueries domainQueries, + QueryFactory queryFactory, + SearchQueryIndexService searchQueryService, + SearchUnitConversionService searchUnitConversionService) { this.assistantClient = assistantClient; - this.edgeDataStoreDao = edgeDataStoreDao; + this.domainQueries = domainQueries; this.queryFactory = queryFactory; this.searchQueryService = searchQueryService; - this.domainSearchService = domainSearchService; - this.wikiArticlesService = wikiArticlesService; - this.edgeSearchUnitConversionService = edgeSearchUnitConversionService; + this.searchUnitConversionService = searchUnitConversionService; } - public List doApiSearch(Context ctx, - EdgeUserSearchParameters params) { + public List doApiSearch(Context ctx, + UserSearchParameters params) { - EdgeSearchQuery processedQuery = queryFactory.createQuery(params); + SearchQuery processedQuery = queryFactory.createQuery(params); - logger.info("Human terms (API): {}", Strings.join(processedQuery.searchTermsHuman, ',')); + logger.info(queryMarker, "Human terms (API): {}", Strings.join(processedQuery.searchTermsHuman, ',')); return searchQueryService.performQuery(ctx, processedQuery); } - public DecoratedSearchResults doSearch(Context ctx, EdgeUserSearchParameters params) { + public DecoratedSearchResults doSearch(Context ctx, UserSearchParameters params) { - Future definitions = wikiArticlesService.getWikiArticle(ctx, params.humanQuery()); - Future eval = edgeSearchUnitConversionService.tryEval(ctx, params.humanQuery()); - EdgeSearchQuery processedQuery = queryFactory.createQuery(params); + Future eval = searchUnitConversionService.tryEval(ctx, params.humanQuery()); + SearchQuery processedQuery = queryFactory.createQuery(params); - logger.info("Human terms: {}", Strings.join(processedQuery.searchTermsHuman, ',')); + logger.info(queryMarker, "Human terms: {}", Strings.join(processedQuery.searchTermsHuman, ',')); - List queryResults = searchQueryService.performQuery(ctx, processedQuery); - List domainResults = domainSearchService.getDomainResults(ctx, processedQuery.specs); + List queryResults = searchQueryService.performQuery(ctx, processedQuery); + + logger.info(queryMarker, "Search Result Count: {}", queryResults.size()); String evalResult = getFutureOrDefault(eval, ""); - WikiArticles wikiArticles = getFutureOrDefault(definitions, new WikiArticles()); return DecoratedSearchResults.builder() .params(params) .problems(getProblems(ctx, evalResult, queryResults, processedQuery)) .evalResult(evalResult) - .wiki(wikiArticles) .results(queryResults) - .domainResults(domainResults) .focusDomain(processedQuery.domain) .focusDomainId(getDomainId(processedQuery.domain)) .build(); @@ -117,7 +109,7 @@ public class EdgeSearchOperator { int domainId = -1; try { if (domain != null) { - return edgeDataStoreDao.getDomainId(new EdgeDomain(domain)).id(); + return domainQueries.getDomainId(new EdgeDomain(domain)).id(); } } catch (NoSuchElementException ex) { @@ -126,7 +118,7 @@ public class EdgeSearchOperator { return domainId; } - private List getProblems(Context ctx, String evalResult, List queryResults, EdgeSearchQuery processedQuery) { + private List getProblems(Context ctx, String evalResult, List queryResults, SearchQuery processedQuery) { final List problems = new ArrayList<>(processedQuery.problems); boolean siteSearch = processedQuery.domain != null; @@ -150,7 +142,7 @@ public class EdgeSearchOperator { } - private Iterable spellCheckTerms(Context ctx, EdgeSearchQuery disjointedQuery) { + private Iterable spellCheckTerms(Context ctx, SearchQuery disjointedQuery) { return Observable.fromIterable(disjointedQuery.searchTermsHuman) .subscribeOn(Schedulers.io()) .flatMap(term -> assistantClient.spellCheck(ctx, term) diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchService.java b/services-core/search-service/src/main/java/nu/marginalia/search/SearchService.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchService.java rename to services-core/search-service/src/main/java/nu/marginalia/search/SearchService.java index 6483c922..ecb21502 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/EdgeSearchService.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/SearchService.java @@ -1,18 +1,18 @@ -package nu.marginalia.wmsa.edge.search; +package nu.marginalia.search; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.name.Named; import lombok.SneakyThrows; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.configuration.WebsiteUrl; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; -import nu.marginalia.wmsa.edge.search.command.IndexCommand; -import nu.marginalia.wmsa.edge.search.svc.*; -import nu.marginalia.wmsa.resource_store.StaticResources; +import nu.marginalia.WebsiteUrl; +import nu.marginalia.client.Context; +import nu.marginalia.model.gson.GsonFactory; +import nu.marginalia.search.command.IndexCommand; +import nu.marginalia.search.svc.*; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.Service; +import nu.marginalia.service.server.StaticResources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import spark.Request; @@ -22,27 +22,27 @@ import spark.Spark; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -public class EdgeSearchService extends Service { +public class SearchService extends Service { private final WebsiteUrl websiteUrl; private StaticResources staticResources; - private static final Logger logger = LoggerFactory.getLogger(EdgeSearchService.class); + private static final Logger logger = LoggerFactory.getLogger(SearchService.class); @SneakyThrows @Inject - public EdgeSearchService(@Named("service-host") String ip, - @Named("service-port") Integer port, - Initialization initialization, - MetricsServer metricsServer, - WebsiteUrl websiteUrl, - StaticResources staticResources, - IndexCommand indexCommand, - EdgeSearchErrorPageService errorPageService, - EdgeSearchAddToCrawlQueueService addToCrawlQueueService, - EdgeSearchFlagSiteService flagSiteService, - EdgeSearchQueryService searchQueryService, - EdgeSearchApiQueryService apiQueryService + public SearchService(@Named("service-host") String ip, + @Named("service-port") Integer port, + Initialization initialization, + MetricsServer metricsServer, + WebsiteUrl websiteUrl, + StaticResources staticResources, + IndexCommand indexCommand, + SearchErrorPageService errorPageService, + SearchAddToCrawlQueueService addToCrawlQueueService, + SearchFlagSiteService flagSiteService, + SearchQueryService searchQueryService, + SearchApiQueryService apiQueryService ) { super(ip, port, initialization, metricsServer); @@ -79,7 +79,7 @@ public class EdgeSearchService extends Service { private Object serveStatic(Request request, Response response) { String resource = request.params("resource"); - staticResources.serveStatic("edge", resource, request, response); + staticResources.serveStatic("search", resource, request, response); return ""; } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/CommandEvaluator.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/CommandEvaluator.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/CommandEvaluator.java rename to services-core/search-service/src/main/java/nu/marginalia/search/command/CommandEvaluator.java index 6d5aa46c..b9cfc852 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/CommandEvaluator.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/CommandEvaluator.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.search.command; +package nu.marginalia.search.command; import com.google.inject.Inject; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.search.command.commands.*; +import nu.marginalia.search.command.commands.*; +import nu.marginalia.client.Context; import java.util.ArrayList; import java.util.List; diff --git a/services-core/search-service/src/main/java/nu/marginalia/search/command/IndexCommand.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/IndexCommand.java new file mode 100644 index 00000000..83ed4a64 --- /dev/null +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/IndexCommand.java @@ -0,0 +1,29 @@ +package nu.marginalia.search.command; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import nu.marginalia.browse.model.BrowseResultSet; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; +import spark.Request; +import spark.Response; + +import java.io.IOException; +import java.util.Collections; + +@Singleton +public class IndexCommand { + + private final MustacheRenderer template; + @Inject + public IndexCommand(RendererFactory rendererFactory) throws IOException { + + template = rendererFactory.renderer("search/index"); + } + + public String render(Request request, Response response) { + response.header("Cache-control", "public,max-age=3600"); + + return template.render(new BrowseResultSet(Collections.emptyList())); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchCommandInterface.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/SearchCommandInterface.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchCommandInterface.java rename to services-core/search-service/src/main/java/nu/marginalia/search/command/SearchCommandInterface.java index 8334b03d..3e2304b7 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchCommandInterface.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/SearchCommandInterface.java @@ -1,6 +1,7 @@ -package nu.marginalia.wmsa.edge.search.command; +package nu.marginalia.search.command; -import nu.marginalia.wmsa.configuration.server.Context; + +import nu.marginalia.client.Context; import java.util.Optional; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchJsParameter.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/SearchJsParameter.java similarity index 88% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchJsParameter.java rename to services-core/search-service/src/main/java/nu/marginalia/search/command/SearchJsParameter.java index f42b3525..0efa224d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/SearchJsParameter.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/SearchJsParameter.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.search.command; +package nu.marginalia.search.command; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSubquery; +import nu.marginalia.index.client.model.query.EdgeSearchSubquery; import javax.annotation.Nullable; import java.util.Arrays; diff --git a/services-core/search-service/src/main/java/nu/marginalia/search/command/SearchParameters.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/SearchParameters.java new file mode 100644 index 00000000..58c64dd6 --- /dev/null +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/SearchParameters.java @@ -0,0 +1,9 @@ +package nu.marginalia.search.command; + +import nu.marginalia.search.model.SearchProfile; + +public record SearchParameters(SearchProfile profile, SearchJsParameter js, boolean detailedResults) { + public String profileStr() { + return profile.name; + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommand.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/BangCommand.java similarity index 85% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommand.java rename to services-core/search-service/src/main/java/nu/marginalia/search/command/commands/BangCommand.java index afb22d1a..5153dea8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommand.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/BangCommand.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.search.command.commands; +package nu.marginalia.search.command.commands; import com.google.inject.Inject; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; -import nu.marginalia.wmsa.edge.search.command.SearchParameters; -import nu.marginalia.wmsa.edge.search.exceptions.RedirectException; +import nu.marginalia.search.command.SearchCommandInterface; +import nu.marginalia.search.command.SearchParameters; +import nu.marginalia.client.Context; +import nu.marginalia.search.exceptions.RedirectException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BrowseCommand.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/BrowseCommand.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BrowseCommand.java rename to services-core/search-service/src/main/java/nu/marginalia/search/command/commands/BrowseCommand.java index e1d256d8..b6f009da 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/BrowseCommand.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/BrowseCommand.java @@ -1,17 +1,20 @@ -package nu.marginalia.wmsa.edge.search.command.commands; +package nu.marginalia.search.command.commands; import com.google.inject.Inject; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklist; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; -import nu.marginalia.wmsa.edge.search.command.SearchParameters; -import nu.marginalia.wmsa.edge.search.model.BrowseResult; -import nu.marginalia.wmsa.edge.search.model.BrowseResultSet; -import nu.marginalia.wmsa.edge.search.results.BrowseResultCleaner; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; +import nu.marginalia.browse.DbBrowseDomainsRandom; +import nu.marginalia.browse.DbBrowseDomainsSimilarCosine; +import nu.marginalia.browse.DbBrowseDomainsSimilarOldAlgo; +import nu.marginalia.browse.model.BrowseResult; +import nu.marginalia.browse.model.BrowseResultSet; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.dbcommon.DbDomainQueries; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklist; +import nu.marginalia.search.command.SearchCommandInterface; +import nu.marginalia.search.command.SearchParameters; +import nu.marginalia.search.results.BrowseResultCleaner; +import nu.marginalia.client.Context; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +24,10 @@ import java.util.function.Predicate; import java.util.regex.Pattern; public class BrowseCommand implements SearchCommandInterface { - private final EdgeDataStoreDao edgeDataStoreDao; + private final DbBrowseDomainsRandom randomDomains; + private final DbBrowseDomainsSimilarCosine similarDomains; + private final DbBrowseDomainsSimilarOldAlgo similarDomainsOld; + private final DbDomainQueries domainQueries; private final EdgeDomainBlacklist blacklist; private final MustacheRenderer browseResultsRenderer; private final BrowseResultCleaner browseResultCleaner; @@ -29,17 +35,22 @@ public class BrowseCommand implements SearchCommandInterface { private final Predicate queryPatternPredicate = Pattern.compile("^browse:[.A-Za-z\\-0-9:]+$").asPredicate(); @Inject - public BrowseCommand(EdgeDataStoreDao edgeDataStoreDao, + public BrowseCommand(DbBrowseDomainsRandom randomDomains, + DbBrowseDomainsSimilarCosine similarDomains, + DbBrowseDomainsSimilarOldAlgo similarDomainsOld, DbDomainQueries domainQueries, EdgeDomainBlacklist blacklist, RendererFactory rendererFactory, BrowseResultCleaner browseResultCleaner) throws IOException { - this.edgeDataStoreDao = edgeDataStoreDao; + this.randomDomains = randomDomains; + this.similarDomains = similarDomains; + this.similarDomainsOld = similarDomainsOld; + this.domainQueries = domainQueries; this.blacklist = blacklist; this.browseResultCleaner = browseResultCleaner; - browseResultsRenderer = rendererFactory.renderer("edge/browse-results"); + browseResultsRenderer = rendererFactory.renderer("search/browse-results"); } @Override @@ -76,7 +87,7 @@ public class BrowseCommand implements SearchCommandInterface { } private BrowseResultSet getRandomEntries(int set) { - var results = edgeDataStoreDao.getRandomDomains(25, blacklist, set); + var results = randomDomains.getRandomDomains(25, blacklist, set); results.removeIf(browseResultCleaner.shouldRemoveResultPredicate()); @@ -84,15 +95,15 @@ public class BrowseCommand implements SearchCommandInterface { } private BrowseResultSet getRelatedEntries(String word) { - var domain = edgeDataStoreDao.getDomainId(new EdgeDomain(word)); + var domain = domainQueries.getDomainId(new EdgeDomain(word)); - var neighbors = edgeDataStoreDao.getDomainNeighborsAdjacentCosine(domain, blacklist, 256); + var neighbors = similarDomains.getDomainNeighborsAdjacentCosine(domain, blacklist, 256); neighbors.removeIf(browseResultCleaner.shouldRemoveResultPredicate()); // If the results are very few, supplement with the alternative shitty algorithm if (neighbors.size() < 25) { Set allNeighbors = new HashSet<>(neighbors); - allNeighbors.addAll(edgeDataStoreDao.getDomainNeighborsAdjacent(domain, blacklist, 50)); + allNeighbors.addAll(similarDomainsOld.getDomainNeighborsAdjacent(domain, blacklist, 50)); neighbors.clear(); neighbors.addAll(allNeighbors); diff --git a/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/ConvertCommand.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/ConvertCommand.java new file mode 100644 index 00000000..d022efac --- /dev/null +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/ConvertCommand.java @@ -0,0 +1,35 @@ +package nu.marginalia.search.command.commands; + +import com.google.inject.Inject; +import nu.marginalia.search.command.SearchCommandInterface; +import nu.marginalia.search.command.SearchParameters; +import nu.marginalia.search.svc.SearchUnitConversionService; +import nu.marginalia.client.Context; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; + +import java.io.IOException; +import java.util.Map; +import java.util.Optional; + +public class ConvertCommand implements SearchCommandInterface { + private final SearchUnitConversionService searchUnitConversionService; + private final MustacheRenderer> conversionRenderer; + + @Inject + public ConvertCommand(SearchUnitConversionService searchUnitConversionService, RendererFactory rendererFactory) throws IOException { + this.searchUnitConversionService = searchUnitConversionService; + + conversionRenderer = rendererFactory.renderer("search/conversion-results"); + } + + @Override + public Optional process(Context ctx, SearchParameters parameters, String query) { + var conversion = searchUnitConversionService.tryConversion(ctx, query); + if (conversion.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(conversionRenderer.render(Map.of("query", query, "result", conversion.get(), "profile", parameters.profileStr()))); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/DefinitionCommand.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/DefinitionCommand.java similarity index 74% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/DefinitionCommand.java rename to services-core/search-service/src/main/java/nu/marginalia/search/command/commands/DefinitionCommand.java index d166fe30..efd99447 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/DefinitionCommand.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/DefinitionCommand.java @@ -1,15 +1,15 @@ -package nu.marginalia.wmsa.edge.search.command.commands; +package nu.marginalia.search.command.commands; import com.google.inject.Inject; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.assistant.client.AssistantClient; -import nu.marginalia.wmsa.edge.assistant.dict.DictionaryResponse; -import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; -import nu.marginalia.wmsa.edge.search.command.SearchParameters; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; +import nu.marginalia.assistant.client.AssistantClient; +import nu.marginalia.assistant.client.model.DictionaryResponse; +import nu.marginalia.client.Context; +import nu.marginalia.search.command.SearchCommandInterface; +import nu.marginalia.search.command.SearchParameters; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +33,7 @@ public class DefinitionCommand implements SearchCommandInterface { throws IOException { - dictionaryRenderer = rendererFactory.renderer("edge/dictionary-results"); + dictionaryRenderer = rendererFactory.renderer("search/dictionary-results"); this.assistantClient = assistantClient; } diff --git a/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/SearchCommand.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/SearchCommand.java new file mode 100644 index 00000000..63f12470 --- /dev/null +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/SearchCommand.java @@ -0,0 +1,51 @@ +package nu.marginalia.search.command.commands; + +import com.google.inject.Inject; +import nu.marginalia.client.Context; +import nu.marginalia.model.dbcommon.DbDomainQueries; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklist; +import nu.marginalia.search.SearchOperator; +import nu.marginalia.search.command.SearchCommandInterface; +import nu.marginalia.search.command.SearchParameters; +import nu.marginalia.search.model.DecoratedSearchResults; +import nu.marginalia.search.model.UrlDetails; +import nu.marginalia.search.query.model.UserSearchParameters; +import nu.marginalia.search.results.BrowseResultCleaner; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; + +import java.io.IOException; +import java.util.Optional; + +public class SearchCommand implements SearchCommandInterface { + private final EdgeDomainBlacklist blacklist; + private final SearchOperator searchOperator; + private final MustacheRenderer searchResultsRenderer; + + + @Inject + public SearchCommand(EdgeDomainBlacklist blacklist, + SearchOperator searchOperator, + RendererFactory rendererFactory + ) throws IOException { + this.blacklist = blacklist; + this.searchOperator = searchOperator; + + searchResultsRenderer = rendererFactory.renderer("search/search-results"); + } + + @Override + public Optional process(Context ctx, SearchParameters parameters, String query) { + UserSearchParameters params = new UserSearchParameters(query, parameters.profile(), parameters.js()); + + DecoratedSearchResults results = searchOperator.doSearch(ctx, params); + + results.results.removeIf(this::isBlacklisted); + + return Optional.of(searchResultsRenderer.render(results)); + } + + private boolean isBlacklisted(UrlDetails details) { + return blacklist.isBlacklisted(details.domainId); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SiteListCommand.java b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/SiteListCommand.java similarity index 66% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SiteListCommand.java rename to services-core/search-service/src/main/java/nu/marginalia/search/command/commands/SiteListCommand.java index 11a564b4..8bce97d1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/command/commands/SiteListCommand.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/command/commands/SiteListCommand.java @@ -1,17 +1,17 @@ -package nu.marginalia.wmsa.edge.search.command.commands; +package nu.marginalia.search.command.commands; import com.google.inject.Inject; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.model.search.EdgeUrlDetails; -import nu.marginalia.wmsa.edge.search.command.SearchCommandInterface; -import nu.marginalia.wmsa.edge.search.command.SearchParameters; -import nu.marginalia.wmsa.edge.search.model.DomainInformation; -import nu.marginalia.wmsa.edge.search.model.EdgeSearchProfile; -import nu.marginalia.wmsa.edge.search.siteinfo.DomainInformationService; -import nu.marginalia.wmsa.edge.search.svc.EdgeSearchQueryIndexService; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; +import nu.marginalia.model.dbcommon.DbDomainQueries; +import nu.marginalia.search.model.UrlDetails; +import nu.marginalia.search.command.SearchCommandInterface; +import nu.marginalia.search.command.SearchParameters; +import nu.marginalia.search.model.DomainInformation; +import nu.marginalia.search.model.SearchProfile; +import nu.marginalia.search.siteinfo.DomainInformationService; +import nu.marginalia.search.svc.SearchQueryIndexService; +import nu.marginalia.client.Context; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,9 +22,9 @@ import java.util.function.Predicate; import java.util.regex.Pattern; public class SiteListCommand implements SearchCommandInterface { - private final EdgeDataStoreDao dataStoreDao; + private final DbDomainQueries domainQueries; private final DomainInformationService domainInformationService; - private final EdgeSearchQueryIndexService searchQueryIndexService; + private final SearchQueryIndexService searchQueryIndexService; private final Logger logger = LoggerFactory.getLogger(getClass()); private final MustacheRenderer siteInfoRenderer; @@ -34,15 +34,15 @@ public class SiteListCommand implements SearchCommandInterface { @Inject public SiteListCommand( DomainInformationService domainInformationService, - EdgeDataStoreDao dataStoreDao, + DbDomainQueries domainQueries, RendererFactory rendererFactory, - EdgeSearchQueryIndexService searchQueryIndexService) + SearchQueryIndexService searchQueryIndexService) throws IOException { - this.dataStoreDao = dataStoreDao; + this.domainQueries = domainQueries; this.domainInformationService = domainInformationService; - siteInfoRenderer = rendererFactory.renderer("edge/site-info"); + siteInfoRenderer = rendererFactory.renderer("search/site-info"); this.searchQueryIndexService = searchQueryIndexService; } @@ -55,12 +55,12 @@ public class SiteListCommand implements SearchCommandInterface { var results = siteInfo(ctx, query); var domain = results.getDomain(); - List resultSet; + List resultSet; Path screenshotPath = null; Integer domainId = -1; if (null != domain) { - resultSet = searchQueryIndexService.performDumbQuery(ctx, EdgeSearchProfile.CORPO, 100, 100, "site:"+domain); - domainId = dataStoreDao.getDomainId(domain).id(); + resultSet = searchQueryIndexService.performDumbQuery(ctx, SearchProfile.CORPO, 100, 100, "site:"+domain); + domainId = domainQueries.getDomainId(domain).id(); screenshotPath = Path.of("/screenshot/" + domainId); } else { diff --git a/services-core/search-service/src/main/java/nu/marginalia/search/db/DbUrlDetailsQuery.java b/services-core/search-service/src/main/java/nu/marginalia/search/db/DbUrlDetailsQuery.java new file mode 100644 index 00000000..25bec7d4 --- /dev/null +++ b/services-core/search-service/src/main/java/nu/marginalia/search/db/DbUrlDetailsQuery.java @@ -0,0 +1,111 @@ +package nu.marginalia.search.db; + +import com.google.common.base.Strings; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.inject.Inject; +import com.zaxxer.hikari.HikariDataSource; +import lombok.SneakyThrows; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.id.EdgeId; +import nu.marginalia.model.id.EdgeIdCollection; +import nu.marginalia.search.model.PageScoreAdjustment; +import nu.marginalia.search.model.UrlDetails; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + + +public class DbUrlDetailsQuery { + private final HikariDataSource dataSource; + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private final Cache> urlIdCache = CacheBuilder.newBuilder().maximumSize(100_000).build(); + + public static double QUALITY_LOWER_BOUND_CUTOFF = -15.; + @Inject + public DbUrlDetailsQuery(HikariDataSource dataSource) + { + this.dataSource = dataSource; + } + + + public synchronized void clearCaches() + { + urlIdCache.invalidateAll(); + } + + private String idList(EdgeIdCollection ids) { + StringJoiner j = new StringJoiner(",", "(", ")"); + for (var id : ids.values()) { + j.add(Integer.toString(id)); + } + return j.toString(); + } + + @SneakyThrows + public List getUrlDetailsMulti(EdgeIdCollection ids) { + if (ids.isEmpty()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(ids.size()); + + try (var connection = dataSource.getConnection()) { + + String idString = idList(ids); + + try (var stmt = connection.prepareStatement( + """ + SELECT ID, DOMAIN_ID, URL, + TITLE, DESCRIPTION, + QUALITY, + WORDS_TOTAL, FORMAT, FEATURES, + IP, DOMAIN_STATE, + DATA_HASH + FROM EC_URL_VIEW + WHERE TITLE IS NOT NULL + AND ID IN + """ + idString)) { + stmt.setFetchSize(ids.size()); + + var rsp = stmt.executeQuery(); + while (rsp.next()) { + var val = new UrlDetails(rsp.getInt(1), + rsp.getInt(2), + new EdgeUrl(rsp.getString(3)), + rsp.getString(4), // title + rsp.getString(5), // description + rsp.getDouble(6), // quality + rsp.getInt(7), // wordsTotal + rsp.getString(8), // format + rsp.getInt(9), // features + rsp.getString(10), // ip + EdgeDomainIndexingState.valueOf(rsp.getString(11)), // domainState + rsp.getLong(12), // dataHash + PageScoreAdjustment.zero(), // urlQualityAdjustment + Integer.MAX_VALUE, // rankingId + Double.MAX_VALUE, // termScore + 1, // resultsFromSameDomain + "", // positions + null // result item + ); + if (val.urlQuality <= QUALITY_LOWER_BOUND_CUTOFF + && Strings.isNullOrEmpty(val.description) + && val.url.path.length() > 1) { + continue; + } + result.add(val); + + } + } + } + + return result; + } + + + + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/exceptions/RedirectException.java b/services-core/search-service/src/main/java/nu/marginalia/search/exceptions/RedirectException.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/exceptions/RedirectException.java rename to services-core/search-service/src/main/java/nu/marginalia/search/exceptions/RedirectException.java index fc551964..eb04a4cb 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/exceptions/RedirectException.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/exceptions/RedirectException.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.search.exceptions; +package nu.marginalia.search.exceptions; public class RedirectException extends RuntimeException { public final String newUrl; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/DecoratedSearchResults.java b/services-core/search-service/src/main/java/nu/marginalia/search/model/DecoratedSearchResults.java similarity index 56% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/DecoratedSearchResults.java rename to services-core/search-service/src/main/java/nu/marginalia/search/model/DecoratedSearchResults.java index 3d4acda8..b841a488 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/DecoratedSearchResults.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/model/DecoratedSearchResults.java @@ -1,23 +1,20 @@ -package nu.marginalia.wmsa.edge.search.model; +package nu.marginalia.search.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import nu.marginalia.wmsa.edge.assistant.dict.WikiArticles; -import nu.marginalia.wmsa.edge.model.search.EdgeUrlDetails; -import nu.marginalia.wmsa.edge.search.query.model.EdgeUserSearchParameters; +import nu.marginalia.browse.model.BrowseResult; +import nu.marginalia.search.query.model.UserSearchParameters; import java.util.List; @AllArgsConstructor @Getter @Builder public class DecoratedSearchResults { - private final EdgeUserSearchParameters params; + private final UserSearchParameters params; private final List problems; private final String evalResult; - private final WikiArticles wiki; - public final List results; - public final List domainResults; + public final List results; private final String focusDomain; private final int focusDomainId; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/DomainInformation.java b/services-core/search-service/src/main/java/nu/marginalia/search/model/DomainInformation.java similarity index 82% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/DomainInformation.java rename to services-core/search-service/src/main/java/nu/marginalia/search/model/DomainInformation.java index 949c9e5f..85bb438c 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/DomainInformation.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/model/DomainInformation.java @@ -1,7 +1,7 @@ -package nu.marginalia.wmsa.edge.search.model; +package nu.marginalia.search.model; import lombok.*; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.model.EdgeDomain; import java.util.List; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgePageScoreAdjustment.java b/services-core/search-service/src/main/java/nu/marginalia/search/model/PageScoreAdjustment.java similarity index 71% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgePageScoreAdjustment.java rename to services-core/search-service/src/main/java/nu/marginalia/search/model/PageScoreAdjustment.java index e0cda818..7bff0296 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgePageScoreAdjustment.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/model/PageScoreAdjustment.java @@ -1,11 +1,11 @@ -package nu.marginalia.wmsa.edge.model.search; +package nu.marginalia.search.model; import lombok.Builder; import lombok.Getter; @Getter @Builder -public class EdgePageScoreAdjustment { +public class PageScoreAdjustment { final double titleAdj; final double titleFullHit; final double urlAdj; @@ -13,8 +13,8 @@ public class EdgePageScoreAdjustment { final double descAdj; final double descHitsAdj; - private static final EdgePageScoreAdjustment zero = new EdgePageScoreAdjustment(0,0, 0,0,0, 0); - public static EdgePageScoreAdjustment zero() { + private static final PageScoreAdjustment zero = new PageScoreAdjustment(0,0, 0,0,0, 0); + public static PageScoreAdjustment zero() { return zero; } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/EdgeSearchProfile.java b/services-core/search-service/src/main/java/nu/marginalia/search/model/SearchProfile.java similarity index 80% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/EdgeSearchProfile.java rename to services-core/search-service/src/main/java/nu/marginalia/search/model/SearchProfile.java index d04f7bd6..0b116986 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/EdgeSearchProfile.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/model/SearchProfile.java @@ -1,13 +1,13 @@ -package nu.marginalia.wmsa.edge.search.model; +package nu.marginalia.search.model; -import nu.marginalia.wmsa.edge.converting.processor.logic.HtmlFeature; -import nu.marginalia.wmsa.edge.index.svc.searchset.SearchSetIdentifier; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSubquery; -import nu.marginalia.wmsa.edge.model.search.domain.SpecificationLimit; +import nu.marginalia.index.query.limit.SpecificationLimit; +import nu.marginalia.model.crawl.HtmlFeature; +import nu.marginalia.index.client.model.query.EdgeSearchSubquery; +import nu.marginalia.index.client.model.query.SearchSetIdentifier; import java.util.Objects; -public enum EdgeSearchProfile { +public enum SearchProfile { DEFAULT("default", SearchSetIdentifier.RETRO), MODERN("modern", SearchSetIdentifier.SMALLWEB), @@ -27,13 +27,13 @@ public enum EdgeSearchProfile { public final String name; public final SearchSetIdentifier searchSetIdentifier; - EdgeSearchProfile(String name, SearchSetIdentifier searchSetIdentifier) { + SearchProfile(String name, SearchSetIdentifier searchSetIdentifier) { this.name = name; this.searchSetIdentifier = searchSetIdentifier; } - private final static EdgeSearchProfile[] values = values(); - public static EdgeSearchProfile getSearchProfile(String param) { + private final static SearchProfile[] values = values(); + public static SearchProfile getSearchProfile(String param) { if (null == param) { return YOLO; } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/EdgeSearchRankingSymbols.java b/services-core/search-service/src/main/java/nu/marginalia/search/model/SearchRankingSymbols.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/EdgeSearchRankingSymbols.java rename to services-core/search-service/src/main/java/nu/marginalia/search/model/SearchRankingSymbols.java index 989db072..e395c228 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/model/EdgeSearchRankingSymbols.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/model/SearchRankingSymbols.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.search.model; +package nu.marginalia.search.model; import java.util.TreeMap; -public class EdgeSearchRankingSymbols { +public class SearchRankingSymbols { private static final TreeMap symbols; static { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeUrlDetails.java b/services-core/search-service/src/main/java/nu/marginalia/search/model/UrlDetails.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeUrlDetails.java rename to services-core/search-service/src/main/java/nu/marginalia/search/model/UrlDetails.java index bd1e7ade..06d077a4 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/model/search/EdgeUrlDetails.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/model/UrlDetails.java @@ -1,17 +1,19 @@ -package nu.marginalia.wmsa.edge.model.search; +package nu.marginalia.search.model; import lombok.*; -import nu.marginalia.wmsa.edge.converting.processor.logic.HtmlFeature; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.index.client.model.results.EdgeSearchResultItem; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.crawl.HtmlFeature; import java.util.EnumSet; import java.util.Objects; import java.util.StringJoiner; @AllArgsConstructor @NoArgsConstructor @With @Getter @ToString -public class EdgeUrlDetails { +public class UrlDetails { public int id; + public int domainId; public EdgeUrl url; public String title; public String description; @@ -25,9 +27,9 @@ public class EdgeUrlDetails { public String ip; public EdgeDomainIndexingState domainState; - public int dataHash; + public long dataHash; - public EdgePageScoreAdjustment urlQualityAdjustment; + public PageScoreAdjustment urlQualityAdjustment; public long rankingId; public double termScore; @@ -86,8 +88,8 @@ public class EdgeUrlDetails { if (other == this) { return true; } - if (other instanceof EdgeUrlDetails) { - return ((EdgeUrlDetails) other).id == id; + if (other instanceof UrlDetails) { + return ((UrlDetails) other).id == id; } return false; } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/NearQueryProcessor.java b/services-core/search-service/src/main/java/nu/marginalia/search/query/NearQueryProcessor.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/NearQueryProcessor.java rename to services-core/search-service/src/main/java/nu/marginalia/search/query/NearQueryProcessor.java index 60b49d37..7a5f6025 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/NearQueryProcessor.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/query/NearQueryProcessor.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.search.query; +package nu.marginalia.search.query; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryFactory.java b/services-core/search-service/src/main/java/nu/marginalia/search/query/QueryFactory.java similarity index 84% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryFactory.java rename to services-core/search-service/src/main/java/nu/marginalia/search/query/QueryFactory.java index 952e0fb2..7ca0a0c5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/QueryFactory.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/query/QueryFactory.java @@ -1,19 +1,25 @@ -package nu.marginalia.wmsa.edge.search.query; +package nu.marginalia.search.query; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.util.language.WordPatterns; -import nu.marginalia.util.language.conf.LanguageModels; -import nu.marginalia.wmsa.edge.assistant.dict.NGramBloomFilter; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.index.model.QueryLimits; -import nu.marginalia.wmsa.edge.index.model.QueryStrategy; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSpecification; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSubquery; -import nu.marginalia.wmsa.edge.model.search.domain.SpecificationLimit; -import nu.marginalia.wmsa.edge.search.query.model.EdgeSearchQuery; -import nu.marginalia.wmsa.edge.search.query.model.EdgeUserSearchParameters; -import nu.marginalia.wmsa.edge.search.valuation.SearchResultValuator; +import nu.marginalia.LanguageModels; +import nu.marginalia.index.client.model.query.EdgeSearchSpecification; +import nu.marginalia.index.client.model.query.EdgeSearchSubquery; +import nu.marginalia.index.query.limit.QueryLimits; +import nu.marginalia.index.query.limit.QueryStrategy; +import nu.marginalia.index.query.limit.SpecificationLimit; +import nu.marginalia.language.statistics.EnglishDictionary; +import nu.marginalia.language.statistics.NGramBloomFilter; +import nu.marginalia.language.statistics.TermFrequencyDict; +import nu.marginalia.query_parser.QueryParser; +import nu.marginalia.query_parser.QueryPermutation; +import nu.marginalia.query_parser.QueryVariants; +import nu.marginalia.query_parser.token.Token; +import nu.marginalia.query_parser.token.TokenType; +import nu.marginalia.search.query.model.SearchQuery; +import nu.marginalia.search.query.model.UserSearchParameters; +import nu.marginalia.language.WordPatterns; +import nu.marginalia.search.valuation.SearchResultValuator; import org.eclipse.jetty.http.HttpStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +38,9 @@ public class QueryFactory { private static final int RETAIN_QUERY_VARIANT_COUNT = 5; private final ThreadLocal queryVariants; + private final QueryParser queryParser = new QueryParser(); + + @Inject public QueryFactory(LanguageModels lm, TermFrequencyDict dict, @@ -48,11 +57,15 @@ public class QueryFactory { } public QueryParser getParser() { - return new QueryParser(englishDictionary, queryVariants.get()); + return new QueryParser(); } - public EdgeSearchQuery createQuery(EdgeUserSearchParameters params) { - final var processedQuery = createQuery(getParser(), params); + public QueryPermutation getQueryPermutation() { + return new QueryPermutation(queryVariants.get()); + } + + public SearchQuery createQuery(UserSearchParameters params) { + final var processedQuery = createQuery(getQueryPermutation(), params); final List subqueries = processedQuery.specs.subqueries; for (var sq : subqueries) { @@ -71,8 +84,8 @@ public class QueryFactory { } } - public EdgeSearchQuery createQuery(QueryParser queryParser, - EdgeUserSearchParameters params) + public SearchQuery createQuery(QueryPermutation queryPermutation, + UserSearchParameters params) { final var query = params.humanQuery(); final var profile = params.profile(); @@ -125,7 +138,7 @@ public class QueryFactory { } } - var queryPermutations = queryParser.permuteQueriesNew(basicQuery); + var queryPermutations = queryPermutation.permuteQueriesNew(basicQuery); List subqueries = new ArrayList<>(); String near = profile.getNearDomain(); @@ -212,7 +225,7 @@ public class QueryFactory { EdgeSearchSpecification specs = specsBuilder.build(); - return new EdgeSearchQuery(specs, searchTermsHuman, domain); + return new SearchQuery(specs, searchTermsHuman, domain); } private SpecificationLimit parseSpecificationLimit(String str) { diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/model/EdgeSearchQuery.java b/services-core/search-service/src/main/java/nu/marginalia/search/query/model/SearchQuery.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/model/EdgeSearchQuery.java rename to services-core/search-service/src/main/java/nu/marginalia/search/query/model/SearchQuery.java index 2e3a246c..615c888e 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/query/model/EdgeSearchQuery.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/query/model/SearchQuery.java @@ -1,19 +1,19 @@ -package nu.marginalia.wmsa.edge.search.query.model; +package nu.marginalia.search.query.model; import lombok.AllArgsConstructor; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSpecification; +import nu.marginalia.index.client.model.query.EdgeSearchSpecification; import java.util.*; @AllArgsConstructor -public class EdgeSearchQuery { +public class SearchQuery { public final EdgeSearchSpecification specs; public final Set problems = new TreeSet<>(); public final List searchTermsHuman; public String domain; - public EdgeSearchQuery(EdgeSearchSpecification justSpecs) { + public SearchQuery(EdgeSearchSpecification justSpecs) { searchTermsHuman = new ArrayList<>(); specs = justSpecs; } diff --git a/services-core/search-service/src/main/java/nu/marginalia/search/query/model/UserSearchParameters.java b/services-core/search-service/src/main/java/nu/marginalia/search/query/model/UserSearchParameters.java new file mode 100644 index 00000000..94936416 --- /dev/null +++ b/services-core/search-service/src/main/java/nu/marginalia/search/query/model/UserSearchParameters.java @@ -0,0 +1,7 @@ +package nu.marginalia.search.query.model; + +import nu.marginalia.search.command.SearchJsParameter; +import nu.marginalia.search.model.SearchProfile; + +public record UserSearchParameters(String humanQuery, SearchProfile profile, SearchJsParameter jsSetting) { +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/BrowseResultCleaner.java b/services-core/search-service/src/main/java/nu/marginalia/search/results/BrowseResultCleaner.java similarity index 75% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/BrowseResultCleaner.java rename to services-core/search-service/src/main/java/nu/marginalia/search/results/BrowseResultCleaner.java index e178171c..d56094d8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/BrowseResultCleaner.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/results/BrowseResultCleaner.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.search.results; +package nu.marginalia.search.results; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.wmsa.edge.assistant.screenshot.ScreenshotService; -import nu.marginalia.wmsa.edge.model.id.EdgeId; -import nu.marginalia.wmsa.edge.search.model.BrowseResult; +import nu.marginalia.browse.model.BrowseResult; +import nu.marginalia.screenshot.ScreenshotService; +import nu.marginalia.model.id.EdgeId; import java.util.HashSet; import java.util.Set; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/SearchResultDecorator.java b/services-core/search-service/src/main/java/nu/marginalia/search/results/SearchResultDecorator.java similarity index 60% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/SearchResultDecorator.java rename to services-core/search-service/src/main/java/nu/marginalia/search/results/SearchResultDecorator.java index 6eca931a..517c8e8a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/results/SearchResultDecorator.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/results/SearchResultDecorator.java @@ -1,17 +1,17 @@ -package nu.marginalia.wmsa.edge.search.results; +package nu.marginalia.search.results; import com.google.inject.Inject; import gnu.trove.list.array.TIntArrayList; import gnu.trove.map.hash.TIntObjectHashMap; import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; +import nu.marginalia.search.db.DbUrlDetailsQuery; +import nu.marginalia.model.EdgeUrl; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.id.EdgeIdList; +import nu.marginalia.index.client.model.results.EdgeSearchResultItem; +import nu.marginalia.search.model.UrlDetails; +import nu.marginalia.search.valuation.SearchResultValuator; import nu.marginalia.util.BrailleBlockPunchCards; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.model.EdgeUrl; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; -import nu.marginalia.wmsa.edge.model.id.EdgeIdList; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultItem; -import nu.marginalia.wmsa.edge.model.search.EdgeUrlDetails; -import nu.marginalia.wmsa.edge.search.valuation.SearchResultValuator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,32 +19,30 @@ import java.util.ArrayList; import java.util.List; public class SearchResultDecorator { - private final EdgeDataStoreDao edgeDataStoreDao; + private final DbUrlDetailsQuery dbUrlDetailsQuery; private final SearchResultValuator valuator; private final Logger logger = LoggerFactory.getLogger(getClass()); - private final boolean dumpTermData = Boolean.getBoolean("search-dump-term-data"); - @Inject - public SearchResultDecorator(EdgeDataStoreDao edgeDataStoreDao, SearchResultValuator valuator) { - this.edgeDataStoreDao = edgeDataStoreDao; + public SearchResultDecorator(DbUrlDetailsQuery dbUrlDetailsQuery, SearchResultValuator valuator) { + this.dbUrlDetailsQuery = dbUrlDetailsQuery; this.valuator = valuator; } - public List getAllUrlDetails(List resultItems) { - TIntObjectHashMap detailsById = new TIntObjectHashMap<>(resultItems.size()); + public List getAllUrlDetails(List resultItems) { + TIntObjectHashMap detailsById = new TIntObjectHashMap<>(resultItems.size()); EdgeIdList idList = resultItems.stream() .mapToInt(EdgeSearchResultItem::getUrlIdInt) .collect(EdgeIdList::new, EdgeIdList::add, EdgeIdList::addAll); - List ret = edgeDataStoreDao.getUrlDetailsMulti(idList); + List ret = dbUrlDetailsQuery.getUrlDetailsMulti(idList); for (var val : ret) { detailsById.put(val.id, val); } - List retList = new ArrayList<>(resultItems.size()); + List retList = new ArrayList<>(resultItems.size()); TIntArrayList missedIds = new TIntArrayList(); for (var resultItem : resultItems) { @@ -62,11 +60,9 @@ public class SearchResultDecorator { details.resultsFromSameDomain = resultItem.resultsFromDomain; details.termScore = calculateTermScore(resultItem, details); - details.positions = getPositions(resultItem); + details.positions = getPositionsString(resultItem); details.resultItem = resultItem; - logger.debug("{} -> {}", details.url, details.termScore); - retList.add(details); } if (!missedIds.isEmpty()) { @@ -76,7 +72,7 @@ public class SearchResultDecorator { return retList; } - private String getPositions(EdgeSearchResultItem resultItem) { + private String getPositionsString(EdgeSearchResultItem resultItem) { Int2IntArrayMap positionsPerSet = new Int2IntArrayMap(8); for (var score : resultItem.scores) { @@ -99,21 +95,10 @@ public class SearchResultDecorator { return a | b; } - private double calculateTermScore(EdgeSearchResultItem resultItem, EdgeUrlDetails details) { + private double calculateTermScore(EdgeSearchResultItem resultItem, UrlDetails details) { final double statePenalty = (details.domainState == EdgeDomainIndexingState.SPECIAL) ? 1.25 : 0; - final double value = valuator.evaluateTerms(resultItem.scores, details.words, details.title.length()); - if (dumpTermData) { - System.out.println("---"); - System.out.println(details.getUrl()); - System.out.println(details.getTitle()); - System.out.println(details.words); - for (var score : resultItem.scores) { - System.out.println(score); - } - System.out.println(value); - } return value + statePenalty; } diff --git a/services-core/search-service/src/main/java/nu/marginalia/search/results/UrlDeduplicator.java b/services-core/search-service/src/main/java/nu/marginalia/search/results/UrlDeduplicator.java new file mode 100644 index 00000000..2f0f9f02 --- /dev/null +++ b/services-core/search-service/src/main/java/nu/marginalia/search/results/UrlDeduplicator.java @@ -0,0 +1,67 @@ +package nu.marginalia.search.results; + +import gnu.trove.list.TLongList; +import gnu.trove.list.array.TLongArrayList; +import gnu.trove.map.hash.TObjectIntHashMap; +import gnu.trove.set.hash.TIntHashSet; +import nu.marginalia.search.model.UrlDetails; +import nu.marginalia.lsh.EasyLSH; +import nu.marginalia.util.BrailleBlockPunchCards; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UrlDeduplicator { + private final int LSH_SIMILARITY_THRESHOLD = 2; + private static final Logger logger = LoggerFactory.getLogger(UrlDeduplicator.class); + + private final TIntHashSet seenSuperficialhashes = new TIntHashSet(200); + private final TLongList seehLSHList = new TLongArrayList(200); + private final TObjectIntHashMap keyCount = new TObjectIntHashMap<>(200, 0.75f, 0); + + private final int resultsPerKey; + public UrlDeduplicator(int resultsPerKey) { + this.resultsPerKey = resultsPerKey; + } + + public boolean shouldRemove(UrlDetails details) { + return !filter(details); + } + + public synchronized boolean filter(UrlDetails details) { + return deduplicateOnSuperficialHash(details) + && deduplicateOnLSH(details) + && limitResultsPerDomain(details); + } + + + private boolean deduplicateOnSuperficialHash(UrlDetails details) { + return seenSuperficialhashes.add(details.getSuperficialHash()); + } + + private boolean deduplicateOnLSH(UrlDetails details) { + long thisHash = details.dataHash; + + if (seehLSHList.forEach(otherHash -> EasyLSH.hammingDistance(thisHash, otherHash) >= LSH_SIMILARITY_THRESHOLD)) + { + seehLSHList.add(thisHash); + return true; + } + return false; + + } + + private boolean limitResultsPerDomain(UrlDetails details) { + final var domain = details.getUrl().getDomain(); + final String key; + + if (!details.isSpecialDomain()) { + key = domain.getLongDomainKey(); + } + else { + key = domain.getDomainKey(); + } + + return keyCount.adjustOrPutValue(key, 1, 1) < resultsPerKey; + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/siteinfo/DomainInformationService.java b/services-core/search-service/src/main/java/nu/marginalia/search/siteinfo/DomainInformationService.java similarity index 93% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/siteinfo/DomainInformationService.java rename to services-core/search-service/src/main/java/nu/marginalia/search/siteinfo/DomainInformationService.java index dce74c28..092f2aff 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/siteinfo/DomainInformationService.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/siteinfo/DomainInformationService.java @@ -1,12 +1,12 @@ -package nu.marginalia.wmsa.edge.search.siteinfo; +package nu.marginalia.search.siteinfo; import com.zaxxer.hikari.HikariDataSource; import lombok.SneakyThrows; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDaoImpl; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.crawl.EdgeDomainIndexingState; -import nu.marginalia.wmsa.edge.model.id.EdgeId; -import nu.marginalia.wmsa.edge.search.model.DomainInformation; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.crawl.EdgeDomainIndexingState; +import nu.marginalia.model.dbcommon.DbDomainQueries; +import nu.marginalia.model.id.EdgeId; +import nu.marginalia.search.model.DomainInformation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,15 +27,15 @@ import java.util.Optional; @Singleton public class DomainInformationService { - private EdgeDataStoreDaoImpl dataStoreDao; + private DbDomainQueries dbDomainQueries; private HikariDataSource dataSource; private final Logger logger = LoggerFactory.getLogger(getClass()); @Inject public DomainInformationService( - EdgeDataStoreDaoImpl dataStoreDao, + DbDomainQueries dbDomainQueries, HikariDataSource dataSource) { - this.dataStoreDao = dataStoreDao; + this.dbDomainQueries = dbDomainQueries; this.dataSource = dataSource; } @@ -47,7 +47,7 @@ public class DomainInformationService { return Optional.empty(); } - Optional domain = dataStoreDao.getDomain(domainId); + Optional domain = dbDomainQueries.getDomain(domainId); if (domain.isEmpty()) { return Optional.empty(); } @@ -103,7 +103,7 @@ public class DomainInformationService { private EdgeId getDomainFromPartial(String site) { try { - return dataStoreDao.getDomainId(new EdgeDomain(site)); + return dbDomainQueries.getDomainId(new EdgeDomain(site)); } catch (Exception ex) { return null; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchAddToCrawlQueueService.java b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchAddToCrawlQueueService.java similarity index 66% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchAddToCrawlQueueService.java rename to services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchAddToCrawlQueueService.java index 694ebc5e..8e4faff5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchAddToCrawlQueueService.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchAddToCrawlQueueService.java @@ -1,10 +1,10 @@ -package nu.marginalia.wmsa.edge.search.svc; +package nu.marginalia.search.svc; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.configuration.WebsiteUrl; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.model.id.EdgeId; +import nu.marginalia.WebsiteUrl; +import nu.marginalia.model.dbcommon.DbDomainQueries; +import nu.marginalia.model.id.EdgeId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import spark.Request; @@ -13,18 +13,18 @@ import spark.Spark; import java.sql.SQLException; -public class EdgeSearchAddToCrawlQueueService { +public class SearchAddToCrawlQueueService { - private EdgeDataStoreDao edgeDataStoreDao; - private WebsiteUrl websiteUrl; - private HikariDataSource dataSource; - private final Logger logger = LoggerFactory.getLogger(EdgeSearchAddToCrawlQueueService.class); + private final DbDomainQueries domainQueries; + private final WebsiteUrl websiteUrl; + private final HikariDataSource dataSource; + private final Logger logger = LoggerFactory.getLogger(SearchAddToCrawlQueueService.class); @Inject - public EdgeSearchAddToCrawlQueueService(EdgeDataStoreDao edgeDataStoreDao, - WebsiteUrl websiteUrl, - HikariDataSource dataSource) { - this.edgeDataStoreDao = edgeDataStoreDao; + public SearchAddToCrawlQueueService(DbDomainQueries domainQueries, + WebsiteUrl websiteUrl, + HikariDataSource dataSource) { + this.domainQueries = domainQueries; this.websiteUrl = websiteUrl; this.dataSource = dataSource; } @@ -61,7 +61,7 @@ public class EdgeSearchAddToCrawlQueueService { } private String getDomainName(int id) { - var domain = edgeDataStoreDao.getDomain(new EdgeId<>(id)); + var domain = domainQueries.getDomain(new EdgeId<>(id)); if (domain.isEmpty()) Spark.halt(404); return domain.get().toString(); diff --git a/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchApiQueryService.java b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchApiQueryService.java new file mode 100644 index 00000000..6fc031be --- /dev/null +++ b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchApiQueryService.java @@ -0,0 +1,98 @@ +package nu.marginalia.search.svc; + +import com.google.common.base.Strings; +import com.google.inject.Inject; +import lombok.SneakyThrows; +import nu.marginalia.index.client.model.results.EdgeSearchResultKeywordScore; +import nu.marginalia.search.client.model.ApiSearchResultQueryDetails; +import nu.marginalia.model.idx.EdgePageWordMetadata; +import nu.marginalia.search.SearchOperator; +import nu.marginalia.search.model.UrlDetails; +import nu.marginalia.search.client.model.ApiSearchResult; +import nu.marginalia.search.client.model.ApiSearchResults; +import nu.marginalia.search.model.SearchProfile; +import nu.marginalia.client.Context; +import nu.marginalia.search.command.SearchJsParameter; +import nu.marginalia.search.query.model.UserSearchParameters; +import spark.Request; +import spark.Response; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class SearchApiQueryService { + private SearchOperator searchOperator; + + @Inject + public SearchApiQueryService(SearchOperator searchOperator) { + this.searchOperator = searchOperator; + } + + @SneakyThrows + public Object apiSearch(Request request, Response response) { + + final var ctx = Context.fromRequest(request); + final String queryParam = request.queryParams("query"); + final int limit; + SearchProfile profile = SearchProfile.YOLO; + + String count = request.queryParamOrDefault("count", "20"); + limit = Integer.parseInt(count); + + String index = request.queryParamOrDefault("index", "0"); + if (!Strings.isNullOrEmpty(index)) { + profile = switch (index) { + case "0" -> SearchProfile.YOLO; + case "1" -> SearchProfile.MODERN; + case "2" -> SearchProfile.DEFAULT; + case "3" -> SearchProfile.CORPO_CLEAN; + default -> SearchProfile.CORPO_CLEAN; + }; + } + + final String humanQuery = queryParam.trim(); + + var results = searchOperator.doApiSearch(ctx, new UserSearchParameters(humanQuery, profile, SearchJsParameter.DEFAULT)); + + return new ApiSearchResults("RESTRICTED", humanQuery, results.stream().map(this::convert).limit(limit).collect(Collectors.toList())); + } + + ApiSearchResult convert(UrlDetails url) { + List> details = new ArrayList<>(); + if (url.resultItem != null) { + var bySet = url.resultItem.scores.stream().collect(Collectors.groupingBy(EdgeSearchResultKeywordScore::set)); + + outer: + for (var entries : bySet.values()) { + List lst = new ArrayList<>(); + for (var entry : entries) { + var metadata = new EdgePageWordMetadata(entry.encodedWordMetadata()); + if (metadata.isEmpty()) + continue outer; + + Set flags = metadata.flagSet().stream().map(Object::toString).collect(Collectors.toSet()); + lst.add(new ApiSearchResultQueryDetails(entry.keyword(), metadata.tfIdf(), metadata.count(), flags)); + } + details.add(lst); + } + } + + return new ApiSearchResult( + url.url.toString(), + url.getTitle(), + url.getDescription(), + sanitizeNaN(url.getTermScore(), -100), + details + ); + } + + private double sanitizeNaN(double value, double alternative) { + if (!Double.isFinite(value)) { + return alternative; + } + return value; + } + +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchErrorPageService.java b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchErrorPageService.java similarity index 94% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchErrorPageService.java rename to services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchErrorPageService.java index 54e75178..07e60ca2 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchErrorPageService.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchErrorPageService.java @@ -1,18 +1,18 @@ -package nu.marginalia.wmsa.edge.search.svc; +package nu.marginalia.search.svc; import com.google.inject.Inject; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexClient; +import nu.marginalia.client.Context; +import nu.marginalia.index.client.EdgeIndexClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import spark.Response; -public class EdgeSearchErrorPageService { +public class SearchErrorPageService { private final EdgeIndexClient indexClient; private final Logger logger = LoggerFactory.getLogger(getClass()); @Inject - public EdgeSearchErrorPageService(EdgeIndexClient indexClient) { + public SearchErrorPageService(EdgeIndexClient indexClient) { this.indexClient = indexClient; } @@ -45,6 +45,7 @@ public class EdgeSearchErrorPageService { } } catch (Exception ex) { + logger.warn("Error during rendering of error page", ex); rsp.body(renderError("Error processing error", """ An error has occurred, additionally, an error occurred while handling that error diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchFlagSiteService.java b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchFlagSiteService.java similarity index 91% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchFlagSiteService.java rename to services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchFlagSiteService.java index fec5c737..5eb960a5 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchFlagSiteService.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchFlagSiteService.java @@ -1,9 +1,9 @@ -package nu.marginalia.wmsa.edge.search.svc; +package nu.marginalia.search.svc; import com.google.inject.Inject; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; import spark.Request; import spark.Response; import spark.Spark; @@ -17,7 +17,7 @@ import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; -public class EdgeSearchFlagSiteService { +public class SearchFlagSiteService { private final MustacheRenderer formTemplate; private final HikariDataSource dataSource; @@ -35,9 +35,9 @@ public class EdgeSearchFlagSiteService { private final Map categoryItemMap = categories.stream().collect(Collectors.toMap(CategoryItem::categoryName, Function.identity())); @Inject - public EdgeSearchFlagSiteService(RendererFactory rendererFactory, - HikariDataSource dataSource) throws IOException { - formTemplate = rendererFactory.renderer("edge/indict/indict-form"); + public SearchFlagSiteService(RendererFactory rendererFactory, + HikariDataSource dataSource) throws IOException { + formTemplate = rendererFactory.renderer("search/indict/indict-form"); this.dataSource = dataSource; } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchQueryIndexService.java b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryIndexService.java similarity index 61% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchQueryIndexService.java rename to services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryIndexService.java index c52584ca..c70ea2cc 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchQueryIndexService.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryIndexService.java @@ -1,40 +1,52 @@ -package nu.marginalia.wmsa.edge.search.svc; +package nu.marginalia.search.svc; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.index.client.EdgeIndexClient; -import nu.marginalia.wmsa.edge.index.model.QueryLimits; -import nu.marginalia.wmsa.edge.index.model.QueryStrategy; -import nu.marginalia.wmsa.edge.model.search.*; -import nu.marginalia.wmsa.edge.model.search.domain.SpecificationLimit; -import nu.marginalia.wmsa.edge.search.model.EdgeSearchProfile; -import nu.marginalia.wmsa.edge.search.query.model.EdgeSearchQuery; -import nu.marginalia.wmsa.edge.search.results.SearchResultDecorator; -import nu.marginalia.wmsa.edge.search.results.UrlDeduplicator; +import nu.marginalia.index.client.EdgeIndexClient; +import nu.marginalia.index.client.model.results.EdgeSearchResultItem; +import nu.marginalia.index.client.model.query.EdgeSearchSpecification; +import nu.marginalia.index.client.model.query.EdgeSearchSubquery; +import nu.marginalia.index.query.limit.QueryLimits; +import nu.marginalia.index.query.limit.QueryStrategy; +import nu.marginalia.index.query.limit.SpecificationLimit; +import nu.marginalia.search.model.PageScoreAdjustment; +import nu.marginalia.search.model.UrlDetails; +import nu.marginalia.search.model.SearchProfile; +import nu.marginalia.search.results.SearchResultDecorator; +import nu.marginalia.search.results.UrlDeduplicator; +import nu.marginalia.client.Context; +import nu.marginalia.search.query.model.SearchQuery; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; import java.util.regex.Pattern; @Singleton -public class EdgeSearchQueryIndexService { +public class SearchQueryIndexService { + private final Logger logger = LoggerFactory.getLogger(getClass()); private final SearchResultDecorator resultDecorator; - private final Comparator resultListComparator; + private final Comparator resultListComparator; private final EdgeIndexClient indexClient; @Inject - public EdgeSearchQueryIndexService(SearchResultDecorator resultDecorator, EdgeIndexClient indexClient) { + public SearchQueryIndexService(SearchResultDecorator resultDecorator, EdgeIndexClient indexClient) { this.resultDecorator = resultDecorator; this.indexClient = indexClient; - Comparator c = Comparator.comparing(ud -> Math.round(10*(ud.getTermScore() - ud.rankingIdAdjustment()))); + Comparator c = Comparator.comparing(ud -> Math.round(10*(ud.getTermScore() - ud.rankingIdAdjustment()))); resultListComparator = c - .thenComparing(EdgeUrlDetails::getRanking) - .thenComparing(EdgeUrlDetails::getId); + .thenComparing(UrlDetails::getRanking) + .thenComparing(UrlDetails::getId); } - public List performDumbQuery(Context ctx, EdgeSearchProfile profile, int limitPerDomain, int limitTotal, String... termsInclude) { + public List performDumbQuery(Context ctx, + SearchProfile profile, + int limitPerDomain, + int limitTotal, + String... termsInclude) + { List sqs = new ArrayList<>(); sqs.add(new EdgeSearchSubquery( @@ -57,30 +69,30 @@ public class EdgeSearchQueryIndexService { .queryStrategy(QueryStrategy.AUTO) .build(); - return performQuery(ctx, new EdgeSearchQuery(specs)); + return performQuery(ctx, new SearchQuery(specs)); } - public List performQuery(Context ctx, EdgeSearchQuery processedQuery) { - + public List performQuery(Context ctx, SearchQuery processedQuery) { final List results = indexClient.query(ctx, processedQuery.specs); - final List resultList = new ArrayList<>(results.size()); + List urlDetails = resultDecorator.getAllUrlDetails(results); - for (var details : resultDecorator.getAllUrlDetails(results)) { - details = details.withUrlQualityAdjustment( - adjustScoreBasedOnQuery(details, processedQuery.specs)); + urlDetails.replaceAll(details -> + details.withUrlQualityAdjustment(adjustScoreBasedOnQuery(details, processedQuery.specs)) + ); - resultList.add(details); - } + urlDetails.sort(resultListComparator); - resultList.sort(resultListComparator); + return limitAndDeduplicateResults(processedQuery, urlDetails); + } + private List limitAndDeduplicateResults(SearchQuery processedQuery, List decoratedResults) { var limits = processedQuery.specs.queryLimits; UrlDeduplicator deduplicator = new UrlDeduplicator(limits.resultsByDomain()); - List retList = new ArrayList<>(limits.resultsTotal()); + List retList = new ArrayList<>(limits.resultsTotal()); - for (var item : resultList) { + for (var item : decoratedResults) { if (retList.size() >= limits.resultsTotal()) break; @@ -94,7 +106,7 @@ public class EdgeSearchQueryIndexService { private final Pattern titleSplitPattern = Pattern.compile("[:!|./]|(\\s-|-\\s)|\\s{2,}"); - private EdgePageScoreAdjustment adjustScoreBasedOnQuery(EdgeUrlDetails p, EdgeSearchSpecification specs) { + private PageScoreAdjustment adjustScoreBasedOnQuery(UrlDetails p, EdgeSearchSpecification specs) { String titleLC = p.title == null ? "" : p.title.toLowerCase(); String descLC = p.description == null ? "" : p.description.toLowerCase(); String urlLC = p.url == null ? "" : p.url.path.toLowerCase(); @@ -135,7 +147,7 @@ public class EdgeSearchQueryIndexService { .sum(); } - return EdgePageScoreAdjustment.builder() + return PageScoreAdjustment.builder() .descAdj(Math.min(termCount, descHits) / (10. * termCount)) .descHitsAdj(descHitsAdj / 10.) .domainAdj(2 * Math.min(termCount, domainHits) / (double) termCount) diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchQueryService.java b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryService.java similarity index 67% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchQueryService.java rename to services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryService.java index 41a50ee6..97151015 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchQueryService.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchQueryService.java @@ -1,14 +1,14 @@ -package nu.marginalia.wmsa.edge.search.svc; +package nu.marginalia.search.svc; import com.google.inject.Inject; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.WebsiteUrl; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.search.command.CommandEvaluator; -import nu.marginalia.wmsa.edge.search.command.SearchJsParameter; -import nu.marginalia.wmsa.edge.search.command.SearchParameters; -import nu.marginalia.wmsa.edge.search.exceptions.RedirectException; -import nu.marginalia.wmsa.edge.search.model.EdgeSearchProfile; +import nu.marginalia.WebsiteUrl; +import nu.marginalia.search.model.SearchProfile; +import nu.marginalia.client.Context; +import nu.marginalia.search.command.CommandEvaluator; +import nu.marginalia.search.command.SearchJsParameter; +import nu.marginalia.search.command.SearchParameters; +import nu.marginalia.search.exceptions.RedirectException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import spark.Request; @@ -16,17 +16,17 @@ import spark.Response; import java.util.Optional; -public class EdgeSearchQueryService { +public class SearchQueryService { private WebsiteUrl websiteUrl; - private final EdgeSearchErrorPageService errorPageService; + private final SearchErrorPageService errorPageService; private final CommandEvaluator searchCommandEvaulator; private final Logger logger = LoggerFactory.getLogger(getClass()); @Inject - public EdgeSearchQueryService( + public SearchQueryService( WebsiteUrl websiteUrl, - EdgeSearchErrorPageService errorPageService, + SearchErrorPageService errorPageService, CommandEvaluator searchCommandEvaulator) { this.websiteUrl = websiteUrl; this.errorPageService = errorPageService; @@ -44,11 +44,11 @@ public class EdgeSearchQueryService { return null; } - final String profileStr = Optional.ofNullable(request.queryParams("profile")).orElse(EdgeSearchProfile.YOLO.name); + final String profileStr = Optional.ofNullable(request.queryParams("profile")).orElse(SearchProfile.YOLO.name); final String humanQuery = queryParam.trim(); var params = new SearchParameters( - EdgeSearchProfile.getSearchProfile(profileStr), + SearchProfile.getSearchProfile(profileStr), SearchJsParameter.parse(request.queryParams("js")), Boolean.parseBoolean(request.queryParams("detailed")) ); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchUnitConversionService.java b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchUnitConversionService.java similarity index 86% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchUnitConversionService.java rename to services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchUnitConversionService.java index 491a1361..d2d75bcf 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/svc/EdgeSearchUnitConversionService.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/svc/SearchUnitConversionService.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.search.svc; +package nu.marginalia.search.svc; -import nu.marginalia.wmsa.client.exception.RemoteException; -import nu.marginalia.wmsa.configuration.server.Context; -import nu.marginalia.wmsa.edge.assistant.client.AssistantClient; +import nu.marginalia.assistant.client.AssistantClient; +import nu.marginalia.client.exception.RemoteException; +import nu.marginalia.client.Context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,7 +15,7 @@ import java.util.function.Predicate; import java.util.regex.Pattern; @Singleton -public class EdgeSearchUnitConversionService { +public class SearchUnitConversionService { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Pattern conversionPattern = Pattern.compile("((\\d+|\\s+|[.()\\-^+%*/]|log[^a-z]|log2[^a-z]|sqrt[^a-z]|log10|cos[^a-z]|sin[^a-z]|tan[^a-z]|log2|pi[^a-z]|e[^a-z]|2pi[^a-z])+)\\s*([a-zA-Z][a-zA-Z^.0-9]*\\s?[a-zA-Z^.0-9]*)\\s+in\\s+([a-zA-Z^.0-9]+\\s?[a-zA-Z^.0-9]*)"); private final Predicate evalPredicate = Pattern.compile("(\\d+|\\s+|[.()\\-^+%*/]|log|log2|sqrt|log10|cos|sin|tan|pi|e|2pi)+").asMatchPredicate(); @@ -23,7 +23,7 @@ public class EdgeSearchUnitConversionService { private final AssistantClient assistantClient; @Inject - public EdgeSearchUnitConversionService(AssistantClient assistantClient) { + public SearchUnitConversionService(AssistantClient assistantClient) { this.assistantClient = assistantClient; } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/valuation/SearchResultValuator.java b/services-core/search-service/src/main/java/nu/marginalia/search/valuation/SearchResultValuator.java similarity index 96% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/valuation/SearchResultValuator.java rename to services-core/search-service/src/main/java/nu/marginalia/search/valuation/SearchResultValuator.java index 3395b019..a5233ad0 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/search/valuation/SearchResultValuator.java +++ b/services-core/search-service/src/main/java/nu/marginalia/search/valuation/SearchResultValuator.java @@ -1,13 +1,13 @@ -package nu.marginalia.wmsa.edge.search.valuation; +package nu.marginalia.search.valuation; import com.google.inject.Inject; import com.google.inject.Singleton; -import nu.marginalia.util.language.WordPatterns; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordFlags; -import nu.marginalia.wmsa.edge.index.model.EdgePageWordMetadata; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchResultKeywordScore; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSubquery; +import nu.marginalia.language.statistics.TermFrequencyDict; +import nu.marginalia.model.crawl.EdgePageWordFlags; +import nu.marginalia.model.idx.EdgePageWordMetadata; +import nu.marginalia.index.client.model.results.EdgeSearchResultKeywordScore; +import nu.marginalia.index.client.model.query.EdgeSearchSubquery; +import nu.marginalia.language.WordPatterns; import org.jetbrains.annotations.NotNull; import java.util.Arrays; diff --git a/marginalia_nu/src/main/resources/static/edge/about.html b/services-core/search-service/src/main/resources/static/search/about.html similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/about.html rename to services-core/search-service/src/main/resources/static/search/about.html diff --git a/marginalia_nu/src/main/resources/static/edge/changelog.html b/services-core/search-service/src/main/resources/static/search/changelog.html similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/changelog.html rename to services-core/search-service/src/main/resources/static/search/changelog.html diff --git a/marginalia_nu/src/main/resources/static/edge/crawler-ips.txt b/services-core/search-service/src/main/resources/static/search/crawler-ips.txt similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/crawler-ips.txt rename to services-core/search-service/src/main/resources/static/search/crawler-ips.txt diff --git a/marginalia_nu/src/main/resources/static/edge/error.html b/services-core/search-service/src/main/resources/static/search/error.html similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/error.html rename to services-core/search-service/src/main/resources/static/search/error.html diff --git a/marginalia_nu/src/main/resources/static/edge/favicon.ico b/services-core/search-service/src/main/resources/static/search/favicon.ico similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/favicon.ico rename to services-core/search-service/src/main/resources/static/search/favicon.ico diff --git a/marginalia_nu/src/main/resources/static/edge/known-issues.html b/services-core/search-service/src/main/resources/static/search/known-issues.html similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/known-issues.html rename to services-core/search-service/src/main/resources/static/search/known-issues.html diff --git a/marginalia_nu/src/main/resources/static/edge/maintenance.html b/services-core/search-service/src/main/resources/static/search/maintenance.html similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/maintenance.html rename to services-core/search-service/src/main/resources/static/search/maintenance.html diff --git a/marginalia_nu/src/main/resources/static/edge/notes.html b/services-core/search-service/src/main/resources/static/search/notes.html similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/notes.html rename to services-core/search-service/src/main/resources/static/search/notes.html diff --git a/marginalia_nu/src/main/resources/static/edge/opensearch.xml b/services-core/search-service/src/main/resources/static/search/opensearch.xml similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/opensearch.xml rename to services-core/search-service/src/main/resources/static/search/opensearch.xml diff --git a/marginalia_nu/src/main/resources/static/edge/robots.txt b/services-core/search-service/src/main/resources/static/search/robots.txt similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/robots.txt rename to services-core/search-service/src/main/resources/static/search/robots.txt diff --git a/marginalia_nu/src/main/resources/static/edge/style-new.css b/services-core/search-service/src/main/resources/static/search/style-new.css similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/style-new.css rename to services-core/search-service/src/main/resources/static/search/style-new.css diff --git a/marginalia_nu/src/main/resources/static/edge/tts.js b/services-core/search-service/src/main/resources/static/search/tts.js similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/tts.js rename to services-core/search-service/src/main/resources/static/search/tts.js diff --git a/marginalia_nu/src/main/resources/static/edge/wiki-clean.html b/services-core/search-service/src/main/resources/static/search/wiki-clean.html similarity index 100% rename from marginalia_nu/src/main/resources/static/edge/wiki-clean.html rename to services-core/search-service/src/main/resources/static/search/wiki-clean.html diff --git a/marginalia_nu/src/main/resources/templates/edge/browse-result.hdb b/services-core/search-service/src/main/resources/templates/search/browse-result.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/edge/browse-result.hdb rename to services-core/search-service/src/main/resources/templates/search/browse-result.hdb diff --git a/marginalia_nu/src/main/resources/templates/edge/browse-results.hdb b/services-core/search-service/src/main/resources/templates/search/browse-results.hdb similarity index 86% rename from marginalia_nu/src/main/resources/templates/edge/browse-results.hdb rename to services-core/search-service/src/main/resources/templates/search/browse-results.hdb index a6a8b0f8..82df7343 100644 --- a/marginalia_nu/src/main/resources/templates/edge/browse-results.hdb +++ b/services-core/search-service/src/main/resources/templates/search/browse-results.hdb @@ -11,10 +11,10 @@ -{{>edge/parts/search-header}} +{{>search/parts/search-header}}
    - {{>edge/parts/search-form}} + {{>search/parts/search-form}}
    @@ -28,7 +28,7 @@
    {{/if}} -{{#each results}}{{>edge/browse-result}}{{/each}} +{{#each results}}{{>search/browse-result}}{{/each}} {{#unless focusDomain}}
    @@ -45,5 +45,5 @@
    -{{>edge/parts/search-footer}} +{{>search/parts/search-footer}} diff --git a/marginalia_nu/src/main/resources/templates/edge/conversion-results.hdb b/services-core/search-service/src/main/resources/templates/search/conversion-results.hdb similarity index 90% rename from marginalia_nu/src/main/resources/templates/edge/conversion-results.hdb rename to services-core/search-service/src/main/resources/templates/search/conversion-results.hdb index 50e5840b..85ff5750 100644 --- a/marginalia_nu/src/main/resources/templates/edge/conversion-results.hdb +++ b/services-core/search-service/src/main/resources/templates/search/conversion-results.hdb @@ -11,10 +11,10 @@ -{{>edge/parts/search-header}} +{{>search/parts/search-header}}
    - {{>edge/parts/search-form}} + {{>search/parts/search-form}}
    @@ -33,5 +33,5 @@
    -{{>edge/parts/search-footer}} +{{>search/parts/search-footer}} \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/templates/edge/dictionary-results.hdb b/services-core/search-service/src/main/resources/templates/search/dictionary-results.hdb similarity index 92% rename from marginalia_nu/src/main/resources/templates/edge/dictionary-results.hdb rename to services-core/search-service/src/main/resources/templates/search/dictionary-results.hdb index b7888418..ed43e2a7 100644 --- a/marginalia_nu/src/main/resources/templates/edge/dictionary-results.hdb +++ b/services-core/search-service/src/main/resources/templates/search/dictionary-results.hdb @@ -11,10 +11,10 @@ -{{>edge/parts/search-header}} +{{>search/parts/search-header}}
    - {{>edge/parts/search-form}} + {{>search/parts/search-form}}
    {{#unless entries}} @@ -44,5 +44,5 @@
    -{{>edge/parts/search-footer}} +{{>search/parts/search-footer}} diff --git a/marginalia_nu/src/main/resources/templates/edge/error-page.hdb b/services-core/search-service/src/main/resources/templates/search/error-page.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/edge/error-page.hdb rename to services-core/search-service/src/main/resources/templates/search/error-page.hdb diff --git a/marginalia_nu/src/main/resources/templates/edge/index.hdb b/services-core/search-service/src/main/resources/templates/search/index.hdb similarity index 86% rename from marginalia_nu/src/main/resources/templates/edge/index.hdb rename to services-core/search-service/src/main/resources/templates/search/index.hdb index fe30d9d6..06942ccf 100644 --- a/marginalia_nu/src/main/resources/templates/edge/index.hdb +++ b/services-core/search-service/src/main/resources/templates/search/index.hdb @@ -19,10 +19,10 @@ -{{>edge/parts/search-header}} +{{>search/parts/search-header}} -{{>edge/parts/search-footer}} +{{>search/parts/search-footer}} diff --git a/marginalia_nu/src/main/resources/templates/edge/indict/indict-form.hdb b/services-core/search-service/src/main/resources/templates/search/indict/indict-form.hdb similarity index 95% rename from marginalia_nu/src/main/resources/templates/edge/indict/indict-form.hdb rename to services-core/search-service/src/main/resources/templates/search/indict/indict-form.hdb index 2fa75eab..5a8ebbce 100644 --- a/marginalia_nu/src/main/resources/templates/edge/indict/indict-form.hdb +++ b/services-core/search-service/src/main/resources/templates/search/indict/indict-form.hdb @@ -12,10 +12,10 @@ -{{>edge/parts/search-header}} +{{>search/parts/search-header}}
    -{{>edge/parts/search-form}} +{{>search/parts/search-form}}
    @@ -76,5 +76,5 @@ you may also reach a human being through email at kontakt@marginalia.nu {{/if}}
    -{{>edge/parts/search-footer}} +{{>search/parts/search-footer}} diff --git a/marginalia_nu/src/main/resources/templates/edge/parts/search-footer.hdb b/services-core/search-service/src/main/resources/templates/search/parts/search-footer.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/edge/parts/search-footer.hdb rename to services-core/search-service/src/main/resources/templates/search/parts/search-footer.hdb diff --git a/marginalia_nu/src/main/resources/templates/edge/parts/search-form.hdb b/services-core/search-service/src/main/resources/templates/search/parts/search-form.hdb similarity index 95% rename from marginalia_nu/src/main/resources/templates/edge/parts/search-form.hdb rename to services-core/search-service/src/main/resources/templates/search/parts/search-form.hdb index 21c3a1a8..ecc35ead 100644 --- a/marginalia_nu/src/main/resources/templates/edge/parts/search-form.hdb +++ b/services-core/search-service/src/main/resources/templates/search/parts/search-form.hdb @@ -28,7 +28,7 @@ \ No newline at end of file diff --git a/marginalia_nu/src/main/resources/templates/edge/parts/search-header.hdb b/services-core/search-service/src/main/resources/templates/search/parts/search-header.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/edge/parts/search-header.hdb rename to services-core/search-service/src/main/resources/templates/search/parts/search-header.hdb diff --git a/marginalia_nu/src/main/resources/templates/edge/parts/site-info-index.hdb b/services-core/search-service/src/main/resources/templates/search/parts/site-info-index.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/edge/parts/site-info-index.hdb rename to services-core/search-service/src/main/resources/templates/search/parts/site-info-index.hdb diff --git a/marginalia_nu/src/main/resources/templates/edge/parts/site-info-links.hdb b/services-core/search-service/src/main/resources/templates/search/parts/site-info-links.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/edge/parts/site-info-links.hdb rename to services-core/search-service/src/main/resources/templates/search/parts/site-info-links.hdb diff --git a/marginalia_nu/src/main/resources/templates/edge/search-result-metadata.hdb b/services-core/search-service/src/main/resources/templates/search/search-result-metadata.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/edge/search-result-metadata.hdb rename to services-core/search-service/src/main/resources/templates/search/search-result-metadata.hdb diff --git a/marginalia_nu/src/main/resources/templates/edge/search-result.hdb b/services-core/search-service/src/main/resources/templates/search/search-result.hdb similarity index 92% rename from marginalia_nu/src/main/resources/templates/edge/search-result.hdb rename to services-core/search-service/src/main/resources/templates/search/search-result.hdb index c5d220c1..59460c8f 100644 --- a/marginalia_nu/src/main/resources/templates/edge/search-result.hdb +++ b/services-core/search-service/src/main/resources/templates/search/search-result.hdb @@ -9,7 +9,7 @@ Info {{#if hasMoreResults}}{{resultsFromSameDomain}}+{{/if}} {{/unless}} -
    {{>edge/search-result-metadata}}
    +
    {{>search/search-result-metadata}}

    diff --git a/marginalia_nu/src/main/resources/templates/edge/search-results.hdb b/services-core/search-service/src/main/resources/templates/search/search-results.hdb similarity index 89% rename from marginalia_nu/src/main/resources/templates/edge/search-results.hdb rename to services-core/search-service/src/main/resources/templates/search/search-results.hdb index b0a0848e..d6af3455 100644 --- a/marginalia_nu/src/main/resources/templates/edge/search-results.hdb +++ b/services-core/search-service/src/main/resources/templates/search/search-results.hdb @@ -14,10 +14,10 @@ -{{>edge/parts/search-header}} +{{>search/parts/search-header}}
    -{{>edge/parts/search-form}} +{{>search/parts/search-form}}
    @@ -42,11 +42,11 @@ {{#unless evalResult}}{{#if problems}}

    Suggestions

      {{#each problems}}
    • {{{.}}}
    • {{/each}}
    {{/if}}{{/unless}} - {{#each domainResults}}{{>edge/browse-result}}{{/each}} - {{#each results}}{{>edge/search-result}}{{/each}} + {{#each domainResults}}{{>search/browse-result}}{{/each}} + {{#each results}}{{>search/search-result}}{{/each}}
    -{{>edge/parts/search-footer}} +{{>search/parts/search-footer}} diff --git a/marginalia_nu/src/main/resources/templates/edge/site-info.hdb b/services-core/search-service/src/main/resources/templates/search/site-info.hdb similarity index 74% rename from marginalia_nu/src/main/resources/templates/edge/site-info.hdb rename to services-core/search-service/src/main/resources/templates/search/site-info.hdb index 6563844c..4e2ac7e2 100644 --- a/marginalia_nu/src/main/resources/templates/edge/site-info.hdb +++ b/services-core/search-service/src/main/resources/templates/search/site-info.hdb @@ -11,10 +11,10 @@ -{{>edge/parts/search-header}} +{{>search/parts/search-header}}
    - {{>edge/parts/search-form}} + {{>search/parts/search-form}}
    @@ -24,14 +24,14 @@
    - {{>edge/parts/site-info-index}} - {{>edge/parts/site-info-links}} + {{>search/parts/site-info-index}} + {{>search/parts/site-info-links}} - {{#each results}}{{>edge/search-result}}{{/each}} + {{#each results}}{{>search/search-result}}{{/each}}
    -{{>edge/parts/search-footer}} +{{>search/parts/search-footer}} diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommandTest.java b/services-core/search-service/src/test/java/nu/marginalia/search/command/commands/BangCommandTest.java similarity index 90% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommandTest.java rename to services-core/search-service/src/test/java/nu/marginalia/search/command/commands/BangCommandTest.java index b2b4c90b..a5de2eb2 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/command/commands/BangCommandTest.java +++ b/services-core/search-service/src/test/java/nu/marginalia/search/command/commands/BangCommandTest.java @@ -1,6 +1,6 @@ -package nu.marginalia.wmsa.edge.search.command.commands; +package nu.marginalia.search.command.commands; -import nu.marginalia.wmsa.edge.search.exceptions.RedirectException; +import nu.marginalia.search.exceptions.RedirectException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/QueryFactoryTest.java b/services-core/search-service/src/test/java/nu/marginalia/search/query/QueryFactoryTest.java similarity index 82% rename from marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/QueryFactoryTest.java rename to services-core/search-service/src/test/java/nu/marginalia/search/query/QueryFactoryTest.java index 987c258d..48842b9a 100644 --- a/marginalia_nu/src/test/java/nu/marginalia/wmsa/edge/search/query/QueryFactoryTest.java +++ b/services-core/search-service/src/test/java/nu/marginalia/search/query/QueryFactoryTest.java @@ -1,14 +1,15 @@ -package nu.marginalia.wmsa.edge.search.query; +package nu.marginalia.search.query; -import nu.marginalia.wmsa.configuration.WmsaHome; -import nu.marginalia.wmsa.edge.assistant.dict.NGramBloomFilter; -import nu.marginalia.wmsa.edge.assistant.dict.TermFrequencyDict; -import nu.marginalia.wmsa.edge.model.search.EdgeSearchSpecification; -import nu.marginalia.wmsa.edge.model.search.domain.SpecificationLimitType; -import nu.marginalia.wmsa.edge.search.command.SearchJsParameter; -import nu.marginalia.wmsa.edge.search.model.EdgeSearchProfile; -import nu.marginalia.wmsa.edge.search.query.model.EdgeUserSearchParameters; -import nu.marginalia.wmsa.edge.search.valuation.SearchResultValuator; +import nu.marginalia.WmsaHome; +import nu.marginalia.index.query.limit.SpecificationLimitType; +import nu.marginalia.language.statistics.EnglishDictionary; +import nu.marginalia.index.client.model.query.EdgeSearchSpecification; +import nu.marginalia.language.statistics.NGramBloomFilter; +import nu.marginalia.language.statistics.TermFrequencyDict; +import nu.marginalia.search.command.SearchJsParameter; +import nu.marginalia.search.model.SearchProfile; +import nu.marginalia.search.query.model.UserSearchParameters; +import nu.marginalia.search.valuation.SearchResultValuator; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -38,7 +39,7 @@ public class QueryFactoryTest { public EdgeSearchSpecification parseAndGetSpecs(String query) { return queryFactory.createQuery( - new EdgeUserSearchParameters(query, EdgeSearchProfile.CORPO, SearchJsParameter.DEFAULT) + new UserSearchParameters(query, SearchProfile.CORPO, SearchJsParameter.DEFAULT) ).specs; } diff --git a/services-core/search-service/src/test/java/nu/marginalia/util/TestLanguageModels.java b/services-core/search-service/src/test/java/nu/marginalia/util/TestLanguageModels.java new file mode 100644 index 00000000..81df1ed9 --- /dev/null +++ b/services-core/search-service/src/test/java/nu/marginalia/util/TestLanguageModels.java @@ -0,0 +1,37 @@ +package nu.marginalia.util; + +import nu.marginalia.LanguageModels; +import nu.marginalia.WmsaHome; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +public class TestLanguageModels { + private static final Path LANGUAGE_MODELS_DEFAULT = WmsaHome.getHomePath().resolve("model"); + + public static Path getLanguageModelsPath() { + final Path languageModelsHome = Optional.ofNullable(System.getenv("LANGUAGE_MODELS_HOME")) + .map(Path::of) + .orElse(LANGUAGE_MODELS_DEFAULT); + + if (!Files.isDirectory(languageModelsHome)) { + throw new IllegalStateException("Could not find $LANGUAGE_MODELS_HOME, see doc/language-models.md"); + } + return languageModelsHome; + } + + public static LanguageModels getLanguageModels() { + + var languageModelsHome = getLanguageModelsPath(); + + return new LanguageModels( + languageModelsHome.resolve("ngrams.bin"), + languageModelsHome.resolve("tfreq-new-algo3.bin"), + languageModelsHome.resolve("opennlp-sentence.bin"), + languageModelsHome.resolve("English.RDR"), + languageModelsHome.resolve("English.DICT"), + languageModelsHome.resolve("opennlp-tokens.bin") + ); + } +} diff --git a/services-satellite/api-service/build.gradle b/services-satellite/api-service/build.gradle new file mode 100644 index 00000000..afcb5e7c --- /dev/null +++ b/services-satellite/api-service/build.gradle @@ -0,0 +1,63 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'application' + id 'com.palantir.docker' version '0.34.0' + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + + +application { + mainClass = 'nu.marginalia.api.ApiMain' + applicationName = 'api-service' +} + +apply from: "$rootProject.projectDir/docker-service.gradle" + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:service') + implementation project(':common:config') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':api:search-api') + + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.guice + implementation libs.rxjava + implementation libs.spark + implementation libs.opencsv + implementation libs.trove + implementation libs.fastutil + implementation libs.bundles.gson + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} + diff --git a/services-satellite/api-service/src/main/java/nu/marginalia/api/ApiMain.java b/services-satellite/api-service/src/main/java/nu/marginalia/api/ApiMain.java new file mode 100644 index 00000000..5d9f80c1 --- /dev/null +++ b/services-satellite/api-service/src/main/java/nu/marginalia/api/ApiMain.java @@ -0,0 +1,28 @@ +package nu.marginalia.api; + +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.SearchServiceDescriptors; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.module.DatabaseModule; +import nu.marginalia.service.server.Initialization; + +public class ApiMain extends MainClass { + + @Inject + public ApiMain(ApiService service) { + } + + public static void main(String... args) { + init(ServiceId.Api, args); + + Injector injector = Guice.createInjector( + new DatabaseModule(), + new ConfigurationModule(SearchServiceDescriptors.descriptors, ServiceId.Api)); + injector.getInstance(ApiMain.class); + injector.getInstance(Initialization.class).setReady(); + } +} diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/ApiService.java b/services-satellite/api-service/src/main/java/nu/marginalia/api/ApiService.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/api/ApiService.java rename to services-satellite/api-service/src/main/java/nu/marginalia/api/ApiService.java index 9c897a9b..2ebe6be8 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/ApiService.java +++ b/services-satellite/api-service/src/main/java/nu/marginalia/api/ApiService.java @@ -1,14 +1,18 @@ -package nu.marginalia.wmsa.api; +package nu.marginalia.api; import com.google.common.base.Strings; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.name.Named; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.api.model.ApiLicense; -import nu.marginalia.wmsa.client.GsonFactory; -import nu.marginalia.wmsa.configuration.server.*; -import nu.marginalia.wmsa.edge.search.client.EdgeSearchClient; +import nu.marginalia.api.model.ApiLicense; +import nu.marginalia.client.Context; +import nu.marginalia.model.gson.GsonFactory; +import nu.marginalia.search.client.EdgeSearchClient; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.RateLimiter; +import nu.marginalia.service.server.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import spark.Request; @@ -17,7 +21,7 @@ import spark.Spark; import java.util.concurrent.ConcurrentHashMap; -public class ApiService extends Service { +public class ApiService extends Service { private final Logger logger = LoggerFactory.getLogger(getClass()); private final Gson gson = GsonFactory.get(); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiLicense.java b/services-satellite/api-service/src/main/java/nu/marginalia/api/model/ApiLicense.java similarity index 89% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiLicense.java rename to services-satellite/api-service/src/main/java/nu/marginalia/api/model/ApiLicense.java index 15a6ae08..32bf6691 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/api/model/ApiLicense.java +++ b/services-satellite/api-service/src/main/java/nu/marginalia/api/model/ApiLicense.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.api.model; +package nu.marginalia.api.model; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; diff --git a/services-satellite/dating-service/build.gradle b/services-satellite/dating-service/build.gradle new file mode 100644 index 00000000..e57e15a8 --- /dev/null +++ b/services-satellite/dating-service/build.gradle @@ -0,0 +1,66 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'application' + id 'com.palantir.docker' version '0.34.0' + id 'jvm-test-suite' +} + +application { + mainClass = 'nu.marginalia.dating.DatingMain' + applicationName = 'dating-service' +} + +apply from: "$rootProject.projectDir/docker-service.gradle" + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:service') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + implementation project(':features:renderer') + implementation project(':features:screenshots') + implementation project(':libraries:language-processing') + implementation project(':features:random-websites') + + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.guice + implementation libs.rxjava + implementation libs.spark + implementation libs.opencsv + implementation libs.trove + implementation libs.fastutil + implementation libs.bundles.gson + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + +} + + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} + + diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingMain.java b/services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingMain.java similarity index 58% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingMain.java rename to services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingMain.java index 83b62e7f..35608956 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingMain.java +++ b/services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingMain.java @@ -1,17 +1,16 @@ -package nu.marginalia.wmsa.edge.dating; +package nu.marginalia.dating; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.configuration.server.Initialization; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.SearchServiceDescriptors; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.module.DatabaseModule; +import nu.marginalia.service.server.Initialization; import spark.Spark; -import java.io.IOException; - public class DatingMain extends MainClass { final DatingService service; @@ -21,13 +20,13 @@ public class DatingMain extends MainClass { } public static void main(String... args) { - init(ServiceDescriptor.DATING, args); + init(ServiceId.Dating, args); Spark.staticFileLocation("/static/dating/"); Injector injector = Guice.createInjector( new DatingModule(), - new ConfigurationModule(), + new ConfigurationModule(SearchServiceDescriptors.descriptors, ServiceId.Dating), new DatabaseModule() ); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingModule.java b/services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingModule.java similarity index 70% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingModule.java rename to services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingModule.java index b92f67ba..e861d4df 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingModule.java +++ b/services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingModule.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.dating; +package nu.marginalia.dating; import com.google.inject.AbstractModule; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingService.java b/services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingService.java similarity index 82% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingService.java rename to services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingService.java index 67753527..2bcafe4a 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingService.java +++ b/services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingService.java @@ -1,18 +1,19 @@ -package nu.marginalia.wmsa.edge.dating; +package nu.marginalia.dating; import com.google.inject.Inject; import com.google.inject.name.Named; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; -import nu.marginalia.wmsa.edge.assistant.screenshot.ScreenshotService; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklist; -import nu.marginalia.wmsa.edge.model.id.EdgeId; -import nu.marginalia.wmsa.edge.search.model.BrowseResult; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; +import nu.marginalia.browse.DbBrowseDomainsRandom; +import nu.marginalia.browse.DbBrowseDomainsSimilarCosine; +import nu.marginalia.browse.model.BrowseResult; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklist; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; +import nu.marginalia.screenshot.ScreenshotService; +import nu.marginalia.model.id.EdgeId; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.Service; import org.jetbrains.annotations.NotNull; import spark.Request; import spark.Response; @@ -24,8 +25,9 @@ import java.util.Map; import java.util.Optional; public class DatingService extends Service { - private final EdgeDataStoreDao edgeDataStoreDao; private final EdgeDomainBlacklist blacklist; + private final DbBrowseDomainsSimilarCosine browseSimilarCosine; + private final DbBrowseDomainsRandom browseRandom; private final MustacheRenderer datingRenderer; private final ScreenshotService screenshotService; private final String SESSION_OBJECT_NAME = "so"; @@ -33,19 +35,21 @@ public class DatingService extends Service { @Inject public DatingService(@Named("service-host") String ip, @Named("service-port") Integer port, - EdgeDataStoreDao edgeDataStoreDao, RendererFactory rendererFactory, Initialization initialization, MetricsServer metricsServer, EdgeDomainBlacklist blacklist, + DbBrowseDomainsSimilarCosine browseSimilarCosine, + DbBrowseDomainsRandom browseRandom, ScreenshotService screenshotService) { super(ip, port, initialization, metricsServer); - this.edgeDataStoreDao = edgeDataStoreDao; this.blacklist = blacklist; datingRenderer = rendererFactory.renderer("dating/dating-view"); + this.browseSimilarCosine = browseSimilarCosine; + this.browseRandom = browseRandom; this.screenshotService = screenshotService; Spark.get("/public/reset", this::getReset); @@ -100,7 +104,7 @@ public class DatingService extends Service { var current = session.getCurrent(); if (current == null) { - BrowseResult res = session.next(edgeDataStoreDao, blacklist); + BrowseResult res = session.next(browseRandom, blacklist); res = findViableDomain(session, res); session.browseForward(res); current = session.getCurrent(); @@ -117,7 +121,7 @@ public class DatingService extends Service { } var session = sessionObjectOpt.get(); - BrowseResult res = session.next(edgeDataStoreDao, blacklist); + BrowseResult res = session.next(browseRandom, blacklist); res = findViableDomain(session, res); @@ -157,7 +161,7 @@ public class DatingService extends Service { var session = sessionObjectOpt.get(); int id = Integer.parseInt(request.params("id")); - BrowseResult res = session.nextSimilar(new EdgeId<>(id), edgeDataStoreDao, blacklist); + BrowseResult res = session.nextSimilar(new EdgeId<>(id), browseSimilarCosine, blacklist); res = findViableDomain(session, res); @@ -170,7 +174,7 @@ public class DatingService extends Service { @NotNull private BrowseResult findViableDomain(DatingSessionObject session, BrowseResult res) { while (!screenshotService.hasScreenshot(new EdgeId<>(res.domainId())) || session.isRecent(res)) { - res = session.next(edgeDataStoreDao, blacklist); + res = session.next(browseRandom, blacklist); } return res; } diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingSessionObject.java b/services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingSessionObject.java similarity index 72% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingSessionObject.java rename to services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingSessionObject.java index 39eed0a6..89d0215d 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/dating/DatingSessionObject.java +++ b/services-satellite/dating-service/src/main/java/nu/marginalia/dating/DatingSessionObject.java @@ -1,10 +1,11 @@ -package nu.marginalia.wmsa.edge.dating; +package nu.marginalia.dating; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDataStoreDao; -import nu.marginalia.wmsa.edge.dbcommon.EdgeDomainBlacklist; -import nu.marginalia.wmsa.edge.model.EdgeDomain; -import nu.marginalia.wmsa.edge.model.id.EdgeId; -import nu.marginalia.wmsa.edge.search.model.BrowseResult; +import nu.marginalia.browse.DbBrowseDomainsRandom; +import nu.marginalia.browse.DbBrowseDomainsSimilarCosine; +import nu.marginalia.browse.model.BrowseResult; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.model.dbcommon.EdgeDomainBlacklist; +import nu.marginalia.model.id.EdgeId; import java.util.LinkedList; @@ -21,15 +22,15 @@ public class DatingSessionObject { return current; } - public BrowseResult next(EdgeDataStoreDao dao, EdgeDomainBlacklist blacklist) { + public BrowseResult next(DbBrowseDomainsRandom random, EdgeDomainBlacklist blacklist) { if (queue.isEmpty()) { - dao.getRandomDomains(25, blacklist, 0).forEach(queue::addLast); + random.getRandomDomains(25, blacklist, 0).forEach(queue::addLast); } return queue.pollFirst(); } - public BrowseResult nextSimilar(EdgeId id, EdgeDataStoreDao dao, EdgeDomainBlacklist blacklist) { - dao.getDomainNeighborsAdjacent(id, blacklist, 25).forEach(queue::addFirst); + public BrowseResult nextSimilar(EdgeId id, DbBrowseDomainsSimilarCosine adjacent, EdgeDomainBlacklist blacklist) { + adjacent.getDomainNeighborsAdjacentCosine(id, blacklist, 25).forEach(queue::addFirst); while (queue.size() > MAX_QUEUE_SIZE) { queue.removeLast(); diff --git a/services-satellite/dating-service/src/main/resources/static/dating/index.html b/services-satellite/dating-service/src/main/resources/static/dating/index.html new file mode 100644 index 00000000..e69de29b diff --git a/services-satellite/dating-service/src/main/resources/static/dating/robots.txt b/services-satellite/dating-service/src/main/resources/static/dating/robots.txt new file mode 100644 index 00000000..e69de29b diff --git a/marginalia_nu/src/main/resources/templates/dating/dating-view.hdb b/services-satellite/dating-service/src/main/resources/templates/dating/dating-view.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/dating/dating-view.hdb rename to services-satellite/dating-service/src/main/resources/templates/dating/dating-view.hdb diff --git a/services-satellite/explorer-service/build.gradle b/services-satellite/explorer-service/build.gradle new file mode 100644 index 00000000..94f8305c --- /dev/null +++ b/services-satellite/explorer-service/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + id 'application' + id 'com.palantir.docker' version '0.34.0' + id 'jvm-test-suite' +} + +application { + mainClass = 'nu.marginalia.explorer.ExplorerMain' + applicationName = 'explorer-service' +} + +apply from: "$rootProject.projectDir/docker-service.gradle" + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':common:service') + implementation project(':common:service-discovery') + implementation project(':common:service-client') + + implementation project(':features:renderer') + implementation project(':features:random-websites') + + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + + implementation libs.prometheus + implementation libs.notnull + implementation libs.guice + implementation libs.rxjava + implementation libs.spark + implementation libs.opencsv + implementation libs.trove + implementation libs.fastutil + implementation libs.bundles.gson + implementation libs.bundles.mariadb + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito +} + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/explorer/ExplorerMain.java b/services-satellite/explorer-service/src/main/java/nu/marginalia/explorer/ExplorerMain.java similarity index 57% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/explorer/ExplorerMain.java rename to services-satellite/explorer-service/src/main/java/nu/marginalia/explorer/ExplorerMain.java index cf6a0d9b..4566fa62 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/explorer/ExplorerMain.java +++ b/services-satellite/explorer-service/src/main/java/nu/marginalia/explorer/ExplorerMain.java @@ -1,13 +1,14 @@ -package nu.marginalia.wmsa.edge.explorer; +package nu.marginalia.explorer; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; -import nu.marginalia.wmsa.configuration.MainClass; -import nu.marginalia.wmsa.configuration.ServiceDescriptor; -import nu.marginalia.wmsa.configuration.module.ConfigurationModule; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.configuration.server.Initialization; +import nu.marginalia.service.MainClass; +import nu.marginalia.service.SearchServiceDescriptors; +import nu.marginalia.service.id.ServiceId; +import nu.marginalia.service.module.ConfigurationModule; +import nu.marginalia.service.module.DatabaseModule; +import nu.marginalia.service.server.Initialization; import spark.Spark; public class ExplorerMain extends MainClass { @@ -19,12 +20,12 @@ public class ExplorerMain extends MainClass { } public static void main(String... args) { - init(ServiceDescriptor.EXPLORER, args); + init(ServiceId.Explorer, args); Spark.staticFileLocation("/static/explore/"); Injector injector = Guice.createInjector( - new ConfigurationModule(), + new ConfigurationModule(SearchServiceDescriptors.descriptors, ServiceId.Explorer), new DatabaseModule() ); diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/explorer/ExplorerService.java b/services-satellite/explorer-service/src/main/java/nu/marginalia/explorer/ExplorerService.java similarity index 95% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/explorer/ExplorerService.java rename to services-satellite/explorer-service/src/main/java/nu/marginalia/explorer/ExplorerService.java index 38ec7b6b..8f967bb1 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/explorer/ExplorerService.java +++ b/services-satellite/explorer-service/src/main/java/nu/marginalia/explorer/ExplorerService.java @@ -1,15 +1,15 @@ -package nu.marginalia.wmsa.edge.explorer; +package nu.marginalia.explorer; import com.google.inject.Inject; import com.google.inject.name.Named; import com.zaxxer.hikari.HikariDataSource; import lombok.SneakyThrows; -import nu.marginalia.wmsa.configuration.server.Initialization; -import nu.marginalia.wmsa.configuration.server.MetricsServer; -import nu.marginalia.wmsa.configuration.server.Service; -import nu.marginalia.wmsa.renderer.mustache.MustacheRenderer; -import nu.marginalia.wmsa.renderer.mustache.RendererFactory; -import nu.marginalia.wmsa.resource_store.StaticResources; +import nu.marginalia.renderer.MustacheRenderer; +import nu.marginalia.renderer.RendererFactory; +import nu.marginalia.service.server.Initialization; +import nu.marginalia.service.server.MetricsServer; +import nu.marginalia.service.server.Service; +import nu.marginalia.service.server.StaticResources; import org.jetbrains.annotations.NotNull; import spark.Request; import spark.Response; @@ -54,18 +54,21 @@ public class ExplorerService extends Service { super(ip, port, initialization, metricsServer); renderer = rendererFactory.renderer("explorer/explorer"); + this.dataSource = dataSource; this.staticResources = staticResources; + Spark.get("/public/", this::serveIndex, this::render); Spark.get("/public/search", this::search, this::render); Spark.get("/public/:resource", this::serveStatic); - } private Object serveStatic(Request request, Response response) { String resource = request.params("resource"); + staticResources.serveStatic("explore", resource, request, response); + return ""; } diff --git a/services-satellite/explorer-service/src/main/resources/static/explore/style.css b/services-satellite/explorer-service/src/main/resources/static/explore/style.css new file mode 100644 index 00000000..e69de29b diff --git a/marginalia_nu/src/main/resources/templates/explorer/explorer-about.hdb b/services-satellite/explorer-service/src/main/resources/templates/explorer/explorer-about.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/explorer/explorer-about.hdb rename to services-satellite/explorer-service/src/main/resources/templates/explorer/explorer-about.hdb diff --git a/marginalia_nu/src/main/resources/templates/explorer/explorer-messages.hdb b/services-satellite/explorer-service/src/main/resources/templates/explorer/explorer-messages.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/explorer/explorer-messages.hdb rename to services-satellite/explorer-service/src/main/resources/templates/explorer/explorer-messages.hdb diff --git a/marginalia_nu/src/main/resources/templates/explorer/explorer-results.hdb b/services-satellite/explorer-service/src/main/resources/templates/explorer/explorer-results.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/explorer/explorer-results.hdb rename to services-satellite/explorer-service/src/main/resources/templates/explorer/explorer-results.hdb diff --git a/marginalia_nu/src/main/resources/templates/explorer/explorer-search.hdb b/services-satellite/explorer-service/src/main/resources/templates/explorer/explorer-search.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/explorer/explorer-search.hdb rename to services-satellite/explorer-service/src/main/resources/templates/explorer/explorer-search.hdb diff --git a/marginalia_nu/src/main/resources/templates/explorer/explorer.hdb b/services-satellite/explorer-service/src/main/resources/templates/explorer/explorer.hdb similarity index 100% rename from marginalia_nu/src/main/resources/templates/explorer/explorer.hdb rename to services-satellite/explorer-service/src/main/resources/templates/explorer/explorer.hdb diff --git a/services-satellite/readme.md b/services-satellite/readme.md new file mode 100644 index 00000000..f276bd5b --- /dev/null +++ b/services-satellite/readme.md @@ -0,0 +1,7 @@ +# Satellite Services + +The satellite services offer non-essential functionality. + +* The [api-service](api-service/) offers a public API +* The [dating-service](dating-service/) is [explore.marginalia.nu](https://explore.marginalia.nu/) +* The [explorer-service](dating-service/) is [explore2.marginalia.nu](https://explore2.marginalia.nu/) diff --git a/settings.gradle b/settings.gradle index 149ff1ea..8f00c50b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,161 @@ -rootProject.name = 'wmsa' +rootProject.name = 'marginalia.nu' -include 'marginalia_nu' -include 'third_party' -include 'protocol' \ No newline at end of file +include 'services-core:index-service' +include 'services-core:assistant-service' +include 'services-core:search-service' + +include 'services-satellite:api-service' +include 'services-satellite:dating-service' +include 'services-satellite:explorer-service' + +include 'libraries:array' +include 'libraries:btree' +include 'libraries:misc' +include 'libraries:language-processing' + +include 'features:screenshots' +include 'features:random-websites' +include 'features:domain-ranking' +include 'features:renderer' +include 'features:query-parser' + +include 'api:search-api' +include 'api:index-api' +include 'api:assistant-api' + +include 'index:lexicon' +include 'index:index-journal' +include 'index:index-query' +include 'index:index-forward' +include 'index:index-reverse' + +include 'common:service-discovery' +include 'common:service-client' +include 'common:service' +include 'common:config' +include 'common:model' + +include 'crawl:crawl-job-extractor-process' +include 'crawl:crawling-process' +include 'crawl:crawling-model' +include 'crawl:converting-process' +include 'crawl:converting-model' +include 'crawl:loading-process' +include 'crawl:common' +include 'crawl:experimental' + +include 'third-party' +include 'protocol' +include 'other:memex' +include 'other:wmsa_old' + +include 'tools:screenshot' + +dependencyResolutionManagement { + + repositories { + mavenLocal() + maven { url "https://artifactory.cronapp.io/public-release/" } + maven { url "https://repo1.maven.org/maven2/" } + maven { url "https://www2.ph.ed.ac.uk/maven2/" } + maven { url "https://jitpack.io/" } + exclusiveContent { + forRepository { + maven { + url = uri("https://jitpack.io") + } + } + filter { + // Only use JitPack for the `gson-record-type-adapter-factory` library + includeModule("com.github.Marcono1234", "gson-record-type-adapter-factory") + } + } + } + + versionCatalogs { + libs { + library('lombok', 'org.projectlombok', 'lombok').version('1.18.24') + library('mariadb-client', 'org.mariadb.jdbc', 'mariadb-java-client').version('3.0.6') + library('hikaricp', 'com.zaxxer:HikariCP:5.0.1') + + library('spark', 'com.sparkjava', 'spark-core').version('2.9.3') + library('guice', 'com.google.inject', 'guice').version('5.1.0') + library('guava', 'com.google.guava', 'guava').version('31.1-jre') + + library('rxjava', 'io.reactivex.rxjava3', 'rxjava').version('3.1.5') + + library('prometheus', 'io.prometheus', 'simpleclient').version('0.16.0') + library('prometheus-servlet', 'io.prometheus', 'simpleclient_servlet').version('0.16.0') + library('prometheus-server', 'io.prometheus', 'simpleclient_httpserver').version('0.16.0') + library('prometheus-hotspot', 'io.prometheus', 'simpleclient_hotspot').version('0.16.0') + + library('slf4j.api', 'org.slf4j', 'slf4j-api').version('1.7.36') + library('slf4j.jdk14', 'org.slf4j', 'slf4j-jdk14').version('2.0.3') + + library('log4j.api', 'org.apache.logging.log4j', 'log4j-api').version('2.17.2') + library('log4j.core', 'org.apache.logging.log4j', 'log4j-core').version('2.17.2') + library('log4j.slf4j', 'org.apache.logging.log4j', 'log4j-slf4j-impl').version('2.17.2') + + library('notnull','org.jetbrains','annotations').version('24.0.0') + + library('trove', 'net.sf.trove4j', 'trove4j').version('3.0.3') + library('fastutil', 'it.unimi.dsi', 'fastutil').version('8.5.8') + + library('okhttp3','com.squareup.okhttp3','okhttp').version('4.10.0') + + library('httpcomponents.core','org.apache.httpcomponents','httpcore').version('4.4.15') + library('httpcomponents.client','org.apache.httpcomponents','httpclient').version('4.5.13') + library('commons.net', 'commons-net','commons-net').version('3.9.0') + library('commons.lang3', 'org.apache.commons','commons-lang3').version('3.12.0') + library('commons.compress','org.apache.commons','commons-compress').version('1.21') + library('commons.io','commons-io','commons-io').version('2.11.0') + + library('ffi','com.github.jnr','jnr-ffi').version('2.2.12') + library('databind','com.fasterxml.jackson.core','jackson-databind').version('2.13.2.1') + + library('crawlercommons', 'com.github.crawler-commons', 'crawler-commons').version('1.2') + + library('stanford.corenlp','edu.stanford.nlp','stanford-corenlp').version('4.4.0') + library('opennlp','org.apache.opennlp','opennlp-tools').version('1.9.4') + + library('roaringbitmap','org.roaringbitmap','RoaringBitmap').version('0.9.32') + library('opencsv','com.opencsv','opencsv').version('5.6') + library('bucket4j','com.github.vladimir-bukhtoyarov','bucket4j-core').version('7.5.0') + + library('protobuf','com.google.protobuf','protobuf-java').version('3.0.0') + library('gson','com.google.code.gson','gson').version('2.9.0') + library('gson-type-adapter','com.github.Marcono1234','gson-record-type-adapter-factory').version('0.2.0') + + library('zstd','com.github.luben','zstd-jni').version('1.5.2-2') + library('lz4','org.lz4','lz4-java').version('1.8.0') + + library('jsoup','org.jsoup','jsoup').version('1.15.3') + library('snakeyaml','org.yaml','snakeyaml').version('1.30') + + library('junit.jupiter','org.junit.jupiter','junit-jupiter-api').version('5.8.2') + library('junit.jupiter.engine','org.junit.jupiter','junit-jupiter-engine').version('') + library('mockito','org.mockito','mockito-junit-jupiter').version('4.5.1') + + library('selenium.chrome','org.seleniumhq.selenium','selenium-chrome-driver').version('4.5.3') + library('selenium.java','org.seleniumhq.selenium','selenium-java').version('4.5.3') + + library('handlebars','com.github.jknack','handlebars').version('4.3.1') + library('handlebars.markdown','com.github.jknack','handlebars-markdown').version('4.2.1') + + bundle('slf4j', ['slf4j.api', 'log4j.api', 'log4j.core', 'log4j.slf4j']) + bundle('slf4j.test', ['slf4j.jdk14']) + bundle('prometheus', ['prometheus', 'prometheus-servlet', 'prometheus-server', 'prometheus-hotspot']) + bundle('mariadb', ['mariadb-client', 'hikaricp']) + bundle('nlp', ['stanford.corenlp', 'opennlp']) + bundle('selenium', ['selenium.chrome', 'selenium.java']) + bundle('handlebars', ['handlebars', 'handlebars.markdown']) + + bundle('gson', ['gson', 'gson-type-adapter']) + bundle('httpcomponents', ['httpcomponents.core', 'httpcomponents.client']) + + bundle('junit', ['junit.jupiter', 'junit.jupiter.engine']) + } + + + } +} \ No newline at end of file diff --git a/third_party/README.md b/third-party/README.md similarity index 92% rename from third_party/README.md rename to third-party/README.md index bd5f5c85..ccca7382 100644 --- a/third_party/README.md +++ b/third-party/README.md @@ -11,5 +11,6 @@ or lack an artifact, or to override some default that is inappropriate for the t * [OpenZIM](https://github.com/openzim/libzim) - GPL-2.0 * [XZ for Java](https://tukaani.org/xz/) - Public Domain * [GSON](https://github.com/google/gson) - Apache-2.0 +* [SymSpell](https://github.com/wolfgarbe/symspell) - LGPL-3.0 * Stanford OpenNLP - Apache-2.0 * OpenJDK - GPL-2.0 (packaged under jdkoverride) \ No newline at end of file diff --git a/third-party/build.gradle b/third-party/build.gradle new file mode 100644 index 00000000..4128ac2b --- /dev/null +++ b/third-party/build.gradle @@ -0,0 +1,31 @@ +plugins { + id 'java' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation libs.bundles.nlp + implementation libs.zstd + implementation libs.commons.compress + implementation libs.ffi + implementation libs.databind + implementation libs.bundles.gson + + implementation 'org.apache.opennlp:opennlp-tools:1.9.4' + implementation 'edu.stanford.nlp:stanford-corenlp:4.4.0' + + implementation 'com.github.luben:zstd-jni:1.5.2-2' + implementation 'org.apache.commons:commons-compress:1.21' + implementation 'com.github.jnr:jnr-ffi:2.2.12' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.2.1' + implementation 'com.google.code.gson:gson:2.9.0' +} + +test { + useJUnitPlatform() +} diff --git a/third_party/src/main/java/ca/rmen/porterstemmer/PorterStemmer.java b/third-party/src/main/java/ca/rmen/porterstemmer/PorterStemmer.java similarity index 100% rename from third_party/src/main/java/ca/rmen/porterstemmer/PorterStemmer.java rename to third-party/src/main/java/ca/rmen/porterstemmer/PorterStemmer.java diff --git a/third_party/src/main/java/com/github/datquocnguyen/FWObject.java b/third-party/src/main/java/com/github/datquocnguyen/FWObject.java similarity index 100% rename from third_party/src/main/java/com/github/datquocnguyen/FWObject.java rename to third-party/src/main/java/com/github/datquocnguyen/FWObject.java diff --git a/third_party/src/main/java/com/github/datquocnguyen/InitialTagger.java b/third-party/src/main/java/com/github/datquocnguyen/InitialTagger.java similarity index 100% rename from third_party/src/main/java/com/github/datquocnguyen/InitialTagger.java rename to third-party/src/main/java/com/github/datquocnguyen/InitialTagger.java diff --git a/third_party/src/main/java/com/github/datquocnguyen/Node.java b/third-party/src/main/java/com/github/datquocnguyen/Node.java similarity index 100% rename from third_party/src/main/java/com/github/datquocnguyen/Node.java rename to third-party/src/main/java/com/github/datquocnguyen/Node.java diff --git a/third_party/src/main/java/com/github/datquocnguyen/RDRPOSTagger.java b/third-party/src/main/java/com/github/datquocnguyen/RDRPOSTagger.java similarity index 100% rename from third_party/src/main/java/com/github/datquocnguyen/RDRPOSTagger.java rename to third-party/src/main/java/com/github/datquocnguyen/RDRPOSTagger.java diff --git a/third_party/src/main/java/com/github/datquocnguyen/Utils.java b/third-party/src/main/java/com/github/datquocnguyen/Utils.java similarity index 100% rename from third_party/src/main/java/com/github/datquocnguyen/Utils.java rename to third-party/src/main/java/com/github/datquocnguyen/Utils.java diff --git a/third_party/src/main/java/com/github/datquocnguyen/WordTag.java b/third-party/src/main/java/com/github/datquocnguyen/WordTag.java similarity index 100% rename from third_party/src/main/java/com/github/datquocnguyen/WordTag.java rename to third-party/src/main/java/com/github/datquocnguyen/WordTag.java diff --git a/third_party/src/main/java/com/google/gson/stream/JsonReader.java b/third-party/src/main/java/com/google/gson/stream/JsonReader.java similarity index 100% rename from third_party/src/main/java/com/google/gson/stream/JsonReader.java rename to third-party/src/main/java/com/google/gson/stream/JsonReader.java diff --git a/third_party/src/main/java/com/upserve/uppend/blobs/NativeIO.java b/third-party/src/main/java/com/upserve/uppend/blobs/NativeIO.java similarity index 100% rename from third_party/src/main/java/com/upserve/uppend/blobs/NativeIO.java rename to third-party/src/main/java/com/upserve/uppend/blobs/NativeIO.java diff --git a/third_party/src/main/java/jdkoverride/LargeLineBufferedReader.java b/third-party/src/main/java/jdkoverride/LargeLineBufferedReader.java similarity index 100% rename from third_party/src/main/java/jdkoverride/LargeLineBufferedReader.java rename to third-party/src/main/java/jdkoverride/LargeLineBufferedReader.java diff --git a/third_party/src/main/java/opennlp/tools/sentdetect/DefaultSDContextGenerator.java b/third-party/src/main/java/opennlp/tools/sentdetect/DefaultSDContextGenerator.java similarity index 99% rename from third_party/src/main/java/opennlp/tools/sentdetect/DefaultSDContextGenerator.java rename to third-party/src/main/java/opennlp/tools/sentdetect/DefaultSDContextGenerator.java index 5f87a0b0..5b01869d 100644 --- a/third_party/src/main/java/opennlp/tools/sentdetect/DefaultSDContextGenerator.java +++ b/third-party/src/main/java/opennlp/tools/sentdetect/DefaultSDContextGenerator.java @@ -177,6 +177,7 @@ public class DefaultSDContextGenerator implements SDContextGenerator { * * @deprecated use {@link #collectFeatures(String, String, String, String, Character)} instead. */ + @Deprecated protected void collectFeatures(String prefix, String suffix, String previous, String next) { collectFeatures(prefix, suffix, previous, next, null); } diff --git a/third_party/src/main/java/opennlp/tools/sentdetect/SentenceDetectorME.java b/third-party/src/main/java/opennlp/tools/sentdetect/SentenceDetectorME.java similarity index 98% rename from third_party/src/main/java/opennlp/tools/sentdetect/SentenceDetectorME.java rename to third-party/src/main/java/opennlp/tools/sentdetect/SentenceDetectorME.java index ad43bbe8..32112efc 100644 --- a/third_party/src/main/java/opennlp/tools/sentdetect/SentenceDetectorME.java +++ b/third-party/src/main/java/opennlp/tools/sentdetect/SentenceDetectorME.java @@ -90,6 +90,7 @@ public class SentenceDetectorME implements SentenceDetector { * @deprecated Use a {@link SentenceDetectorFactory} to extend * SentenceDetector functionality. */ + @Deprecated public SentenceDetectorME(SentenceModel model, Factory factory) { this.model = model.getMaxentModel(); // if the model has custom EOS characters set, use this to get the context @@ -137,14 +138,14 @@ public class SentenceDetectorME implements SentenceDetector { } private int getFirstWS(String s, int pos) { - while (pos < s.length() && !StringUtil.isWhitespace(s.charAt(pos))) - pos++; + for (; pos < s.length() && !Character.isWhitespace(s.charAt(pos)); pos++); + return pos; } private int getFirstNonWS(String s, int pos) { - while (pos < s.length() && StringUtil.isWhitespace(s.charAt(pos))) - pos++; + for (; pos < s.length() && Character.isWhitespace(s.charAt(pos)); pos++); + return pos; } @@ -297,6 +298,7 @@ public class SentenceDetectorME implements SentenceDetector { * {@link #train(String, ObjectStream, SentenceDetectorFactory, TrainingParameters)} * and pass in af {@link SentenceDetectorFactory}. */ + @Deprecated public static SentenceModel train(String languageCode, ObjectStream samples, boolean useTokenEnd, Dictionary abbreviations, TrainingParameters mlParams) throws IOException { diff --git a/third_party/src/main/java/org/openzim/ZIMTypes/ArticleEntry.java b/third-party/src/main/java/org/openzim/ZIMTypes/ArticleEntry.java similarity index 100% rename from third_party/src/main/java/org/openzim/ZIMTypes/ArticleEntry.java rename to third-party/src/main/java/org/openzim/ZIMTypes/ArticleEntry.java diff --git a/third_party/src/main/java/org/openzim/ZIMTypes/DirectoryEntry.java b/third-party/src/main/java/org/openzim/ZIMTypes/DirectoryEntry.java similarity index 100% rename from third_party/src/main/java/org/openzim/ZIMTypes/DirectoryEntry.java rename to third-party/src/main/java/org/openzim/ZIMTypes/DirectoryEntry.java diff --git a/third_party/src/main/java/org/openzim/ZIMTypes/RedirectEntry.java b/third-party/src/main/java/org/openzim/ZIMTypes/RedirectEntry.java similarity index 100% rename from third_party/src/main/java/org/openzim/ZIMTypes/RedirectEntry.java rename to third-party/src/main/java/org/openzim/ZIMTypes/RedirectEntry.java diff --git a/third_party/src/main/java/org/openzim/ZIMTypes/ZIMFile.java b/third-party/src/main/java/org/openzim/ZIMTypes/ZIMFile.java similarity index 100% rename from third_party/src/main/java/org/openzim/ZIMTypes/ZIMFile.java rename to third-party/src/main/java/org/openzim/ZIMTypes/ZIMFile.java diff --git a/third_party/src/main/java/org/openzim/ZIMTypes/ZIMReader.java b/third-party/src/main/java/org/openzim/ZIMTypes/ZIMReader.java similarity index 98% rename from third_party/src/main/java/org/openzim/ZIMTypes/ZIMReader.java rename to third-party/src/main/java/org/openzim/ZIMTypes/ZIMReader.java index 3e3bd58f..1f001d36 100644 --- a/third_party/src/main/java/org/openzim/ZIMTypes/ZIMReader.java +++ b/third-party/src/main/java/org/openzim/ZIMTypes/ZIMReader.java @@ -20,9 +20,6 @@ package org.openzim.ZIMTypes; import com.github.luben.zstd.RecyclingBufferPool; import com.github.luben.zstd.ZstdInputStream; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; import org.openzim.util.RandomAcessFileZIMInputStream; import org.openzim.util.Utilities; import org.tukaani.xz.SingleXZInputStream; @@ -204,13 +201,17 @@ public class ZIMReader { } - @Getter @AllArgsConstructor static class DataKey implements Comparable { public final long cluster; public final long blob; + DataKey(long cluster, long blob) { + this.cluster = cluster; + this.blob = blob; + } + @Override - public int compareTo(@NotNull DataKey o) { + public int compareTo(DataKey o) { if (o.cluster != cluster) { return (int)(cluster - o.cluster); } diff --git a/third_party/src/main/java/org/openzim/util/RandomAcessFileZIMInputStream.java b/third-party/src/main/java/org/openzim/util/RandomAcessFileZIMInputStream.java similarity index 100% rename from third_party/src/main/java/org/openzim/util/RandomAcessFileZIMInputStream.java rename to third-party/src/main/java/org/openzim/util/RandomAcessFileZIMInputStream.java diff --git a/third_party/src/main/java/org/openzim/util/Utilities.java b/third-party/src/main/java/org/openzim/util/Utilities.java similarity index 100% rename from third_party/src/main/java/org/openzim/util/Utilities.java rename to third-party/src/main/java/org/openzim/util/Utilities.java diff --git a/third_party/src/main/java/org/tukaani/xz/BlockInputStream.java b/third-party/src/main/java/org/tukaani/xz/BlockInputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/BlockInputStream.java rename to third-party/src/main/java/org/tukaani/xz/BlockInputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/BlockOutputStream.java b/third-party/src/main/java/org/tukaani/xz/BlockOutputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/BlockOutputStream.java rename to third-party/src/main/java/org/tukaani/xz/BlockOutputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/CorruptedInputException.java b/third-party/src/main/java/org/tukaani/xz/CorruptedInputException.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/CorruptedInputException.java rename to third-party/src/main/java/org/tukaani/xz/CorruptedInputException.java diff --git a/third_party/src/main/java/org/tukaani/xz/CountingInputStream.java b/third-party/src/main/java/org/tukaani/xz/CountingInputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/CountingInputStream.java rename to third-party/src/main/java/org/tukaani/xz/CountingInputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/CountingOutputStream.java b/third-party/src/main/java/org/tukaani/xz/CountingOutputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/CountingOutputStream.java rename to third-party/src/main/java/org/tukaani/xz/CountingOutputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/DeltaCoder.java b/third-party/src/main/java/org/tukaani/xz/DeltaCoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/DeltaCoder.java rename to third-party/src/main/java/org/tukaani/xz/DeltaCoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/DeltaDecoder.java b/third-party/src/main/java/org/tukaani/xz/DeltaDecoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/DeltaDecoder.java rename to third-party/src/main/java/org/tukaani/xz/DeltaDecoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/DeltaInputStream.java b/third-party/src/main/java/org/tukaani/xz/DeltaInputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/DeltaInputStream.java rename to third-party/src/main/java/org/tukaani/xz/DeltaInputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/FilterCoder.java b/third-party/src/main/java/org/tukaani/xz/FilterCoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/FilterCoder.java rename to third-party/src/main/java/org/tukaani/xz/FilterCoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/FilterDecoder.java b/third-party/src/main/java/org/tukaani/xz/FilterDecoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/FilterDecoder.java rename to third-party/src/main/java/org/tukaani/xz/FilterDecoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/FilterEncoder.java b/third-party/src/main/java/org/tukaani/xz/FilterEncoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/FilterEncoder.java rename to third-party/src/main/java/org/tukaani/xz/FilterEncoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/FilterOptions.java b/third-party/src/main/java/org/tukaani/xz/FilterOptions.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/FilterOptions.java rename to third-party/src/main/java/org/tukaani/xz/FilterOptions.java diff --git a/third_party/src/main/java/org/tukaani/xz/FinishableOutputStream.java b/third-party/src/main/java/org/tukaani/xz/FinishableOutputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/FinishableOutputStream.java rename to third-party/src/main/java/org/tukaani/xz/FinishableOutputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/IndexIndicatorException.java b/third-party/src/main/java/org/tukaani/xz/IndexIndicatorException.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/IndexIndicatorException.java rename to third-party/src/main/java/org/tukaani/xz/IndexIndicatorException.java diff --git a/third_party/src/main/java/org/tukaani/xz/LZMA2Coder.java b/third-party/src/main/java/org/tukaani/xz/LZMA2Coder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/LZMA2Coder.java rename to third-party/src/main/java/org/tukaani/xz/LZMA2Coder.java diff --git a/third_party/src/main/java/org/tukaani/xz/LZMA2Decoder.java b/third-party/src/main/java/org/tukaani/xz/LZMA2Decoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/LZMA2Decoder.java rename to third-party/src/main/java/org/tukaani/xz/LZMA2Decoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/LZMA2Encoder.java b/third-party/src/main/java/org/tukaani/xz/LZMA2Encoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/LZMA2Encoder.java rename to third-party/src/main/java/org/tukaani/xz/LZMA2Encoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/LZMA2InputStream.java b/third-party/src/main/java/org/tukaani/xz/LZMA2InputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/LZMA2InputStream.java rename to third-party/src/main/java/org/tukaani/xz/LZMA2InputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/LZMA2Options.java b/third-party/src/main/java/org/tukaani/xz/LZMA2Options.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/LZMA2Options.java rename to third-party/src/main/java/org/tukaani/xz/LZMA2Options.java diff --git a/third_party/src/main/java/org/tukaani/xz/LZMA2OutputStream.java b/third-party/src/main/java/org/tukaani/xz/LZMA2OutputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/LZMA2OutputStream.java rename to third-party/src/main/java/org/tukaani/xz/LZMA2OutputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/MemoryLimitException.java b/third-party/src/main/java/org/tukaani/xz/MemoryLimitException.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/MemoryLimitException.java rename to third-party/src/main/java/org/tukaani/xz/MemoryLimitException.java diff --git a/third_party/src/main/java/org/tukaani/xz/RawCoder.java b/third-party/src/main/java/org/tukaani/xz/RawCoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/RawCoder.java rename to third-party/src/main/java/org/tukaani/xz/RawCoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/SingleXZInputStream.java b/third-party/src/main/java/org/tukaani/xz/SingleXZInputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/SingleXZInputStream.java rename to third-party/src/main/java/org/tukaani/xz/SingleXZInputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/UnsupportedOptionsException.java b/third-party/src/main/java/org/tukaani/xz/UnsupportedOptionsException.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/UnsupportedOptionsException.java rename to third-party/src/main/java/org/tukaani/xz/UnsupportedOptionsException.java diff --git a/third_party/src/main/java/org/tukaani/xz/XZ.java b/third-party/src/main/java/org/tukaani/xz/XZ.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/XZ.java rename to third-party/src/main/java/org/tukaani/xz/XZ.java diff --git a/third_party/src/main/java/org/tukaani/xz/XZFormatException.java b/third-party/src/main/java/org/tukaani/xz/XZFormatException.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/XZFormatException.java rename to third-party/src/main/java/org/tukaani/xz/XZFormatException.java diff --git a/third_party/src/main/java/org/tukaani/xz/XZIOException.java b/third-party/src/main/java/org/tukaani/xz/XZIOException.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/XZIOException.java rename to third-party/src/main/java/org/tukaani/xz/XZIOException.java diff --git a/third_party/src/main/java/org/tukaani/xz/XZInputStream.java b/third-party/src/main/java/org/tukaani/xz/XZInputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/XZInputStream.java rename to third-party/src/main/java/org/tukaani/xz/XZInputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/XZOutputStream.java b/third-party/src/main/java/org/tukaani/xz/XZOutputStream.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/XZOutputStream.java rename to third-party/src/main/java/org/tukaani/xz/XZOutputStream.java diff --git a/third_party/src/main/java/org/tukaani/xz/check/CRC32.java b/third-party/src/main/java/org/tukaani/xz/check/CRC32.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/check/CRC32.java rename to third-party/src/main/java/org/tukaani/xz/check/CRC32.java diff --git a/third_party/src/main/java/org/tukaani/xz/check/CRC64.java b/third-party/src/main/java/org/tukaani/xz/check/CRC64.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/check/CRC64.java rename to third-party/src/main/java/org/tukaani/xz/check/CRC64.java diff --git a/third_party/src/main/java/org/tukaani/xz/check/Check.java b/third-party/src/main/java/org/tukaani/xz/check/Check.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/check/Check.java rename to third-party/src/main/java/org/tukaani/xz/check/Check.java diff --git a/third_party/src/main/java/org/tukaani/xz/check/None.java b/third-party/src/main/java/org/tukaani/xz/check/None.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/check/None.java rename to third-party/src/main/java/org/tukaani/xz/check/None.java diff --git a/third_party/src/main/java/org/tukaani/xz/check/SHA256.java b/third-party/src/main/java/org/tukaani/xz/check/SHA256.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/check/SHA256.java rename to third-party/src/main/java/org/tukaani/xz/check/SHA256.java diff --git a/third_party/src/main/java/org/tukaani/xz/common/DecoderUtil.java b/third-party/src/main/java/org/tukaani/xz/common/DecoderUtil.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/common/DecoderUtil.java rename to third-party/src/main/java/org/tukaani/xz/common/DecoderUtil.java diff --git a/third_party/src/main/java/org/tukaani/xz/common/EncoderUtil.java b/third-party/src/main/java/org/tukaani/xz/common/EncoderUtil.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/common/EncoderUtil.java rename to third-party/src/main/java/org/tukaani/xz/common/EncoderUtil.java diff --git a/third_party/src/main/java/org/tukaani/xz/common/StreamFlags.java b/third-party/src/main/java/org/tukaani/xz/common/StreamFlags.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/common/StreamFlags.java rename to third-party/src/main/java/org/tukaani/xz/common/StreamFlags.java diff --git a/third_party/src/main/java/org/tukaani/xz/common/Util.java b/third-party/src/main/java/org/tukaani/xz/common/Util.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/common/Util.java rename to third-party/src/main/java/org/tukaani/xz/common/Util.java diff --git a/third_party/src/main/java/org/tukaani/xz/delta/DeltaCoder.java b/third-party/src/main/java/org/tukaani/xz/delta/DeltaCoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/delta/DeltaCoder.java rename to third-party/src/main/java/org/tukaani/xz/delta/DeltaCoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/delta/DeltaDecoder.java b/third-party/src/main/java/org/tukaani/xz/delta/DeltaDecoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/delta/DeltaDecoder.java rename to third-party/src/main/java/org/tukaani/xz/delta/DeltaDecoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/index/IndexBase.java b/third-party/src/main/java/org/tukaani/xz/index/IndexBase.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/index/IndexBase.java rename to third-party/src/main/java/org/tukaani/xz/index/IndexBase.java diff --git a/third_party/src/main/java/org/tukaani/xz/index/IndexEncoder.java b/third-party/src/main/java/org/tukaani/xz/index/IndexEncoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/index/IndexEncoder.java rename to third-party/src/main/java/org/tukaani/xz/index/IndexEncoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/index/IndexHash.java b/third-party/src/main/java/org/tukaani/xz/index/IndexHash.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/index/IndexHash.java rename to third-party/src/main/java/org/tukaani/xz/index/IndexHash.java diff --git a/third_party/src/main/java/org/tukaani/xz/index/IndexRecord.java b/third-party/src/main/java/org/tukaani/xz/index/IndexRecord.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/index/IndexRecord.java rename to third-party/src/main/java/org/tukaani/xz/index/IndexRecord.java diff --git a/third_party/src/main/java/org/tukaani/xz/lz/LZDecoder.java b/third-party/src/main/java/org/tukaani/xz/lz/LZDecoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/lz/LZDecoder.java rename to third-party/src/main/java/org/tukaani/xz/lz/LZDecoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/lzma/LZMACoder.java b/third-party/src/main/java/org/tukaani/xz/lzma/LZMACoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/lzma/LZMACoder.java rename to third-party/src/main/java/org/tukaani/xz/lzma/LZMACoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/lzma/LZMADecoder.java b/third-party/src/main/java/org/tukaani/xz/lzma/LZMADecoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/lzma/LZMADecoder.java rename to third-party/src/main/java/org/tukaani/xz/lzma/LZMADecoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/lzma/State.java b/third-party/src/main/java/org/tukaani/xz/lzma/State.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/lzma/State.java rename to third-party/src/main/java/org/tukaani/xz/lzma/State.java diff --git a/third_party/src/main/java/org/tukaani/xz/package-info.java b/third-party/src/main/java/org/tukaani/xz/package-info.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/package-info.java rename to third-party/src/main/java/org/tukaani/xz/package-info.java diff --git a/third_party/src/main/java/org/tukaani/xz/rangecoder/RangeCoder.java b/third-party/src/main/java/org/tukaani/xz/rangecoder/RangeCoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/rangecoder/RangeCoder.java rename to third-party/src/main/java/org/tukaani/xz/rangecoder/RangeCoder.java diff --git a/third_party/src/main/java/org/tukaani/xz/rangecoder/RangeDecoder.java b/third-party/src/main/java/org/tukaani/xz/rangecoder/RangeDecoder.java similarity index 100% rename from third_party/src/main/java/org/tukaani/xz/rangecoder/RangeDecoder.java rename to third-party/src/main/java/org/tukaani/xz/rangecoder/RangeDecoder.java diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/SymSpell.java b/third-party/src/main/java/symspell/SymSpell.java similarity index 99% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/SymSpell.java rename to third-party/src/main/java/symspell/SymSpell.java index 2f98254a..45bac274 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/dict/SymSpell.java +++ b/third-party/src/main/java/symspell/SymSpell.java @@ -1,4 +1,4 @@ -package nu.marginalia.wmsa.edge.assistant.dict; +package symspell; import java.io.BufferedReader; diff --git a/tools/screenshot/build.gradle b/tools/screenshot/build.gradle new file mode 100644 index 00000000..f1a2c21a --- /dev/null +++ b/tools/screenshot/build.gradle @@ -0,0 +1,46 @@ +plugins { + id 'java' + id "io.freefair.lombok" version "5.3.3.3" + + id 'jvm-test-suite' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + +dependencies { + implementation project(':third-party') + implementation project(':protocol') + implementation project(':common:model') + implementation project(':features:screenshots') + implementation project(':common:service') + + implementation libs.lombok + annotationProcessor libs.lombok + implementation libs.bundles.slf4j + implementation libs.bundles.selenium + implementation libs.bundles.mariadb + implementation libs.notnull + implementation libs.commons.compress + implementation libs.commons.io + implementation libs.guice + + testImplementation libs.bundles.slf4j.test + testImplementation libs.bundles.junit + testImplementation libs.mockito + +} + + +test { + useJUnitPlatform() +} + +task fastTests(type: Test) { + useJUnitPlatform { + excludeTags "slow" + } +} \ No newline at end of file diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/ScreenshotCaptureToolMain.java b/tools/screenshot/src/main/java/nu/marginalia/screenshot/ScreenshotCaptureToolMain.java similarity index 97% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/ScreenshotCaptureToolMain.java rename to tools/screenshot/src/main/java/nu/marginalia/screenshot/ScreenshotCaptureToolMain.java index 8ddd1b9d..26b83336 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/tools/ScreenshotCaptureToolMain.java +++ b/tools/screenshot/src/main/java/nu/marginalia/screenshot/ScreenshotCaptureToolMain.java @@ -1,8 +1,8 @@ -package nu.marginalia.wmsa.edge.tools; +package nu.marginalia.screenshot.tool; import com.zaxxer.hikari.HikariDataSource; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; -import nu.marginalia.wmsa.edge.model.EdgeDomain; +import nu.marginalia.model.EdgeDomain; +import nu.marginalia.service.module.DatabaseModule; import org.jetbrains.annotations.NotNull; import org.openqa.selenium.OutputType; import org.openqa.selenium.PageLoadStrategy; diff --git a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/screenshot/ScreenshotLoaderMain.java b/tools/screenshot/src/main/java/nu/marginalia/screenshot/ScreenshotLoaderMain.java similarity index 92% rename from marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/screenshot/ScreenshotLoaderMain.java rename to tools/screenshot/src/main/java/nu/marginalia/screenshot/ScreenshotLoaderMain.java index a69420f4..7ca7c320 100644 --- a/marginalia_nu/src/main/java/nu/marginalia/wmsa/edge/assistant/screenshot/ScreenshotLoaderMain.java +++ b/tools/screenshot/src/main/java/nu/marginalia/screenshot/ScreenshotLoaderMain.java @@ -1,6 +1,7 @@ -package nu.marginalia.wmsa.edge.assistant.screenshot; +package nu.marginalia.screenshot.tool; -import nu.marginalia.wmsa.configuration.module.DatabaseModule; + +import nu.marginalia.service.module.DatabaseModule; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.mariadb.jdbc.Driver;

    - This is a game where you explore more or less random and obscure websites around the Internet, based on the - database of the Marginalia Search Engine. -

}rPooYX?gz0j6cmKu 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