1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 from __future__
import unicode_literals
21 from collections
import OrderedDict
22 from tempfile
import NamedTemporaryFile
23 from zipfile
import ZipFile
25 from flask
import session
, flash
26 from sqlalchemy
import cast
, Date
28 from indico
.core
.config
import Config
29 from indico
.core
.db
.sqlalchemy
.links
import LinkType
30 from indico
.util
.date_time
import format_date
31 from indico
.util
.i18n
import _
32 from indico
.util
.fs
import secure_filename
33 from indico
.util
.string
import to_unicode
34 from indico
.util
.tasks
import delete_file
35 from indico
.web
.flask
.util
import send_file
36 from indico
.web
.forms
.base
import FormDefaults
37 from indico
.modules
.attachments
.forms
import AttachmentPackageForm
38 from indico
.modules
.attachments
.models
.attachments
import Attachment
, AttachmentFile
, AttachmentType
39 from indico
.modules
.attachments
.models
.folders
import AttachmentFolder
40 from MaKaC
.conference
import SubContribution
43 def _get_start_date(obj
):
44 if isinstance(obj
, SubContribution
):
45 return obj
.getContribution().getAdjustedStartDate()
47 return obj
.getAdjustedStartDate()
50 class AttachmentPackageGeneratorMixin
:
52 def _filter_attachments(self
, filter_data
):
53 added_since
= filter_data
.get('added_since', None)
54 filter_type
= filter_data
['filter_type']
57 if filter_type
== 'all':
58 attachments
= self
._get
_all
_attachments
(added_since
)
59 elif filter_type
== 'sessions':
60 attachments
= self
._filter
_by
_sessions
(filter_data
.get('sessions', []), added_since
)
61 elif filter_type
== 'contributions':
62 attachments
= self
._filter
_by
_contributions
(filter_data
.get('contributions', []), added_since
)
63 elif filter_type
== 'dates':
64 attachments
= self
._filter
_by
_dates
(filter_data
.get('dates'))
66 return self
._filter
_protected
(attachments
)
68 def _filter_protected(self
, attachments
):
69 return [attachment
for attachment
in attachments
if attachment
.can_access(session
.user
)]
71 def _get_all_attachments(self
, added_since
):
72 query
= self
._build
_base
_query
()
75 query
= self
._filter
_by
_date
(query
, added_since
)
77 return [att
for att
in query
if att
.folder
.linked_object
and _get_start_date(att
.folder
.linked_object
)]
79 def _build_base_query(self
):
80 return Attachment
.find(Attachment
.type == AttachmentType
.file, ~AttachmentFolder
.is_deleted
,
81 ~Attachment
.is_deleted
, AttachmentFolder
.event_id
== int(self
._conf
.getId()),
82 _join
=AttachmentFolder
)
84 def _filter_by_sessions(self
, session_ids
, added_since
):
85 query
= self
._build
_base
_query
().filter(AttachmentFolder
.link_type
.in_([LinkType
.session
, LinkType
.contribution
,
86 LinkType
.subcontribution
]))
89 query
= self
._filter
_by
_date
(query
, added_since
)
91 return [att
for att
in query
if att
.folder
.linked_object
.getSession()
92 and att
.folder
.linked_object
.getSession().getId() in session_ids
]
94 def _filter_by_contributions(self
, contribution_ids
, added_since
):
95 query
= self
._build
_base
_query
().filter(AttachmentFolder
.contribution_id
.in_(contribution_ids
),
96 AttachmentFolder
.link_type
.in_([LinkType
.contribution
,
97 LinkType
.subcontribution
]))
100 query
= self
._filter
_by
_date
(query
, added_since
)
102 return [att
for att
in query
if att
.folder
.linked_object
and _get_start_date(att
.folder
.linked_object
)]
104 def _filter_by_dates(self
, dates
):
105 return [att
for att
in self
._build
_base
_query
() if att
.folder
.linked_object
and
106 unicode(_get_start_date(att
.folder
.linked_object
)) in dates
]
108 def _filter_by_date(self
, query
, added_since
):
109 return query
.join(Attachment
.file).filter(cast(AttachmentFile
.created_dt
, Date
) >= added_since
)
111 def _generate_zip_file(self
, attachments
):
112 temp_file
= NamedTemporaryFile(suffix
='indico.tmp', dir=Config
.getInstance().getTempDir())
113 with
ZipFile(temp_file
.name
, 'w', allowZip64
=True) as zip_handler
:
115 for attachment
in attachments
:
116 name
= self
._prepare
_folder
_structure
(attachment
)
118 with attachment
.file.storage
.get_local_path(attachment
.file.storage_file_id
) as filepath
:
119 zip_handler
.write(filepath
, name
)
121 # Delete the temporary file after some time. Even for a large file we don't
122 # need a higher delay since the webserver will keep it open anyway until it's
123 # done sending it to the client.
124 delete_file
.apply_async(args
=[temp_file
.name
], countdown
=3600)
125 temp_file
.delete
= False
126 return send_file('material-{}.zip'.format(self
._conf
.id), temp_file
.name
, 'application/zip', inline
=False)
128 def _prepare_folder_structure(self
, attachment
):
129 event_dir
= secure_filename(self
._conf
.getTitle(), None)
130 segments
= [event_dir
] if event_dir
else []
131 segments
.extend(self
._get
_base
_path
(attachment
))
132 if not attachment
.folder
.is_default
:
133 segments
.append(secure_filename(attachment
.folder
.title
, unicode(attachment
.folder
.id)))
134 segments
.append(attachment
.file.filename
)
135 path
= os
.path
.join(*filter(None, segments
))
136 while path
in self
.used
:
137 # prepend the id if there's a path collision
138 segments
[-1] = '{}-{}'.format(attachment
.id, segments
[-1])
139 path
= os
.path
.join(*filter(None, segments
))
142 def _get_base_path(self
, attachment
):
143 obj
= linked_object
= attachment
.folder
.linked_object
145 while obj
!= self
._conf
:
146 owner
= obj
.getOwner()
147 if isinstance(obj
, SubContribution
):
148 start_date
= owner
.getAdjustedStartDate()
150 start_date
= obj
.getAdjustedStartDate()
152 if start_date
is not None:
153 paths
.append(secure_filename(start_date
.strftime('%H%M_{}').format(obj
.getTitle()), ''))
155 paths
.append(secure_filename(obj
.getTitle(), unicode(obj
.getId())))
158 if isinstance(linked_object
, SubContribution
):
159 linked_obj_start_date
= linked_object
.getOwner().getAdjustedStartDate()
161 linked_obj_start_date
= linked_object
.getAdjustedStartDate()
163 if attachment
.folder
.linked_object
!= self
._conf
and linked_obj_start_date
is not None:
164 paths
.append(secure_filename(linked_obj_start_date
.strftime('%Y%m%d_%A'), ''))
166 return reversed(paths
)
169 class AttachmentPackageMixin(AttachmentPackageGeneratorMixin
):
173 form
, skipped_fields
= self
._prepare
_form
()
174 if form
.validate_on_submit():
175 attachments
= self
._filter
_attachments
(form
.data
)
177 return self
._generate
_zip
_file
(attachments
)
179 flash(_('There are no materials matching your criteria.'), 'warning')
181 return self
.wp
.render_template('generate_package.html', self
._conf
, form
=form
, skipped_fields
=skipped_fields
)
183 def _prepare_form(self
):
184 form
= AttachmentPackageForm(obj
=FormDefaults(filter_type
='all'))
185 filter_types
= OrderedDict(all
=_('Everything'))
186 sessions
= self
._load
_session
_data
()
187 contributions
= self
._load
_contribution
_data
()
188 dates
= self
._load
_dates
()
191 filter_types
['sessions'] = _('Specific sessions')
193 filter_types
['contributions'] = _('Specific contributions')
195 filter_types
['dates'] = _('Specific days')
197 skipped_fields
= {'sessions', 'contributions', 'dates'} - filter_types
.viewkeys()
198 form
.filter_type
.choices
= filter_types
.items()
199 form
.sessions
.choices
= sessions
200 form
.contributions
.choices
= contributions
201 form
.dates
.choices
= dates
202 return form
, skipped_fields
204 def _load_session_data(self
):
205 return [(session
.getId(), to_unicode(session
.getTitle())) for session
in self
._conf
.getSessionList()]
207 def _load_contribution_data(self
):
208 return [(contrib
.getId(), to_unicode(contrib
.getTitle()))
209 for contrib
in self
._conf
.getContributionList() if contrib
.getOwner() == self
._conf
210 and contrib
.getStartDate()]
212 def _load_dates(self
):
213 offset
= self
._conf
.getAdjustedEndDate() - self
._conf
.getAdjustedStartDate()
214 return [(unicode(self
._conf
.getAdjustedStartDate() + datetime
.timedelta(days
=day_number
)),
215 format_date(self
._conf
.getAdjustedStartDate() + datetime
.timedelta(days
=day_number
), 'short'))
216 for day_number
in xrange(offset
.days
+ 1)]