file_controller.py 22KB

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