[Sync] + new Article sync

This commit is contained in:
Roman Timashev committed 2017-09-13 14:06:59 +03:00
1 parent 953ef0bc28
commit 2e5aa4d21e
5 files changed
+257 -285

No files matched your search

+7 -1
View File
@@ -1,6 +1,7 @@
import settings
from db import collection
from sync.core import Sync
from sync.core.SyncArticle import SyncArticle
from sync.core.SyncDocument import SyncDocument
from sync.core.SyncPage import SyncPage
from sync.core.SyncPerson import SyncPerson
@@ -57,17 +58,22 @@ async def run(run_interval=0):
collection=c(settings.collection_collective),
)
articles = SyncArticle(
provider=io(settings.dir_articles),
collection=c(settings.collection_articles),
)
while True:
# main(io(settings.dir_documents), sync_documents, settings.collection_documents)
# main(io(settings.dir_awards), sync_documents, settings.collection_awards)
# main(io(settings.dir_schedules), sync_schedules, settings.collection_schedules)
# main(io(settings.dir_articles), sync_posts, settings.collection_articles)
# main(io(settings.dir_gallery), sync_albums, settings.collection_albums)
await asyncio.wait([
main(documents),
main(pages),
main(persons),
main(articles),
])
if run_interval == 0:
+58
View File
@@ -0,0 +1,58 @@
import os
import logging
import settings
from sync import untouched
from sync.core import Sync
from sync.data import Provider
from sync.models.Article import Article
from sync.models.Person import Person
from utils.text.transform import url_encode_text
logger = logging.getLogger(settings.name + '.Article')
class SyncArticle(Sync):
async def run(self):
logger.info('Checking for update')
documents = await Article.scan(self.provider)
documents_id = [doc.id for doc in documents]
# self.validate(documents)
logger.info('Found {} Item(s)'.format(len(documents)))
# SKIP UNTOUCHED DOCUMENTS
documents = await untouched(documents)
logger.info('Changed {} Items(s)'.format(len(documents)))
# UPDATING
if settings.update_enabled:
if len(documents) == 0:
logger.info('No Items to update')
for document in documents:
logger.info('Updating Item {}'.format(document.id))
await document.build(
sizes=settings.image_sizes,
)
await document.save()
logger.info('Updated Item {}'.format(document.id))
# DELETING
if settings.delete_enabled:
documents_delete = list(self.query({'id': {'$nin': documents_id}}))
if len(documents_delete) == 0:
logger.info('No Items to delete')
else:
for document in documents_delete:
logger.info('Deleting Item {}'.format(document['id']))
q = {'_id': document['_id']}
self.delete(q)
# await self.clean()
-92
View File
@@ -1,92 +0,0 @@
import os
from tempfile import mkstemp
import lxml.html
import kazimir
from sync.core import Sync
from utils.fn import last_good, lmap, combine
from utils.hash import hash_str, hash_file
from utils.image import create_image
from utils.image.resize import image_magick_pdf_to_img
from utils.text.transform import url_encode_text, url_encode_file
class SyncPost(Sync):
def __init__(self, collection, provider, document_type, image_url_base, dir_local_images, sizes, origin):
super().__init__()
self.collection = collection
self.provider = provider
self.sizes = sizes
self.type = document_type
self.image_url_base = image_url_base
self.dir_local_images = dir_local_images
self.origin = origin
def create_id(self, document):
new_id = url_encode_text(document['title'])
document['id'] = document['id'] if 'id' in document else new_id
return document
def create_hash(self, document):
files = sorted(document['images'])
hashes = lmap(
self.provider.hash,
lmap(
lambda i: os.path.join(document['folder'], i),
files
)
)
document['hash'] = hash_str(
combine([hash_str(document)] + hashes)
)
return document
def create(self, document):
if 'date' not in document or not document['date']:
return None
if 'title' not in document or not document['title']:
return None
if self.type:
document['type'] = self.type
post_html = lxml.html.fromstring(document['post'])
images = []
for img in post_html.cssselect('img'):
src = img.get('src')
relative_image_path = os.path.join(document['folder'], src)
img_path = self.provider.get_local(relative_image_path)
if os.path.exists(img_path):
img_id = url_encode_text(os.path.splitext(src)[0])
url_fn = lambda size, ext: self.image_url_base.format(
id=document['id'],
img=img_id,
size=size,
ext=ext.lower()
)
image = create_image(img_path, self.sizes, url_fn, self.dir_local_images)
if image:
images.append(image)
img.set('src', image['data']['big']['url'])
else:
print('fail: ', document['folder'], src)
document['post'] = kazimir.html_from_tree(post_html)
document['images'] = images
document['origin'] = self.origin
return document
def create_remove_query(self, query):
if self.type:
query = {
**query,
'type': self.type,
'origin': self.origin
}
return query
+192
View File
@@ -0,0 +1,192 @@
import logging
import os
import lxml.html
import kazimir
import settings
from db import collection
from kazimir import kazimir_to_html
from sync import create_date_and_title_from_folder_name, images_from_html, create_post_from_image_list, create_date
from sync.data import Provider, list_images
from sync.models import Model
from sync.models.Image import Image
from utils.fn import swap_ext, first, map_cases, constant, combine
from utils.hash import hash_str
from utils.io import read_yaml_md, parse_yaml_front_matter
from utils.text.transform import url_encode_text
logger = logging.getLogger(settings.name + '.Person')
store = collection(settings.collection_articles)
def find(item_id: str):
q = {'id': item_id}
try:
return store.find_one(q)
except ValueError:
pass
return None
class Article(Model):
@staticmethod
async def scan(provider):
documents = [i for i in provider.scan('.') if provider.is_dir(i)]
documents = [Article.read(provider, i) for i in documents]
return documents
@staticmethod
def read(provider, path):
file_time_formats = ['%Y.%m.%d']
# PARSE FOLDER NAME -> GET OPTIONAL DATE/TITLE
doc_date, doc_title = create_date_and_title_from_folder_name(path, file_time_formats)
document = {
'folder': path,
'date': doc_date,
'title': doc_title,
}
# CREATE BASIC_MANIFEST BASED ON FOLDER NAME AND IMAGE CONTENT
images = list_images(provider, path)
images = [os.path.relpath(i, path) for i in images]
document['images'] = images
document['post'] = create_post_from_image_list(images)
# TRY TO FILL UP BASIC_MANIFEST WITH MD_MANIFEST
manifest_file = md_from_folder(provider, path)
if manifest_file:
manifest = get_manifest(provider, manifest_file)
else:
manifest = {}
return Article(
provider,
path,
params={
**document,
**manifest,
},
)
def __init__(self, provider, file, params=None):
# self.__id_template: str = '{category}-{file}'
# self.__url_template: str = settings.document_url_template
# self.__url_preview_template: str = settings.document_url_preview_template
self.params = params if params else {}
self.origin = settings.origin
self.version = '2'
self.post = None
self.images = None
super().__init__(provider, store, file)
def init(self):
self.__set_id()
self.__set_hash()
def validate(self):
document = self.params
if 'date' not in document or not document['date']:
return None
if 'title' not in document or not document['title']:
return None
async def build(self, **kwargs):
sizes = kwargs['sizes']
post, images = await create_post(self.provider, self.id, self.params, sizes)
self.post = post
self.images = images
post, images = await create_post(self.provider, self.id, self.params, sizes)
self.post = kazimir.html_from_tree(post)
self.images = images
def bake(self):
return {
**self.params,
'images': [i.id for i in self.images],
'post': self.post,
'id': self.id,
'hash': self.hash,
'file': self.file,
'origin': self.origin,
'version': self.version,
}
def __set_id(self):
if 'id' in self.params:
self.id = self.params['id']
else:
self.id = url_encode_text(self.params['title'])
def __set_hash(self):
f = self.params['folder']
images = sorted(self.params['images'])
images = [os.path.join(f, i) for i in images]
images = [self.provider.hash(i) for i in images]
self.hash = hash_str(
combine([hash_str(self.params)] + images)
)
def __str__(self):
return '<Article hash={} file={} id={}>'.format(self.hash, self.file, self.id)
def md_from_folder(provider, i):
return first(provider.type_filter(i, '.md'))
def get_manifest(provider, path):
data = provider.read(path).read().decode('utf-8')
manifest = parse_yaml_front_matter(data)
if 'date' in manifest:
manifest['date'] = create_date(manifest['date'], settings.date_formats)
if 'until' in manifest:
manifest['until'] = create_date(manifest['until'], settings.date_formats)
if 'id' in manifest:
manifest['id'] = str(manifest['id'])
html = kazimir_to_html(manifest['content'])
return {
**manifest,
'post': html,
'images': images_from_html(html) # Add images for hashing
}
async def create_post(provider, doc_id, document, sizes):
image_url_base = settings.image_url_base + 'post-{id}-{img}-{size}{ext}'
post_html = lxml.html.fromstring(document['post'])
images = []
for img in post_html.cssselect('img'):
src = img.get('src')
relative_image_path = os.path.join(document['folder'], src)
img_path = provider.get_local(relative_image_path)
if os.path.exists(img_path):
img_id = url_encode_text(os.path.splitext(src)[0])
url_fn = lambda file, size, ext: image_url_base.format(
id=doc_id,
img=img_id,
size=size,
ext=ext.lower()
)
image = await Image.new(provider, img_path, sizes, url_fn)
if image:
url = image.get_size('big')['url']
images.append(image)
img.set('src', url)
else:
logger.error('Fail to get Image', document['folder'], src)
return post_html, images
-192
View File
@@ -1,192 +0,0 @@
import os
import settings
from kazimir import kazimir_to_html
from sync.data import list_images
from sync import create_date_and_title_from_folder_name, create_post_from_image_list, create_date, images_from_html, \
synced_images_ids, untouched
from sync.core.post import SyncPost
from utils.fn import lmap, map_cases, first, key_mapper, lmapfn, constant
from utils.io import read_yaml_md, parse_yaml_front_matter
def create_document_from_folder(sync, path, file_time_formats):
# PARSE FOLDER NAME -> GET OPTIONAL DATE/TITLE
doc_date, doc_title = create_date_and_title_from_folder_name(path, file_time_formats)
document = {
'folder': path,
'date': doc_date,
'title': doc_title,
}
# CREATE BASIC_MANIFEST BASED ON FOLDER NAME AND IMAGE CONTENT
images = lmap(
lambda img: os.path.relpath(img, path),
list_images(sync.provider, path)
)
post = create_post_from_image_list(images)
# TRY TO FILL UP BASIC_MANIFEST WITH MD_MANIFEST
params = map_cases(
document,
[(
lambda doc: md_from_folder(sync.provider, path),
lambda doc: get_manifest(sync.provider, md_from_folder(sync.provider, path)),
)],
constant({})
)
document = {
'images': images,
'post': post,
**document,
**params,
}
document = sync.create_id(document)
document = sync.create_hash(document)
return document
def get_manifest(provider, path):
data = provider.read(path).read().decode('utf-8')
manifest = parse_yaml_front_matter(data)
if 'date' in manifest:
manifest['date'] = create_date(manifest['date'], settings.date_formats)
if 'until' in manifest:
manifest['until'] = create_date(manifest['until'], settings.date_formats)
if 'id' in manifest:
manifest['id'] = str(manifest['id'])
html = kazimir_to_html(manifest['content'])
return {
**manifest,
'post': html,
'images': images_from_html(html) # Add images for hashing
}
def md_from_folder(provider, i):
return first(provider.type_filter(i, '.md'))
def get_folder_documents(sync, file_time_formats):
# GET EVENT FOLDERS
documents = lmap(
lambda i: i,
filter(
lambda i: sync.provider.is_dir(i),
sync.provider.scan('.')
)
)
return lmap(
lambda doc: create_document_from_folder(sync, doc, file_time_formats),
documents
)
# GET EVENT FILES
def get_file_documents(sync, file_time_formats):
documents = sync.provider.type_filter('.', '.md')
documents = lmap(
lambda i: (i,) + create_date_and_title_from_folder_name(i, file_time_formats),
documents
)
documents = lmap(
lambda i: {
'file': i[0],
'date': i[1],
'title': i[2],
},
documents
)
# OVERWRITE BASIC_MANIFEST WITH MD_MANIFEST
documents = lmap(
lambda i: {
**i,
**get_manifest(sync.provider, i['file'])
},
documents
)
# CREATE DOCUMENT IDENTITY
documents = lmap(sync.create_id, documents)
# CREATE HASH OF DOCUMENT FILE
documents = lmap(sync.create_hash, documents)
return documents
def main(sync, file_time_formats, update_documents=True, delete_documents=True):
documents = get_folder_documents(sync, file_time_formats)
documents += get_file_documents(sync, file_time_formats)
# CREATE SCOPE OF CURRENT SESSION
scope_documents_ids = lmapfn(documents)(
lambda i: i['id']
)
# SKIP UNTOUCHED DOCUMENTS
# documents = untouched(documents, sync)
# MAP EVENT MANIFEST -> EVENT_OBJECT
documents = lmap(
sync.create,
documents
)
# FILTER INCORRECT EVENTS
documents = list(filter(None, documents))
# REPLACE IMAGES OBJECTS WITH IT _ID IN MONGODB
documents = lmap(
key_mapper('images', synced_images_ids),
documents
)
# SYNC EVENT_OBJECT WITH DB
if update_documents:
documents = lmap(
sync.update,
documents
)
documents_to_delete = []
if delete_documents:
remove_query = sync.create_remove_query({'id': {'$nin': scope_documents_ids}})
documents_to_delete = sync.query(remove_query)
documents_to_delete = lmap(
sync.delete,
map(
lambda i: {'_id': i['_id']},
documents_to_delete
)
)
return documents, documents_to_delete
def sync_posts(provider, collection, update=True, delete=True):
image_url_base = settings.image_url_base + 'post-{id}-{img}-{size}{ext}'
return main(
SyncPost(
collection,
provider,
'post',
image_url_base,
settings.dir_static_images,
settings.event_image_sizes,
origin=settings.origin
),
file_time_formats=['%Y.%m.%d'],
update_documents=update,
delete_documents=delete
)