ArchiveBox/archivebox/index.py

271 lines
8.3 KiB
Python
Raw Normal View History

import os
import json
from datetime import datetime
from string import Template
2019-03-12 16:50:58 +00:00
try:
from distutils.dir_util import copy_tree
except ImportError:
print('[X] Missing "distutils" python package. To install it, run:')
print(' pip install distutils')
2017-07-05 21:33:51 +00:00
from config import (
OUTPUT_DIR,
2018-06-11 01:12:55 +00:00
TEMPLATES_DIR,
GIT_SHA,
2018-04-17 13:13:38 +00:00
FOOTER_INFO,
2017-07-05 21:33:51 +00:00
)
2017-10-23 09:58:41 +00:00
from util import (
chmod_file,
urlencode,
2017-10-23 09:58:41 +00:00
derived_link_info,
check_link_structure,
check_links_structure,
wget_output_path,
latest_output,
2017-10-23 09:58:41 +00:00
)
2019-03-21 05:28:12 +00:00
from parse import parse_links
from links import validate_links
from logs import (
2019-03-22 19:09:39 +00:00
log_indexing_process_started,
2019-03-21 05:28:12 +00:00
log_indexing_started,
log_indexing_finished,
log_parsing_started,
log_parsing_finished,
)
TITLE_LOADING_MSG = 'Not yet archived...'
### Homepage index for all the links
def write_links_index(out_dir, links, finished=False):
"""create index.html file for a given list of links"""
2019-03-22 19:09:39 +00:00
log_indexing_process_started()
check_links_structure(links)
2019-03-22 19:09:39 +00:00
log_indexing_started(out_dir, 'index.json')
2019-02-07 06:06:21 +00:00
write_json_links_index(out_dir, links)
2019-03-21 05:28:12 +00:00
log_indexing_finished(out_dir, 'index.json')
2019-02-07 06:06:21 +00:00
2019-03-22 19:09:39 +00:00
log_indexing_started(out_dir, 'index.html')
write_html_links_index(out_dir, links, finished=finished)
2019-03-21 05:28:12 +00:00
log_indexing_finished(out_dir, 'index.html')
2019-02-07 06:06:21 +00:00
2019-03-21 05:28:12 +00:00
def load_links_index(out_dir=OUTPUT_DIR, import_path=None):
"""parse and load existing index with any new links from import_path merged in"""
existing_links = []
if out_dir:
existing_links = parse_json_links_index(out_dir)
check_links_structure(existing_links)
new_links = []
if import_path:
# parse and validate the import file
log_parsing_started(import_path)
raw_links, parser_name = parse_links(import_path)
new_links = validate_links(raw_links)
check_links_structure(new_links)
# merge existing links in out_dir and new links
all_links = validate_links(existing_links + new_links)
check_links_structure(all_links)
num_new_links = len(all_links) - len(existing_links)
if import_path and parser_name:
log_parsing_finished(num_new_links, parser_name)
return all_links, new_links
def write_json_links_index(out_dir, links):
"""write the json link index to a given path"""
check_links_structure(links)
path = os.path.join(out_dir, 'index.json')
index_json = {
'info': 'ArchiveBox Index',
'help': 'https://github.com/pirate/ArchiveBox',
'version': GIT_SHA,
'num_links': len(links),
'updated': str(datetime.now().timestamp()),
'links': links,
}
with open(path, 'w', encoding='utf-8') as f:
json.dump(index_json, f, indent=4, default=str)
chmod_file(path)
2019-03-21 05:28:12 +00:00
def parse_json_links_index(out_dir=OUTPUT_DIR):
"""parse a archive index json file and return the list of links"""
2017-10-23 09:58:41 +00:00
index_path = os.path.join(out_dir, 'index.json')
if os.path.exists(index_path):
with open(index_path, 'r', encoding='utf-8') as f:
links = json.load(f)['links']
check_links_structure(links)
return links
2017-10-23 09:58:41 +00:00
return []
def write_html_links_index(out_dir, links, finished=False):
"""write the html link index to a given path"""
check_links_structure(links)
path = os.path.join(out_dir, 'index.html')
2018-06-11 01:12:55 +00:00
copy_tree(os.path.join(TEMPLATES_DIR, 'static'), os.path.join(out_dir, 'static'))
2018-04-17 11:00:40 +00:00
2018-09-12 23:25:48 +00:00
with open(os.path.join(out_dir, 'robots.txt'), 'w+') as f:
f.write('User-agent: *\nDisallow: /')
2018-06-11 01:12:55 +00:00
with open(os.path.join(TEMPLATES_DIR, 'index.html'), 'r', encoding='utf-8') as f:
index_html = f.read()
2018-06-11 01:12:55 +00:00
with open(os.path.join(TEMPLATES_DIR, 'index_row.html'), 'r', encoding='utf-8') as f:
link_row_html = f.read()
full_links_info = (derived_link_info(link) for link in links)
link_rows = '\n'.join(
Template(link_row_html).substitute(**{
**link,
'title': (
link['title']
or (link['base_url'] if link['is_archived'] else TITLE_LOADING_MSG)
),
2019-03-20 08:30:00 +00:00
'favicon_url': (
os.path.join('archive', link['timestamp'], 'favicon.ico')
# if link['is_archived'] else 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
),
'archive_url': urlencode(
wget_output_path(link) or 'index.html'
),
})
for link in full_links_info
)
2017-07-04 11:24:03 +00:00
template_vars = {
'num_links': len(links),
'date_updated': datetime.now().strftime('%Y-%m-%d'),
'time_updated': datetime.now().strftime('%Y-%m-%d %H:%M'),
2018-04-17 13:14:01 +00:00
'footer_info': FOOTER_INFO,
'git_sha': GIT_SHA,
'short_git_sha': GIT_SHA[:8],
'rows': link_rows,
'status': 'finished' if finished else 'running',
2017-07-04 11:24:03 +00:00
}
with open(path, 'w', encoding='utf-8') as f:
2017-07-05 21:33:51 +00:00
f.write(Template(index_html).substitute(**template_vars))
2017-10-23 09:58:41 +00:00
chmod_file(path)
2017-07-05 21:33:51 +00:00
2019-03-21 05:28:12 +00:00
def patch_links_index(link, out_dir=OUTPUT_DIR):
"""hack to in-place update one row's info in the generated index html"""
title = link['title'] or latest_output(link)['title']
successful = len(tuple(filter(None, latest_output(link).values())))
# Patch JSON index
changed = False
2019-03-21 05:28:12 +00:00
json_file_links = parse_json_links_index(out_dir)
for saved_link in json_file_links:
if saved_link['url'] == link['url']:
saved_link['title'] = title
saved_link['history'] = link['history']
changed = True
break
if changed:
2019-03-21 05:28:12 +00:00
write_json_links_index(out_dir, json_file_links)
# Patch HTML index
2019-03-21 05:28:12 +00:00
html_path = os.path.join(out_dir, 'index.html')
html = open(html_path, 'r').read().split('\n')
for idx, line in enumerate(html):
if title and ('<span data-title-for="{}"'.format(link['url']) in line):
html[idx] = '<span>{}</span>'.format(title)
elif successful and ('<span data-number-for="{}"'.format(link['url']) in line):
html[idx] = '<span>{}</span>'.format(successful)
break
with open(html_path, 'w') as f:
f.write('\n'.join(html))
2019-03-21 05:28:12 +00:00
2017-10-23 09:58:41 +00:00
### Individual link index
def write_link_index(out_dir, link):
link['updated'] = str(datetime.now().timestamp())
write_json_link_index(out_dir, link)
write_html_link_index(out_dir, link)
def write_json_link_index(out_dir, link):
"""write a json file with some info about the link"""
check_link_structure(link)
path = os.path.join(out_dir, 'index.json')
with open(path, 'w', encoding='utf-8') as f:
json.dump(link, f, indent=4, default=str)
chmod_file(path)
2017-10-23 09:58:41 +00:00
def parse_json_link_index(out_dir):
"""load the json link index from a given directory"""
existing_index = os.path.join(out_dir, 'index.json')
if os.path.exists(existing_index):
with open(existing_index, 'r', encoding='utf-8') as f:
link_json = json.load(f)
check_link_structure(link_json)
return link_json
2017-10-23 09:58:41 +00:00
return {}
2019-03-21 05:28:12 +00:00
def load_json_link_index(out_dir, link):
"""check for an existing link archive in the given directory,
and load+merge it into the given link dict
"""
link = {
**parse_json_link_index(out_dir),
**link,
}
link.update({
'history': link.get('history') or {},
})
2019-03-21 05:28:12 +00:00
check_link_structure(link)
return link
def write_html_link_index(out_dir, link):
check_link_structure(link)
2019-03-08 22:46:14 +00:00
with open(os.path.join(TEMPLATES_DIR, 'link_index.html'), 'r', encoding='utf-8') as f:
link_html = f.read()
path = os.path.join(out_dir, 'index.html')
link = derived_link_info(link)
with open(path, 'w', encoding='utf-8') as f:
f.write(Template(link_html).substitute({
**link,
'title': (
link['title']
or (link['base_url'] if link['is_archived'] else TITLE_LOADING_MSG)
),
'archive_url': urlencode(
wget_output_path(link)
or (link['domain'] if link['is_archived'] else 'about:blank')
),
2019-03-21 05:28:12 +00:00
'extension': link['extension'] or 'html',
'tags': link['tags'].strip() or 'untagged',
'status': 'Archived' if link['is_archived'] else 'Not yet archived',
'status_color': 'success' if link['is_archived'] else 'danger',
}))
chmod_file(path)