file_controller.py 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. # coding=utf-8
  2. import typing
  3. import transaction
  4. from depot.io.local import LocalStoredFile
  5. from depot.manager import DepotManager
  6. from pyramid.config import Configurator
  7. from pyramid.response import FileResponse, FileIter
  8. from tracim.exceptions import EmptyLabelNotAllowed
  9. from tracim.models.data import UserRoleInWorkspace
  10. try: # Python 3.5+
  11. from http import HTTPStatus
  12. except ImportError:
  13. from http import client as HTTPStatus
  14. from tracim import TracimRequest
  15. from tracim.extensions import hapic
  16. from tracim.lib.core.content import ContentApi
  17. from tracim.views.controllers import Controller
  18. from tracim.views.core_api.schemas import FileContentSchema
  19. from tracim.views.core_api.schemas import ContentPreviewSizedPathSchema
  20. from tracim.views.core_api.schemas import RevisionPreviewSizedPathSchema
  21. from tracim.views.core_api.schemas import PageQuerySchema
  22. from tracim.views.core_api.schemas import WorkspaceAndContentRevisionIdPathSchema # nopep8
  23. from tracim.views.core_api.schemas import FileRevisionSchema
  24. from tracim.views.core_api.schemas import SetContentStatusSchema
  25. from tracim.views.core_api.schemas import FileContentModifySchema
  26. from tracim.views.core_api.schemas import WorkspaceAndContentIdPathSchema
  27. from tracim.views.core_api.schemas import NoContentSchema
  28. from tracim.lib.utils.authorization import require_content_types
  29. from tracim.lib.utils.authorization import require_workspace_role
  30. from tracim.models.context_models import ContentInContext
  31. from tracim.models.context_models import RevisionInContext
  32. from tracim.models.contents import ContentTypeLegacy as ContentType
  33. from tracim.models.contents import file_type
  34. from tracim.models.revision_protection import new_revision
  35. from preview_generator.manager import PreviewManager
  36. FILE_ENDPOINTS_TAG = 'Files'
  37. class FileController(Controller):
  38. # File data
  39. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  40. @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
  41. @require_content_types([file_type])
  42. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  43. #@hapic.input_files()
  44. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  45. def upload_file(self, context, request: TracimRequest, hapic_data=None):
  46. app_config = request.registry.settings['CFG']
  47. api = ContentApi(
  48. current_user=request.current_user,
  49. session=request.dbsession,
  50. config=app_config,
  51. )
  52. content = api.get_one(
  53. hapic_data.path.content_id,
  54. content_type=ContentType.Any
  55. )
  56. file = request.POST['files']
  57. with new_revision(
  58. session=request.dbsession,
  59. tm=transaction.manager,
  60. content=content
  61. ):
  62. api.update_file_data(
  63. content,
  64. new_filename=file.filename,
  65. new_mimetype=file.type,
  66. new_content=file.file,
  67. )
  68. return
  69. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  70. @require_workspace_role(UserRoleInWorkspace.READER)
  71. @require_content_types([file_type])
  72. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  73. @hapic.output_file([])
  74. def download_file(self, context, request: TracimRequest, hapic_data=None):
  75. app_config = request.registry.settings['CFG']
  76. api = ContentApi(
  77. current_user=request.current_user,
  78. session=request.dbsession,
  79. config=app_config,
  80. )
  81. content = api.get_one(
  82. hapic_data.path.content_id,
  83. content_type=ContentType.Any
  84. )
  85. file = DepotManager.get().get(content.depot_file)
  86. response = request.response
  87. response.content_type = file.content_type
  88. response.app_iter = FileIter(file)
  89. return response
  90. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  91. @require_workspace_role(UserRoleInWorkspace.READER)
  92. @require_content_types([file_type])
  93. @hapic.input_path(WorkspaceAndContentRevisionIdPathSchema())
  94. @hapic.output_file([])
  95. def download_revisions_file(self, context, request: TracimRequest, hapic_data=None): # nopep8
  96. app_config = request.registry.settings['CFG']
  97. api = ContentApi(
  98. current_user=request.current_user,
  99. session=request.dbsession,
  100. config=app_config,
  101. )
  102. content = api.get_one(
  103. hapic_data.path.content_id,
  104. content_type=ContentType.Any
  105. )
  106. revision = api.get_one_revision(
  107. revision_id=hapic_data.path.revision_id,
  108. content=content
  109. )
  110. file = DepotManager.get().get(revision.depot_file)
  111. response = request.response
  112. response.content_type = file.content_type
  113. response.app_iter = FileIter(file)
  114. return response
  115. # preview
  116. # pdf
  117. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  118. @require_workspace_role(UserRoleInWorkspace.READER)
  119. @require_content_types([file_type])
  120. @hapic.input_query(PageQuerySchema())
  121. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  122. @hapic.output_file([])
  123. def preview_pdf(self, context, request: TracimRequest, hapic_data=None):
  124. app_config = request.registry.settings['CFG']
  125. preview_manager = PreviewManager(app_config.PREVIEW_CACHE_DIR, create_folder=True) # nopep8
  126. api = ContentApi(
  127. current_user=request.current_user,
  128. session=request.dbsession,
  129. config=app_config,
  130. )
  131. content = api.get_one(
  132. hapic_data.path.content_id,
  133. content_type=ContentType.Any
  134. )
  135. file_path = api.get_one_revision_filepath(content.revision_id)
  136. if hapic_data.query.page >= preview_manager.get_page_nb(file_path):
  137. raise Exception('page {page} of content {content_id} does not exist'.format(
  138. page=hapic_data.query.page,
  139. content_id=content.content_id),
  140. )
  141. pdf_preview_path = preview_manager.get_pdf_preview(file_path, page=hapic_data.query.page) # nopep8
  142. return FileResponse(pdf_preview_path)
  143. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  144. @require_workspace_role(UserRoleInWorkspace.READER)
  145. @require_content_types([file_type])
  146. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  147. @hapic.output_file([])
  148. def preview_pdf_full(self, context, request: TracimRequest, hapic_data=None):
  149. app_config = request.registry.settings['CFG']
  150. preview_manager = PreviewManager(app_config.PREVIEW_CACHE_DIR, create_folder=True) # nopep8
  151. api = ContentApi(
  152. current_user=request.current_user,
  153. session=request.dbsession,
  154. config=app_config,
  155. )
  156. content = api.get_one(
  157. hapic_data.path.content_id,
  158. content_type=ContentType.Any
  159. )
  160. file_path = api.get_one_revision_filepath(content.revision_id)
  161. pdf_preview_path = preview_manager.get_pdf_preview(file_path)
  162. return FileResponse(pdf_preview_path)
  163. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  164. @require_workspace_role(UserRoleInWorkspace.READER)
  165. @require_content_types([file_type])
  166. @hapic.input_path(WorkspaceAndContentRevisionIdPathSchema())
  167. @hapic.input_query(PageQuerySchema())
  168. @hapic.output_file([])
  169. def preview_pdf_revision(self, context, request: TracimRequest, hapic_data=None):
  170. app_config = request.registry.settings['CFG']
  171. preview_manager = PreviewManager(app_config.PREVIEW_CACHE_DIR, create_folder=True) # nopep8
  172. api = ContentApi(
  173. current_user=request.current_user,
  174. session=request.dbsession,
  175. config=app_config,
  176. )
  177. content = api.get_one(
  178. hapic_data.path.content_id,
  179. content_type=ContentType.Any
  180. )
  181. revision = api.get_one_revision(
  182. revision_id=hapic_data.path.revision_id,
  183. content=content
  184. )
  185. file_path = api.get_one_revision_filepath(revision.revision_id)
  186. if hapic_data.query.page >= preview_manager.get_page_nb(file_path):
  187. raise Exception('page {page} of content {content_id} does not exist'.format(
  188. page=hapic_data.query.page,
  189. content_id=content.content_id),
  190. )
  191. pdf_preview_path = preview_manager.get_pdf_preview(file_path, page=hapic_data.query.page) # nopep8
  192. return FileResponse(pdf_preview_path)
  193. # jpg
  194. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  195. @require_workspace_role(UserRoleInWorkspace.READER)
  196. @require_content_types([file_type])
  197. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  198. @hapic.input_query(PageQuerySchema())
  199. @hapic.output_file([])
  200. def preview_jpg(self, context, request: TracimRequest, hapic_data=None):
  201. app_config = request.registry.settings['CFG']
  202. preview_manager = PreviewManager(app_config.PREVIEW_CACHE_DIR, create_folder=True) # nopep8
  203. api = ContentApi(
  204. current_user=request.current_user,
  205. session=request.dbsession,
  206. config=app_config,
  207. )
  208. content = api.get_one(
  209. hapic_data.path.content_id,
  210. content_type=ContentType.Any
  211. )
  212. file_path = api.get_one_revision_filepath(content.revision_id)
  213. if hapic_data.query.page >= preview_manager.get_page_nb(file_path):
  214. raise Exception('page {page} of content {content_id} does not exist'.format(
  215. page=hapic_data.query.page,
  216. content_id=content.content_id),
  217. )
  218. jpg_preview_path = preview_manager.get_jpeg_preview(file_path, page=hapic_data.query.page) # nopep8
  219. return FileResponse(jpg_preview_path)
  220. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  221. @require_workspace_role(UserRoleInWorkspace.READER)
  222. @require_content_types([file_type])
  223. @hapic.input_query(PageQuerySchema())
  224. @hapic.input_path(ContentPreviewSizedPathSchema())
  225. @hapic.output_file([])
  226. def sized_preview_jpg(self, context, request: TracimRequest, hapic_data=None):
  227. app_config = request.registry.settings['CFG']
  228. preview_manager = PreviewManager(app_config.PREVIEW_CACHE_DIR, create_folder=True) # nopep8
  229. api = ContentApi(
  230. current_user=request.current_user,
  231. session=request.dbsession,
  232. config=app_config,
  233. )
  234. content = api.get_one(
  235. hapic_data.path.content_id,
  236. content_type=ContentType.Any
  237. )
  238. file_path = api.get_one_revision_filepath(content.revision_id)
  239. if hapic_data.query.page >= preview_manager.get_page_nb(file_path):
  240. raise Exception('page {page} of content {content_id} does not exist'.format(
  241. page=hapic_data.query.page,
  242. content_id=content.content_id),
  243. )
  244. jpg_preview_path = preview_manager.get_jpeg_preview(
  245. file_path,
  246. page=hapic_data.query.page,
  247. width=hapic_data.path.width,
  248. height=hapic_data.path.height,
  249. )
  250. return FileResponse(jpg_preview_path)
  251. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  252. @require_workspace_role(UserRoleInWorkspace.READER)
  253. @require_content_types([file_type])
  254. @hapic.input_path(RevisionPreviewSizedPathSchema())
  255. @hapic.input_query(PageQuerySchema())
  256. @hapic.output_file([])
  257. def sized_preview_jpg_revision(self, context, request: TracimRequest, hapic_data=None):
  258. app_config = request.registry.settings['CFG']
  259. preview_manager = PreviewManager(app_config.PREVIEW_CACHE_DIR, create_folder=True) # nopep8
  260. api = ContentApi(
  261. current_user=request.current_user,
  262. session=request.dbsession,
  263. config=app_config,
  264. )
  265. content = api.get_one(
  266. hapic_data.path.content_id,
  267. content_type=ContentType.Any
  268. )
  269. revision = api.get_one_revision(
  270. revision_id=hapic_data.path.revision_id,
  271. content=content
  272. )
  273. file_path = api.get_one_revision_filepath(revision.revision_id)
  274. if hapic_data.query.page >= preview_manager.get_page_nb(file_path):
  275. raise Exception('page {page} of content {content_id} does not exist'.format(
  276. page=hapic_data.query.page,
  277. content_id=content.content_id),
  278. )
  279. jpg_preview_path = preview_manager.get_jpeg_preview(
  280. file_path,
  281. page=hapic_data.query.page,
  282. width=hapic_data.path.width,
  283. height=hapic_data.path.height,
  284. )
  285. return FileResponse(jpg_preview_path)
  286. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  287. @require_workspace_role(UserRoleInWorkspace.READER)
  288. @require_content_types([file_type])
  289. @hapic.output_file([])
  290. def allowed_dim_preview_jpg(self, context, request: TracimRequest, hapic_data=None):
  291. raise NotImplemented()
  292. # File infos
  293. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  294. @require_workspace_role(UserRoleInWorkspace.READER)
  295. @require_content_types([file_type])
  296. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  297. @hapic.output_body(FileContentSchema())
  298. def get_file_infos(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: # nopep8
  299. """
  300. Get thread content
  301. """
  302. app_config = request.registry.settings['CFG']
  303. api = ContentApi(
  304. current_user=request.current_user,
  305. session=request.dbsession,
  306. config=app_config,
  307. )
  308. content = api.get_one(
  309. hapic_data.path.content_id,
  310. content_type=ContentType.Any
  311. )
  312. return api.get_content_in_context(content)
  313. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  314. @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
  315. @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
  316. @require_content_types([file_type])
  317. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  318. @hapic.input_body(FileContentModifySchema())
  319. @hapic.output_body(FileContentSchema())
  320. def update_file_info(self, context, request: TracimRequest, hapic_data=None) -> ContentInContext: # nopep8
  321. """
  322. update thread
  323. """
  324. app_config = request.registry.settings['CFG']
  325. api = ContentApi(
  326. current_user=request.current_user,
  327. session=request.dbsession,
  328. config=app_config,
  329. )
  330. content = api.get_one(
  331. hapic_data.path.content_id,
  332. content_type=ContentType.Any
  333. )
  334. with new_revision(
  335. session=request.dbsession,
  336. tm=transaction.manager,
  337. content=content
  338. ):
  339. api.update_content(
  340. item=content,
  341. new_label=hapic_data.body.label,
  342. new_content=hapic_data.body.raw_content,
  343. )
  344. api.save(content)
  345. return api.get_content_in_context(content)
  346. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  347. @require_workspace_role(UserRoleInWorkspace.READER)
  348. @require_content_types([file_type])
  349. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  350. @hapic.output_body(FileRevisionSchema(many=True))
  351. def get_file_revisions(
  352. self,
  353. context,
  354. request: TracimRequest,
  355. hapic_data=None
  356. ) -> typing.List[RevisionInContext]:
  357. """
  358. get file revisions
  359. """
  360. app_config = request.registry.settings['CFG']
  361. api = ContentApi(
  362. current_user=request.current_user,
  363. session=request.dbsession,
  364. config=app_config,
  365. )
  366. content = api.get_one(
  367. hapic_data.path.content_id,
  368. content_type=ContentType.Any
  369. )
  370. revisions = content.revisions
  371. return [
  372. api.get_revision_in_context(revision)
  373. for revision in revisions
  374. ]
  375. @hapic.with_api_doc(tags=[FILE_ENDPOINTS_TAG])
  376. @hapic.handle_exception(EmptyLabelNotAllowed, HTTPStatus.BAD_REQUEST)
  377. @require_workspace_role(UserRoleInWorkspace.CONTRIBUTOR)
  378. @require_content_types([file_type])
  379. @hapic.input_path(WorkspaceAndContentIdPathSchema())
  380. @hapic.input_body(SetContentStatusSchema())
  381. @hapic.output_body(NoContentSchema(), default_http_code=HTTPStatus.NO_CONTENT) # nopep8
  382. def set_file_status(self, context, request: TracimRequest, hapic_data=None) -> None: # nopep8
  383. """
  384. set file status
  385. """
  386. app_config = request.registry.settings['CFG']
  387. api = ContentApi(
  388. current_user=request.current_user,
  389. session=request.dbsession,
  390. config=app_config,
  391. )
  392. content = api.get_one(
  393. hapic_data.path.content_id,
  394. content_type=ContentType.Any
  395. )
  396. with new_revision(
  397. session=request.dbsession,
  398. tm=transaction.manager,
  399. content=content
  400. ):
  401. api.set_status(
  402. content,
  403. hapic_data.body.status,
  404. )
  405. api.save(content)
  406. return
  407. def bind(self, configurator: Configurator) -> None:
  408. # file info #
  409. # Get file info
  410. configurator.add_route(
  411. 'file_info',
  412. '/workspaces/{workspace_id}/files/{content_id}',
  413. request_method='GET'
  414. )
  415. configurator.add_view(self.get_file_infos, route_name='file_info') # nopep8
  416. # update file
  417. configurator.add_route(
  418. 'update_file_info',
  419. '/workspaces/{workspace_id}/files/{content_id}',
  420. request_method='PUT'
  421. ) # nopep8
  422. configurator.add_view(self.update_file_info, route_name='update_file_info') # nopep8
  423. # raw file #
  424. # upload raw file
  425. configurator.add_route(
  426. 'upload_file',
  427. '/workspaces/{workspace_id}/files/{content_id}/raw', # nopep8
  428. request_method='PUT'
  429. )
  430. configurator.add_view(self.upload_file, route_name='upload_file') # nopep8
  431. # download raw file
  432. configurator.add_route(
  433. 'download_file',
  434. '/workspaces/{workspace_id}/files/{content_id}/raw', # nopep8
  435. request_method='GET'
  436. )
  437. configurator.add_view(self.download_file, route_name='download_file') # nopep8
  438. # download raw file of revision
  439. configurator.add_route(
  440. 'download_revision',
  441. '/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/raw', # nopep8
  442. request_method='GET'
  443. )
  444. configurator.add_view(self.download_revisions_file, route_name='download_revision') # nopep8
  445. # previews #
  446. # get preview pdf full
  447. configurator.add_route(
  448. 'preview_pdf_full',
  449. '/workspaces/{workspace_id}/files/{content_id}/preview/pdf/full', # nopep8
  450. request_method='GET'
  451. )
  452. configurator.add_view(self.preview_pdf_full, route_name='preview_pdf_full') # nopep8
  453. # get preview pdf
  454. configurator.add_route(
  455. 'preview_pdf',
  456. '/workspaces/{workspace_id}/files/{content_id}/preview/pdf', # nopep8
  457. request_method='GET'
  458. )
  459. configurator.add_view(self.preview_pdf, route_name='preview_pdf') # nopep8
  460. # get preview jpg allowed dims
  461. configurator.add_route(
  462. 'allowed_dim_preview_jpg',
  463. '/workspaces/{workspace_id}/files/{content_id}/preview/jpg/allowed_dims', # nopep8
  464. request_method='GET'
  465. )
  466. configurator.add_view(self.allowed_dim_preview_jpg, route_name='allowed_dim_preview_jpg') # nopep8
  467. # get preview jpg
  468. configurator.add_route(
  469. 'preview_jpg',
  470. '/workspaces/{workspace_id}/files/{content_id}/preview/jpg', # nopep8
  471. request_method='GET'
  472. )
  473. configurator.add_view(self.preview_jpg, route_name='preview_jpg') # nopep8
  474. # get preview jpg with size
  475. configurator.add_route(
  476. 'sized_preview_jpg',
  477. '/workspaces/{workspace_id}/files/{content_id}/preview/jpg/{width}x{height}', # nopep8
  478. request_method='GET'
  479. )
  480. configurator.add_view(self.sized_preview_jpg, route_name='sized_preview_jpg') # nopep8
  481. # get jpg preview for revision
  482. configurator.add_route(
  483. 'sized_preview_jpg_revision',
  484. '/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/jpg/{width}x{height}', # nopep8
  485. request_method='GET'
  486. )
  487. configurator.add_view(self.sized_preview_jpg_revision, route_name='sized_preview_jpg_revision') # nopep8
  488. # get jpg preview for revision
  489. configurator.add_route(
  490. 'preview_pdf_revision',
  491. '/workspaces/{workspace_id}/files/{content_id}/revisions/{revision_id}/preview/pdf', # nopep8
  492. request_method='GET'
  493. )
  494. configurator.add_view(self.preview_pdf_revision, route_name='preview_pdf_revision') # nopep8
  495. # others #
  496. # get file revisions
  497. configurator.add_route(
  498. 'file_revisions',
  499. '/workspaces/{workspace_id}/files/{content_id}/revisions', # nopep8
  500. request_method='GET'
  501. )
  502. configurator.add_view(self.get_file_revisions, route_name='file_revisions') # nopep8
  503. # get file status
  504. configurator.add_route(
  505. 'set_file_status',
  506. '/workspaces/{workspace_id}/files/{content_id}/status', # nopep8
  507. request_method='PUT'
  508. )
  509. configurator.add_view(self.set_file_status, route_name='set_file_status') # nopep8