##// END OF EJS Templates
Moved exceptions to a separate module
neko259 -
r1602:beb32860 default
parent child Browse files
Show More
@@ -0,0 +1,10
1 class BannedException(Exception):
2 pass
3
4
5 class ArchiveException(Exception):
6 pass
7
8
9 class SyncException(Exception):
10 pass
@@ -1,181 +1,181
1 import logging
1 import logging
2
2
3 from datetime import datetime, timedelta, date
3 from datetime import datetime, timedelta, date
4 from datetime import time as dtime
4 from datetime import time as dtime
5
5
6 from boards.abstracts.exceptions import BannedException, ArchiveException
6 from django.db import models, transaction
7 from django.db import models, transaction
7 from django.utils import timezone
8 from django.utils import timezone
8 from django.dispatch import Signal
9 from django.dispatch import Signal
9
10
10 import boards
11 import boards
11
12
12 from boards.models.user import Ban
13 from boards.models.user import Ban
13 from boards.mdx_neboard import Parser
14 from boards.mdx_neboard import Parser
14 from boards.models import Attachment
15 from boards.models import Attachment
15 from boards import utils
16 from boards import utils
16
17
17 __author__ = 'neko259'
18 __author__ = 'neko259'
18
19
19 POSTS_PER_DAY_RANGE = 7
20 POSTS_PER_DAY_RANGE = 7
20 NO_IP = '0.0.0.0'
21 NO_IP = '0.0.0.0'
21
22
22
23
23 post_import_deps = Signal()
24 post_import_deps = Signal()
24
25
25
26
26 class PostManager(models.Manager):
27 class PostManager(models.Manager):
27 @transaction.atomic
28 @transaction.atomic
28 def create_post(self, title: str, text: str, file=None, thread=None,
29 def create_post(self, title: str, text: str, file=None, thread=None,
29 ip=NO_IP, tags: list=None, opening_posts: list=None,
30 ip=NO_IP, tags: list=None, opening_posts: list=None,
30 tripcode='', monochrome=False, images=[]):
31 tripcode='', monochrome=False, images=[]):
31 """
32 """
32 Creates new post
33 Creates new post
33 """
34 """
34
35
35 if thread is not None and thread.is_archived():
36 if thread is not None and thread.is_archived():
36 raise Exception('Cannot post into an archived thread')
37 raise ArchiveException('Cannot post into an archived thread')
37
38
38 if not utils.is_anonymous_mode():
39 if not utils.is_anonymous_mode():
39 is_banned = Ban.objects.filter(ip=ip).exists()
40 is_banned = Ban.objects.filter(ip=ip).exists()
40 else:
41 else:
41 is_banned = False
42 is_banned = False
42
43
43 # TODO Raise specific exception and catch it in the views
44 if is_banned:
44 if is_banned:
45 raise Exception("This user is banned")
45 raise BannedException("This user is banned")
46
46
47 if not tags:
47 if not tags:
48 tags = []
48 tags = []
49 if not opening_posts:
49 if not opening_posts:
50 opening_posts = []
50 opening_posts = []
51
51
52 posting_time = timezone.now()
52 posting_time = timezone.now()
53 new_thread = False
53 new_thread = False
54 if not thread:
54 if not thread:
55 thread = boards.models.thread.Thread.objects.create(
55 thread = boards.models.thread.Thread.objects.create(
56 bump_time=posting_time, last_edit_time=posting_time,
56 bump_time=posting_time, last_edit_time=posting_time,
57 monochrome=monochrome)
57 monochrome=monochrome)
58 list(map(thread.tags.add, tags))
58 list(map(thread.tags.add, tags))
59 boards.models.thread.Thread.objects.process_oldest_threads()
59 boards.models.thread.Thread.objects.process_oldest_threads()
60 new_thread = True
60 new_thread = True
61
61
62 pre_text = Parser().preparse(text)
62 pre_text = Parser().preparse(text)
63
63
64 post = self.create(title=title,
64 post = self.create(title=title,
65 text=pre_text,
65 text=pre_text,
66 pub_time=posting_time,
66 pub_time=posting_time,
67 poster_ip=ip,
67 poster_ip=ip,
68 thread=thread,
68 thread=thread,
69 last_edit_time=posting_time,
69 last_edit_time=posting_time,
70 tripcode=tripcode,
70 tripcode=tripcode,
71 opening=new_thread)
71 opening=new_thread)
72 post.threads.add(thread)
72 post.threads.add(thread)
73
73
74 logger = logging.getLogger('boards.post.create')
74 logger = logging.getLogger('boards.post.create')
75
75
76 logger.info('Created post [{}] with text [{}] by {}'.format(post,
76 logger.info('Created post [{}] with text [{}] by {}'.format(post,
77 post.get_text(),post.poster_ip))
77 post.get_text(),post.poster_ip))
78
78
79 if file:
79 if file:
80 self._add_file_to_post(file, post)
80 self._add_file_to_post(file, post)
81 for image in images:
81 for image in images:
82 post.images.add(image)
82 post.images.add(image)
83
83
84 post.connect_threads(opening_posts)
84 post.connect_threads(opening_posts)
85 post.set_global_id()
85 post.set_global_id()
86
86
87 # Thread needs to be bumped only when the post is already created
87 # Thread needs to be bumped only when the post is already created
88 if not new_thread:
88 if not new_thread:
89 thread.last_edit_time = posting_time
89 thread.last_edit_time = posting_time
90 thread.bump()
90 thread.bump()
91 thread.save()
91 thread.save()
92
92
93 return post
93 return post
94
94
95 def delete_posts_by_ip(self, ip):
95 def delete_posts_by_ip(self, ip):
96 """
96 """
97 Deletes all posts of the author with same IP
97 Deletes all posts of the author with same IP
98 """
98 """
99
99
100 posts = self.filter(poster_ip=ip)
100 posts = self.filter(poster_ip=ip)
101 for post in posts:
101 for post in posts:
102 post.delete()
102 post.delete()
103
103
104 @utils.cached_result()
104 @utils.cached_result()
105 def get_posts_per_day(self) -> float:
105 def get_posts_per_day(self) -> float:
106 """
106 """
107 Gets average count of posts per day for the last 7 days
107 Gets average count of posts per day for the last 7 days
108 """
108 """
109
109
110 day_end = date.today()
110 day_end = date.today()
111 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
111 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
112
112
113 day_time_start = timezone.make_aware(datetime.combine(
113 day_time_start = timezone.make_aware(datetime.combine(
114 day_start, dtime()), timezone.get_current_timezone())
114 day_start, dtime()), timezone.get_current_timezone())
115 day_time_end = timezone.make_aware(datetime.combine(
115 day_time_end = timezone.make_aware(datetime.combine(
116 day_end, dtime()), timezone.get_current_timezone())
116 day_end, dtime()), timezone.get_current_timezone())
117
117
118 posts_per_period = float(self.filter(
118 posts_per_period = float(self.filter(
119 pub_time__lte=day_time_end,
119 pub_time__lte=day_time_end,
120 pub_time__gte=day_time_start).count())
120 pub_time__gte=day_time_start).count())
121
121
122 ppd = posts_per_period / POSTS_PER_DAY_RANGE
122 ppd = posts_per_period / POSTS_PER_DAY_RANGE
123
123
124 return ppd
124 return ppd
125
125
126 @transaction.atomic
126 @transaction.atomic
127 def import_post(self, title: str, text: str, pub_time: str, global_id,
127 def import_post(self, title: str, text: str, pub_time: str, global_id,
128 opening_post=None, tags=list(), files=list(),
128 opening_post=None, tags=list(), files=list(),
129 tripcode=None, version=1):
129 tripcode=None, version=1):
130 is_opening = opening_post is None
130 is_opening = opening_post is None
131 if is_opening:
131 if is_opening:
132 thread = boards.models.thread.Thread.objects.create(
132 thread = boards.models.thread.Thread.objects.create(
133 bump_time=pub_time, last_edit_time=pub_time)
133 bump_time=pub_time, last_edit_time=pub_time)
134 list(map(thread.tags.add, tags))
134 list(map(thread.tags.add, tags))
135 else:
135 else:
136 thread = opening_post.get_thread()
136 thread = opening_post.get_thread()
137
137
138 post = self.create(title=title,
138 post = self.create(title=title,
139 text=text,
139 text=text,
140 pub_time=pub_time,
140 pub_time=pub_time,
141 poster_ip=NO_IP,
141 poster_ip=NO_IP,
142 last_edit_time=pub_time,
142 last_edit_time=pub_time,
143 global_id=global_id,
143 global_id=global_id,
144 opening=is_opening,
144 opening=is_opening,
145 thread=thread,
145 thread=thread,
146 tripcode=tripcode,
146 tripcode=tripcode,
147 version=version)
147 version=version)
148
148
149 for file in files:
149 for file in files:
150 self._add_file_to_post(file, post)
150 self._add_file_to_post(file, post)
151
151
152 post.threads.add(thread)
152 post.threads.add(thread)
153
153
154 url_to_post = '[post]{}[/post]'.format(str(global_id))
154 url_to_post = '[post]{}[/post]'.format(str(global_id))
155 replies = self.filter(text__contains=url_to_post)
155 replies = self.filter(text__contains=url_to_post)
156 for reply in replies:
156 for reply in replies:
157 post_import_deps.send(reply.__class__)
157 post_import_deps.send(reply.__class__)
158
158
159 @transaction.atomic
159 @transaction.atomic
160 def update_post(self, post, title: str, text: str, pub_time: str,
160 def update_post(self, post, title: str, text: str, pub_time: str,
161 tags=list(), files=list(), tripcode=None, version=1):
161 tags=list(), files=list(), tripcode=None, version=1):
162 post.title = title
162 post.title = title
163 post.text = text
163 post.text = text
164 post.pub_time = pub_time
164 post.pub_time = pub_time
165 post.tripcode = tripcode
165 post.tripcode = tripcode
166 post.version = version
166 post.version = version
167 post.save()
167 post.save()
168
168
169 post.clear_cache()
169 post.clear_cache()
170
170
171 post.images.clear()
171 post.images.clear()
172 post.attachments.clear()
172 post.attachments.clear()
173 for file in files:
173 for file in files:
174 self._add_file_to_post(file, post)
174 self._add_file_to_post(file, post)
175
175
176 thread = post.get_thread()
176 thread = post.get_thread()
177 thread.tags.clear()
177 thread.tags.clear()
178 list(map(thread.tags.add, tags))
178 list(map(thread.tags.add, tags))
179
179
180 def _add_file_to_post(self, file, post):
180 def _add_file_to_post(self, file, post):
181 post.attachments.add(Attachment.objects.create_with_hash(file))
181 post.attachments.add(Attachment.objects.create_with_hash(file))
@@ -1,301 +1,298
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2
2
3 from boards.abstracts.exceptions import SyncException
4 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
3 from boards.models.attachment.downloaders import download
5 from boards.models.attachment.downloaders import download
4 from boards.utils import get_file_mimetype, get_file_hash
6 from boards.utils import get_file_mimetype, get_file_hash
5 from django.db import transaction
7 from django.db import transaction
6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
7
8
8 EXCEPTION_NODE = 'Sync node returned an error: {}.'
9 EXCEPTION_NODE = 'Sync node returned an error: {}.'
9 EXCEPTION_OP = 'Load the OP first.'
10 EXCEPTION_OP = 'Load the OP first.'
10 EXCEPTION_DOWNLOAD = 'File was not downloaded.'
11 EXCEPTION_DOWNLOAD = 'File was not downloaded.'
11 EXCEPTION_HASH = 'File hash does not match attachment hash.'
12 EXCEPTION_HASH = 'File hash does not match attachment hash.'
12 EXCEPTION_SIGNATURE = 'Invalid model signature for {}.'
13 EXCEPTION_SIGNATURE = 'Invalid model signature for {}.'
13 EXCEPTION_AUTHOR_SIGNATURE = 'Model {} has no author signature.'
14 EXCEPTION_AUTHOR_SIGNATURE = 'Model {} has no author signature.'
14 ENCODING_UNICODE = 'unicode'
15 ENCODING_UNICODE = 'unicode'
15
16
16 TAG_MODEL = 'model'
17 TAG_MODEL = 'model'
17 TAG_REQUEST = 'request'
18 TAG_REQUEST = 'request'
18 TAG_RESPONSE = 'response'
19 TAG_RESPONSE = 'response'
19 TAG_ID = 'id'
20 TAG_ID = 'id'
20 TAG_STATUS = 'status'
21 TAG_STATUS = 'status'
21 TAG_MODELS = 'models'
22 TAG_MODELS = 'models'
22 TAG_TITLE = 'title'
23 TAG_TITLE = 'title'
23 TAG_TEXT = 'text'
24 TAG_TEXT = 'text'
24 TAG_THREAD = 'thread'
25 TAG_THREAD = 'thread'
25 TAG_PUB_TIME = 'pub-time'
26 TAG_PUB_TIME = 'pub-time'
26 TAG_SIGNATURES = 'signatures'
27 TAG_SIGNATURES = 'signatures'
27 TAG_SIGNATURE = 'signature'
28 TAG_SIGNATURE = 'signature'
28 TAG_CONTENT = 'content'
29 TAG_CONTENT = 'content'
29 TAG_ATTACHMENTS = 'attachments'
30 TAG_ATTACHMENTS = 'attachments'
30 TAG_ATTACHMENT = 'attachment'
31 TAG_ATTACHMENT = 'attachment'
31 TAG_TAGS = 'tags'
32 TAG_TAGS = 'tags'
32 TAG_TAG = 'tag'
33 TAG_TAG = 'tag'
33 TAG_ATTACHMENT_REFS = 'attachment-refs'
34 TAG_ATTACHMENT_REFS = 'attachment-refs'
34 TAG_ATTACHMENT_REF = 'attachment-ref'
35 TAG_ATTACHMENT_REF = 'attachment-ref'
35 TAG_TRIPCODE = 'tripcode'
36 TAG_TRIPCODE = 'tripcode'
36 TAG_VERSION = 'version'
37 TAG_VERSION = 'version'
37
38
38 TYPE_GET = 'get'
39 TYPE_GET = 'get'
39
40
40 ATTR_VERSION = 'version'
41 ATTR_VERSION = 'version'
41 ATTR_TYPE = 'type'
42 ATTR_TYPE = 'type'
42 ATTR_NAME = 'name'
43 ATTR_NAME = 'name'
43 ATTR_VALUE = 'value'
44 ATTR_VALUE = 'value'
44 ATTR_MIMETYPE = 'mimetype'
45 ATTR_MIMETYPE = 'mimetype'
45 ATTR_KEY = 'key'
46 ATTR_KEY = 'key'
46 ATTR_REF = 'ref'
47 ATTR_REF = 'ref'
47 ATTR_URL = 'url'
48 ATTR_URL = 'url'
48 ATTR_ID_TYPE = 'id-type'
49 ATTR_ID_TYPE = 'id-type'
49
50
50 ID_TYPE_MD5 = 'md5'
51 ID_TYPE_MD5 = 'md5'
51
52
52 STATUS_SUCCESS = 'success'
53 STATUS_SUCCESS = 'success'
53
54
54
55
55 class SyncException(Exception):
56 pass
57
58
59 class SyncManager:
56 class SyncManager:
60 @staticmethod
57 @staticmethod
61 def generate_response_get(model_list: list):
58 def generate_response_get(model_list: list):
62 response = et.Element(TAG_RESPONSE)
59 response = et.Element(TAG_RESPONSE)
63
60
64 status = et.SubElement(response, TAG_STATUS)
61 status = et.SubElement(response, TAG_STATUS)
65 status.text = STATUS_SUCCESS
62 status.text = STATUS_SUCCESS
66
63
67 models = et.SubElement(response, TAG_MODELS)
64 models = et.SubElement(response, TAG_MODELS)
68
65
69 for post in model_list:
66 for post in model_list:
70 model = et.SubElement(models, TAG_MODEL)
67 model = et.SubElement(models, TAG_MODEL)
71 model.set(ATTR_NAME, 'post')
68 model.set(ATTR_NAME, 'post')
72
69
73 global_id = post.global_id
70 global_id = post.global_id
74
71
75 attachments = post.attachments.all()
72 attachments = post.attachments.all()
76 if global_id.content:
73 if global_id.content:
77 model.append(et.fromstring(global_id.content))
74 model.append(et.fromstring(global_id.content))
78 if len(attachments) > 0:
75 if len(attachments) > 0:
79 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
76 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
80 for file in attachments:
77 for file in attachments:
81 SyncManager._attachment_to_xml(
78 SyncManager._attachment_to_xml(
82 None, attachment_refs, file.file.file,
79 None, attachment_refs, file.file.file,
83 file.hash, file.file.url)
80 file.hash, file.file.url)
84 else:
81 else:
85 content_tag = et.SubElement(model, TAG_CONTENT)
82 content_tag = et.SubElement(model, TAG_CONTENT)
86
83
87 tag_id = et.SubElement(content_tag, TAG_ID)
84 tag_id = et.SubElement(content_tag, TAG_ID)
88 global_id.to_xml_element(tag_id)
85 global_id.to_xml_element(tag_id)
89
86
90 title = et.SubElement(content_tag, TAG_TITLE)
87 title = et.SubElement(content_tag, TAG_TITLE)
91 title.text = post.title
88 title.text = post.title
92
89
93 text = et.SubElement(content_tag, TAG_TEXT)
90 text = et.SubElement(content_tag, TAG_TEXT)
94 text.text = post.get_sync_text()
91 text.text = post.get_sync_text()
95
92
96 thread = post.get_thread()
93 thread = post.get_thread()
97 if post.is_opening():
94 if post.is_opening():
98 tag_tags = et.SubElement(content_tag, TAG_TAGS)
95 tag_tags = et.SubElement(content_tag, TAG_TAGS)
99 for tag in thread.get_tags():
96 for tag in thread.get_tags():
100 tag_tag = et.SubElement(tag_tags, TAG_TAG)
97 tag_tag = et.SubElement(tag_tags, TAG_TAG)
101 tag_tag.text = tag.name
98 tag_tag.text = tag.name
102 else:
99 else:
103 tag_thread = et.SubElement(content_tag, TAG_THREAD)
100 tag_thread = et.SubElement(content_tag, TAG_THREAD)
104 thread_id = et.SubElement(tag_thread, TAG_ID)
101 thread_id = et.SubElement(tag_thread, TAG_ID)
105 thread.get_opening_post().global_id.to_xml_element(thread_id)
102 thread.get_opening_post().global_id.to_xml_element(thread_id)
106
103
107 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
104 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
108 pub_time.text = str(post.get_pub_time_str())
105 pub_time.text = str(post.get_pub_time_str())
109
106
110 if post.tripcode:
107 if post.tripcode:
111 tripcode = et.SubElement(content_tag, TAG_TRIPCODE)
108 tripcode = et.SubElement(content_tag, TAG_TRIPCODE)
112 tripcode.text = post.tripcode
109 tripcode.text = post.tripcode
113
110
114 if len(attachments) > 0:
111 if len(attachments) > 0:
115 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
112 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
116 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
113 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
117
114
118 for file in attachments:
115 for file in attachments:
119 SyncManager._attachment_to_xml(
116 SyncManager._attachment_to_xml(
120 attachments_tag, attachment_refs, file.file.file,
117 attachments_tag, attachment_refs, file.file.file,
121 file.hash, file.file.url)
118 file.hash, file.file.url)
122 version_tag = et.SubElement(content_tag, TAG_VERSION)
119 version_tag = et.SubElement(content_tag, TAG_VERSION)
123 version_tag.text = str(post.version)
120 version_tag.text = str(post.version)
124
121
125 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
122 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
126 global_id.save()
123 global_id.save()
127
124
128 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
125 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
129 post_signatures = global_id.signature_set.all()
126 post_signatures = global_id.signature_set.all()
130 if post_signatures:
127 if post_signatures:
131 signatures = post_signatures
128 signatures = post_signatures
132 else:
129 else:
133 key = KeyPair.objects.get(public_key=global_id.key)
130 key = KeyPair.objects.get(public_key=global_id.key)
134 signature = Signature(
131 signature = Signature(
135 key_type=key.key_type,
132 key_type=key.key_type,
136 key=key.public_key,
133 key=key.public_key,
137 signature=key.sign(global_id.content),
134 signature=key.sign(global_id.content),
138 global_id=global_id,
135 global_id=global_id,
139 )
136 )
140 signature.save()
137 signature.save()
141 signatures = [signature]
138 signatures = [signature]
142 for signature in signatures:
139 for signature in signatures:
143 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
140 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
144 signature_tag.set(ATTR_TYPE, signature.key_type)
141 signature_tag.set(ATTR_TYPE, signature.key_type)
145 signature_tag.set(ATTR_VALUE, signature.signature)
142 signature_tag.set(ATTR_VALUE, signature.signature)
146 signature_tag.set(ATTR_KEY, signature.key)
143 signature_tag.set(ATTR_KEY, signature.key)
147
144
148 return et.tostring(response, ENCODING_UNICODE)
145 return et.tostring(response, ENCODING_UNICODE)
149
146
150 @staticmethod
147 @staticmethod
151 @transaction.atomic
148 @transaction.atomic
152 def parse_response_get(response_xml, hostname):
149 def parse_response_get(response_xml, hostname):
153 tag_root = et.fromstring(response_xml)
150 tag_root = et.fromstring(response_xml)
154 tag_status = tag_root.find(TAG_STATUS)
151 tag_status = tag_root.find(TAG_STATUS)
155 if STATUS_SUCCESS == tag_status.text:
152 if STATUS_SUCCESS == tag_status.text:
156 tag_models = tag_root.find(TAG_MODELS)
153 tag_models = tag_root.find(TAG_MODELS)
157 for tag_model in tag_models:
154 for tag_model in tag_models:
158 tag_content = tag_model.find(TAG_CONTENT)
155 tag_content = tag_model.find(TAG_CONTENT)
159
156
160 content_str = et.tostring(tag_content, ENCODING_UNICODE)
157 content_str = et.tostring(tag_content, ENCODING_UNICODE)
161
158
162 tag_id = tag_content.find(TAG_ID)
159 tag_id = tag_content.find(TAG_ID)
163 global_id, exists = GlobalId.from_xml_element(tag_id)
160 global_id, exists = GlobalId.from_xml_element(tag_id)
164 signatures = SyncManager._verify_model(global_id, content_str, tag_model)
161 signatures = SyncManager._verify_model(global_id, content_str, tag_model)
165
162
166 version = int(tag_content.find(TAG_VERSION).text)
163 version = int(tag_content.find(TAG_VERSION).text)
167 is_old = exists and global_id.post.version < version
164 is_old = exists and global_id.post.version < version
168 if exists and not is_old:
165 if exists and not is_old:
169 print('Post with same ID exists and is up to date.')
166 print('Post with same ID exists and is up to date.')
170 else:
167 else:
171 global_id.content = content_str
168 global_id.content = content_str
172 global_id.save()
169 global_id.save()
173 for signature in signatures:
170 for signature in signatures:
174 signature.global_id = global_id
171 signature.global_id = global_id
175 signature.save()
172 signature.save()
176
173
177 title = tag_content.find(TAG_TITLE).text or ''
174 title = tag_content.find(TAG_TITLE).text or ''
178 text = tag_content.find(TAG_TEXT).text or ''
175 text = tag_content.find(TAG_TEXT).text or ''
179 pub_time = tag_content.find(TAG_PUB_TIME).text
176 pub_time = tag_content.find(TAG_PUB_TIME).text
180 tripcode_tag = tag_content.find(TAG_TRIPCODE)
177 tripcode_tag = tag_content.find(TAG_TRIPCODE)
181 if tripcode_tag is not None:
178 if tripcode_tag is not None:
182 tripcode = tripcode_tag.text or ''
179 tripcode = tripcode_tag.text or ''
183 else:
180 else:
184 tripcode = ''
181 tripcode = ''
185
182
186 thread = tag_content.find(TAG_THREAD)
183 thread = tag_content.find(TAG_THREAD)
187 tags = []
184 tags = []
188 if thread:
185 if thread:
189 thread_id = thread.find(TAG_ID)
186 thread_id = thread.find(TAG_ID)
190 op_global_id, exists = GlobalId.from_xml_element(thread_id)
187 op_global_id, exists = GlobalId.from_xml_element(thread_id)
191 if exists:
188 if exists:
192 opening_post = Post.objects.get(global_id=op_global_id)
189 opening_post = Post.objects.get(global_id=op_global_id)
193 else:
190 else:
194 raise SyncException(EXCEPTION_OP)
191 raise SyncException(EXCEPTION_OP)
195 else:
192 else:
196 opening_post = None
193 opening_post = None
197 tag_tags = tag_content.find(TAG_TAGS)
194 tag_tags = tag_content.find(TAG_TAGS)
198 for tag_tag in tag_tags:
195 for tag_tag in tag_tags:
199 tag, created = Tag.objects.get_or_create(
196 tag, created = Tag.objects.get_or_create(
200 name=tag_tag.text)
197 name=tag_tag.text)
201 tags.append(tag)
198 tags.append(tag)
202
199
203 # TODO Check that the replied posts are already present
200 # TODO Check that the replied posts are already present
204 # before adding new ones
201 # before adding new ones
205
202
206 files = []
203 files = []
207 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
204 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
208 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
205 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
209 for attachment in tag_attachments:
206 for attachment in tag_attachments:
210 tag_ref = tag_refs.find("{}[@ref='{}']".format(
207 tag_ref = tag_refs.find("{}[@ref='{}']".format(
211 TAG_ATTACHMENT_REF, attachment.text))
208 TAG_ATTACHMENT_REF, attachment.text))
212 url = tag_ref.get(ATTR_URL)
209 url = tag_ref.get(ATTR_URL)
213 attached_file = download(hostname + url)
210 attached_file = download(hostname + url)
214 if attached_file is None:
211 if attached_file is None:
215 raise SyncException(EXCEPTION_DOWNLOAD)
212 raise SyncException(EXCEPTION_DOWNLOAD)
216
213
217 hash = get_file_hash(attached_file)
214 hash = get_file_hash(attached_file)
218 if hash != attachment.text:
215 if hash != attachment.text:
219 raise SyncException(EXCEPTION_HASH)
216 raise SyncException(EXCEPTION_HASH)
220
217
221 files.append(attached_file)
218 files.append(attached_file)
222
219
223 if is_old:
220 if is_old:
224 post = global_id.post
221 post = global_id.post
225 Post.objects.update_post(
222 Post.objects.update_post(
226 post, title=title, text=text, pub_time=pub_time,
223 post, title=title, text=text, pub_time=pub_time,
227 tags=tags, files=files, tripcode=tripcode,
224 tags=tags, files=files, tripcode=tripcode,
228 version=version)
225 version=version)
229 print('Parsed updated post {}'.format(global_id))
226 print('Parsed updated post {}'.format(global_id))
230 else:
227 else:
231 Post.objects.import_post(
228 Post.objects.import_post(
232 title=title, text=text, pub_time=pub_time,
229 title=title, text=text, pub_time=pub_time,
233 opening_post=opening_post, tags=tags,
230 opening_post=opening_post, tags=tags,
234 global_id=global_id, files=files, tripcode=tripcode,
231 global_id=global_id, files=files, tripcode=tripcode,
235 version=version)
232 version=version)
236 print('Parsed new post {}'.format(global_id))
233 print('Parsed new post {}'.format(global_id))
237 else:
234 else:
238 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
235 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
239
236
240 @staticmethod
237 @staticmethod
241 def generate_response_list():
238 def generate_response_list():
242 response = et.Element(TAG_RESPONSE)
239 response = et.Element(TAG_RESPONSE)
243
240
244 status = et.SubElement(response, TAG_STATUS)
241 status = et.SubElement(response, TAG_STATUS)
245 status.text = STATUS_SUCCESS
242 status.text = STATUS_SUCCESS
246
243
247 models = et.SubElement(response, TAG_MODELS)
244 models = et.SubElement(response, TAG_MODELS)
248
245
249 for post in Post.objects.prefetch_related('global_id').all():
246 for post in Post.objects.prefetch_related('global_id').all():
250 tag_model = et.SubElement(models, TAG_MODEL)
247 tag_model = et.SubElement(models, TAG_MODEL)
251 tag_id = et.SubElement(tag_model, TAG_ID)
248 tag_id = et.SubElement(tag_model, TAG_ID)
252 post.global_id.to_xml_element(tag_id)
249 post.global_id.to_xml_element(tag_id)
253 tag_version = et.SubElement(tag_model, TAG_VERSION)
250 tag_version = et.SubElement(tag_model, TAG_VERSION)
254 tag_version.text = str(post.version)
251 tag_version.text = str(post.version)
255
252
256 return et.tostring(response, ENCODING_UNICODE)
253 return et.tostring(response, ENCODING_UNICODE)
257
254
258 @staticmethod
255 @staticmethod
259 def _verify_model(global_id, content_str, tag_model):
256 def _verify_model(global_id, content_str, tag_model):
260 """
257 """
261 Verifies all signatures for a single model.
258 Verifies all signatures for a single model.
262 """
259 """
263
260
264 signatures = []
261 signatures = []
265
262
266 tag_signatures = tag_model.find(TAG_SIGNATURES)
263 tag_signatures = tag_model.find(TAG_SIGNATURES)
267 has_author_signature = False
264 has_author_signature = False
268 for tag_signature in tag_signatures:
265 for tag_signature in tag_signatures:
269 signature_type = tag_signature.get(ATTR_TYPE)
266 signature_type = tag_signature.get(ATTR_TYPE)
270 signature_value = tag_signature.get(ATTR_VALUE)
267 signature_value = tag_signature.get(ATTR_VALUE)
271 signature_key = tag_signature.get(ATTR_KEY)
268 signature_key = tag_signature.get(ATTR_KEY)
272
269
273 if global_id.key_type == signature_type and\
270 if global_id.key_type == signature_type and\
274 global_id.key == signature_key:
271 global_id.key == signature_key:
275 has_author_signature = True
272 has_author_signature = True
276
273
277 signature = Signature(key_type=signature_type,
274 signature = Signature(key_type=signature_type,
278 key=signature_key,
275 key=signature_key,
279 signature=signature_value)
276 signature=signature_value)
280
277
281 if not KeyPair.objects.verify(signature, content_str):
278 if not KeyPair.objects.verify(signature, content_str):
282 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
279 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
283
280
284 signatures.append(signature)
281 signatures.append(signature)
285 if not has_author_signature:
282 if not has_author_signature:
286 raise SyncException(EXCEPTION_AUTHOR_SIGNATURE.format(content_str))
283 raise SyncException(EXCEPTION_AUTHOR_SIGNATURE.format(content_str))
287
284
288 return signatures
285 return signatures
289
286
290 @staticmethod
287 @staticmethod
291 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
288 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
292 if tag_attachments is not None:
289 if tag_attachments is not None:
293 mimetype = get_file_mimetype(file)
290 mimetype = get_file_mimetype(file)
294 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
291 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
295 attachment.set(ATTR_MIMETYPE, mimetype)
292 attachment.set(ATTR_MIMETYPE, mimetype)
296 attachment.set(ATTR_ID_TYPE, ID_TYPE_MD5)
293 attachment.set(ATTR_ID_TYPE, ID_TYPE_MD5)
297 attachment.text = hash
294 attachment.text = hash
298
295
299 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
296 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
300 attachment_ref.set(ATTR_REF, hash)
297 attachment_ref.set(ATTR_REF, hash)
301 attachment_ref.set(ATTR_URL, url)
298 attachment_ref.set(ATTR_URL, url)
General Comments 0
You need to be logged in to leave comments. Login now