190 lines
8.5 KiB
Python
190 lines
8.5 KiB
Python
"""Link files in _incoming/: .url / .txt reading, arXiv rewrite, and the download against a local test server."""
|
|
import http.server
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import unittest
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, os.path.join(HERE, ".."))
|
|
sys.path.insert(0, HERE)
|
|
|
|
import bookio
|
|
import fetch
|
|
import ingest
|
|
import settings
|
|
|
|
try:
|
|
from helpers import make_pdf
|
|
except ImportError:
|
|
make_pdf = None
|
|
|
|
PDF_BYTES = b""
|
|
|
|
|
|
class Handler(http.server.BaseHTTPRequestHandler):
|
|
hits = []
|
|
|
|
def log_message(self, *a):
|
|
pass
|
|
|
|
def send(self, code, body=b"", ctype="application/pdf", extra=None):
|
|
self.send_response(code)
|
|
self.send_header("Content-Type", ctype)
|
|
self.send_header("Content-Length", str(len(body)))
|
|
for k, v in (extra or {}).items():
|
|
self.send_header(k, v)
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def do_GET(self):
|
|
Handler.hits.append(self.path)
|
|
if self.path == "/files/Deep-Work.pdf":
|
|
self.send(200, PDF_BYTES)
|
|
elif self.path == "/redirect":
|
|
self.send(302, extra={"Location": "/files/Deep-Work.pdf"})
|
|
elif self.path == "/pdf/2401.01234": # no extension, like arxiv.org/pdf/<id>
|
|
self.send(200, PDF_BYTES)
|
|
elif self.path == "/dl?id=7":
|
|
self.send(200, PDF_BYTES, extra={"Content-Disposition": 'attachment; filename="Named By Server.pdf"'})
|
|
elif self.path == "/page":
|
|
self.send(200, b"<!doctype html><html><head><title>Sign in</title></head><body>login</body></html>", "text/html")
|
|
elif self.path == "/cut":
|
|
self.send(200, PDF_BYTES[:200])
|
|
elif self.path == "/empty":
|
|
self.send(200, b"")
|
|
else:
|
|
self.send(404, b"not found", "text/plain")
|
|
|
|
|
|
@unittest.skipIf(make_pdf is None, "reportlab not installed")
|
|
class FetchTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
global PDF_BYTES
|
|
d = tempfile.mkdtemp()
|
|
make_pdf(os.path.join(d, "a.pdf"), [["Deep Work", "By Cal Newport"], ["text"]], title="Deep Work")
|
|
PDF_BYTES = open(os.path.join(d, "a.pdf"), "rb").read()
|
|
cls.server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
threading.Thread(target=cls.server.serve_forever, daemon=True).start()
|
|
cls.base = "http://127.0.0.1:%d" % cls.server.server_port
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
cls.server.shutdown()
|
|
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.d = self.tmp.name
|
|
self.cfg = settings.load()
|
|
self.out = os.path.join(self.d, "out")
|
|
|
|
def tearDown(self):
|
|
self.tmp.cleanup()
|
|
|
|
def urlfile(self, content, name="Some Link.url"):
|
|
p = os.path.join(self.d, name)
|
|
with open(p, "w", encoding="utf-8") as f:
|
|
f.write(content)
|
|
return p
|
|
|
|
def test_download_follows_redirects_and_verifies_pdf(self):
|
|
r = fetch.fetch_from_file(self.cfg, self.urlfile(self.base + "/redirect\n"), self.out)
|
|
self.assertEqual(os.path.basename(r["pdf"]), "Deep-Work.pdf")
|
|
self.assertEqual(r["pages"], 2)
|
|
self.assertEqual(open(r["pdf"], "rb").read(), PDF_BYTES)
|
|
self.assertEqual(r["source_url"], self.base + "/redirect")
|
|
self.assertEqual(r["name_hints"], ["Some Link.url"])
|
|
|
|
def test_windows_shortcut_format_and_txt(self):
|
|
r = fetch.fetch_from_file(self.cfg, self.urlfile("[InternetShortcut]\r\nURL=%s/files/Deep-Work.pdf\r\nIconIndex=0\r\n" % self.base), self.out)
|
|
self.assertTrue(r["pdf"].endswith("Deep-Work.pdf"))
|
|
r = fetch.fetch_from_file(self.cfg, self.urlfile("# my link\n%s/files/Deep-Work.pdf\n" % self.base, "note.txt"), os.path.join(self.d, "o2"))
|
|
self.assertTrue(os.path.exists(r["pdf"]))
|
|
|
|
def test_name_from_url_without_extension_and_from_content_disposition(self):
|
|
r = fetch.fetch_from_file(self.cfg, self.urlfile(self.base + "/pdf/2401.01234"), self.out)
|
|
self.assertEqual(os.path.basename(r["pdf"]), "2401.01234.pdf")
|
|
r = fetch.fetch_from_file(self.cfg, self.urlfile(self.base + "/dl?id=7"), os.path.join(self.d, "o2"))
|
|
self.assertEqual(os.path.basename(r["pdf"]), "Named By Server.pdf")
|
|
|
|
def test_sidecar_json_next_to_the_url_file_is_carried_over(self):
|
|
self.urlfile('{"title": "T", "author": "A"}', "Some Link.json")
|
|
r = fetch.fetch_from_file(self.cfg, self.urlfile(self.base + "/files/Deep-Work.pdf"), self.out)
|
|
self.assertEqual(json.load(open(os.path.splitext(r["pdf"])[0] + ".json"))["author"], "A")
|
|
|
|
def test_html_page_is_refused_with_a_clear_reason(self):
|
|
with self.assertRaises(fetch.FetchError) as cm:
|
|
fetch.fetch_from_file(self.cfg, self.urlfile(self.base + "/page"), self.out)
|
|
self.assertIn("did not return a PDF", str(cm.exception))
|
|
self.assertIn("HTML page", str(cm.exception))
|
|
self.assertEqual(os.listdir(self.out) and [n for n in os.listdir(self.out) if n.endswith(".pdf")], [])
|
|
|
|
def test_http_404_cut_off_pdf_and_empty_download(self):
|
|
for path, text in (("/nothing", "HTTP 404 (not found)"), ("/cut", "cannot read it"), ("/empty", "empty")):
|
|
with self.assertRaises(fetch.FetchError) as cm:
|
|
fetch.fetch_from_file(self.cfg, self.urlfile(self.base + path), self.out)
|
|
self.assertIn(text, str(cm.exception), path)
|
|
|
|
def test_too_large_is_refused(self):
|
|
self.cfg["fetch"]["max_mb"] = 0.0005 # about 500 bytes; the test PDF is a few KB
|
|
with self.assertRaises(fetch.FetchError) as cm:
|
|
fetch.fetch_from_file(self.cfg, self.urlfile(self.base + "/files/Deep-Work.pdf"), self.out)
|
|
self.assertIn("larger than the allowed maximum", str(cm.exception))
|
|
|
|
def test_unreachable_host(self):
|
|
with self.assertRaises(fetch.FetchError) as cm:
|
|
fetch.fetch_from_file(self.cfg, self.urlfile("http://127.0.0.1:9/x.pdf"), self.out)
|
|
self.assertIn("refused the connection", str(cm.exception))
|
|
|
|
def test_ingest_stores_the_downloaded_pdf_and_records_the_source_url(self):
|
|
r = fetch.fetch_from_file(self.cfg, self.urlfile(self.base + "/files/Deep-Work.pdf"), self.out)
|
|
self.cfg["books_dir"] = os.path.join(self.d, "books")
|
|
self.cfg["work_dir"] = os.path.join(self.d, "work")
|
|
book_dir = ingest.run(self.cfg, r["pdf"], source_url=r["source_url"])
|
|
book = bookio.read_book(book_dir)
|
|
self.assertEqual(book["source_url"], self.base + "/files/Deep-Work.pdf")
|
|
self.assertEqual((book["author"], book["title"]), ("Cal Newport", "Deep Work"))
|
|
self.assertTrue(os.path.exists(os.path.join(book_dir, "Deep-Work.pdf")))
|
|
|
|
|
|
class PureTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
|
|
def tearDown(self):
|
|
self.tmp.cleanup()
|
|
|
|
def read(self, content):
|
|
p = os.path.join(self.tmp.name, "x.url")
|
|
with open(p, "w", encoding="utf-8-sig") as f:
|
|
f.write(content)
|
|
return fetch.read_url_file(p)
|
|
|
|
def test_arxiv_abs_is_rewritten(self):
|
|
self.assertEqual(fetch.normalize_url("https://arxiv.org/abs/2401.01234"), "https://arxiv.org/pdf/2401.01234")
|
|
self.assertEqual(fetch.normalize_url("http://www.arxiv.org/abs/math/0301001v2"), "https://arxiv.org/pdf/math/0301001v2")
|
|
self.assertEqual(fetch.normalize_url("https://arxiv.org/pdf/2401.01234"), "https://arxiv.org/pdf/2401.01234")
|
|
self.assertEqual(fetch.normalize_url("https://example.org/a.pdf"), "https://example.org/a.pdf")
|
|
|
|
def test_reading_link_files(self):
|
|
self.assertEqual(self.read("https://example.org/a.pdf \n\n"), "https://example.org/a.pdf")
|
|
self.assertEqual(self.read("[InternetShortcut]\nURL=https://example.org/b.pdf\nIDList=\n"), "https://example.org/b.pdf")
|
|
for bad, why in (("", "empty"), ("ftp://x/y.pdf", "not a web address"), ("see https://a.org/x.pdf", "not a web address"),
|
|
("https://a.org/1.pdf\nhttps://a.org/2.pdf", "exactly one")):
|
|
with self.assertRaises(fetch.FetchError) as cm:
|
|
self.read(bad)
|
|
self.assertIn(why, str(cm.exception), bad)
|
|
|
|
def test_safe_names(self):
|
|
self.assertEqual(fetch.safe_name("a%20b.pdf"), "a b.pdf")
|
|
self.assertEqual(fetch.safe_name(""), "download.pdf")
|
|
self.assertEqual(fetch.safe_name("2401.01234"), "2401.01234.pdf")
|
|
self.assertEqual(fetch.name_from_response("HTTP/1.1 200 OK\r\n", "https://x.org/files/My%20Book.pdf?x=1"), "My Book.pdf")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|