Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lock notification #7026

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions frontend/src/pages/lib-content-view/lib-content-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { navigate } from '@gatsbyjs/reach-router';
import { gettext, siteRoot, username } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import collabServer from '../../utils/collab-server';
import { Dirent, FileTag, RepoTag, RepoInfo } from '../../models';
import TreeNode from '../../components/tree-view/tree-node';
import treeHelper from '../../components/tree-view/tree-helper';
Expand All @@ -32,7 +31,7 @@ import Detail from '../../components/dirent-detail';
import DirColumnView from '../../components/dir-view-mode/dir-column-view';
import SelectedDirentsToolbar from '../../components/toolbar/selected-dirents-toolbar';
import { VIEW_TYPE } from '../../metadata/constants';

import WebSocketClient from '../../utils/websocket-service';
import '../../css/lib-content-view.css';

dayjs.extend(relativeTime);
Expand All @@ -49,12 +48,12 @@ class LibContentView extends React.Component {

constructor(props) {
super(props);

let isTreePanelShown = true;
const storedTreePanelState = localStorage.getItem('sf_dir_view_tree_panel_open');
if (storedTreePanelState != undefined) {
isTreePanelShown = storedTreePanelState == 'true';
}
this.socket = new WebSocketClient(this.onMessageCallback, this.props.repoID);
this.state = {
currentMode: cookie.load('seafile_view_mode') || LIST_MODE,
isTreePanelShown: isTreePanelShown, // display the 'dirent tree' side panel
Expand Down Expand Up @@ -149,6 +148,38 @@ class LibContentView extends React.Component {
this.calculatePara(this.props);
}

onMessageCallback = (data) => {
if (data.type === 'file-lock-changed') {
const currentUrl = window.location.href;
const parsedUrl = new URL(currentUrl);
const pathParts = parsedUrl.pathname.split('/').filter(part => part.length > 0);
const dirRouter = decodeURIComponent(pathParts.slice(3).join('/'));
let notiUrlIndex = '';
if (data.content.path.includes('/')) {
notiUrlIndex = data.content.path.lastIndexOf('/');
}
const notifRouter = data.content.path.slice(0, notiUrlIndex);
if (dirRouter === notifRouter) {
const dirent = { name: data.content.path.split('/').pop() };
if (data.content.change_event === 'locked') {
if (data.content.expire === -1) {
this.updateDirent(dirent, 'is_freezed', true);
} else {
this.updateDirent(dirent, 'is_freezed', false);
}
this.updateDirent(dirent, 'is_locked', true);
this.updateDirent(dirent, 'locked_by_me', true);
let lockName = data.content.lock_user.split('@');
this.updateDirent(dirent, 'lock_owner_name', lockName[0]);
} else if (data.content.change_event === 'unlocked') {
this.updateDirent(dirent, 'is_locked', false);
this.updateDirent(dirent, 'locked_by_me', false);
this.updateDirent(dirent, 'lock_owner_name', '');
}
}
}
};

UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.repoID !== this.props.repoID) {
this.calculatePara(nextProps);
Expand Down Expand Up @@ -244,7 +275,6 @@ class LibContentView extends React.Component {

componentWillUnmount() {
window.onpopstate = this.oldonpopstate;
collabServer.unwatchRepo(this.props.repoID, this.onRepoUpdateEvent);
this.unsubscribeEvent();
this.unsubscribeEventBus && this.unsubscribeEventBus();
this.props.eventBus.dispatch(EVENT_BUS_TYPE.CURRENT_LIBRARY_CHANGED, {
Expand All @@ -255,6 +285,7 @@ class LibContentView extends React.Component {
isLibView: false,
currentRepoInfo: null,
});
this.socket.close();
}

componentDidUpdate(prevProps, prevState) {
Expand Down Expand Up @@ -383,11 +414,7 @@ class LibContentView extends React.Component {

// load data
loadDirData = (path) => {
let repoID = this.props.repoID;

// listen current repo
collabServer.watchRepo(repoID, this.onRepoUpdateEvent);

// list used FileTags
this.updateUsedRepoTags();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { gettext, canGenerateShareLink, isPro, mediaUrl, canLockUnlockFile } from '../../../utils/constants';
import ButtonGroup from './button-group';
import ButtonItem from './button-item';
import CollabUsersButton from './collab-users-button';

Check warning on line 7 in frontend/src/pages/markdown-editor/header-toolbar/header-toolbar.js

View workflow job for this annotation

GitHub Actions / build

'CollabUsersButton' is defined but never used
import MoreMenu from './more-menu';
import FileInfo from './file-info';
import Icon from '../../../components/icon';
Expand All @@ -15,7 +15,6 @@

import '../css/header-toolbar.css';

const { seafileCollabServer } = window.app.config;
const { canDownloadFile, repoID, filePath } = window.app.pageOptions;

const propTypes = {
Expand Down Expand Up @@ -133,13 +132,6 @@
isStarred={this.props.fileInfo.isStarred}
/>
<div className="topbar-btn-container">
{(seafileCollabServer && this.props.collabUsers.length > 0) &&
<CollabUsersButton
className="collab-users-dropdown"
users={this.props.collabUsers}
id="usersButton"
/>
}
<ButtonGroup>
{(canLockUnlockFile && !isLocked) && (
<ButtonItem
Expand Down Expand Up @@ -261,13 +253,6 @@
<FileInfo toggleStar={this.props.toggleStar} editorApi={this.props.editorApi}
fileInfo={this.props.fileInfo}/>
<div className="topbar-btn-container">
{(seafileCollabServer && this.props.collabUsers.length > 0) &&
<CollabUsersButton
className="collab-users-dropdown"
users={this.props.collabUsers}
id="usersButton"
/>
}
<ButtonGroup>
{saving ?
<button type={'button'} className={'btn btn-icon btn-secondary btn-active'}>
Expand Down
26 changes: 1 addition & 25 deletions frontend/src/pages/markdown-editor/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { Fragment } from 'react';
import io from 'socket.io-client';
import {
EXTERNAL_EVENTS,
EventBus,
Expand All @@ -22,7 +21,7 @@ const CryptoJS = require('crypto-js');
const URL = require('url-parse');

const { repoID, filePath, fileName, isLocked, lockedByMe } = window.app.pageOptions;
const { siteRoot, serviceUrl, seafileCollabServer } = window.app.config;
const { siteRoot, serviceUrl } = window.app.config;
const userInfo = window.app.userInfo;
const IMAGE_SUFFIXES = ['png', 'PNG', 'jpg', 'JPG', 'jpeg', 'JPEG', 'gif', 'GIF'];

Expand All @@ -46,7 +45,6 @@ class MarkdownEditor extends React.Component {
id: '',
},
editorMode: 'rich',
collabServer: seafileCollabServer ? seafileCollabServer : null,
showMarkdownEditorDialog: false,
showShareLinkDialog: false,
showInsertFileDialog: false,
Expand All @@ -64,16 +62,6 @@ class MarkdownEditor extends React.Component {
};

this.timer = null;

if (this.state.collabServer) {
const socket = io(this.state.collabServer);
this.socket = socket;
socket.on('presence', (data) => this.receivePresenceData(data));
socket.on('repo_update', (data) => this.receiveUpdateData(data));
socket.on('connect', () => {
this.socket_id = socket.id;
});
}
this.editorRef = React.createRef();
this.isParticipant = false;
this.editorSelection = null;
Expand All @@ -92,18 +80,6 @@ class MarkdownEditor extends React.Component {
}
};

emitSwitchEditor = (is_editing = false) => {
if (userInfo && this.state.collabServer) {
const { repoID, path } = this.state.fileInfo;
this.socket.emit('presence', {
request: 'editing',
doc_id: CryptoJS.MD5(repoID + path).toString(),
user: userInfo,
is_editing,
});
}
};

receiveUpdateData(data) {
let currentTime = new Date();
if ((parseFloat(currentTime - this.lastModifyTime) / 1000) <= 5) {
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/pages/plain-markdown-editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const propTypes = {
};

const { repoID, filePath, fileName, isLocked, lockedByMe } = window.app.pageOptions;
const { seafileCollabServer } = window.app.config;
const userInfo = window.app.userInfo;

const initOptions = {
Expand All @@ -41,7 +40,6 @@ const initOptions = {
id: '',
},
editorMode: 'plain',
collabServer: seafileCollabServer ? seafileCollabServer : null,
showMarkdownEditorDialog: false,
showShareLinkDialog: false,
showInsertFileDialog: false,
Expand Down
43 changes: 0 additions & 43 deletions frontend/src/utils/collab-server.js

This file was deleted.

2 changes: 1 addition & 1 deletion frontend/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ export const fileServerRoot = window.app.config.fileServerRoot;
export const useGoFileserver = window.app.config.useGoFileserver;
export const seafileVersion = window.app.config.seafileVersion;
export const serviceURL = window.app.config.serviceURL;
export const notificationServerUrl = window.app.config.notificationServerUrl;
export const appAvatarURL = window.app.config.avatarURL;
export const faviconPath = window.app.config.faviconPath;
export const loginBGPath = window.app.config.loginBGPath;
export const enableRepoAutoDel = window.app.config.enableRepoAutoDel;

// pageOptions
export const trashReposExpireDays = window.app.pageOptions.trashReposExpireDays;
export const seafileCollabServer = window.app.pageOptions.seafileCollabServer;
export const name = window.app.pageOptions.name;
export const contactEmail = window.app.pageOptions.contactEmail;
export const username = window.app.pageOptions.username;
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/utils/user-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ class UserAPI {
form.append('reshare', reshare);
return this.req.put(url, form);
}

getNotificationToken(repoID) {
const url = this.server + '/api/v2.1/repos/' + repoID + '/repo-notification-jwt-token/';
return this.req.get(url);
}
}

let userAPI = new UserAPI();
Expand Down
105 changes: 105 additions & 0 deletions frontend/src/utils/websocket-service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { userAPI } from './user-api';
import { notificationServerUrl } from './constants';


class WebSocketClient {
constructor(onMessageCallback, repoId) {

this.url = notificationServerUrl; // WebSocket address;
this.repoId = repoId;
this.socket = null;
this.shouldReconnect = true;
this.onMessageCallback = onMessageCallback;
if (notificationServerUrl !== '') {
this.connect();
}
}

async connect() {
this.socket = new WebSocket(this.url);

this.socket.onopen = async () => {
const msg = await this.formatSubscriptionMsg();
this.socket.send(JSON.stringify(msg));
};

// listen message from WebSocket server
this.socket.onmessage = (event) => {
const parsedData = JSON.parse(event.data);
// jwt-expire reconnect
if (parsedData.type === 'jwt-expired') {
const msg = this.formatSubscriptionMsg();
this.socket.send(JSON.stringify(msg));
} else {
this.onMessageCallback(parsedData);
}
};

this.socket.onerror = (error) => {
throw error;
};

// reconnect WebSocket
this.socket.onclose = () => {
if (this.shouldReconnect) {
this.reconnect();
}
};
}

async getRepoJwtToken() {
const response = await userAPI.getNotificationToken(this.repoId).then(res => {
return res.data;
}).catch(err => {
throw err;
});
return response.token;
}

async formatSubscriptionMsg() {
const repoToken = await this.getRepoJwtToken();
const jsonData = {
type: 'subscribe',
content: {
repos: [
{
id: this.repoId,
jwt_token: repoToken,
},
],
},
};
return jsonData;
}

formatUnSubscriptionMsg() {
const jsonData = {
type: 'unsubscribe',
content: {
repos: [
{
id: this.repoId
},
],
},
};
return jsonData;
}

close() {
this.shouldReconnect = false;
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
const msg = this.formatUnSubscriptionMsg();
this.socket.send(JSON.stringify(msg));
this.socket.close();
}
}

reconnect() {
setTimeout(() => {
this.connect(this.repoId);
}, 5000);
}
}

export default WebSocketClient;
3 changes: 2 additions & 1 deletion seahub/base/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ def base(request):
'side_nav_footer_custom_html': SIDE_NAV_FOOTER_CUSTOM_HTML,
'about_dialog_custom_html': ABOUT_DIALOG_CUSTOM_HTML,
'enable_repo_auto_del': ENABLE_REPO_AUTO_DEL,
'enable_seadoc': ENABLE_SEADOC
'enable_seadoc': ENABLE_SEADOC,
'notification_server_url': dj_settings.NOTIFICATION_SERVER_URL,
}

if request.user.is_staff:
Expand Down
Loading
Loading