Skip to content
Snippets Groups Projects
Commit e4b69976 authored by uhensler's avatar uhensler
Browse files

OO-4009: Save edited file content, locking

parent 0592f2b5
No related branches found
No related tags found
No related merge requests found
Showing
with 230 additions and 32 deletions
......@@ -72,7 +72,9 @@ public class OnlyOfficeEditor implements DocEditor {
@Override
public boolean isLockedForMe(VFSLeaf vfsLeaf, Identity identity, Mode mode) {
// TODO uh Auto-generated method stub
if (onlyOfficeService.isLockNeeded(mode)) {
return onlyOfficeService.isLockedForMe(vfsLeaf, identity);
}
return false;
}
......
......@@ -24,6 +24,8 @@ import java.io.File;
import org.olat.core.commons.services.doceditor.DocEditor.Mode;
import org.olat.core.commons.services.vfs.VFSMetadata;
import org.olat.core.id.Identity;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.lock.LockResult;
/**
*
......@@ -37,9 +39,19 @@ public interface OnlyOfficeService {
File getFile(String fileId);
boolean canUpdateContent(String fileId, Identity identity);
VFSLeaf getVfsLeaf(String fileId);
boolean updateContent(String fileId, Identity identity, String url);
boolean canUpdateContent(VFSLeaf vfsLeaf, Identity identity, String documentKey);
boolean updateContent(VFSLeaf vfsLeaf, Identity identity, String url);
boolean isLockNeeded(Mode mode);
boolean isLockedForMe(VFSLeaf vfsLeaf, Identity identity);
LockResult lock(VFSLeaf vfsLeaf, Identity identity);
void unlock(VFSLeaf vfsLeaf);
boolean isSupportedFormat(String suffix, Mode mode);
......
......@@ -22,21 +22,26 @@ package org.olat.core.commons.services.doceditor.onlyoffice.manager;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import org.olat.basesecurity.BaseSecurityManager;
import org.olat.core.commons.services.doceditor.DocEditor.Mode;
import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService;
import org.olat.core.commons.services.doceditor.wopi.WopiService;
import org.olat.core.commons.services.vfs.VFSMetadata;
import org.olat.core.commons.services.vfs.VFSRepositoryService;
import org.olat.core.id.Identity;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSLockApplicationType;
import org.olat.core.util.vfs.VFSLockManager;
import org.olat.core.util.vfs.VFSManager;
import org.olat.core.util.vfs.lock.LockInfo;
import org.olat.core.util.vfs.lock.LockResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -51,36 +56,53 @@ public class OnlyOfficeServiceImpl implements OnlyOfficeService {
private static final OLog log = Tracing.createLoggerFor(OnlyOfficeServiceImpl.class);
private static final String LOCK_APP_NAME = "onlyoffice";
private static DateFormat LAST_MODIFIED = new SimpleDateFormat("yyyyMMddHHmmSS");
@Autowired
private WopiService wopiService;
@Autowired
private VFSRepositoryService vfsRepositoryService;
@Autowired
private VFSLockManager lockManager;
@Autowired
private BaseSecurityManager securityManager;
@Override
public boolean fileExists(String fileId) {
return wopiService.fileExists(fileId);
return vfsRepositoryService.getItemFor(fileId) != null? true: false;
}
@Override
public File getFile(String fileId) {
return wopiService.getFile(fileId);
VFSLeaf vfsLeaf = getVfsLeaf(fileId);
if (vfsLeaf != null) {
String uri = vfsLeaf.getMetaInfo().getUri();
try {
return Paths.get(new URL(uri).toURI()).toFile();
} catch (Exception e) {
log.error("", e);
}
}
return null;
}
@Override
public VFSLeaf getVfsLeaf(String fileId) {
VFSItem item = vfsRepositoryService.getItemFor(fileId);
if (item instanceof VFSLeaf) {
return (VFSLeaf) item;
}
return null;
}
@Override
public boolean canUpdateContent(String fileId, Identity identity) {
return true;
//TODO uh check lock
// VFSLeaf vfsLeaf = wopiService.getVfsLeaf(fileId);
// return !isLockedForMe(vfsLeaf, access.getIdentity());
public boolean canUpdateContent(VFSLeaf vfsLeaf, Identity identity, String documentKey) {
String currentDocumentKey = getDocumentKey(vfsLeaf.getMetaInfo());
return currentDocumentKey.equals(documentKey) && !isLockedForMe(vfsLeaf, identity);
}
@Override
public boolean updateContent(String fileId, Identity identity, String url) {
VFSLeaf vfsLeaf = wopiService.getVfsLeaf(fileId);
public boolean updateContent(VFSLeaf vfsLeaf, Identity identity, String url) {
boolean updated = false;
try (InputStream in = new URL(url).openStream()) {
//TODO uh versionCntrolled
......@@ -94,12 +116,46 @@ public class OnlyOfficeServiceImpl implements OnlyOfficeService {
log.error("", e);
}
if (updated) {
log.debug("File updated. File ID: " + fileId);
//TODO uh lock
// refreshLock(vfsLeaf);
log.debug("File updated. File name: " + vfsLeaf.getName());
refreshLock(vfsLeaf);
}
return updated;
}
private void refreshLock(VFSLeaf vfsLeaf) {
LockInfo lock = lockManager.getLock(vfsLeaf);
if (lock != null) {
long inADay = System.currentTimeMillis() + (24 * 60 * 60 * 1000);
lock.setExpiresAt(inADay);
}
}
@Override
public boolean isLockNeeded(Mode mode) {
return Mode.EDIT.equals(mode);
}
@Override
public boolean isLockedForMe(VFSLeaf vfsLeaf, Identity identity) {
return lockManager.isLockedForMe(vfsLeaf, identity, VFSLockApplicationType.collaboration, LOCK_APP_NAME);
}
@Override
public LockResult lock(VFSLeaf vfsLeaf, Identity identity) {
LockResult lock = lockManager.lock(vfsLeaf, identity, VFSLockApplicationType.collaboration, LOCK_APP_NAME);
log.debug("Locked file. File name: " + vfsLeaf.getName() + ", Identity: " + identity);
return lock;
}
@Override
public void unlock(VFSLeaf vfsLeaf) {
LockInfo lock = lockManager.getLock(vfsLeaf);
if (lock != null && LOCK_APP_NAME.equals(lock.getAppName())) {
lock.getTokens().clear();
lockManager.unlock(vfsLeaf, VFSLockApplicationType.collaboration);
log.debug("Unlocked file. File name: " + vfsLeaf.getName());
}
}
@Override
public boolean isSupportedFormat(String suffix, Mode mode) {
......
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.commons.services.doceditor.onlyoffice.restapi;
/**
*
* Initial date: 16 Apr 2019<br>
* @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
*
*/
enum CallbackStatus {
ErrorDocumentNotFound(0),
Editing(1),
MustSave(2),
ErrorCorrupted(3),
ClosedWithoutChanges(4),
MustForceSave(6),
ErrorCorruptedForce(7);
private final int value;
private CallbackStatus(int value) {
this.value = value;
}
static CallbackStatus valueOf(int value) {
for (CallbackStatus callbackStatus : CallbackStatus.values()) {
if (callbackStatus.value == value) {
return callbackStatus;
}
}
return null;
}
}
......@@ -35,8 +35,6 @@ public class CallbackVO {
// see https://api.onlyoffice.com/editors/callback
static final int STATUS_READY_FOR_SAVING = 2;
private List<Action> actions;
private Integer forcesavetype;
private String key;
......
......@@ -21,7 +21,6 @@ package org.olat.core.commons.services.doceditor.onlyoffice.restapi;
import static org.olat.core.commons.services.doceditor.onlyoffice.restapi.CallbackResponseVO.error;
import static org.olat.core.commons.services.doceditor.onlyoffice.restapi.CallbackResponseVO.success;
import static org.olat.core.commons.services.doceditor.onlyoffice.restapi.CallbackVO.STATUS_READY_FOR_SAVING;
import java.io.File;
import java.util.List;
......@@ -45,6 +44,8 @@ import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService;
import org.olat.core.id.Identity;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.lock.LockResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -87,10 +88,37 @@ public class OnlyOfficeWebService {
}
CallbackResponseVO responseVO;
switch(callbackVO.getStatus()) {
case STATUS_READY_FOR_SAVING:
CallbackStatus status = CallbackStatus.valueOf(callbackVO.getStatus());
switch(status) {
case Editing:
responseVO = lock(fileId, callbackVO);
break;
case ClosedWithoutChanges:
// This callback is called
// A) if a user closes a document without changes.
// B) if a user does not edit a document for about one minute.
// Case B) results in a opened, unlocked file. If the user starts to edit the file again callback "Editing" is called and the file is locked again.
// However, is is possible to edit the file in the meantime in another editor and all changes made in ONLYOFFICE are lost!
// (This is the same implementation like in Alfresco, ownCloud, Nextcloud etc.)
responseVO = unlock(fileId, callbackVO);
break;
case MustSave:
case MustForceSave:
responseVO = updateContent(fileId, callbackVO);
break;
case ErrorCorrupted:
log.warn("ONLYOFFICE has reported that saving the document has failed. File ID: " + fileId);
responseVO = success();
break;
case ErrorCorruptedForce:
log.warn("ONLYOFFICE has reported that saving the document has failed. File ID: " + fileId);
responseVO = success();
break;
case ErrorDocumentNotFound:
// I never get that status, so I do not know, how to reproduce it.
log.warn("ONLYOFFICE has reported that no doc with the specified key can be found. File ID: " + fileId);
responseVO = success();
break;
default:
// nothing to do
responseVO = success();
......@@ -99,19 +127,53 @@ public class OnlyOfficeWebService {
return Response.ok(responseVO).build();
}
private CallbackResponseVO updateContent(String fileId, CallbackVO callbackVO) {
private CallbackResponseVO lock(String fileId, CallbackVO callbackVO) {
String IdentityId = callbackVO.getUsers()[0];
Identity identity = onlyOfficeService.getIdentity(IdentityId);
if (identity == null) {
if (identity == null) return error();
VFSLeaf vfsLeaf = onlyOfficeService.getVfsLeaf(fileId);
if (vfsLeaf == null) return error();
boolean isLockedForMe = onlyOfficeService.isLockedForMe(vfsLeaf, identity);
if (isLockedForMe) return error();
boolean canUpdate = onlyOfficeService.canUpdateContent(vfsLeaf, identity, callbackVO.getKey());
if (!canUpdate) {
log.debug("ONLYOFFICE has no right to update file. File ID: " + fileId + ", identity: " + IdentityId);
return error();
}
boolean canUpdate = onlyOfficeService.canUpdateContent(fileId, identity);
LockResult lock = onlyOfficeService.lock(vfsLeaf, identity);
return lock != null? success(): error();
}
private CallbackResponseVO unlock(String fileId, CallbackVO callbackVO) {
VFSLeaf vfsLeaf = onlyOfficeService.getVfsLeaf(fileId);
if (vfsLeaf == null) return error();
boolean lastUser = callbackVO.getUsers() == null || callbackVO.getUsers().length == 0;
if (lastUser) {
onlyOfficeService.unlock(vfsLeaf);
}
return success();
}
private CallbackResponseVO updateContent(String fileId, CallbackVO callbackVO) {
String IdentityId = callbackVO.getUsers()[0];
Identity identity = onlyOfficeService.getIdentity(IdentityId);
if (identity == null) return error();
VFSLeaf vfsLeaf = onlyOfficeService.getVfsLeaf(fileId);
if (vfsLeaf == null) return error();
boolean canUpdate = onlyOfficeService.canUpdateContent(vfsLeaf, identity, callbackVO.getKey());
if (!canUpdate) {
log.debug("Access has not right to update file. File ID: " + fileId + ", identity: " + IdentityId);
log.debug("ONLYOFFICE has no right to update file. File ID: " + fileId + ", identity: " + IdentityId);
return error();
}
boolean updated = onlyOfficeService.updateContent(fileId, identity, callbackVO.getUrl());
boolean updated = onlyOfficeService.updateContent(vfsLeaf, identity, callbackVO.getUrl());
onlyOfficeService.unlock(vfsLeaf);
return updated? success(): error();
}
......
......@@ -402,11 +402,10 @@ public class ApiConfigBuilder {
private String mode;
private User user;
// not implemented yet
// private Customization customization;
// private Object recent;
// private String createUrl;
// private Customization costumisazion;
// private String createUrl;
// private Embedded embedded;
// private Customization costumisazion;
public String getCallbackUrl() {
return callbackUrl;
......
......@@ -21,7 +21,9 @@ package org.olat.core.commons.services.doceditor.onlyoffice.ui;
import org.olat.core.commons.services.doceditor.DocEditor.Mode;
import org.olat.core.commons.services.doceditor.DocEditorSecurityCallback;
import org.olat.core.commons.services.doceditor.DocEditorSecurityCallbackBuilder;
import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeModule;
import org.olat.core.commons.services.doceditor.onlyoffice.OnlyOfficeService;
import org.olat.core.commons.services.vfs.VFSMetadata;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
......@@ -47,11 +49,25 @@ public class OnlyOfficeEditorController extends BasicController {
@Autowired
private OnlyOfficeModule onlyOfficeModule;
@Autowired
private OnlyOfficeService onlyOfficeService;
public OnlyOfficeEditorController(UserRequest ureq, WindowControl wControl, VFSLeaf vfsLeaf,
final DocEditorSecurityCallback securityCallback) {
super(ureq, wControl);
DocEditorSecurityCallback secCallback = securityCallback;
if (onlyOfficeService.isLockNeeded(secCallback.getMode())) {
if (onlyOfficeService.isLockedForMe(vfsLeaf, getIdentity())) {
secCallback = DocEditorSecurityCallbackBuilder.clone(secCallback)
.withMode(Mode.VIEW)
.build();
showWarning("editor.warning.locked");
} else {
onlyOfficeService.lock(vfsLeaf, getIdentity());
}
}
VelocityContainer mainVC = createVelocityContainer("editor");
VFSMetadata vfsMetadata = vfsLeaf.getMetaInfo();
......@@ -59,7 +75,7 @@ public class OnlyOfficeEditorController extends BasicController {
mainVC.contextPut("warning", translate("editor.warning.no.metadata"));
} else {
String apiConfig = ApiConfigBuilder.builder(vfsMetadata, getIdentity())
.withEdit(Mode.EDIT.equals(securityCallback.getMode()))
.withEdit(Mode.EDIT.equals(secCallback.getMode()))
.buildJson();
log.debug("OnlyOffice ApiConfig: " + apiConfig);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment