1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-03 07:55:36 +00:00

Feature/onlyoffice converter (#4937)

* onlyoffice-converter: initial setup

* onlyoffice-converter: hardcoded converter

* onlyoffice-converter: converter v0.1

* onlyoffice-converter: config tweaks

* onlyoffice-connector: jwt typo fix

* format

* onlyoffice-converter: async fix

* onlyoffice-converter: jwt fix

Co-authored-by: Dmitrii Vershinin <dmitry.vershinin@onlyoffice.com>
This commit is contained in:
Sergey Linnik
2021-09-13 11:37:15 +03:00
committed by lian
parent a24c4abf8a
commit 2e17d92991
11 changed files with 259 additions and 9 deletions

View File

@@ -272,6 +272,9 @@ class DirentListItem extends React.Component {
case 'Open via Client':
this.onOpenViaClient();
break;
case 'Convert with ONLYOFFICE':
this.onConvertWithONLYOFFICE();
break;
default:
break;
}
@@ -365,6 +368,31 @@ class DirentListItem extends React.Component {
location.href = url;
}
onConvertWithONLYOFFICE = ()=> {
let repoID = this.props.repoID;
let user = username;
let fileUri = this.getDirentPath(this.props.dirent)
fetch(siteRoot+'onlyoffice/convert', {
method: 'POST',
body: JSON.stringify({
username: user,
fileUri: fileUri,
repo_id: repoID,
}),
headers: {
'Content-Type': 'application/json'
}
}).then(res => {
if(res.status >= 400) throw new Error()
//Replace with changes in the state
//like this one => this.addNodeToTree(name, parentPath, 'file');
window.location.reload();
})
.catch(() => {
toaster.danger('Could not convert the file');
})
}
onItemDownload = (e) => {
e.nativeEvent.stopImmediatePropagation();
let dirent = this.props.dirent;

View File

@@ -82,6 +82,9 @@ export const enableTC = window.app.pageOptions.enableTC;
export const enableVideoThumbnail = window.app.pageOptions.enableVideoThumbnail;
export const enableOnlyoffice = window.app.pageOptions.enableOnlyoffice || false;
export const onlyofficeConverterExtensions = window.app.pageOptions.onlyofficeConverterExtensions || [];
// dtable
export const workspaceID = window.app.pageOptions.workspaceID;
export const showLogoutIcon = window.app.pageOptions.showLogoutIcon;

View File

@@ -21,6 +21,7 @@ const TextTranslation = {
'ACCESS_LOG' : {key : 'Access Log', value : gettext('Access Log')},
'TAGS': {key: 'Tags', value: gettext('Tags')},
'RELATED_FILES': {key: 'Related Files', value: gettext('Related Files')},
'ONLYOFFICE_CONVERT': {key: 'Convert with ONLYOFFICE', value: gettext('Convert with ONLYOFFICE')}
};
export default TextTranslation;

View File

@@ -1,4 +1,4 @@
import { mediaUrl, gettext, serviceURL, siteRoot, isPro, enableFileComment, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled } from './constants';
import { mediaUrl, gettext, serviceURL, siteRoot, isPro, enableFileComment, fileAuditEnabled, canGenerateShareLink, canGenerateUploadLink, shareLinkPasswordMinLength, username, folderPermEnabled, onlyofficeConverterExtensions, enableOnlyoffice } from './constants';
import { strChineseFirstPY } from './pinyin-by-unicode';
import TextTranslation from './text-translation';
import React from 'react';
@@ -523,7 +523,7 @@ export const Utils = {
getFileOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
let list = [];
const { SHARE, DOWNLOAD, DELETE, RENAME, MOVE, COPY, TAGS, UNLOCK, LOCK,
COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT } = TextTranslation;
COMMENT, HISTORY, ACCESS_LOG, OPEN_VIA_CLIENT, ONLYOFFICE_CONVERT } = TextTranslation;
const permission = dirent.permission;
const { isCustomPermission, customPermission } = Utils.getUserPermission(permission);
@@ -610,14 +610,24 @@ export const Utils = {
list.push(HISTORY);
}
if (permission == 'rw' && enableOnlyoffice &&
onlyofficeConverterExtensions.includes(this.getFileExtension(dirent.name, false))) {
list.push(ONLYOFFICE_CONVERT);
}
// if the last item of menuList is Divider, delete the last item
if (list[list.length - 1] === 'Divider') {
list.pop();
}
return list;
},
getFileExtension: function (fileName, withoutDot) {
let parts = fileName.toLowerCase().split(".");
return withoutDot ? parts.pop() : "." + parts.pop();
},
getDirentOperationList: function(isRepoOwner, currentRepoInfo, dirent, isContextmenu) {
return dirent.type == 'dir' ?
Utils.getFolderOperationList(isRepoOwner, currentRepoInfo, dirent, isContextmenu) :

View File

@@ -26,6 +26,7 @@ from seahub.settings import SEAFILE_VERSION, \
CUSTOM_LOGIN_BG_PATH, ENABLE_SHARE_LINK_REPORT_ABUSE, \
PRIVACY_POLICY_LINK, TERMS_OF_SERVICE_LINK
from seahub.onlyoffice.settings import ENABLE_ONLYOFFICE, ONLYOFFICE_CONVERTER_EXTENSIONS
from seahub.constants import DEFAULT_ADMIN
from seahub.utils import get_site_name, get_service_url
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
@@ -133,6 +134,8 @@ def base(request):
'FILE_SERVER_ROOT': file_server_root,
'USE_GO_FILESERVER': seaserv.USE_GO_FILESERVER if hasattr(seaserv, 'USE_GO_FILESERVER') else False,
'LOGIN_URL': dj_settings.LOGIN_URL,
'enableOnlyoffice': ENABLE_ONLYOFFICE,
'onlyofficeConverterExtensions': ONLYOFFICE_CONVERTER_EXTENSIONS,
'thumbnail_size_for_original': THUMBNAIL_SIZE_FOR_ORIGINAL,
'enable_guest_invitation': ENABLE_GUEST_INVITATION,
'enable_terms_and_conditions': config.ENABLE_TERMS_AND_CONDITIONS,

View File

@@ -0,0 +1,64 @@
import logging
import requests
from seahub.onlyoffice.converterUtils import getFileName, getFileExt
from seahub.onlyoffice.settings import ONLYOFFICE_CONVERTER_URL, ONLYOFFICE_JWT_SECRET, ONLYOFFICE_JWT_HEADER
logger = logging.getLogger(__name__)
def getConverterUri(docUri, fromExt, toExt, docKey, isAsync, filePass = None):
if not fromExt:
fromExt = getFileExt(docUri)
title = getFileName(docUri)
payload = {
'url': docUri,
'outputtype': toExt.replace('.', ''),
'filetype': fromExt.replace('.', ''),
'title': title,
'key': docKey,
'password': filePass
}
headers={'accept': 'application/json'}
if isAsync:
payload.setdefault('async', True)
if ONLYOFFICE_JWT_SECRET:
import jwt
token = jwt.encode(payload, ONLYOFFICE_JWT_SECRET, algorithm='HS256')
headerToken = jwt.encode({'payload': payload}, ONLYOFFICE_JWT_SECRET, algorithm='HS256')
payload['token'] = token
headers[ONLYOFFICE_JWT_HEADER] = f'Bearer {headerToken}'
response = requests.post(ONLYOFFICE_CONVERTER_URL, json=payload, headers=headers )
json = response.json()
return getResponseUri(json)
def getResponseUri(json):
isEnd = json.get('endConvert')
error = json.get('error')
if error:
processError(error)
if isEnd:
return json.get('fileUrl')
def processError(error):
prefix = 'Error occurred in the ConvertService: '
mapping = {
'-8': f'{prefix}Error document VKey',
'-7': f'{prefix}Error document request',
'-6': f'{prefix}Error database',
'-5': f'{prefix}Incorrect password',
'-4': f'{prefix}Error download error',
'-3': f'{prefix}Error convertation error',
'-2': f'{prefix}Error convertation timeout',
'-1': f'{prefix}Error convertation unknown'
}
logger.error(f'[OnlyOffice] Converter URI Error Code: {error}')
raise Exception(mapping.get(str(error), f'Error Code: {error}'))

View File

@@ -0,0 +1,39 @@
from seahub.onlyoffice.settings import EXT_DOCUMENT, \
EXT_SPREADSHEET, EXT_PRESENTATION
def getFileName(fileUri):
ind = fileUri.rfind('/')
return fileUri[ind+1:]
def getFilePathWithoutName(fileUri):
ind = fileUri.rfind('/')
return fileUri[:ind]
def getFileNameWithoutExt(fileUri):
fn = getFileName(fileUri)
ind = fn.rfind('.')
return fn[:ind]
def getFileExt(fileUri):
fn = getFileName(fileUri)
ind = fn.rfind('.')
return fn[ind:].lower()
def getFileType(fileUri):
ext = getFileExt(fileUri)
if ext in EXT_DOCUMENT:
return 'word'
if ext in EXT_SPREADSHEET:
return 'cell'
if ext in EXT_PRESENTATION:
return 'slide'
return 'word'
def getInternalExtension(fileType):
mapping = {
'word': '.docx',
'cell': '.xlsx',
'slide': '.pptx'
}
return mapping.get(fileType, None)

View File

@@ -3,13 +3,41 @@ from django.conf import settings
ENABLE_ONLYOFFICE = getattr(settings, 'ENABLE_ONLYOFFICE', False)
ONLYOFFICE_APIJS_URL = getattr(settings, 'ONLYOFFICE_APIJS_URL', '')
ONLYOFFICE_CONVERTER_URL = getattr(settings, 'ONLYOFFICE_CONVERTER_URL', '')
ONLYOFFICE_FILE_EXTENSION = getattr(settings, 'ONLYOFFICE_FILE_EXTENSION', ())
ONLYOFFICE_EDIT_FILE_EXTENSION = getattr(settings, 'ONLYOFFICE_EDIT_FILE_EXTENSION', ())
VERIFY_ONLYOFFICE_CERTIFICATE = getattr(settings, 'VERIFY_ONLYOFFICE_CERTIFICATE', True)
ONLYOFFICE_JWT_HEADER = getattr(settings, 'ONLYOFFICE_JWT_HEADER', 'Authorization')
ONLYOFFICE_JWT_SECRET = getattr(settings, 'ONLYOFFICE_JWT_SECRET', '')
# if True, file will be saved when user click save btn on file editing page
ONLYOFFICE_FORCE_SAVE = getattr(settings, 'ONLYOFFICE_FORCE_SAVE', False)
ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN = getattr(settings, 'ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN', False)
ONLYOFFICE_CONVERTER_EXTENSIONS = [
".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt",
".fodt", ".ott", ".xlsm", ".xls", ".xltx", ".xltm",
".xlt", ".ods", ".fods", ".ots", ".pptm", ".ppt",
".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot",
".odp", ".fodp", ".otp", ".rtf", ".mht", ".html", ".htm", ".xml", ".epub", ".fb2"
]
EXT_SPREADSHEET = [
".xls", ".xlsx", ".xlsm",
".xlt", ".xltx", ".xltm",
".ods", ".fods", ".ots", ".csv"
]
EXT_PRESENTATION = [
".pps", ".ppsx", ".ppsm",
".ppt", ".pptx", ".pptm",
".pot", ".potx", ".potm",
".odp", ".fodp", ".otp"
]
EXT_DOCUMENT = [
".doc", ".docx", ".docm",
".dot", ".dotx", ".dotm",
".odt", ".fodt", ".ott", ".rtf", ".txt",
".html", ".htm", ".mht", ".xml",
".pdf", ".djvu", ".fb2", ".epub", ".xps"
]

View File

@@ -7,10 +7,13 @@ import requests
from django.core.cache import cache
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from seaserv import seafile_api
from django.shortcuts import render
from seaserv import seafile_api
from seahub.onlyoffice.settings import VERIFY_ONLYOFFICE_CERTIFICATE
from seahub.onlyoffice.utils import generate_onlyoffice_cache_key
from seahub.onlyoffice.utils import generate_onlyoffice_cache_key, get_onlyoffice_dict
from seahub.onlyoffice.converterUtils import getFileNameWithoutExt, getFileExt, getFileType, getInternalExtension, getFilePathWithoutName
from seahub.onlyoffice.converter import getConverterUri
from seahub.utils import gen_inner_file_upload_url, is_pro_version
from seahub.utils.file_op import if_locked_by_online_office
@@ -161,3 +164,70 @@ def onlyoffice_editor_callback(request):
seafile_api.unlock_file(repo_id, file_path)
return HttpResponse('{"error": 0}')
@csrf_exempt
def onlyoffice_convert(request):
if request.method != 'POST':
logger.error('Request method if not POST.')
return render(request, '404.html')
body = json.loads(request.body)
username = body.get('username')
fileUri = body.get('fileUri')
filePass = body.get('filePass') or None
repo_id = body.get('repo_id')
folderName = getFilePathWithoutName(fileUri)+'/'
fileExt = getFileExt(fileUri)
fileType = getFileType(fileUri)
newExt = getInternalExtension(fileType)
if(not newExt):
logger.error('[OnlyOffice] Could not generate internal extension.')
return HttpResponse(status=500)
doc_dic = get_onlyoffice_dict(request, username, repo_id, fileUri)
downloadUri = doc_dic["doc_url"]
key = doc_dic["doc_key"]
newUri = getConverterUri(downloadUri, fileExt, newExt, key, False, filePass)
if(not newUri):
logger.error('[OnlyOffice] No response from file converter.')
return HttpResponse(status=500)
onlyoffice_resp = requests.get(newUri, verify=VERIFY_ONLYOFFICE_CERTIFICATE)
if not onlyoffice_resp:
logger.error('[OnlyOffice] No response from file content url.')
return HttpResponse(status=500)
fake_obj_id = {'online_office_update': True}
update_token = seafile_api.get_fileserver_access_token(repo_id,
json.dumps(fake_obj_id),
'update',
username)
if not update_token:
logger.error('[OnlyOffice] No fileserver access token.')
return HttpResponse(status=500)
fileName = getFileNameWithoutExt(fileUri)+newExt
seafile_api.post_empty_file(repo_id, folderName, fileName, username)
files = {
'file': onlyoffice_resp.content,
'target_file': folderName+fileName,
}
update_url = gen_inner_file_upload_url('update-api', update_token)
requests.post(update_url, files=files)
return HttpResponse(status=200)

View File

@@ -135,7 +135,9 @@
showLogoutIcon: {% if show_logout_icon %} true {% else %} false {% endif %},
additionalShareDialogNote: {% if additional_share_dialog_note %} {{ additional_share_dialog_note|safe }} {% else %} null {% endif %},
additionalAppBottomLinks: {% if additional_app_bottom_links %} {{ additional_app_bottom_links|safe }} {% else %} null {% endif %},
additionalAboutDialogLinks: {% if additional_about_dialog_links %} {{ additional_about_dialog_links|safe }} {% else %} null {% endif %}
additionalAboutDialogLinks: {% if additional_about_dialog_links %} {{ additional_about_dialog_links|safe }} {% else %} null {% endif %},
enableOnlyoffice: {% if enableOnlyoffice %} true {% else %} false {% endif %},
onlyofficeConverterExtensions: {% if onlyofficeConverterExtensions %} {{onlyofficeConverterExtensions|safe}} {% else %} null {% endif %}
}
};
</script>

View File

@@ -866,8 +866,10 @@ if getattr(settings, 'ENABLE_ADFS_LOGIN', False):
if getattr(settings, 'ENABLE_ONLYOFFICE', False):
from seahub.onlyoffice.views import onlyoffice_editor_callback
from seahub.onlyoffice.views import onlyoffice_convert
urlpatterns += [
url(r'^onlyoffice/editor-callback/$', onlyoffice_editor_callback, name='onlyoffice_editor_callback'),
url(r'^onlyoffice/convert', onlyoffice_convert, name='onlyoffice_convert')
]
if getattr(settings, 'ENABLE_BISHENG_OFFICE', False):