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:
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
@@ -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) :
|
||||
|
@@ -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,
|
||||
|
64
seahub/onlyoffice/converter.py
Normal file
64
seahub/onlyoffice/converter.py
Normal 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}'))
|
39
seahub/onlyoffice/converterUtils.py
Normal file
39
seahub/onlyoffice/converterUtils.py
Normal 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)
|
@@ -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"
|
||||
]
|
@@ -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)
|
||||
|
@@ -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>
|
||||
|
@@ -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):
|
||||
|
Reference in New Issue
Block a user