mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-02 07:27:04 +00:00
Sync apply folder ex props (#5666)
* init sync apply folder ex-props * clear worker-map * opt apply and remove useless code * use new sync-apply-folder-ex-props api * opt try exception
This commit is contained in:
@@ -20,54 +20,16 @@ class ConfirmApplyFolderPropertiesDialog extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
submitting: true
|
submitting: false
|
||||||
};
|
};
|
||||||
this.timer = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { repoID, path } = this.props;
|
|
||||||
seafileAPI.queryFolderItemsExtendedPropertiesStatus(repoID, path).then(res => {
|
|
||||||
if (res.data.is_finished) {
|
|
||||||
this.timer && clearInterval(this.timer);
|
|
||||||
this.setState({ submitting: false });
|
|
||||||
} else {
|
|
||||||
this.queryStatus();
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
//
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.timer && clearInterval(this.timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
queryStatus = () =>{
|
|
||||||
const { repoID, path } = this.props;
|
|
||||||
this.timer = setInterval(() => {
|
|
||||||
seafileAPI.queryFolderItemsExtendedPropertiesStatus(repoID, path).then(res => {
|
|
||||||
if (res.data.is_finished === true) {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
toaster.success(gettext('Applied folder properties'));
|
|
||||||
this.props.toggle();
|
|
||||||
}
|
|
||||||
}).catch(error => {
|
|
||||||
clearInterval(this.timer);
|
|
||||||
this.timer = null;
|
|
||||||
let errorMsg = Utils.getErrorMsg(error);
|
|
||||||
toaster.danger(errorMsg);
|
|
||||||
this.setState({ submitting: false });
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
submit = () => {
|
submit = () => {
|
||||||
const { repoID, path } = this.props;
|
const { repoID, path } = this.props;
|
||||||
this.setState({ submitting: true });
|
this.setState({ submitting: true });
|
||||||
seafileAPI.setFolderItemsExtendedProperties(repoID, path).then(() => {
|
seafileAPI.applyFolderExtendedProperties(repoID, path).then(() => {
|
||||||
this.queryStatus();
|
toaster.success('Applied folder properties');
|
||||||
|
this.props.toggle();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
let errorMsg = Utils.getErrorMsg(error);
|
let errorMsg = Utils.getErrorMsg(error);
|
||||||
toaster.danger(errorMsg);
|
toaster.danger(errorMsg);
|
||||||
@@ -89,7 +51,7 @@ class ConfirmApplyFolderPropertiesDialog extends React.Component {
|
|||||||
</p>
|
</p>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color='secondary' onClick={this.props.toggle}>{gettext('Cancel')}</Button>
|
<Button color='secondary' onClick={this.props.toggle} disabled={submitting}>{gettext('Cancel')}</Button>
|
||||||
<Button color='primary' className='flex-shrink-0 apply-properties' disabled={submitting} onClick={this.submit}>
|
<Button color='primary' className='flex-shrink-0 apply-properties' disabled={submitting} onClick={this.submit}>
|
||||||
{submitting ? (<Loading />) : (<>{gettext('Submit')}</>)}
|
{submitting ? (<Loading />) : (<>{gettext('Submit')}</>)}
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -1254,7 +1254,7 @@ class LibContentView extends React.Component {
|
|||||||
|
|
||||||
this.addDirent(newFileName, 'file', res.data.size);
|
this.addDirent(newFileName, 'file', res.data.size);
|
||||||
this.setState({isConvertLoading: false});
|
this.setState({isConvertLoading: false});
|
||||||
let message = gettext('Successfully converted file.')
|
let message = gettext('Successfully converted file.');
|
||||||
toaster.success(message);
|
toaster.success(message);
|
||||||
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from threading import Lock
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@@ -18,7 +22,7 @@ from seahub.api2.throttling import UserRateThrottle
|
|||||||
from seahub.api2.utils import api_error
|
from seahub.api2.utils import api_error
|
||||||
from seahub.base.templatetags.seahub_tags import email2nickname
|
from seahub.base.templatetags.seahub_tags import email2nickname
|
||||||
from seahub.settings import DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, \
|
from seahub.settings import DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, \
|
||||||
EX_PROPS_TABLE, EX_EDITABLE_COLUMNS, SEAF_EVENTS_IO_SERVER_URL
|
EX_PROPS_TABLE, EX_EDITABLE_COLUMNS
|
||||||
from seahub.tags.models import FileUUIDMap
|
from seahub.tags.models import FileUUIDMap
|
||||||
from seahub.utils import normalize_file_path, EMPTY_SHA1
|
from seahub.utils import normalize_file_path, EMPTY_SHA1
|
||||||
from seahub.utils.repo import parse_repo_perm
|
from seahub.utils.repo import parse_repo_perm
|
||||||
@@ -28,27 +32,8 @@ from seahub.views import check_folder_permission
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def request_can_set_ex_props(repo_id, path):
|
class QueryException(Exception):
|
||||||
url = SEAF_EVENTS_IO_SERVER_URL.strip('/') + '/can-set-ex-props'
|
pass
|
||||||
resp = requests.post(url, json={'repo_id': repo_id, 'path': path})
|
|
||||||
return resp.json()
|
|
||||||
|
|
||||||
|
|
||||||
def add_set_folder_ex_props_task(repo_id, path, username):
|
|
||||||
url = SEAF_EVENTS_IO_SERVER_URL.strip('/') + '/set-folder-items-ex-props'
|
|
||||||
context = {
|
|
||||||
'repo_id': repo_id,
|
|
||||||
'path': path,
|
|
||||||
'文件负责人': email2nickname(username)
|
|
||||||
}
|
|
||||||
resp = requests.post(url, json=context)
|
|
||||||
return resp.json()
|
|
||||||
|
|
||||||
|
|
||||||
def query_set_ex_props_status(repo_id, path):
|
|
||||||
url = SEAF_EVENTS_IO_SERVER_URL.strip('/') + '/query-set-ex-props-status'
|
|
||||||
resp = requests.get(url, params={'repo_id': repo_id, 'path': path})
|
|
||||||
return resp.json()
|
|
||||||
|
|
||||||
|
|
||||||
def check_table(seatable_api: SeaTableAPI):
|
def check_table(seatable_api: SeaTableAPI):
|
||||||
@@ -100,13 +85,9 @@ class ExtendedPropertiesView(APIView):
|
|||||||
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
resp_json = request_can_set_ex_props(repo_id, path)
|
can_set, error_type = ApplyFolderExtendedPropertiesView.can_set_file_or_folder(repo_id, path)
|
||||||
if not resp_json.get('can_set', False):
|
if not can_set:
|
||||||
if resp_json.get('error_type') == 'higher_being_set':
|
return api_error(status.HTTP_400_BAD_REQUEST, 'Another task is running')
|
||||||
error_msg = 'Another task is running'
|
|
||||||
else:
|
|
||||||
error_msg = 'Please try again later'
|
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
||||||
|
|
||||||
# check base
|
# check base
|
||||||
try:
|
try:
|
||||||
@@ -261,13 +242,9 @@ class ExtendedPropertiesView(APIView):
|
|||||||
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
resp_json = request_can_set_ex_props(repo_id, path)
|
can_set, error_type = ApplyFolderExtendedPropertiesView.can_set_file_or_folder(repo_id, path)
|
||||||
if not resp_json.get('can_set', False):
|
if not can_set:
|
||||||
if resp_json.get('error_type') == 'higher_being_set':
|
return api_error(status.HTTP_400_BAD_REQUEST, 'Another task is running')
|
||||||
error_msg = 'Another task is running'
|
|
||||||
else:
|
|
||||||
error_msg = 'Please try again later'
|
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
||||||
|
|
||||||
# check base
|
# check base
|
||||||
try:
|
try:
|
||||||
@@ -358,19 +335,206 @@ class ExtendedPropertiesView(APIView):
|
|||||||
return Response({'success': True})
|
return Response({'success': True})
|
||||||
|
|
||||||
|
|
||||||
class FolderItemsExtendedPropertiesView(APIView):
|
class ApplyFolderExtendedPropertiesView(APIView):
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
throttle_classes = (UserRateThrottle,)
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
apply_lock = Lock()
|
||||||
|
worker_map = defaultdict(list)
|
||||||
|
list_max = 1000
|
||||||
|
step = 500
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_set_file_or_folder(cls, repo_id, path):
|
||||||
|
"""
|
||||||
|
:return: can_apply -> bool, error_type -> string or None
|
||||||
|
"""
|
||||||
|
paths = cls.worker_map.get(repo_id, [])
|
||||||
|
for cur_path in paths:
|
||||||
|
if path.startswith(cur_path):
|
||||||
|
return False, 'higer_folder_applying'
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_apply_folder(cls, repo_id, path):
|
||||||
|
"""
|
||||||
|
:return: can_apply -> bool, error_type -> string or None
|
||||||
|
"""
|
||||||
|
for cur_repo_id, paths in cls.worker_map.items():
|
||||||
|
if repo_id != cur_repo_id:
|
||||||
|
continue
|
||||||
|
for cur_path in paths:
|
||||||
|
if cur_path.startswith(path):
|
||||||
|
return False, 'sub_folder_applying'
|
||||||
|
if path.startswith(cur_path):
|
||||||
|
return False, 'higer_folder_applying'
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
def md5_repo_id_parent_path(self, repo_id, parent_path):
|
||||||
|
parent_path = parent_path.rstrip('/') if parent_path != '/' else '/'
|
||||||
|
return hashlib.md5((repo_id + parent_path).encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def query_fileuuids_map(self, repo_id, file_paths):
|
||||||
|
"""
|
||||||
|
:return: {file_path: fileuuid}
|
||||||
|
"""
|
||||||
|
file_path_2_uuid_map = {}
|
||||||
|
no_uuid_file_paths = []
|
||||||
|
|
||||||
|
# query uuids
|
||||||
|
for i in range(0, len(file_paths), self.step):
|
||||||
|
parent_path_2_filenames_map = defaultdict(list)
|
||||||
|
for file_path in file_paths[i: i+self.step]:
|
||||||
|
parent_path, filename = os.path.split(file_path)
|
||||||
|
parent_path_2_filenames_map[parent_path].append(filename)
|
||||||
|
for parent_path, filenames in parent_path_2_filenames_map.items():
|
||||||
|
md5 = self.md5_repo_id_parent_path(repo_id, parent_path)
|
||||||
|
results = FileUUIDMap.objects.filter(repo_id=repo_id, repo_id_parent_path_md5=md5, filename__in=filenames)
|
||||||
|
for uuid_item in results:
|
||||||
|
file_path_2_uuid_map[os.path.join(parent_path, uuid_item.filename)] = uuid_item.uuid.hex
|
||||||
|
## some filename no uuids
|
||||||
|
for filename in filenames:
|
||||||
|
cur_file_path = os.path.join(parent_path, filename)
|
||||||
|
if cur_file_path not in file_path_2_uuid_map:
|
||||||
|
no_uuid_file_paths.append({'file_path': cur_file_path, 'uuid': uuid4().hex, 'repo_id_parent_path_md5': md5})
|
||||||
|
# create uuids
|
||||||
|
for i in range(0, len(no_uuid_file_paths), self.step):
|
||||||
|
uuid_objs = []
|
||||||
|
for j in range(i, min(i+self.step, len(no_uuid_file_paths))):
|
||||||
|
no_uuid_file_path = no_uuid_file_paths[j]
|
||||||
|
kwargs = {
|
||||||
|
'uuid': no_uuid_file_path['uuid'],
|
||||||
|
'repo_id': repo_id,
|
||||||
|
'repo_id_parent_path_md5': no_uuid_file_path['repo_id_parent_path_md5'],
|
||||||
|
'parent_path': os.path.dirname(no_uuid_file_path['file_path']),
|
||||||
|
'filename': os.path.basename(no_uuid_file_path['file_path']),
|
||||||
|
'is_dir': 0
|
||||||
|
}
|
||||||
|
uuid_objs.append(FileUUIDMap(**kwargs))
|
||||||
|
FileUUIDMap.objects.bulk_create(uuid_objs)
|
||||||
|
for j in range(i, min(i+self.step, len(no_uuid_file_paths))):
|
||||||
|
file_path_2_uuid_map[no_uuid_file_paths[j]['file_path']] = no_uuid_file_paths[j]['uuid']
|
||||||
|
|
||||||
|
return file_path_2_uuid_map
|
||||||
|
|
||||||
|
def query_path_2_row_id_map(self, repo_id, query_list, seatable_api: SeaTableAPI):
|
||||||
|
"""
|
||||||
|
:return: path_2_row_id_map -> {path: row_id}
|
||||||
|
"""
|
||||||
|
path_2_row_id_map = {}
|
||||||
|
for i in range(0, len(query_list), self.step):
|
||||||
|
paths_str = ', '.join(map(lambda x: f"'{x['path']}'", query_list[i: i+self.step]))
|
||||||
|
sql = f"SELECT `_id`, `Path` FROM `{EX_PROPS_TABLE}` WHERE `Repo ID`='{repo_id}' AND `Path` IN ({paths_str})"
|
||||||
|
resp_json = seatable_api.query(sql, convert=True)
|
||||||
|
rows = resp_json['results']
|
||||||
|
path_2_row_id_map.update({row['Path']: row['_id'] for row in rows})
|
||||||
|
return path_2_row_id_map
|
||||||
|
|
||||||
|
def query_ex_props_by_path(self, repo_id, path, seatable_api: SeaTableAPI):
|
||||||
|
columns_str = ', '.join(map(lambda x: f"`{x}`", EX_EDITABLE_COLUMNS))
|
||||||
|
sql = f"SELECT {columns_str} FROM `{EX_PROPS_TABLE}` WHERE `Repo ID` = '{repo_id}' AND `Path` = '{path}'"
|
||||||
|
resp_json = seatable_api.query(sql, convert=True)
|
||||||
|
if not resp_json['results']:
|
||||||
|
return None
|
||||||
|
row = resp_json['results'][0]
|
||||||
|
return row
|
||||||
|
|
||||||
|
def update_ex_props(self, update_list, ex_props, seatable_api: SeaTableAPI):
|
||||||
|
for i in range(0, len(update_list), self.step):
|
||||||
|
updates = []
|
||||||
|
for j in range(i, min(len(update_list), i+self.step)):
|
||||||
|
updates.append({
|
||||||
|
'row_id': update_list[j]['row_id'],
|
||||||
|
'row': ex_props
|
||||||
|
})
|
||||||
|
seatable_api.update_rows_by_dtable_db(EX_PROPS_TABLE, updates)
|
||||||
|
|
||||||
|
def insert_ex_props(self, repo_id, insert_list, ex_props, context, seatable_api: SeaTableAPI):
|
||||||
|
for i in range(0, len(insert_list), self.step):
|
||||||
|
rows = []
|
||||||
|
for j in range(i, min(len(insert_list), i+self.step)):
|
||||||
|
row = {
|
||||||
|
'Repo ID': repo_id,
|
||||||
|
'File': os.path.basename(insert_list[j]['path']),
|
||||||
|
'UUID': insert_list[j].get('fileuuid'),
|
||||||
|
'Path': insert_list[j]['path'],
|
||||||
|
'创建日期': str(datetime.fromtimestamp(insert_list[j]['mtime'])),
|
||||||
|
'文件负责人': context['文件负责人']
|
||||||
|
}
|
||||||
|
row.update(ex_props)
|
||||||
|
rows.append(row)
|
||||||
|
seatable_api.batch_append_rows(EX_PROPS_TABLE, rows)
|
||||||
|
|
||||||
|
def apply_folder(self, repo_id, folder_path, context, seatable_api: SeaTableAPI, folder_props):
|
||||||
|
stack = [folder_path]
|
||||||
|
|
||||||
|
query_list = [] # [{path, type}]
|
||||||
|
file_query_list = [] # [path]
|
||||||
|
|
||||||
|
update_list = [] # [{}]
|
||||||
|
insert_list = [] # [{}]
|
||||||
|
|
||||||
|
# query folder props
|
||||||
|
while stack:
|
||||||
|
current_path = stack.pop()
|
||||||
|
dirents = seafile_api.list_dir_by_path(repo_id, current_path)
|
||||||
|
if not dirents:
|
||||||
|
continue
|
||||||
|
for dirent in dirents:
|
||||||
|
dirent_path = os.path.join(current_path, dirent.obj_name)
|
||||||
|
if stat.S_ISDIR(dirent.mode):
|
||||||
|
query_list.append({'path': dirent_path, 'type': 'dir', 'mtime': dirent.mtime})
|
||||||
|
stack.append(dirent_path)
|
||||||
|
else:
|
||||||
|
if dirent.obj_id == EMPTY_SHA1:
|
||||||
|
continue
|
||||||
|
query_list.append({'path': dirent_path, 'type': 'file', 'mtime': dirent.mtime})
|
||||||
|
file_query_list.append(dirent_path)
|
||||||
|
# query ex-props
|
||||||
|
if len(query_list) >= self.list_max:
|
||||||
|
file_path_2_uuid_map = self.query_fileuuids_map(repo_id, file_query_list)
|
||||||
|
path_2_row_id_map = self.query_path_2_row_id_map(repo_id, query_list, seatable_api)
|
||||||
|
for query_item in query_list:
|
||||||
|
if query_item['path'] in path_2_row_id_map:
|
||||||
|
query_item['row_id'] = path_2_row_id_map.get(query_item['path'])
|
||||||
|
update_list.append(query_item)
|
||||||
|
else:
|
||||||
|
if query_item['type'] == 'file':
|
||||||
|
query_item['fileuuid'] = file_path_2_uuid_map.get(query_item['path'])
|
||||||
|
insert_list.append(query_item)
|
||||||
|
query_list = file_query_list = []
|
||||||
|
# update ex-props
|
||||||
|
if len(update_list) >= self.list_max:
|
||||||
|
self.update_ex_props(update_list, folder_props, seatable_api)
|
||||||
|
update_list = []
|
||||||
|
# insert ex-props
|
||||||
|
if len(insert_list) >= self.list_max:
|
||||||
|
self.insert_ex_props(repo_id, insert_list, folder_props, context, seatable_api)
|
||||||
|
insert_list = []
|
||||||
|
|
||||||
|
# handle query/update/insert left
|
||||||
|
file_path_2_uuid_map = self.query_fileuuids_map(repo_id, file_query_list)
|
||||||
|
path_2_row_id_map = self.query_path_2_row_id_map(repo_id, query_list, seatable_api)
|
||||||
|
for query_item in query_list:
|
||||||
|
if query_item['path'] in path_2_row_id_map:
|
||||||
|
query_item['row_id'] = path_2_row_id_map.get(query_item['path'])
|
||||||
|
update_list.append(query_item)
|
||||||
|
else:
|
||||||
|
if query_item['type'] == 'file':
|
||||||
|
query_item['fileuuid'] = file_path_2_uuid_map.get(query_item['path'])
|
||||||
|
insert_list.append(query_item)
|
||||||
|
self.update_ex_props(update_list, folder_props, seatable_api)
|
||||||
|
self.insert_ex_props(repo_id, insert_list, folder_props, context, seatable_api)
|
||||||
|
|
||||||
def post(self, request, repo_id):
|
def post(self, request, repo_id):
|
||||||
if not all((DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_PROPS_TABLE)):
|
if not all((DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_PROPS_TABLE)):
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, 'Feature not enabled')
|
return api_error(status.HTTP_403_FORBIDDEN, 'Feature not enabled')
|
||||||
# arguments check
|
# arguments check
|
||||||
path = request.data.get('path')
|
path = request.data.get('path')
|
||||||
|
path = normalize_file_path(path)
|
||||||
if not path:
|
if not path:
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
|
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
|
||||||
path = normalize_file_path(path)
|
|
||||||
parent_dir = os.path.dirname(path)
|
parent_dir = os.path.dirname(path)
|
||||||
|
|
||||||
dirent = seafile_api.get_dirent_by_path(repo_id, path)
|
dirent = seafile_api.get_dirent_by_path(repo_id, path)
|
||||||
@@ -383,64 +547,42 @@ class FolderItemsExtendedPropertiesView(APIView):
|
|||||||
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
||||||
|
|
||||||
|
# with lock check repo and path can apply props
|
||||||
|
with self.apply_lock:
|
||||||
|
can_apply, _ = self.can_apply_folder(repo_id, path)
|
||||||
|
if not can_apply:
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, 'Another task is running')
|
||||||
|
# with lock add repo and path to apply task map
|
||||||
|
self.worker_map[repo_id].append(path)
|
||||||
|
|
||||||
# request props from seatable
|
# request props from seatable
|
||||||
try:
|
try:
|
||||||
seatable_api = SeaTableAPI(SEATABLE_EX_PROPS_BASE_API_TOKEN, DTABLE_WEB_SERVER)
|
seatable_api = SeaTableAPI(SEATABLE_EX_PROPS_BASE_API_TOKEN, DTABLE_WEB_SERVER)
|
||||||
except:
|
except:
|
||||||
logger.error('server: %s token: %s seatable-api fail', DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN)
|
logger.error('server: %s token: %s seatable-api fail', DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN)
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Props table invalid')
|
return api_error(status.HTTP_400_BAD_REQUEST, 'Props base invalid')
|
||||||
|
|
||||||
sql = f"SELECT * FROM `{EX_PROPS_TABLE}` WHERE `Repo ID`='{repo_id}' AND `Path`='{path}'"
|
folder_props = self.query_ex_props_by_path(repo_id, path, seatable_api)
|
||||||
|
if not folder_props:
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, 'The folder is not be set extended properties')
|
||||||
|
|
||||||
|
# apply props
|
||||||
|
context = {'文件负责人': email2nickname(request.user.username)}
|
||||||
try:
|
try:
|
||||||
result = seatable_api.query(sql)
|
self.apply_folder(repo_id, path, context, seatable_api, folder_props)
|
||||||
except Exception as e:
|
except QueryException as e:
|
||||||
logger.exception('query sql: %s error: %s', sql, e)
|
logger.exception('apply folder: %s ex-props query dtable-db error: %s', path, e)
|
||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||||
rows = result.get('results')
|
except Exception as e:
|
||||||
if not rows:
|
logger.exception('apply folder: %s ex-props error: %s', path, e)
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, '%s not set extended properties' % path)
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||||
|
finally:
|
||||||
resp_json = add_set_folder_ex_props_task(repo_id, path, request.user.username)
|
# remove path from worker
|
||||||
|
with self.apply_lock:
|
||||||
error_type = resp_json.get('error_type')
|
for repo_id, paths in self.worker_map.items():
|
||||||
if error_type == 'higher_being_set':
|
if repo_id != repo_id:
|
||||||
error_msg = 'Another task is running'
|
continue
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
self.worker_map[repo_id] = [cur_path for cur_path in paths if cur_path != path]
|
||||||
elif error_type == 'server_busy':
|
if not self.worker_map[repo_id]:
|
||||||
error_msg = 'Server is busy'
|
del self.worker_map[repo_id]
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
||||||
elif error_type == 'sub_folder_setting':
|
|
||||||
error_msg = 'Another task is running'
|
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
||||||
|
|
||||||
return Response({'success': True})
|
return Response({'success': True})
|
||||||
|
|
||||||
|
|
||||||
class FolderItemsPropertiesStatusQueryView(APIView):
|
|
||||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
|
||||||
permission_classes = (IsAuthenticated,)
|
|
||||||
throttle_classes = (UserRateThrottle,)
|
|
||||||
|
|
||||||
def get(self, request, repo_id):
|
|
||||||
if not all((DTABLE_WEB_SERVER, SEATABLE_EX_PROPS_BASE_API_TOKEN, EX_PROPS_TABLE)):
|
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, 'Feature not enabled')
|
|
||||||
# arguments check
|
|
||||||
path = request.GET.get('path')
|
|
||||||
if not path:
|
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, 'path invalid')
|
|
||||||
path = normalize_file_path(path)
|
|
||||||
parent_dir = os.path.dirname(path)
|
|
||||||
|
|
||||||
dirent = seafile_api.get_dirent_by_path(repo_id, path)
|
|
||||||
if not dirent:
|
|
||||||
return api_error(status.HTTP_404_NOT_FOUND, 'Folder %s not found' % path)
|
|
||||||
if not stat.S_ISDIR(dirent.mode):
|
|
||||||
return api_error(status.HTTP_400_BAD_REQUEST, '%s is not a folder' % path)
|
|
||||||
|
|
||||||
# permission check
|
|
||||||
if not parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_edit_on_web:
|
|
||||||
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
|
||||||
|
|
||||||
resp_json = query_set_ex_props_status(repo_id, path)
|
|
||||||
|
|
||||||
return Response(resp_json)
|
|
||||||
|
@@ -867,11 +867,6 @@ if os.environ.get('SEAFILE_DOCS', None):
|
|||||||
LOGO_WIDTH = ''
|
LOGO_WIDTH = ''
|
||||||
ENABLE_WIKI = True
|
ENABLE_WIKI = True
|
||||||
|
|
||||||
####################
|
|
||||||
# events io server #
|
|
||||||
####################
|
|
||||||
SEAF_EVENTS_IO_SERVER_URL = 'http://127.0.0.1:6066'
|
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
# extended properties #
|
# extended properties #
|
||||||
#######################
|
#######################
|
||||||
|
@@ -119,8 +119,7 @@ from seahub.ocm_via_webdav.ocm_api import OCMProviderView
|
|||||||
from seahub.api2.endpoints.repo_share_links import RepoShareLinks, RepoShareLink
|
from seahub.api2.endpoints.repo_share_links import RepoShareLinks, RepoShareLink
|
||||||
from seahub.api2.endpoints.repo_upload_links import RepoUploadLinks, RepoUploadLink
|
from seahub.api2.endpoints.repo_upload_links import RepoUploadLinks, RepoUploadLink
|
||||||
|
|
||||||
from seahub.api2.endpoints.extended_properties import ExtendedPropertiesView, FolderItemsExtendedPropertiesView, \
|
from seahub.api2.endpoints.extended_properties import ExtendedPropertiesView, ApplyFolderExtendedPropertiesView
|
||||||
FolderItemsPropertiesStatusQueryView
|
|
||||||
|
|
||||||
# Admin
|
# Admin
|
||||||
from seahub.api2.endpoints.admin.abuse_reports import AdminAbuseReportsView, AdminAbuseReportView
|
from seahub.api2.endpoints.admin.abuse_reports import AdminAbuseReportsView, AdminAbuseReportView
|
||||||
@@ -429,8 +428,7 @@ urlpatterns = [
|
|||||||
|
|
||||||
## user:file:extended-props
|
## user:file:extended-props
|
||||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/extended-properties/$', ExtendedPropertiesView.as_view(), name='api-v2.1-extended-properties'),
|
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/extended-properties/$', ExtendedPropertiesView.as_view(), name='api-v2.1-extended-properties'),
|
||||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/folder-items-extended-properties/$', FolderItemsExtendedPropertiesView.as_view(), name='api-v2.1-folder-items-extended-properties'),
|
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/apply-folder-extended-properties/$', ApplyFolderExtendedPropertiesView.as_view(), name='api-v2.1-apply-folder-extended-properties'),
|
||||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/folder-items-extended-properties/status-query/$', FolderItemsPropertiesStatusQueryView.as_view(), name='api-v2.1-folder-items-extended-properties-status-query'),
|
|
||||||
|
|
||||||
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/auto-delete/$', RepoAutoDeleteView.as_view(), name='api-v2.1-repo-auto-delete'),
|
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/auto-delete/$', RepoAutoDeleteView.as_view(), name='api-v2.1-repo-auto-delete'),
|
||||||
|
|
||||||
|
@@ -65,13 +65,15 @@ class SeaTableAPI:
|
|||||||
resp = requests.get(url, headers=self.headers)
|
resp = requests.get(url, headers=self.headers)
|
||||||
return parse_response(resp)['metadata']
|
return parse_response(resp)['metadata']
|
||||||
|
|
||||||
def query(self, sql, convert=None, server_only=None):
|
def query(self, sql, convert=None, server_only=None, parameters=None):
|
||||||
url = f"{self.dtable_db_url.strip('/')}/api/v1/query/{self.dtable_uuid}/?from=dtable_web"
|
url = f"{self.dtable_db_url.strip('/')}/api/v1/query/{self.dtable_uuid}/?from=dtable_web"
|
||||||
data = {'sql': sql}
|
data = {'sql': sql}
|
||||||
if convert is not None:
|
if convert is not None:
|
||||||
data['convert_keys'] = convert
|
data['convert_keys'] = convert
|
||||||
if server_only is not None:
|
if server_only is not None:
|
||||||
data['server_only'] = server_only
|
data['server_only'] = server_only
|
||||||
|
if parameters:
|
||||||
|
data['parameters'] = parameters
|
||||||
resp = requests.post(url, json=data, headers=self.headers)
|
resp = requests.post(url, json=data, headers=self.headers)
|
||||||
return parse_response(resp)
|
return parse_response(resp)
|
||||||
|
|
||||||
@@ -109,9 +111,36 @@ class SeaTableAPI:
|
|||||||
resp = requests.put(url, headers=self.headers, json=data)
|
resp = requests.put(url, headers=self.headers, json=data)
|
||||||
return parse_response(resp)
|
return parse_response(resp)
|
||||||
|
|
||||||
|
def batch_append_rows(self, table_name, rows):
|
||||||
|
url = f"{self.dtable_server_url.strip('/')}/api/v1/dtables/{self.dtable_uuid}/batch-append-rows/?from=dtable_web"
|
||||||
|
data = {
|
||||||
|
'table_name': table_name,
|
||||||
|
'rows': rows
|
||||||
|
}
|
||||||
|
resp = requests.post(url, headers=self.headers, json=data)
|
||||||
|
return parse_response(resp)
|
||||||
|
|
||||||
def get_table_by_name(self, table_name):
|
def get_table_by_name(self, table_name):
|
||||||
metadata = self.get_metadata()
|
metadata = self.get_metadata()
|
||||||
for table in metadata['tables']:
|
for table in metadata['tables']:
|
||||||
if table['name'] == table_name:
|
if table['name'] == table_name:
|
||||||
return table
|
return table
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def update_rows_by_dtable_db(self, table_name, updates):
|
||||||
|
url = f"{self.dtable_db_url.strip('/')}/api/v1/update-rows/{self.dtable_uuid}/?from=dtable_web"
|
||||||
|
data = {
|
||||||
|
'table_name': table_name,
|
||||||
|
'updates': updates
|
||||||
|
}
|
||||||
|
resp = requests.put(url, headers=self.headers, json=data)
|
||||||
|
return parse_response(resp)
|
||||||
|
|
||||||
|
def insert_rows_by_dtable_db(self, table_name, rows):
|
||||||
|
url = f"{self.dtable_db_url.strip('/')}/api/v1/insert-rows/{self.dtable_uuid}/?from=dtable_web"
|
||||||
|
data = {
|
||||||
|
'table_name': table_name,
|
||||||
|
'rows': rows
|
||||||
|
}
|
||||||
|
resp = requests.post(url, headers=self.headers, json=data)
|
||||||
|
return parse_response(resp)
|
||||||
|
Reference in New Issue
Block a user