##// END OF EJS Templates
Added tripcode to the synced post items list
neko259 -
r1556:4b03d4eb default
parent child Browse files
Show More
@@ -1,270 +1,275 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2
2
3 from boards.models.attachment.downloaders import download
3 from boards.models.attachment.downloaders import download
4 from boards.utils import get_file_mimetype, get_file_hash
4 from boards.utils import get_file_mimetype, get_file_hash
5 from django.db import transaction
5 from django.db import transaction
6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
7
7
8 EXCEPTION_NODE = 'Sync node returned an error: {}'
8 EXCEPTION_NODE = 'Sync node returned an error: {}'
9 EXCEPTION_OP = 'Load the OP first'
9 EXCEPTION_OP = 'Load the OP first'
10 EXCEPTION_DOWNLOAD = 'File was not downloaded'
10 EXCEPTION_DOWNLOAD = 'File was not downloaded'
11 EXCEPTION_HASH = 'File hash does not match attachment hash'
11 EXCEPTION_HASH = 'File hash does not match attachment hash'
12 EXCEPTION_SIGNATURE = 'Invalid model signature for {}'
12 EXCEPTION_SIGNATURE = 'Invalid model signature for {}'
13 ENCODING_UNICODE = 'unicode'
13 ENCODING_UNICODE = 'unicode'
14
14
15 TAG_MODEL = 'model'
15 TAG_MODEL = 'model'
16 TAG_REQUEST = 'request'
16 TAG_REQUEST = 'request'
17 TAG_RESPONSE = 'response'
17 TAG_RESPONSE = 'response'
18 TAG_ID = 'id'
18 TAG_ID = 'id'
19 TAG_STATUS = 'status'
19 TAG_STATUS = 'status'
20 TAG_MODELS = 'models'
20 TAG_MODELS = 'models'
21 TAG_TITLE = 'title'
21 TAG_TITLE = 'title'
22 TAG_TEXT = 'text'
22 TAG_TEXT = 'text'
23 TAG_THREAD = 'thread'
23 TAG_THREAD = 'thread'
24 TAG_PUB_TIME = 'pub-time'
24 TAG_PUB_TIME = 'pub-time'
25 TAG_SIGNATURES = 'signatures'
25 TAG_SIGNATURES = 'signatures'
26 TAG_SIGNATURE = 'signature'
26 TAG_SIGNATURE = 'signature'
27 TAG_CONTENT = 'content'
27 TAG_CONTENT = 'content'
28 TAG_ATTACHMENTS = 'attachments'
28 TAG_ATTACHMENTS = 'attachments'
29 TAG_ATTACHMENT = 'attachment'
29 TAG_ATTACHMENT = 'attachment'
30 TAG_TAGS = 'tags'
30 TAG_TAGS = 'tags'
31 TAG_TAG = 'tag'
31 TAG_TAG = 'tag'
32 TAG_ATTACHMENT_REFS = 'attachment-refs'
32 TAG_ATTACHMENT_REFS = 'attachment-refs'
33 TAG_ATTACHMENT_REF = 'attachment-ref'
33 TAG_ATTACHMENT_REF = 'attachment-ref'
34 TAG_TRIPCODE = 'tripcode'
34
35
35 TYPE_GET = 'get'
36 TYPE_GET = 'get'
36
37
37 ATTR_VERSION = 'version'
38 ATTR_VERSION = 'version'
38 ATTR_TYPE = 'type'
39 ATTR_TYPE = 'type'
39 ATTR_NAME = 'name'
40 ATTR_NAME = 'name'
40 ATTR_VALUE = 'value'
41 ATTR_VALUE = 'value'
41 ATTR_MIMETYPE = 'mimetype'
42 ATTR_MIMETYPE = 'mimetype'
42 ATTR_KEY = 'key'
43 ATTR_KEY = 'key'
43 ATTR_REF = 'ref'
44 ATTR_REF = 'ref'
44 ATTR_URL = 'url'
45 ATTR_URL = 'url'
45
46
46 STATUS_SUCCESS = 'success'
47 STATUS_SUCCESS = 'success'
47
48
48
49
49 class SyncException(Exception):
50 class SyncException(Exception):
50 pass
51 pass
51
52
52
53
53 class SyncManager:
54 class SyncManager:
54 @staticmethod
55 @staticmethod
55 def generate_response_get(model_list: list):
56 def generate_response_get(model_list: list):
56 response = et.Element(TAG_RESPONSE)
57 response = et.Element(TAG_RESPONSE)
57
58
58 status = et.SubElement(response, TAG_STATUS)
59 status = et.SubElement(response, TAG_STATUS)
59 status.text = STATUS_SUCCESS
60 status.text = STATUS_SUCCESS
60
61
61 models = et.SubElement(response, TAG_MODELS)
62 models = et.SubElement(response, TAG_MODELS)
62
63
63 for post in model_list:
64 for post in model_list:
64 model = et.SubElement(models, TAG_MODEL)
65 model = et.SubElement(models, TAG_MODEL)
65 model.set(ATTR_NAME, 'post')
66 model.set(ATTR_NAME, 'post')
66
67
67 global_id = post.global_id
68 global_id = post.global_id
68
69
69 images = post.images.all()
70 images = post.images.all()
70 attachments = post.attachments.all()
71 attachments = post.attachments.all()
71 if global_id.content:
72 if global_id.content:
72 model.append(et.fromstring(global_id.content))
73 model.append(et.fromstring(global_id.content))
73 if len(images) > 0 or len(attachments) > 0:
74 if len(images) > 0 or len(attachments) > 0:
74 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
75 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
75 for image in images:
76 for image in images:
76 SyncManager._attachment_to_xml(
77 SyncManager._attachment_to_xml(
77 None, attachment_refs, image.image.file,
78 None, attachment_refs, image.image.file,
78 image.hash, image.image.url)
79 image.hash, image.image.url)
79 for file in attachments:
80 for file in attachments:
80 SyncManager._attachment_to_xml(
81 SyncManager._attachment_to_xml(
81 None, attachment_refs, file.file.file,
82 None, attachment_refs, file.file.file,
82 file.hash, file.file.url)
83 file.hash, file.file.url)
83 else:
84 else:
84 content_tag = et.SubElement(model, TAG_CONTENT)
85 content_tag = et.SubElement(model, TAG_CONTENT)
85
86
86 tag_id = et.SubElement(content_tag, TAG_ID)
87 tag_id = et.SubElement(content_tag, TAG_ID)
87 global_id.to_xml_element(tag_id)
88 global_id.to_xml_element(tag_id)
88
89
89 title = et.SubElement(content_tag, TAG_TITLE)
90 title = et.SubElement(content_tag, TAG_TITLE)
90 title.text = post.title
91 title.text = post.title
91
92
92 text = et.SubElement(content_tag, TAG_TEXT)
93 text = et.SubElement(content_tag, TAG_TEXT)
93 text.text = post.get_sync_text()
94 text.text = post.get_sync_text()
94
95
95 thread = post.get_thread()
96 thread = post.get_thread()
96 if post.is_opening():
97 if post.is_opening():
97 tag_tags = et.SubElement(content_tag, TAG_TAGS)
98 tag_tags = et.SubElement(content_tag, TAG_TAGS)
98 for tag in thread.get_tags():
99 for tag in thread.get_tags():
99 tag_tag = et.SubElement(tag_tags, TAG_TAG)
100 tag_tag = et.SubElement(tag_tags, TAG_TAG)
100 tag_tag.text = tag.name
101 tag_tag.text = tag.name
101 else:
102 else:
102 tag_thread = et.SubElement(content_tag, TAG_THREAD)
103 tag_thread = et.SubElement(content_tag, TAG_THREAD)
103 thread_id = et.SubElement(tag_thread, TAG_ID)
104 thread_id = et.SubElement(tag_thread, TAG_ID)
104 thread.get_opening_post().global_id.to_xml_element(thread_id)
105 thread.get_opening_post().global_id.to_xml_element(thread_id)
105
106
106 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
107 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
107 pub_time.text = str(post.get_pub_time_str())
108 pub_time.text = str(post.get_pub_time_str())
108
109
110 if post.tripcode:
111 tripcode = et.SubElement(content_tag, TAG_TRIPCODE)
112 tripcode.text = post.tripcode
113
109 if len(images) > 0 or len(attachments) > 0:
114 if len(images) > 0 or len(attachments) > 0:
110 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
115 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
111 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
116 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
112
117
113 for image in images:
118 for image in images:
114 SyncManager._attachment_to_xml(
119 SyncManager._attachment_to_xml(
115 attachments_tag, attachment_refs, image.image.file,
120 attachments_tag, attachment_refs, image.image.file,
116 image.hash, image.image.url)
121 image.hash, image.image.url)
117 for file in attachments:
122 for file in attachments:
118 SyncManager._attachment_to_xml(
123 SyncManager._attachment_to_xml(
119 attachments_tag, attachment_refs, file.file.file,
124 attachments_tag, attachment_refs, file.file.file,
120 file.hash, file.file.url)
125 file.hash, file.file.url)
121
126
122 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
127 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
123 global_id.save()
128 global_id.save()
124
129
125 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
130 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
126 post_signatures = global_id.signature_set.all()
131 post_signatures = global_id.signature_set.all()
127 if post_signatures:
132 if post_signatures:
128 signatures = post_signatures
133 signatures = post_signatures
129 else:
134 else:
130 key = KeyPair.objects.get(public_key=global_id.key)
135 key = KeyPair.objects.get(public_key=global_id.key)
131 signature = Signature(
136 signature = Signature(
132 key_type=key.key_type,
137 key_type=key.key_type,
133 key=key.public_key,
138 key=key.public_key,
134 signature=key.sign(global_id.content),
139 signature=key.sign(global_id.content),
135 global_id=global_id,
140 global_id=global_id,
136 )
141 )
137 signature.save()
142 signature.save()
138 signatures = [signature]
143 signatures = [signature]
139 for signature in signatures:
144 for signature in signatures:
140 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
145 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
141 signature_tag.set(ATTR_TYPE, signature.key_type)
146 signature_tag.set(ATTR_TYPE, signature.key_type)
142 signature_tag.set(ATTR_VALUE, signature.signature)
147 signature_tag.set(ATTR_VALUE, signature.signature)
143 signature_tag.set(ATTR_KEY, signature.key)
148 signature_tag.set(ATTR_KEY, signature.key)
144
149
145 return et.tostring(response, ENCODING_UNICODE)
150 return et.tostring(response, ENCODING_UNICODE)
146
151
147 @staticmethod
152 @staticmethod
148 @transaction.atomic
153 @transaction.atomic
149 def parse_response_get(response_xml, hostname):
154 def parse_response_get(response_xml, hostname):
150 tag_root = et.fromstring(response_xml)
155 tag_root = et.fromstring(response_xml)
151 tag_status = tag_root.find(TAG_STATUS)
156 tag_status = tag_root.find(TAG_STATUS)
152 if STATUS_SUCCESS == tag_status.text:
157 if STATUS_SUCCESS == tag_status.text:
153 tag_models = tag_root.find(TAG_MODELS)
158 tag_models = tag_root.find(TAG_MODELS)
154 for tag_model in tag_models:
159 for tag_model in tag_models:
155 tag_content = tag_model.find(TAG_CONTENT)
160 tag_content = tag_model.find(TAG_CONTENT)
156
161
157 content_str = et.tostring(tag_content, ENCODING_UNICODE)
162 content_str = et.tostring(tag_content, ENCODING_UNICODE)
158 signatures = SyncManager._verify_model(content_str, tag_model)
163 signatures = SyncManager._verify_model(content_str, tag_model)
159
164
160 tag_id = tag_content.find(TAG_ID)
165 tag_id = tag_content.find(TAG_ID)
161 global_id, exists = GlobalId.from_xml_element(tag_id)
166 global_id, exists = GlobalId.from_xml_element(tag_id)
162
167
163 if exists:
168 if exists:
164 print('Post with same ID already exists')
169 print('Post with same ID already exists')
165 else:
170 else:
166 global_id.content = content_str
171 global_id.content = content_str
167 global_id.save()
172 global_id.save()
168 for signature in signatures:
173 for signature in signatures:
169 signature.global_id = global_id
174 signature.global_id = global_id
170 signature.save()
175 signature.save()
171
176
172 title = tag_content.find(TAG_TITLE).text or ''
177 title = tag_content.find(TAG_TITLE).text or ''
173 text = tag_content.find(TAG_TEXT).text or ''
178 text = tag_content.find(TAG_TEXT).text or ''
174 pub_time = tag_content.find(TAG_PUB_TIME).text
179 pub_time = tag_content.find(TAG_PUB_TIME).text
175
180
176 thread = tag_content.find(TAG_THREAD)
181 thread = tag_content.find(TAG_THREAD)
177 tags = []
182 tags = []
178 if thread:
183 if thread:
179 thread_id = thread.find(TAG_ID)
184 thread_id = thread.find(TAG_ID)
180 op_global_id, exists = GlobalId.from_xml_element(thread_id)
185 op_global_id, exists = GlobalId.from_xml_element(thread_id)
181 if exists:
186 if exists:
182 opening_post = Post.objects.get(global_id=op_global_id)
187 opening_post = Post.objects.get(global_id=op_global_id)
183 else:
188 else:
184 raise SyncException(EXCEPTION_OP)
189 raise SyncException(EXCEPTION_OP)
185 else:
190 else:
186 opening_post = None
191 opening_post = None
187 tag_tags = tag_content.find(TAG_TAGS)
192 tag_tags = tag_content.find(TAG_TAGS)
188 for tag_tag in tag_tags:
193 for tag_tag in tag_tags:
189 tag, created = Tag.objects.get_or_create(
194 tag, created = Tag.objects.get_or_create(
190 name=tag_tag.text)
195 name=tag_tag.text)
191 tags.append(tag)
196 tags.append(tag)
192
197
193 # TODO Check that the replied posts are already present
198 # TODO Check that the replied posts are already present
194 # before adding new ones
199 # before adding new ones
195
200
196 files = []
201 files = []
197 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
202 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
198 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
203 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
199 for attachment in tag_attachments:
204 for attachment in tag_attachments:
200 tag_ref = tag_refs.find("{}[@ref='{}']".format(
205 tag_ref = tag_refs.find("{}[@ref='{}']".format(
201 TAG_ATTACHMENT_REF, attachment.text))
206 TAG_ATTACHMENT_REF, attachment.text))
202 url = tag_ref.get(ATTR_URL)
207 url = tag_ref.get(ATTR_URL)
203 attached_file = download(hostname + url)
208 attached_file = download(hostname + url)
204 if attached_file is None:
209 if attached_file is None:
205 raise SyncException(EXCEPTION_DOWNLOAD)
210 raise SyncException(EXCEPTION_DOWNLOAD)
206
211
207 hash = get_file_hash(attached_file)
212 hash = get_file_hash(attached_file)
208 if hash != attachment.text:
213 if hash != attachment.text:
209 raise SyncException(EXCEPTION_HASH)
214 raise SyncException(EXCEPTION_HASH)
210
215
211 files.append(attached_file)
216 files.append(attached_file)
212
217
213 Post.objects.import_post(
218 Post.objects.import_post(
214 title=title, text=text, pub_time=pub_time,
219 title=title, text=text, pub_time=pub_time,
215 opening_post=opening_post, tags=tags,
220 opening_post=opening_post, tags=tags,
216 global_id=global_id, files=files)
221 global_id=global_id, files=files)
217 else:
222 else:
218 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
223 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
219
224
220 @staticmethod
225 @staticmethod
221 def generate_response_pull():
226 def generate_response_pull():
222 response = et.Element(TAG_RESPONSE)
227 response = et.Element(TAG_RESPONSE)
223
228
224 status = et.SubElement(response, TAG_STATUS)
229 status = et.SubElement(response, TAG_STATUS)
225 status.text = STATUS_SUCCESS
230 status.text = STATUS_SUCCESS
226
231
227 models = et.SubElement(response, TAG_MODELS)
232 models = et.SubElement(response, TAG_MODELS)
228
233
229 for post in Post.objects.all():
234 for post in Post.objects.all():
230 tag_id = et.SubElement(models, TAG_ID)
235 tag_id = et.SubElement(models, TAG_ID)
231 post.global_id.to_xml_element(tag_id)
236 post.global_id.to_xml_element(tag_id)
232
237
233 return et.tostring(response, ENCODING_UNICODE)
238 return et.tostring(response, ENCODING_UNICODE)
234
239
235 @staticmethod
240 @staticmethod
236 def _verify_model(content_str, tag_model):
241 def _verify_model(content_str, tag_model):
237 """
242 """
238 Verifies all signatures for a single model.
243 Verifies all signatures for a single model.
239 """
244 """
240
245
241 signatures = []
246 signatures = []
242
247
243 tag_signatures = tag_model.find(TAG_SIGNATURES)
248 tag_signatures = tag_model.find(TAG_SIGNATURES)
244 for tag_signature in tag_signatures:
249 for tag_signature in tag_signatures:
245 signature_type = tag_signature.get(ATTR_TYPE)
250 signature_type = tag_signature.get(ATTR_TYPE)
246 signature_value = tag_signature.get(ATTR_VALUE)
251 signature_value = tag_signature.get(ATTR_VALUE)
247 signature_key = tag_signature.get(ATTR_KEY)
252 signature_key = tag_signature.get(ATTR_KEY)
248
253
249 signature = Signature(key_type=signature_type,
254 signature = Signature(key_type=signature_type,
250 key=signature_key,
255 key=signature_key,
251 signature=signature_value)
256 signature=signature_value)
252
257
253 if not KeyPair.objects.verify(signature, content_str):
258 if not KeyPair.objects.verify(signature, content_str):
254 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
259 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
255
260
256 signatures.append(signature)
261 signatures.append(signature)
257
262
258 return signatures
263 return signatures
259
264
260 @staticmethod
265 @staticmethod
261 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
266 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
262 if tag_attachments is not None:
267 if tag_attachments is not None:
263 mimetype = get_file_mimetype(file)
268 mimetype = get_file_mimetype(file)
264 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
269 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
265 attachment.set(ATTR_MIMETYPE, mimetype)
270 attachment.set(ATTR_MIMETYPE, mimetype)
266 attachment.text = hash
271 attachment.text = hash
267
272
268 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
273 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
269 attachment_ref.set(ATTR_REF, hash)
274 attachment_ref.set(ATTR_REF, hash)
270 attachment_ref.set(ATTR_URL, url)
275 attachment_ref.set(ATTR_URL, url)
@@ -1,29 +1,30 b''
1 # 0 Title #
1 # 0 Title #
2
2
3 "post" model reference
3 "post" model reference
4
4
5 # 1 Description #
5 # 1 Description #
6
6
7 "post" is a model that defines an imageboard message, or post.
7 "post" is a model that defines an imageboard message, or post.
8
8
9 # 2 Fields #
9 # 2 Fields #
10
10
11 # 2.1 Mandatory fields #
11 # 2.1 Mandatory fields #
12
12
13 * title -- text field.
13 * title -- text field.
14 * text -- text field.
14 * text -- text field.
15 * pub-time -- timestamp (TBD: Define format).
15 * pub-time -- timestamp (TBD: Define format).
16
16
17 # 2.2 Optional fields #
17 # 2.2 Optional fields #
18
18
19 * attachments -- defines attachments such as images.
19 * attachments -- defines attachments such as images.
20 * attachment -- contains and attachment or link to attachment to be downloaded
20 * attachment -- contains and attachment or link to attachment to be downloaded
21 manually. Required attributes are mimetype and name.
21 manually. Required attributes are mimetype and name.
22
22
23 This field is used for the opening post (thread):
23 This field is used for the opening post (thread):
24
24
25 * tags -- text tag name list.
25 * tags -- text tag name list.
26
26
27 This field is used for non-opening post:
27 This field is used for non-opening post:
28
28
29 * thread -- ID of a post model that this post is related to.
29 * thread -- ID of a post model that this post is related to.
30 * tripcode
General Comments 0
You need to be logged in to leave comments. Login now