##// END OF EJS Templates
Fixed file comparison in different-size chunks
neko259 -
r1858:0077bddc default
parent child Browse files
Show More
@@ -1,148 +1,154 b''
1 from itertools import zip_longest
1 from itertools import zip_longest
2
2
3 import boards
3 import boards
4 from boards.models import STATUS_ARCHIVE
4 from boards.models import STATUS_ARCHIVE
5 from django.core.files.images import get_image_dimensions
5 from django.core.files.images import get_image_dimensions
6 from django.db import models
6 from django.db import models
7
7
8 from boards import utils
8 from boards import utils
9 from boards.models.attachment.viewers import get_viewers, AbstractViewer, \
9 from boards.models.attachment.viewers import get_viewers, AbstractViewer, \
10 FILE_TYPES_IMAGE
10 FILE_TYPES_IMAGE
11 from boards.utils import get_upload_filename, get_extension, cached_result
11 from boards.utils import get_upload_filename, get_extension, cached_result
12
12
13
13
14 class AttachmentManager(models.Manager):
14 class AttachmentManager(models.Manager):
15 def create_with_hash(self, file):
15 def create_with_hash(self, file):
16 file_hash = utils.get_file_hash(file)
16 file_hash = utils.get_file_hash(file)
17 attachment = self.get_existing_duplicate(file_hash, file)
17 attachment = self.get_existing_duplicate(file_hash, file)
18 if not attachment:
18 if not attachment:
19 # FIXME Use full mimetype here, need to modify viewers too
19 # FIXME Use full mimetype here, need to modify viewers too
20 file_type = get_extension(file.name)
20 file_type = get_extension(file.name)
21 attachment = self.create(file=file, mimetype=file_type,
21 attachment = self.create(file=file, mimetype=file_type,
22 hash=file_hash)
22 hash=file_hash)
23
23
24 return attachment
24 return attachment
25
25
26 def create_from_url(self, url):
26 def create_from_url(self, url):
27 existing = self.filter(url=url)
27 existing = self.filter(url=url)
28 if len(existing) > 0:
28 if len(existing) > 0:
29 attachment = existing[0]
29 attachment = existing[0]
30 else:
30 else:
31 attachment = self.create(url=url)
31 attachment = self.create(url=url)
32 return attachment
32 return attachment
33
33
34 def get_random_images(self, count, tags=None):
34 def get_random_images(self, count, tags=None):
35 images = self.filter(mimetype__in=FILE_TYPES_IMAGE).exclude(
35 images = self.filter(mimetype__in=FILE_TYPES_IMAGE).exclude(
36 attachment_posts__thread__status=STATUS_ARCHIVE)
36 attachment_posts__thread__status=STATUS_ARCHIVE)
37 if tags is not None:
37 if tags is not None:
38 images = images.filter(attachment_posts__threads__tags__in=tags)
38 images = images.filter(attachment_posts__threads__tags__in=tags)
39 return images.order_by('?')[:count]
39 return images.order_by('?')[:count]
40
40
41 def get_existing_duplicate(self, file_hash, file):
41 def get_existing_duplicate(self, file_hash, file):
42 """
42 """
43 Gets an attachment with the same file if one exists.
43 Gets an attachment with the same file if one exists.
44 """
44 """
45 existing = self.filter(hash=file_hash)
45 existing = self.filter(hash=file_hash)
46 attachment = None
46 attachment = None
47 for existing_attachment in existing:
47 for existing_attachment in existing:
48 existing_file = existing_attachment.file
48 existing_file = existing_attachment.file
49
49
50 file_chunks = file.chunks()
50 file_chunks = file.chunks()
51 existing_file_chunks = existing_file.chunks()
51 existing_file_chunks = existing_file.chunks()
52
52
53 if self._compare_chunks(file_chunks, existing_file_chunks):
53 if self._compare_chunks(file_chunks, existing_file_chunks):
54 attachment = existing_attachment
54 attachment = existing_attachment
55 return attachment
55 return attachment
56
56
57 def _compare_chunks(self, chunks1, chunks2):
57 def _compare_chunks(self, chunks1, chunks2):
58 """
58 """
59 Compares 2 chunks of different sizes (e.g. first chunk array contains
59 Compares 2 chunks of different sizes (e.g. first chunk array contains
60 all data in 1 chunk, and other one -- in a multiple of smaller ones.
60 all data in 1 chunk, and other one -- in a multiple of smaller ones.
61 """
61 """
62 equal = True
62 equal = True
63
63
64 position1 = 0
64 position1 = 0
65 position2 = 0
65 position2 = 0
66 chunk1 = None
66 chunk1 = None
67 chunk2 = None
67 chunk2 = None
68 chunk1ended = False
68 chunk1ended = False
69 chunk2ended = False
69 chunk2ended = False
70 while True:
70 while True:
71 if not chunk1 or len(chunk1) <= position1:
71 if not chunk1 or len(chunk1) <= position1:
72 try:
72 try:
73 chunk1 = chunks1.__next__()
73 chunk1 = chunks1.__next__()
74 position1 = 0
74 position1 = 0
75 except StopIteration:
75 except StopIteration:
76 chunk1ended = True
76 chunk1ended = True
77 if not chunk2 or len(chunk2) <= position2:
77 if not chunk2 or len(chunk2) <= position2:
78 try:
78 try:
79 chunk2 = chunks2.__next__()
79 chunk2 = chunks2.__next__()
80 position2 = 0
80 position2 = 0
81 except StopIteration:
81 except StopIteration:
82 chunk2ended = True
82 chunk2ended = True
83
83
84 if chunk1ended and chunk2ended:
84 if chunk1ended and chunk2ended:
85 # Same size chunksm checked for equality previously
86 break
87 elif chunk1ended or chunk2ended:
88 # Different size chunks, not equal
89 equal = False
85 break
90 break
86 elif chunk1[position1] != chunk2[position2]:
91 elif chunk1[position1] != chunk2[position2]:
92 # Different bytes, not equal
87 equal = False
93 equal = False
88 break
94 break
89 else:
95 else:
90 position1 += 1
96 position1 += 1
91 position2 += 1
97 position2 += 1
92 return equal
98 return equal
93
99
94
100
95 class Attachment(models.Model):
101 class Attachment(models.Model):
96 objects = AttachmentManager()
102 objects = AttachmentManager()
97
103
98 class Meta:
104 class Meta:
99 app_label = 'boards'
105 app_label = 'boards'
100 ordering = ('id',)
106 ordering = ('id',)
101
107
102 file = models.FileField(upload_to=get_upload_filename, null=True)
108 file = models.FileField(upload_to=get_upload_filename, null=True)
103 mimetype = models.CharField(max_length=50, null=True)
109 mimetype = models.CharField(max_length=50, null=True)
104 hash = models.CharField(max_length=36, null=True)
110 hash = models.CharField(max_length=36, null=True)
105 alias = models.TextField(unique=True, null=True)
111 alias = models.TextField(unique=True, null=True)
106 url = models.TextField(blank=True, default='')
112 url = models.TextField(blank=True, default='')
107
113
108 def get_view(self):
114 def get_view(self):
109 file_viewer = None
115 file_viewer = None
110 for viewer in get_viewers():
116 for viewer in get_viewers():
111 if viewer.supports(self.mimetype):
117 if viewer.supports(self.mimetype):
112 file_viewer = viewer
118 file_viewer = viewer
113 break
119 break
114 if file_viewer is None:
120 if file_viewer is None:
115 file_viewer = AbstractViewer
121 file_viewer = AbstractViewer
116
122
117 return file_viewer(self.file, self.mimetype, self.hash, self.url).get_view()
123 return file_viewer(self.file, self.mimetype, self.hash, self.url).get_view()
118
124
119 def __str__(self):
125 def __str__(self):
120 return self.url or self.file.url
126 return self.url or self.file.url
121
127
122 def get_random_associated_post(self):
128 def get_random_associated_post(self):
123 posts = boards.models.Post.objects.filter(attachments__in=[self])
129 posts = boards.models.Post.objects.filter(attachments__in=[self])
124 return posts.order_by('?').first()
130 return posts.order_by('?').first()
125
131
126 @cached_result()
132 @cached_result()
127 def get_size(self):
133 def get_size(self):
128 if self.file:
134 if self.file:
129 if self.mimetype in FILE_TYPES_IMAGE:
135 if self.mimetype in FILE_TYPES_IMAGE:
130 return get_image_dimensions(self.file)
136 return get_image_dimensions(self.file)
131 else:
137 else:
132 return 200, 150
138 return 200, 150
133
139
134 def get_thumb_url(self):
140 def get_thumb_url(self):
135 split = self.file.url.rsplit('.', 1)
141 split = self.file.url.rsplit('.', 1)
136 w, h = 200, 150
142 w, h = 200, 150
137 return '%s.%sx%s.%s' % (split[0], w, h, split[1])
143 return '%s.%sx%s.%s' % (split[0], w, h, split[1])
138
144
139 @cached_result()
145 @cached_result()
140 def get_preview_size(self):
146 def get_preview_size(self):
141 if self.mimetype in FILE_TYPES_IMAGE:
147 if self.mimetype in FILE_TYPES_IMAGE:
142 preview_path = self.file.path.replace('.', '.200x150.')
148 preview_path = self.file.path.replace('.', '.200x150.')
143 return get_image_dimensions(preview_path)
149 return get_image_dimensions(preview_path)
144 else:
150 else:
145 return 200, 150
151 return 200, 150
146
152
147 def is_internal(self):
153 def is_internal(self):
148 return self.url is None or len(self.url) == 0
154 return self.url is None or len(self.url) == 0
General Comments 0
You need to be logged in to leave comments. Login now