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