Skip to content

Commit 26ee0c4

Browse files
committed
some fixes
1 parent b994fa8 commit 26ee0c4

File tree

2 files changed

+268
-264
lines changed

2 files changed

+268
-264
lines changed

src/redturtle/rsync/adapters/adapter.py

Lines changed: 228 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55
from zope.component import adapter
66
from zope.interface import implementer
77
from zope.interface import Interface
8+
from plone import api
9+
from datetime import datetime
10+
from redturtle.rsync.scripts.rsync import logger
811

912
import json
1013
import requests
14+
import re
15+
import uuid
1116

1217

1318
class TimeoutHTTPAdapter(HTTPAdapter):
@@ -37,6 +42,15 @@ class RsyncAdapterBase:
3742
def __init__(self, context, request):
3843
self.context = context
3944
self.request = request
45+
self.options = None
46+
self.logdata = []
47+
self.n_updated = 0
48+
self.n_created = 0
49+
self.n_items = 0
50+
self.n_todelete = 0
51+
self.sync_uids = set()
52+
self.start = datetime.now()
53+
self.end = None
4054

4155
def requests_retry_session(
4256
self,
@@ -63,12 +77,95 @@ def requests_retry_session(
6377
session.mount("https://", http_adapter)
6478
return session
6579

66-
def log_item_title(self, start, options):
80+
def log_item_title(self, start):
6781
"""
6882
Return the title of the log item for the rsync command.
6983
"""
7084
return f"Report sync {start.strftime('%d-%m-%Y %H:%M:%S')}"
7185

86+
def autolink(self, text):
87+
"""
88+
Fix links in the text.
89+
"""
90+
return re.sub(
91+
r"(https?://\S+|/\S+)",
92+
r'<a href="\1">\1</a>',
93+
text,
94+
re.MULTILINE | re.DOTALL,
95+
)
96+
97+
def get_frontend_url(self, item):
98+
frontend_domain = api.portal.get_registry_record(
99+
name="volto.frontend_domain", default=""
100+
)
101+
if not frontend_domain or frontend_domain == "https://":
102+
frontend_domain = "http://localhost:3000"
103+
if frontend_domain.endswith("/"):
104+
frontend_domain = frontend_domain[:-1]
105+
portal_url = api.portal.get().portal_url()
106+
107+
return item.absolute_url().replace(portal_url, frontend_domain)
108+
109+
def log_info(self, msg, type="info", force_sys_log=False):
110+
"""
111+
append a message to the logdata list and print it.
112+
113+
"""
114+
style = ""
115+
if type == "error":
116+
style = "padding:5px;background-color:red;color:#fff"
117+
if type == "warning":
118+
style = "padding:5px;background-color:#ff9d00;color:#fff"
119+
msg = f"[{datetime.now().strftime('%d-%m-%Y %H:%M:%S')}] {msg}"
120+
self.logdata.append(f'<p style="{style}">{self.autolink(msg)}</p>')
121+
122+
# print the message on standard output
123+
if type == "error":
124+
logger.error(msg)
125+
elif type == "warning":
126+
logger.warning(msg)
127+
else:
128+
if self.options.verbose or force_sys_log:
129+
logger.info(msg)
130+
131+
def get_log_container(self):
132+
logpath = getattr(self.options, "logpath", None)
133+
if not logpath:
134+
logger.warning("No logpath specified, skipping log write into database.")
135+
return
136+
logcontainer = api.content.get(logpath)
137+
if not logcontainer:
138+
logger.warning(
139+
f'Log container not found with path "{logpath}", skipping log write into database.'
140+
)
141+
return
142+
return logcontainer
143+
144+
def write_log(self):
145+
"""
146+
Write the log into the database.
147+
"""
148+
logcontainer = self.get_log_container()
149+
if not logcontainer:
150+
return
151+
description = f"{self.n_items} elementi trovati, {self.n_created} creati, {self.n_updated} aggiornati, {self.n_todelete} da eliminare"
152+
blockid = str(uuid.uuid4())
153+
api.content.create(
154+
logcontainer,
155+
"Document",
156+
title=self.log_item_title(start=self.start),
157+
description=description,
158+
blocks={
159+
blockid: {
160+
"@type": "html",
161+
"html": "\n".join(self.logdata),
162+
}
163+
},
164+
blocks_layout={
165+
"items": [blockid],
166+
},
167+
)
168+
72169
def set_args(self, parser):
73170
"""
74171
Set some additional arguments for the rsync command.
@@ -82,33 +179,144 @@ def set_args(self, parser):
82179
"""
83180
return
84181

85-
def get_data(self, options):
182+
def get_data(self):
183+
""" """
184+
try:
185+
data = self.do_get_data()
186+
except Exception as e:
187+
logger.exception(e)
188+
msg = f"Error in data generation: {e}"
189+
self.log_info(msg=msg, type="error")
190+
return
191+
if not data:
192+
msg = "No data to sync."
193+
self.log_info(msg=msg, type="warning")
194+
return
195+
return data
196+
197+
def convert_source_data(self, data):
198+
"""
199+
If needed, convert the source data to a format that can be used by the rsync command.
200+
"""
201+
return data, None
202+
203+
def find_item_from_row(self, row):
204+
"""
205+
Find the item in the context from the given row of data.
206+
This method should be implemented by subclasses to find the specific type of content item.
207+
"""
208+
try:
209+
return self.do_find_item_from_row(row=row)
210+
except Exception as e:
211+
msg = f"[Error] Unable to find item from row {row}: {e}"
212+
self.log_info(msg=msg, type="error")
213+
return None
214+
215+
def create_item(self, row):
216+
"""
217+
Create the item.
218+
"""
219+
try:
220+
res = self.do_create_item(row=row)
221+
except Exception as e:
222+
msg = f"[Error] Unable to create item {row}: {e}"
223+
self.log_info(msg=msg, type="error")
224+
return
225+
if not res:
226+
msg = f"[Error] item {row} not created."
227+
self.log_info(msg=msg, type="error")
228+
return
229+
230+
# adapter could create a list of items (maybe also children or related items)
231+
if isinstance(res, list):
232+
self.n_created += len(res)
233+
for item in res:
234+
msg = f"[CREATED] {'/'.join(item.getPhysicalPath())}"
235+
self.log_info(msg=msg)
236+
else:
237+
self.n_created += 1
238+
msg = f"[CREATED] {'/'.join(res.getPhysicalPath())}"
239+
self.log_info(msg=msg)
240+
return res
241+
242+
def update_item(self, item, row):
243+
"""
244+
Handle update of the item.
245+
"""
246+
try:
247+
res = self.do_update_item(item=item, row=row)
248+
except Exception as e:
249+
msg = f"[Error] Unable to update item {self.get_frontend_url(item)}: {e}"
250+
self.log_info(msg=msg, type="error")
251+
return
252+
253+
if not res:
254+
msg = f"[SKIPPED] {self.get_frontend_url(item)}"
255+
self.log_info(msg=msg)
256+
return
257+
258+
if isinstance(res, list):
259+
self.n_updated += len(res)
260+
for updated in res:
261+
msg = f"[UPDATED] {updated.absolute_url()}"
262+
self.log_info(msg=msg)
263+
self.sync_uids.add(updated.UID())
264+
updated.reindexObject()
265+
else:
266+
self.n_updated += 1
267+
msg = f"[UPDATED] {self.get_frontend_url(item)}"
268+
self.log_info(msg=msg)
269+
self.sync_uids.add(item.UID())
270+
item.reindexObject()
271+
272+
def delete_items(self, data):
273+
"""
274+
See if there are items to delete.
275+
"""
276+
res = self.do_delete_items(data=data)
277+
if not res:
278+
return
279+
if isinstance(res, list):
280+
self.n_todelete += len(res)
281+
for item in res:
282+
msg = f"[DELETED] {item}"
283+
self.log_info(msg=msg)
284+
else:
285+
self.n_todelete += 1
286+
msg = f"[DELETED] {res}"
287+
self.log_info(msg=msg)
288+
289+
def do_get_data(self):
86290
"""
87291
Convert the data to be used for the rsync command.
88292
Return:
89293
- data: the data to be used for the rsync command
90294
- error: an error message if there was an error, None otherwise
91295
"""
92-
error = None
93296
data = None
94297
# first, read source data
95-
if getattr(options, "source_path", None):
96-
file_path = Path(options.source_path)
298+
if getattr(self.options, "source_path", None):
299+
file_path = Path(self.options.source_path)
97300
if file_path.exists() and file_path.is_file():
98301
with open(file_path, "r") as f:
99302
try:
100303
data = json.load(f)
101304
except json.JSONDecodeError:
102305
data = f.read()
103306
else:
104-
error = f"Source file not found in: {file_path}"
105-
return data, error
106-
elif getattr(options, "source_url", None):
307+
self.log_info(
308+
msg=f"Source file not found in: {file_path}", type="warning"
309+
)
310+
return
311+
elif getattr(self.options, "source_url", None):
107312
http = self.requests_retry_session(retries=7, timeout=30.0)
108-
response = http.get(options.source_url)
313+
response = http.get(self.options.source_url)
109314
if response.status_code != 200:
110-
error = f"Error getting data from {options.source_url}: {response.status_code}"
111-
return data, error
315+
self.log_info(
316+
msg=f"Error getting data from {self.options.source_url}: {response.status_code}",
317+
type="warning",
318+
)
319+
return
112320
if "application/json" in response.headers.get("Content-Type", ""):
113321
try:
114322
data = response.json()
@@ -117,44 +325,27 @@ def get_data(self, options):
117325
else:
118326
data = response.content
119327

120-
if data:
121-
data, error = self.convert_source_data(data)
122-
return data, error
328+
return self.convert_source_data(data)
123329

124-
def convert_source_data(self, data):
125-
"""
126-
If needed, convert the source data to a format that can be used by the rsync command.
127-
"""
128-
return data, None
330+
def do_find_item_from_row(self, row):
331+
raise NotImplementedError()
129332

130-
def find_item_from_row(self, row):
333+
def do_update_item(self, item, row):
131334
"""
132-
Find the item in the context from the given row of data.
133-
This method should be implemented by subclasses to find the specific type of content item.
335+
Update the item from the given row of data.
336+
This method should be implemented by subclasses to update the specific type of content item.
134337
"""
135338
raise NotImplementedError()
136339

137-
def create_item(self, row, options):
340+
def do_create_item(self, row):
138341
"""
139342
Create a new content item from the given row of data.
140343
This method should be implemented by subclasses to create the specific type of content item.
141344
"""
142345
raise NotImplementedError()
143346

144-
def update_item(self, item, row):
347+
def do_delete_items(self, data):
145348
"""
146-
Update an existing content item from the given row of data.
147-
This method should be implemented by subclasses to update the specific type of content item.
349+
Delete items
148350
"""
149351
raise NotImplementedError()
150-
151-
def delete_items(self, data, sync_uids):
152-
"""
153-
params:
154-
- data: the data to be used for the rsync command
155-
- sync_uids: the uids of the items thata has been updated
156-
157-
Delete items if needed.
158-
This method should be implemented by subclasses to delete the specific type of content item.
159-
"""
160-
return

0 commit comments

Comments
 (0)