Merge branch 'develop' into branch feature/image-thumbnail

This commit is contained in:
Timofey Boyko 2023-04-17 12:31:05 +03:00
commit d2f4748d79
1451 changed files with 33734 additions and 20106 deletions

40
.github/workflows/build-ffvideo.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: ffvideo build
on:
push:
branches:
- release/v1.0.0
paths:
- 'build/install/docker/**.ffvideo'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [linux/amd64]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./build/install/docker/Dockerfile.ffvideo
push: true
tags: onlyoffice/ffvideo:6.0

43
.github/workflows/storybook-publish.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: storybook build/publish
on:
push:
branches:
- develop
paths:
- 'public/**'
- 'packages/components/**'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
platform: [linux/amd64]
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18.x'
- run: yarn
- run: yarn storybook-build
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Upload storybook files
run: |
aws s3 cp ./packages/components/storybook-static/ ${{ secrets.AWS_BUCKER_URL }}/ \
--recursive
- name: Invalidate AWS CLOUDFRONT cache
run: |
aws cloudfront create-invalidation \
-- --paths "/*" \
--distribution-id ${{ secrets.AWS_DISTRIBUTION_ID }}

View File

@ -219,18 +219,11 @@ set_core_machinekey () {
}
install_json() {
if [ ! -e /usr/bin/json ]; then
echo -n "Install json package... "
npm i json -g >/dev/null 2>&1
echo "OK"
fi
#Creating a user-defined .json
if [ ! -e $USER_CONF ]; then
echo "{}" >> $USER_CONF
chown ${PACKAGE_SYSNAME}:${PACKAGE_SYSNAME} $USER_CONF
fi
}
restart_services() {
@ -485,7 +478,7 @@ setup_docs() {
local JSON_DSCONF="$JSON $DS_CONF -e"
#Changing the Docs port in nginx conf
sed 's/\(listen .*:\)\([0-9]\{2,5\}\b\)\( default_server\)\?\(;\)/\1'${DS_PORT}'\3\4/' -i $NGINX_CONF/ds.conf
sed 's/\(listen .*:\)\([0-9]\{2,5\}\b\)\( default_server\)\?\(;\)/\1'${DOCUMENT_SERVER_PORT}'\3\4/' -i $NGINX_CONF/ds.conf
sed "0,/proxy_pass .*;/{s/proxy_pass .*;/proxy_pass http:\/\/${DOCUMENT_SERVER_HOST}:${DOCUMENT_SERVER_PORT};/}" -i $NGINX_CONF/${PACKAGE_SYSNAME}.conf
#Enable JWT validation for Docs
@ -601,6 +594,17 @@ setup_rabbitmq() {
product_configuration(){
echo -n "Configuring ${PRODUCT}... "
#Creating environment configuration files
enviromentFiles=("appsettings.$ENVIRONMENT.json" "apisystem.$ENVIRONMENT.json" "elastic.$ENVIRONMENT.json" "rabbitmq.$ENVIRONMENT.json")
for i in "${!enviromentFiles[@]}";
do
if [ ! -e "$APP_DIR/${enviromentFiles[$i]}" ]; then
echo "{}" >> "$APP_DIR/${enviromentFiles[$i]}"
chown ${PACKAGE_SYSNAME}:${PACKAGE_SYSNAME} "$APP_DIR/${enviromentFiles[$i]}"
fi
done
$JSON $APP_DIR/plugins.json -e "this.pluginsConf={'path': \"$PRODUCT_DIR/Tools/radicale/plugins/\" }" >/dev/null 2>&1
set_core_machinekey

View File

@ -227,14 +227,12 @@ CMD ["ASC.Files.dll", "ASC.Files"]
## ASC.Files.Service ##
FROM dotnetrun AS files_services
RUN apt-get -y update && \
apt-get install -yq ffmpeg &&\
rm -rf /var/lib/apt/lists/*
ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64
WORKDIR ${BUILD_PATH}/products/ASC.Files/service/
COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py
COPY --from=base --chown=onlyoffice:onlyoffice ${BUILD_PATH}/services/ASC.Files.Service/service/ .
COPY --from=onlyoffice/ffvideo:6.0 --chown=onlyoffice:onlyoffice /usr/local /usr/local/
CMD ["ASC.Files.Service.dll", "ASC.Files.Service", "core:eventBus:subscriptionClientName=asc_event_bus_files_service_queue"]

View File

@ -225,7 +225,6 @@ CMD ["ASC.Files.dll", "ASC.Files"]
FROM dotnetrun AS files_services
RUN apt-get -y update && \
apt-get install -yq ffmpeg
WORKDIR ${BUILD_PATH}/products/ASC.Files/service/
COPY --chown=onlyoffice:onlyoffice docker-entrypoint.py ./docker-entrypoint.py

View File

@ -0,0 +1,585 @@
FROM ubuntu:20.04 AS base
ENV DEBIAN_FRONTEND="noninteractive"
ENV TZ="Etc/UTC"
RUN apt-get -yqq update && \
apt-get install -yq --no-install-recommends ca-certificates expat libgomp1 && \
apt-get autoremove -y && \
apt-get clean -y
FROM base as build
ENV FFMPEG_VERSION=6.0 \
AOM_VERSION=v1.0.0 \
CHROMAPRINT_VERSION=1.5.0 \
FDKAAC_VERSION=0.1.5 \
FONTCONFIG_VERSION=2.12.4 \
FREETYPE_VERSION=2.10.4 \
FRIBIDI_VERSION=0.19.7 \
KVAZAAR_VERSION=2.0.0 \
LAME_VERSION=3.100 \
LIBASS_VERSION=0.13.7 \
LIBPTHREAD_STUBS_VERSION=0.4 \
LIBVIDSTAB_VERSION=1.1.0 \
LIBXCB_VERSION=1.13.1 \
XCBPROTO_VERSION=1.13 \
OGG_VERSION=1.3.2 \
OPENCOREAMR_VERSION=0.1.5 \
OPUS_VERSION=1.2 \
OPENJPEG_VERSION=2.1.2 \
THEORA_VERSION=1.1.1 \
VORBIS_VERSION=1.3.5 \
VPX_VERSION=1.8.0 \
WEBP_VERSION=1.0.2 \
X264_VERSION=20170226-2245-stable \
X265_VERSION=3.4 \
XAU_VERSION=1.0.9 \
XORG_MACROS_VERSION=1.19.2 \
XPROTO_VERSION=7.0.31 \
XVID_VERSION=1.3.4 \
LIBXML2_VERSION=2.9.12 \
LIBBLURAY_VERSION=1.1.2 \
LIBZMQ_VERSION=4.3.2 \
LIBSRT_VERSION=1.4.1 \
LIBARIBB24_VERSION=1.0.3 \
LIBPNG_VERSION=1.6.9 \
LIBVMAF_VERSION=2.1.1 \
SRC=/usr/local
ARG FREETYPE_SHA256SUM="5eab795ebb23ac77001cfb68b7d4d50b5d6c7469247b0b01b2c953269f658dac freetype-2.10.4.tar.gz"
ARG FRIBIDI_SHA256SUM="3fc96fa9473bd31dcb5500bdf1aa78b337ba13eb8c301e7c28923fea982453a8 0.19.7.tar.gz"
ARG LIBASS_SHA256SUM="8fadf294bf701300d4605e6f1d92929304187fca4b8d8a47889315526adbafd7 0.13.7.tar.gz"
ARG LIBVIDSTAB_SHA256SUM="14d2a053e56edad4f397be0cb3ef8eb1ec3150404ce99a426c4eb641861dc0bb v1.1.0.tar.gz"
ARG OGG_SHA256SUM="e19ee34711d7af328cb26287f4137e70630e7261b17cbe3cd41011d73a654692 libogg-1.3.2.tar.gz"
ARG OPUS_SHA256SUM="77db45a87b51578fbc49555ef1b10926179861d854eb2613207dc79d9ec0a9a9 opus-1.2.tar.gz"
ARG THEORA_SHA256SUM="40952956c47811928d1e7922cda3bc1f427eb75680c3c37249c91e949054916b libtheora-1.1.1.tar.gz"
ARG VORBIS_SHA256SUM="6efbcecdd3e5dfbf090341b485da9d176eb250d893e3eb378c428a2db38301ce libvorbis-1.3.5.tar.gz"
ARG XVID_SHA256SUM="4e9fd62728885855bc5007fe1be58df42e5e274497591fec37249e1052ae316f xvidcore-1.3.4.tar.gz"
ARG LIBBLURAY_SHA256SUM="a3dd452239b100dc9da0d01b30e1692693e2a332a7d29917bf84bb10ea7c0b42 libbluray-1.1.2.tar.bz2"
ARG LIBZMQ_SHA256SUM="02ecc88466ae38cf2c8d79f09cfd2675ba299a439680b64ade733e26a349edeb v4.3.2.tar.gz"
ARG LIBARIBB24_SHA256SUM="f61560738926e57f9173510389634d8c06cabedfa857db4b28fb7704707ff128 v1.0.3.tar.gz"
ARG LD_LIBRARY_PATH=/opt/ffmpeg/lib
ARG MAKEFLAGS="-j2"
ARG PKG_CONFIG_PATH="/opt/ffmpeg/share/pkgconfig:/opt/ffmpeg/lib/pkgconfig:/opt/ffmpeg/lib64/pkgconfig"
ARG PREFIX=/opt/ffmpeg
ARG LD_LIBRARY_PATH="/opt/ffmpeg/lib:/opt/ffmpeg/lib64"
ARG DEBIAN_FRONTEND=noninteractive
RUN buildDeps="autoconf \
automake \
cmake \
curl \
bzip2 \
libexpat1-dev \
g++ \
gcc \
git \
gperf \
libtool \
make \
meson \
nasm \
perl \
pkg-config \
python \
libssl-dev \
yasm \
zlib1g-dev" && \
apt-get -yqq update && \
apt-get install -yq --no-install-recommends ${buildDeps}
## libvmaf https://github.com/Netflix/vmaf
RUN \
if which meson || false; then \
echo "Building VMAF." && \
DIR=/tmp/vmaf && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://github.com/Netflix/vmaf/archive/v${LIBVMAF_VERSION}.tar.gz && \
tar -xz --strip-components=1 -f v${LIBVMAF_VERSION}.tar.gz && \
cd /tmp/vmaf/libvmaf && \
meson build --buildtype release --prefix=${PREFIX} && \
ninja -vC build && \
ninja -vC build install && \
mkdir -p ${PREFIX}/share/model/ && \
cp -r /tmp/vmaf/model/* ${PREFIX}/share/model/ && \
rm -rf ${DIR}; \
else \
echo "VMAF skipped."; \
fi
## opencore-amr https://sourceforge.net/projects/opencore-amr/
RUN \
DIR=/tmp/opencore-amr && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sL https://sourceforge.net/projects/opencore-amr/files/opencore-amr/opencore-amr-${OPENCOREAMR_VERSION}.tar.gz/download | \
tar -zx --strip-components=1 && \
./configure --prefix="${PREFIX}" --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
## x264 http://www.videolan.org/developers/x264.html
RUN \
DIR=/tmp/x264 && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sL https://download.videolan.org/pub/videolan/x264/snapshots/x264-snapshot-${X264_VERSION}.tar.bz2 | \
tar -jx --strip-components=1 && \
./configure --prefix="${PREFIX}" --enable-shared --enable-pic --disable-cli && \
make && \
make install && \
rm -rf ${DIR}
### x265 http://x265.org/
RUN \
DIR=/tmp/x265 && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sL https://github.com/videolan/x265/archive/refs/tags/${X265_VERSION}.tar.gz | \
tar -zx && \
cd x265-${X265_VERSION}/build/linux && \
sed -i "/-DEXTRA_LIB/ s/$/ -DCMAKE_INSTALL_PREFIX=\${PREFIX}/" multilib.sh && \
sed -i "/^cmake/ s/$/ -DENABLE_CLI=OFF/" multilib.sh && \
./multilib.sh && \
make -C 8bit install && \
rm -rf ${DIR}
### libogg https://www.xiph.org/ogg/
RUN \
DIR=/tmp/ogg && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO http://downloads.xiph.org/releases/ogg/libogg-${OGG_VERSION}.tar.gz && \
echo ${OGG_SHA256SUM} | sha256sum --check && \
tar -zx --strip-components=1 -f libogg-${OGG_VERSION}.tar.gz && \
./configure --prefix="${PREFIX}" --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
### libopus https://www.opus-codec.org/
RUN \
DIR=/tmp/opus && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://archive.mozilla.org/pub/opus/opus-${OPUS_VERSION}.tar.gz && \
echo ${OPUS_SHA256SUM} | sha256sum --check && \
tar -zx --strip-components=1 -f opus-${OPUS_VERSION}.tar.gz && \
autoreconf -fiv && \
./configure --prefix="${PREFIX}" --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
### libvorbis https://xiph.org/vorbis/
RUN \
DIR=/tmp/vorbis && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO http://downloads.xiph.org/releases/vorbis/libvorbis-${VORBIS_VERSION}.tar.gz && \
echo ${VORBIS_SHA256SUM} | sha256sum --check && \
tar -zx --strip-components=1 -f libvorbis-${VORBIS_VERSION}.tar.gz && \
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
### libtheora http://www.theora.org/
RUN \
DIR=/tmp/theora && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO http://downloads.xiph.org/releases/theora/libtheora-${THEORA_VERSION}.tar.gz && \
echo ${THEORA_SHA256SUM} | sha256sum --check && \
tar -zx --strip-components=1 -f libtheora-${THEORA_VERSION}.tar.gz && \
./configure --prefix="${PREFIX}" --with-ogg="${PREFIX}" --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
### libvpx https://www.webmproject.org/code/
RUN \
DIR=/tmp/vpx && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sL https://codeload.github.com/webmproject/libvpx/tar.gz/v${VPX_VERSION} | \
tar -zx --strip-components=1 && \
./configure --prefix="${PREFIX}" --enable-vp8 --enable-vp9 --enable-vp9-highbitdepth --enable-pic --enable-shared \
--disable-debug --disable-examples --disable-docs --disable-install-bins && \
make && \
make install && \
rm -rf ${DIR}
### libwebp https://developers.google.com/speed/webp/
RUN \
DIR=/tmp/vebp && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sL https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-${WEBP_VERSION}.tar.gz | \
tar -zx --strip-components=1 && \
./configure --prefix="${PREFIX}" --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
### libmp3lame http://lame.sourceforge.net/
RUN \
DIR=/tmp/lame && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sL https://sourceforge.net/projects/lame/files/lame/${LAME_VERSION}/lame-${LAME_VERSION}.tar.gz/download | \
tar -zx --strip-components=1 && \
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" --enable-shared --enable-nasm --disable-frontend && \
make && \
make install && \
rm -rf ${DIR}
### xvid https://www.xvid.com/
RUN \
DIR=/tmp/xvid && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO http://downloads.xvid.org/downloads/xvidcore-${XVID_VERSION}.tar.gz && \
echo ${XVID_SHA256SUM} | sha256sum --check && \
tar -zx -f xvidcore-${XVID_VERSION}.tar.gz && \
cd xvidcore/build/generic && \
./configure --prefix="${PREFIX}" --bindir="${PREFIX}/bin" && \
make && \
make install && \
rm -rf ${DIR}
### fdk-aac https://github.com/mstorsjo/fdk-aac
RUN \
DIR=/tmp/fdk-aac && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sL https://github.com/mstorsjo/fdk-aac/archive/v${FDKAAC_VERSION}.tar.gz | \
tar -zx --strip-components=1 && \
autoreconf -fiv && \
./configure --prefix="${PREFIX}" --enable-shared --datadir="${DIR}" && \
make && \
make install && \
rm -rf ${DIR}
## openjpeg https://github.com/uclouvain/openjpeg
RUN \
DIR=/tmp/openjpeg && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sL https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz | \
tar -zx --strip-components=1 && \
cmake -DBUILD_THIRDPARTY:BOOL=ON -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
make && \
make install && \
rm -rf ${DIR}
## freetype https://www.freetype.org/
RUN \
DIR=/tmp/freetype && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.gz && \
echo ${FREETYPE_SHA256SUM} | sha256sum --check && \
tar -zx --strip-components=1 -f freetype-${FREETYPE_VERSION}.tar.gz && \
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
## libvstab https://github.com/georgmartius/vid.stab
RUN \
DIR=/tmp/vid.stab && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://github.com/georgmartius/vid.stab/archive/v${LIBVIDSTAB_VERSION}.tar.gz && \
echo ${LIBVIDSTAB_SHA256SUM} | sha256sum --check && \
tar -zx --strip-components=1 -f v${LIBVIDSTAB_VERSION}.tar.gz && \
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
make && \
make install && \
rm -rf ${DIR}
## fridibi https://www.fribidi.org/
RUN \
DIR=/tmp/fribidi && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://github.com/fribidi/fribidi/archive/${FRIBIDI_VERSION}.tar.gz && \
echo ${FRIBIDI_SHA256SUM} | sha256sum --check && \
tar -zx --strip-components=1 -f ${FRIBIDI_VERSION}.tar.gz && \
sed -i 's/^SUBDIRS =.*/SUBDIRS=gen.tab charset lib bin/' Makefile.am && \
./bootstrap --no-config --auto && \
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
make -j1 && \
make install && \
rm -rf ${DIR}
## fontconfig https://www.freedesktop.org/wiki/Software/fontconfig/
RUN \
DIR=/tmp/fontconfig && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://www.freedesktop.org/software/fontconfig/release/fontconfig-${FONTCONFIG_VERSION}.tar.bz2 && \
tar -jx --strip-components=1 -f fontconfig-${FONTCONFIG_VERSION}.tar.bz2 && \
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
## libass https://github.com/libass/libass
RUN \
DIR=/tmp/libass && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://github.com/libass/libass/archive/${LIBASS_VERSION}.tar.gz && \
echo ${LIBASS_SHA256SUM} | sha256sum --check && \
tar -zx --strip-components=1 -f ${LIBASS_VERSION}.tar.gz && \
./autogen.sh && \
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
## kvazaar https://github.com/ultravideo/kvazaar
RUN \
DIR=/tmp/kvazaar && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://github.com/ultravideo/kvazaar/archive/v${KVAZAAR_VERSION}.tar.gz && \
tar -zx --strip-components=1 -f v${KVAZAAR_VERSION}.tar.gz && \
./autogen.sh && \
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
RUN \
DIR=/tmp/aom && \
git clone --branch ${AOM_VERSION} --depth 1 https://aomedia.googlesource.com/aom ${DIR} ; \
cd ${DIR} ; \
rm -rf CMakeCache.txt CMakeFiles ; \
mkdir -p ./aom_build ; \
cd ./aom_build ; \
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" -DBUILD_SHARED_LIBS=1 ..; \
make ; \
make install ; \
rm -rf ${DIR}
## libxcb (and supporting libraries) for screen capture https://xcb.freedesktop.org/
RUN \
DIR=/tmp/xorg-macros && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://www.x.org/archive//individual/util/util-macros-${XORG_MACROS_VERSION}.tar.gz && \
tar -zx --strip-components=1 -f util-macros-${XORG_MACROS_VERSION}.tar.gz && \
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
make && \
make install && \
rm -rf ${DIR}
RUN \
DIR=/tmp/xproto && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://www.x.org/archive/individual/proto/xproto-${XPROTO_VERSION}.tar.gz && \
tar -zx --strip-components=1 -f xproto-${XPROTO_VERSION}.tar.gz && \
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
make && \
make install && \
rm -rf ${DIR}
RUN \
DIR=/tmp/libXau && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://www.x.org/archive/individual/lib/libXau-${XAU_VERSION}.tar.gz && \
tar -zx --strip-components=1 -f libXau-${XAU_VERSION}.tar.gz && \
./configure --srcdir=${DIR} --prefix="${PREFIX}" && \
make && \
make install && \
rm -rf ${DIR}
RUN \
DIR=/tmp/libpthread-stubs && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://xcb.freedesktop.org/dist/libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
tar -zx --strip-components=1 -f libpthread-stubs-${LIBPTHREAD_STUBS_VERSION}.tar.gz && \
./configure --prefix="${PREFIX}" && \
make && \
make install && \
rm -rf ${DIR}
RUN \
DIR=/tmp/libxcb-proto && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://xcb.freedesktop.org/dist/xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
tar -zx --strip-components=1 -f xcb-proto-${XCBPROTO_VERSION}.tar.gz && \
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
./configure --prefix="${PREFIX}" && \
make && \
make install && \
rm -rf ${DIR}
RUN \
DIR=/tmp/libxcb && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://xcb.freedesktop.org/dist/libxcb-${LIBXCB_VERSION}.tar.gz && \
tar -zx --strip-components=1 -f libxcb-${LIBXCB_VERSION}.tar.gz && \
ACLOCAL_PATH="${PREFIX}/share/aclocal" ./autogen.sh && \
./configure --prefix="${PREFIX}" --disable-static --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
## libxml2 - for libbluray
RUN \
DIR=/tmp/libxml2 && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sL https://github.com/GNOME/libxml2/archive/refs/tags/v${LIBXML2_VERSION}.tar.gz | \
tar -xz --strip-components=1 && \
./autogen.sh --prefix="${PREFIX}" --with-ftp=no --with-http=no --with-python=no && \
make && \
make install && \
rm -rf ${DIR}
## libbluray - Requires libxml, freetype, and fontconfig
RUN \
DIR=/tmp/libbluray && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://download.videolan.org/pub/videolan/libbluray/${LIBBLURAY_VERSION}/libbluray-${LIBBLURAY_VERSION}.tar.bz2 && \
echo ${LIBBLURAY_SHA256SUM} | sha256sum --check && \
tar -jx --strip-components=1 -f libbluray-${LIBBLURAY_VERSION}.tar.bz2 && \
./configure --prefix="${PREFIX}" --disable-examples --disable-bdjava-jar --disable-static --enable-shared && \
make && \
make install && \
rm -rf ${DIR}
## libzmq https://github.com/zeromq/libzmq/
RUN \
DIR=/tmp/libzmq && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://github.com/zeromq/libzmq/archive/v${LIBZMQ_VERSION}.tar.gz && \
echo ${LIBZMQ_SHA256SUM} | sha256sum --check && \
tar -xz --strip-components=1 -f v${LIBZMQ_VERSION}.tar.gz && \
./autogen.sh && \
./configure --prefix="${PREFIX}" && \
make && \
make check && \
make install && \
rm -rf ${DIR}
## libsrt https://github.com/Haivision/srt
RUN \
DIR=/tmp/srt && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://github.com/Haivision/srt/archive/v${LIBSRT_VERSION}.tar.gz && \
tar -xz --strip-components=1 -f v${LIBSRT_VERSION}.tar.gz && \
cmake -DCMAKE_INSTALL_PREFIX="${PREFIX}" . && \
make && \
make install && \
rm -rf ${DIR}
## libpng
RUN \
DIR=/tmp/png && \
mkdir -p ${DIR} && \
cd ${DIR} && \
git clone https://git.code.sf.net/p/libpng/code ${DIR} -b v${LIBPNG_VERSION} --depth 1 && \
./autogen.sh && \
./configure --prefix="${PREFIX}" && \
make check && \
make install && \
rm -rf ${DIR}
## libaribb24
RUN \
DIR=/tmp/b24 && \
mkdir -p ${DIR} && \
cd ${DIR} && \
curl -sLO https://github.com/nkoriyama/aribb24/archive/v${LIBARIBB24_VERSION}.tar.gz && \
echo ${LIBARIBB24_SHA256SUM} | sha256sum --check && \
tar -xz --strip-components=1 -f v${LIBARIBB24_VERSION}.tar.gz && \
autoreconf -fiv && \
./configure CFLAGS="-I${PREFIX}/include -fPIC" --prefix="${PREFIX}" && \
make && \
make install && \
rm -rf ${DIR}
## Download ffmpeg https://ffmpeg.org/
RUN \
DIR=/tmp/ffmpeg && mkdir -p ${DIR} && cd ${DIR} && \
curl -sLO https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \
tar -jx --strip-components=1 -f ffmpeg-${FFMPEG_VERSION}.tar.bz2 && \
./configure --disable-debug --disable-doc --disable-ffplay --enable-shared --enable-gpl --extra-libs=-ldl && \
make ; make install
## Build ffmpeg https://ffmpeg.org/
RUN \
DIR=/tmp/ffmpeg && cd ${DIR} && \
./configure \
--disable-debug \
--disable-doc \
--disable-ffplay \
--enable-fontconfig \
--enable-gpl \
--enable-libaom \
--enable-libaribb24 \
--enable-libass \
--enable-libbluray \
--enable-libfdk_aac \
--enable-libfreetype \
--enable-libkvazaar \
--enable-libmp3lame \
--enable-libopencore-amrnb \
--enable-libopencore-amrwb \
--enable-libopenjpeg \
--enable-libopus \
--enable-libsrt \
--enable-libtheora \
--enable-libvidstab \
--enable-libvmaf \
--enable-libvorbis \
--enable-libvpx \
--enable-libwebp \
--enable-libx264 \
--enable-libx265 \
--enable-libxcb \
--enable-libxvid \
--enable-libzmq \
--enable-nonfree \
--enable-openssl \
--enable-postproc \
--enable-shared \
--enable-small \
--enable-version3 \
--extra-cflags="-I${PREFIX}/include" \
--extra-ldflags="-L${PREFIX}/lib" \
--extra-libs=-ldl \
--extra-libs=-lpthread \
--prefix="${PREFIX}" && \
make clean && \
make && \
make install && \
make tools/zmqsend && cp tools/zmqsend ${PREFIX}/bin/ && \
make distclean && \
hash -r && \
cd tools && \
make qt-faststart && cp qt-faststart ${PREFIX}/bin/
## cleanup
RUN \
ldd ${PREFIX}/bin/ffmpeg | grep opt/ffmpeg | cut -d ' ' -f 3 | xargs -i cp {} /usr/local/lib/ && \
for lib in /usr/local/lib/*.so.*; do ln -s "${lib##*/}" "${lib%%.so.*}".so; done && \
cp ${PREFIX}/bin/* /usr/local/bin/ && \
cp -r ${PREFIX}/share/ffmpeg /usr/local/share/ && \
LD_LIBRARY_PATH=/usr/local/lib ffmpeg -buildconf && \
cp -r ${PREFIX}/include/libav* ${PREFIX}/include/libpostproc ${PREFIX}/include/libsw* /usr/local/include && \
mkdir -p /usr/local/lib/pkgconfig && \
for pc in ${PREFIX}/lib/pkgconfig/libav*.pc ${PREFIX}/lib/pkgconfig/libpostproc.pc ${PREFIX}/lib/pkgconfig/libsw*.pc; do \
sed "s:${PREFIX}:/usr/local:g" <"$pc" >/usr/local/lib/pkgconfig/"${pc##*/}"; \
done
FROM base AS release
ENV LD_LIBRARY_PATH=/usr/local/lib:/usr/local/lib64
COPY --from=build /usr/local /usr/local/
ENTRYPOINT ["bash"]

View File

@ -1,4 +1,12 @@
version: "3.8"
x-healthcheck:
&x-healthcheck
test: curl --fail http://127.0.0.1 || exit 1
interval: 60s
retries: 5
start_period: 20s
timeout: 10s
x-service: &x-service-base
container_name: base
restart: always
@ -54,31 +62,49 @@ services:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-backup-background:${DOCKER_TAG}"
container_name: ${BACKUP_BACKGRUOND_TASKS_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_BACKUP_BACKGRUOND_TASKS}/health/ || exit 1
onlyoffice-backup:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-backup:${DOCKER_TAG}"
container_name: ${BACKUP_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_BACKUP}/health/ || exit 1
onlyoffice-clear-events:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-clear-events:${DOCKER_TAG}"
container_name: ${CLEAR_EVENTS_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_CLEAR_EVENTS}/health/ || exit 1
onlyoffice-files:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-files:${DOCKER_TAG}"
container_name: ${FILES_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_FILES}/health/ || exit 1
onlyoffice-files-services:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-files-services:${DOCKER_TAG}"
container_name: ${FILES_SERVICES_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_FILES_SERVICES}/health/ || exit 1
onlyoffice-people-server:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-people-server:${DOCKER_TAG}"
container_name: ${PEOPLE_SERVER_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_PEOPLE_SERVER}/health/ || exit 1
onlyoffice-socket:
<<: *x-service-base
@ -91,21 +117,33 @@ services:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-studio-notify:${DOCKER_TAG}"
container_name: ${STUDIO_NOTIFY_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_STUDIO_NOTIFY}/health/ || exit 1
onlyoffice-api:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-api:${DOCKER_TAG}"
container_name: ${API_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_API}/health/ || exit 1
onlyoffice-api-system:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-api-system:${DOCKER_TAG}"
container_name: ${API_SYSTEM_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_API_SYSTEM}/health/ || exit 1
onlyoffice-studio:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-studio:${DOCKER_TAG}"
container_name: ${STUDIO_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_STUDIO}/health/ || exit 1
onlyoffice-ssoauth:
<<: *x-service-base
@ -135,6 +173,9 @@ services:
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-proxy:${DOCKER_TAG}"
container_name: ${PROXY_HOST}
restart: always
healthcheck:
<<: *x-healthcheck
test: nginx -t || exit 1
expose:
- "8081"
- "8099"

View File

@ -1,4 +1,12 @@
version: "3.8"
x-healthcheck:
&x-healthcheck
test: curl --fail http://127.0.0.1 || exit 1
interval: 60s
retries: 5
start_period: 20s
timeout: 10s
x-service:
&x-service-base
container_name: base
@ -33,6 +41,9 @@ services:
<<: *x-service-base
image: "${REPO}/${DOCKER_IMAGE_PREFIX}-notify:${DOCKER_TAG}"
container_name: ${NOTIFY_HOST}
healthcheck:
<<: *x-healthcheck
test: curl --fail http://${SERVICE_NOTIFY}/health/ || exit 1
networks:
default:

View File

@ -2,4 +2,9 @@ PUSHD %~dp0..
set dir=%~dp0..
echo %dir%
dotnet test common\Tests\Frontend.Translations.Tests\Frontend.Translations.Tests.csproj --filter Name~SpellCheckTest -l:html --environment "BASE_DIR=%dir%" --results-directory "%dir%/TestsResults"
set save=false
if /I "%1" == "-s" set save=%2 & shift
shift
dotnet test common\Tests\Frontend.Translations.Tests\Frontend.Translations.Tests.csproj --filter Name~SpellCheckTest -l:html --environment "BASE_DIR=%dir%" --environment "SAVE=%save%" --results-directory "%dir%/TestsResults"

View File

@ -3,6 +3,15 @@ echo "Run script directory:" $dir
dir=$(builtin cd $rd/../; pwd)
save=false
while getopts s: flag
do
case "${flag}" in
s) save=${OPTARG};;
esac
done
echo "Root directory:" $dir
dotnet test $dir/common/Tests/Frontend.Translations.Tests/Frontend.Translations.Tests.csproj --filter Name~SpellCheckTest -l:html --results-directory "$dir/TestsResults" --environment "BASE_DIR=$dir"
dotnet test $dir/common/Tests/Frontend.Translations.Tests/Frontend.Translations.Tests.csproj --filter Name~SpellCheckTest -l:html --results-directory "$dir/TestsResults" --environment "BASE_DIR=$dir" --environment "SAVE=$save"

View File

@ -417,7 +417,7 @@ public class LdapUserImporter : IDisposable
if (!actualPortalLdapGroups.Contains(portalUserLdapGroup))
{
_logger.DebugTrySyncUserGroupMembershipRemovingUserFromGroup(userInfo.UserName, ldapUser.Sid, portalUserLdapGroup.Name, portalUserLdapGroup.Sid);
UserManager.RemoveUserFromGroup(userInfo.Id, portalUserLdapGroup.ID);
await UserManager.RemoveUserFromGroup(userInfo.Id, portalUserLdapGroup.ID);
}
}

View File

@ -299,7 +299,7 @@ public class LdapOperationJob : DistributedTaskProgress
{
_logger.DebugTurnOffLDAP();
TurnOffLDAP();
await TurnOffLDAP();
var ldapCurrentUserPhotos = _settingsManager.Load<LdapCurrentUserPhotos>().GetDefault();
_settingsManager.Save(ldapCurrentUserPhotos);
@ -349,7 +349,7 @@ public class LdapOperationJob : DistributedTaskProgress
: "", "");
}
private void TurnOffLDAP()
private async Task TurnOffLDAP()
{
const double percents = 48;
@ -380,7 +380,7 @@ public class LdapOperationJob : DistributedTaskProgress
_logger.DebugSaveUserInfo(existingLDAPUser.GetUserInfoString());
_userManager.UpdateUserInfo(existingLDAPUser);
await _userManager.UpdateUserInfo(existingLDAPUser);
break;
case LdapOperationType.SaveTest:
case LdapOperationType.SyncTest:
@ -667,7 +667,7 @@ public class LdapOperationJob : DistributedTaskProgress
SetProgress(20, Resource.LdapSettingsStatusRemovingOldUsers, "");
ldapUsers = RemoveOldDbUsers(ldapUsers);
ldapUsers = await RemoveOldDbUsers(ldapUsers);
SetProgress(30,
OperationType == LdapOperationType.Save || OperationType == LdapOperationType.SaveTest
@ -729,7 +729,7 @@ public class LdapOperationJob : DistributedTaskProgress
SetProgress(90, Resource.LdapSettingsStatusRemovingOldUsers, "");
RemoveOldDbUsers(newUniqueLdapGroupUsers);
await RemoveOldDbUsers(newUniqueLdapGroupUsers);
}
private async Task SyncDbGroups(Dictionary<GroupInfo, List<UserInfo>> ldapGroupsWithUsers)
@ -895,7 +895,7 @@ public class LdapOperationJob : DistributedTaskProgress
++index, count,
_userFormatter.GetUserName(dbUser, DisplayUserNameFormat.Default)));
_userManager.RemoveUserFromGroup(dbUser.Id, dbLdapGroup.ID);
await _userManager.RemoveUserFromGroup(dbUser.Id, dbLdapGroup.ID);
}
index = 0;
@ -1012,7 +1012,7 @@ public class LdapOperationJob : DistributedTaskProgress
/// </summary>
/// <param name="ldapUsers">list of actual LDAP users</param>
/// <returns>New list of actual LDAP users</returns>
private List<UserInfo> RemoveOldDbUsers(List<UserInfo> ldapUsers)
private async Task<List<UserInfo>> RemoveOldDbUsers(List<UserInfo> ldapUsers)
{
var dbLdapUsers = _userManager.GetUsers(EmployeeStatus.All).Where(u => u.Sid != null).ToList();
@ -1064,7 +1064,7 @@ public class LdapOperationJob : DistributedTaskProgress
_logger.DebugSaveUserInfo(removedUser.GetUserInfoString());
_userManager.UpdateUserInfo(removedUser);
await _userManager.UpdateUserInfo(removedUser);
break;
case LdapOperationType.SaveTest:
case LdapOperationType.SyncTest:

View File

@ -126,7 +126,7 @@ public class LdapUserManager
return portalUserInfo;
}
if (!TryChangeExistingUserName(ldapUserInfo.UserName, onlyGetChanges))
if (!await TryChangeExistingUserName(ldapUserInfo.UserName, onlyGetChanges))
{
_logger.DebugUserAlredyExistsForUserName(ldapUserInfo.Sid, ldapUserInfo.UserName);
@ -177,7 +177,7 @@ public class LdapUserManager
return portalUserInfo;
}
private bool TryChangeExistingUserName(string ldapUserName, bool onlyGetChanges)
private async Task<bool> TryChangeExistingUserName(string ldapUserName, bool onlyGetChanges)
{
try
{
@ -209,7 +209,7 @@ public class LdapUserManager
_logger.DebugSaveUserInfo(otherUser.GetUserInfoString());
_userManager.UpdateUserInfo(otherUser);
await _userManager.UpdateUserInfo(otherUser);
return true;
}
@ -343,9 +343,11 @@ public class LdapUserManager
return wrapper;
}
_logger.DebugSyncUserLdapUpdaiting(ldapUserInfo.Sid, ldapUserInfo.UserName);
UserInfo uf;
if (!TryUpdateUserWithLDAPInfo(userToUpdate, ldapUserInfo, onlyGetChanges, out uf))
_logger.DebugSyncUserLdapUpdaiting(ldapUserInfo.Sid, ldapUserInfo.UserName);
var (updated, uf) = await TryUpdateUserWithLDAPInfo(userToUpdate, ldapUserInfo, onlyGetChanges);
if (!updated)
{
if (onlyGetChanges)
{
@ -379,7 +381,7 @@ public class LdapUserManager
var newContacts = new List<string>(ldapUser.ContactsList);
for (int i = 0; i < portalUserContacts.Count; i += 2)
for (var i = 0; i < portalUserContacts.Count; i += 2)
{
if (portalUserContacts[i] == EXT_MOB_PHONE || portalUserContacts[i] == EXT_MAIL
|| portalUserContacts[i] == EXT_PHONE || portalUserContacts[i] == EXT_SKYPE)
@ -516,9 +518,9 @@ public class LdapUserManager
return needUpdate;
}
private bool TryUpdateUserWithLDAPInfo(UserInfo userToUpdate, UserInfo updateInfo, bool onlyGetChanges, out UserInfo portlaUserInfo)
private async Task<(bool, UserInfo)> TryUpdateUserWithLDAPInfo(UserInfo userToUpdate, UserInfo updateInfo, bool onlyGetChanges)
{
portlaUserInfo = Constants.LostUser;
var portlaUserInfo = Constants.LostUser;
try
{
@ -527,11 +529,11 @@ public class LdapUserManager
var settings = _settingsManager.Load<LdapSettings>();
if (!userToUpdate.UserName.Equals(updateInfo.UserName, StringComparison.InvariantCultureIgnoreCase)
&& !TryChangeExistingUserName(updateInfo.UserName, onlyGetChanges))
&& !await TryChangeExistingUserName(updateInfo.UserName, onlyGetChanges))
{
_logger.DebugUpdateUserUserNameAlredyExists(userToUpdate.Id, userToUpdate.UserName, updateInfo.UserName);
return false;
return (false, portlaUserInfo);
}
if (!userToUpdate.Email.Equals(updateInfo.Email, StringComparison.InvariantCultureIgnoreCase)
@ -539,7 +541,7 @@ public class LdapUserManager
{
_logger.DebugUpdateUserEmailAlreadyExists(userToUpdate.Id, userToUpdate.Email, updateInfo.Email);
return false;
return (false, portlaUserInfo);
}
if (userToUpdate.Email != updateInfo.Email && !(updateInfo.ActivationStatus == EmployeeActivationStatus.AutoGenerated &&
@ -589,10 +591,10 @@ public class LdapUserManager
{
_logger.DebugSaveUserInfo(userToUpdate.GetUserInfoString());
portlaUserInfo = _userManager.UpdateUserInfo(userToUpdate);
portlaUserInfo = await _userManager.UpdateUserInfo(userToUpdate);
}
return true;
return (true, portlaUserInfo);
}
catch (Exception ex)
{
@ -600,7 +602,7 @@ public class LdapUserManager
userToUpdate.Sid, ex);
}
return false;
return (false, portlaUserInfo);
}
public async Task<UserInfo> TryGetAndSyncLdapUserInfo(string login, string password)
@ -678,7 +680,7 @@ public class LdapUserManager
log.DebugTryGetAndSyncLdapUserInfoDisablingUser(login, uInfo);
uInfo.Status = EmployeeStatus.Terminated;
uInfo.Sid = null;
userManager.UpdateUserInfo(uInfo);
await userManager.UpdateUserInfo(uInfo);
await cookiesManager.ResetUserCookie(uInfo.Id);
}
}
@ -730,7 +732,7 @@ public class LdapUserManager
{
userInfo.Sid = null;
userInfo.Status = EmployeeStatus.Terminated;
_userManager.UpdateUserInfo(userInfo);
await _userManager.UpdateUserInfo(userInfo);
throw new Exception("The user did not pass the configuration check by ldap group settings");
}

View File

@ -40,7 +40,7 @@ public class WarmupServicesStartupTask : IStartupTask
}
public Task ExecuteAsync(CancellationToken cancellationToken)
{
{
var processedFailed = 0;
var processedSuccessed = 0;
var startTime = DateTime.UtcNow;
@ -52,13 +52,13 @@ public class WarmupServicesStartupTask : IStartupTask
logger.TraceWarmupStarted();
tenantManager.SetCurrentTenant("localhost");
tenantManager.SetCurrentTenant("localhost");
foreach (var service in GetServices(_services))
{
try
{
scope.ServiceProvider.GetServices(service);
{
scope.ServiceProvider.GetService(service);
processedSuccessed++;
}
@ -72,9 +72,9 @@ public class WarmupServicesStartupTask : IStartupTask
var processed = processedSuccessed + processedFailed;
logger.TraceWarmupFinished(processed,
processedSuccessed,
processedFailed,
logger.TraceWarmupFinished(processed,
processedSuccessed,
processedFailed,
(DateTime.UtcNow - startTime).TotalMilliseconds);
}
@ -86,7 +86,7 @@ public class WarmupServicesStartupTask : IStartupTask
return services
.Where(descriptor => descriptor.ImplementationType != typeof(WarmupServicesStartupTask))
.Where(descriptor => descriptor.ServiceType.ContainsGenericParameters == false)
.Select(descriptor => descriptor.ServiceType)
.Select(descriptor => descriptor.ServiceType)
.Distinct();
}
}

View File

@ -146,7 +146,7 @@ public static class ServiceCollectionExtension
var logger = sp.GetRequiredService<ILogger<DefaultActiveMQPersistentConnection>>();
var factory = new Apache.NMS.NMSConnectionFactory(activeMQConfiguration.Uri);
var retryCount = 5;
if (!string.IsNullOrEmpty(cfg["core:eventBus:connectRetryCount"]))
@ -195,10 +195,8 @@ public static class ServiceCollectionExtension
/// Add a IHostedService for given type.
/// Only one copy of this instance type will active in multi process architecture.
/// </remarks>
public static void AddActivePassiveHostedService<T>(this IServiceCollection services) where T : class, IHostedService
public static void AddActivePassiveHostedService<T>(this IServiceCollection services, DIHelper diHelper) where T : class, IHostedService
{
var diHelper = new DIHelper(services);
diHelper.TryAdd<IRegisterInstanceDao<T>, RegisterInstanceDao<T>>();
diHelper.TryAdd<IRegisterInstanceManager<T>, RegisterInstanceManager<T>>();

View File

@ -160,6 +160,8 @@ public static class MimeMapping
AddMimeMapping(".dotm", "application/vnd.ms-word.template.macroEnabled.12");
AddMimeMapping(".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template");
AddMimeMapping(".dp", "application/commonground");
AddMimeMapping(".dps", "application/presentation");
AddMimeMapping(".dpt", "application/presentation");
AddMimeMapping(".drw", "application/drafting");
AddMimeMapping(".dump", "application/octet-stream");
AddMimeMapping(".dv", "video/x-dv");
@ -181,6 +183,8 @@ public static class MimeMapping
AddMimeMapping(".eps", "application/postscript");
AddMimeMapping(".epub", "application/epub+zip");
AddMimeMapping(".es", "application/x-esrehber");
AddMimeMapping(".et", "application/spreadsheet");
AddMimeMapping(".ett", "application/spreadsheet");
AddMimeMapping(".etx", "text/x-setext");
AddMimeMapping(".evy", "application/envoy");
AddMimeMapping(".evy", "application/x-envoy");
@ -616,6 +620,7 @@ public static class MimeMapping
AddMimeMapping(".stl", "application/vndms-pkistl");
AddMimeMapping(".stm", "text/html");
AddMimeMapping(".stp", "application/step");
AddMimeMapping(".stw", "application/vnd.sun.xml.writer.template");
AddMimeMapping(".sv4cpio", "application/x-sv4cpio");
AddMimeMapping(".sv4crc", "application/x-sv4crc");
AddMimeMapping(".svf", "image/vnd.dwg");
@ -625,6 +630,9 @@ public static class MimeMapping
AddMimeMapping(".svg", "image/svg+xml");
AddMimeMapping(".svgt", "image/svg+xml");
AddMimeMapping(".swf", "application/x-shockwave-flash");
AddMimeMapping(".sxi", "application/presentation");
AddMimeMapping(".sxc", "application/vnd.sun.xml.calc");
AddMimeMapping(".sxw", "application/vnd.sun.xml.writer");
AddMimeMapping(".t", "application/x-troff");
AddMimeMapping(".talk", "text/x-speech");
AddMimeMapping(".tar", "application/x-tar");
@ -719,7 +727,8 @@ public static class MimeMapping
AddMimeMapping(".wp6", "application/wordperfect");
AddMimeMapping(".wpd", "application/wordperfect");
AddMimeMapping(".wpd", "application/x-wpwin");
AddMimeMapping(".wps", "application/vnd.ms-works");
AddMimeMapping(".wps", "application/document");
AddMimeMapping(".wpt", "application/document");
AddMimeMapping(".wq1", "application/x-lotus");
AddMimeMapping(".wri", "application/mswrite");
AddMimeMapping(".wri", "application/x-wri");

View File

@ -26,13 +26,6 @@
namespace ASC.Core.Common;
[Scope]
public class CommonLinkUtilitySettings
{
public string ServerUri { get; set; }
}
[Scope]
public class BaseCommonLinkUtility
{
@ -43,14 +36,22 @@ public class BaseCommonLinkUtility
private string _vpath;
protected IHttpContextAccessor _httpContextAccessor;
public string ServerUri
{
set
{
var uri = new Uri(value.Replace('*', 'x').Replace('+', 'x'));
_serverRoot = new UriBuilder(uri.Scheme, uri.Host != "x" ? uri.Host : LocalHost, uri.Port);
_vpath = "/" + uri.AbsolutePath.Trim('/');
}
}
public BaseCommonLinkUtility(
CoreBaseSettings coreBaseSettings,
CoreSettings coreSettings,
TenantManager tenantManager,
ILoggerProvider options,
CommonLinkUtilitySettings settings)
: this(null, coreBaseSettings, coreSettings, tenantManager, options, settings)
ILoggerProvider options)
: this(null, coreBaseSettings, coreSettings, tenantManager, options)
{
}
@ -59,37 +60,29 @@ public class BaseCommonLinkUtility
CoreBaseSettings coreBaseSettings,
CoreSettings coreSettings,
TenantManager tenantManager,
ILoggerProvider options,
CommonLinkUtilitySettings settings)
ILoggerProvider options)
{
var serverUri = settings.ServerUri;
if (!string.IsNullOrEmpty(serverUri))
try
{
var uri = new Uri(serverUri.Replace('*', 'x').Replace('+', 'x'));
_serverRoot = new UriBuilder(uri.Scheme, uri.Host != "x" ? uri.Host : LocalHost, uri.Port);
_vpath = "/" + uri.AbsolutePath.Trim('/');
_httpContextAccessor = httpContextAccessor;
if (_httpContextAccessor?.HttpContext?.Request != null)
{
var u = _httpContextAccessor?.HttpContext.Request.GetUrlRewriter();
ArgumentNullException.ThrowIfNull(u);
_serverRoot = new UriBuilder(u.Scheme, LocalHost, u.Port);
}
else if (_serverRoot == null)
{
_serverRoot = new UriBuilder(Uri.UriSchemeHttp, LocalHost);
}
}
else
catch (Exception error)
{
try
{
_httpContextAccessor = httpContextAccessor;
var uriBuilder = new UriBuilder(Uri.UriSchemeHttp, LocalHost);
if (_httpContextAccessor?.HttpContext?.Request != null)
{
var u = _httpContextAccessor?.HttpContext.Request.GetUrlRewriter();
ArgumentNullException.ThrowIfNull(u);
uriBuilder = new UriBuilder(u.Scheme, LocalHost, u.Port);
}
_serverRoot = uriBuilder;
}
catch (Exception error)
{
options.CreateLogger("ASC.Web").ErrorWithException(error);
}
options.CreateLogger("ASC.Web").ErrorWithException(error);
}
_coreBaseSettings = coreBaseSettings;

View File

@ -31,7 +31,7 @@ public interface ITariffService
{
IDictionary<string, Dictionary<string, decimal>> GetProductPriceInfo(params string[] productIds);
IEnumerable<PaymentInfo> GetPayments(int tenantId);
Tariff GetTariff(int tenantId, bool withRequestToPaymentSystem = true);
Tariff GetTariff(int tenantId, bool withRequestToPaymentSystem = true, bool refresh = false);
Task<Uri> GetShoppingUri(int tenant, string currency = null, string language = null, string customerEmail = null, Dictionary<string, int> quantity = null, string backUrl = null);
Uri GetShoppingUri(int? tenant, int quotaId, string affiliateId, string currency = null, string language = null, string customerId = null, string quantity = null);
Uri GetShoppingUri(string[] productIds, string affiliateId = null, string currency = null, string language = null, string customerId = null, string quantity = null);

View File

@ -53,7 +53,7 @@ public class Tariff
return t != null
&& t.DueDate == DueDate
&& t.Quotas.Count == Quotas.Count
&& t.Quotas.Any(q => Quotas.Contains(q))
&& t.Quotas.Any(Quotas.Contains)
&& t.CustomerId == CustomerId;
}
}

View File

@ -23,7 +23,7 @@
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Core.Billing;
[Singletone]
@ -145,18 +145,9 @@ public class TariffService : ITariffService
_cache = _tariffServiceStorage.Cache;
_notify = _tariffServiceStorage.Notify;
_dbContextFactory = coreDbContextManager;
//var range = (_configuration["core.payment-user-range"] ?? "").Split('-');
//if (!int.TryParse(range[0], out _activeUsersMin))
//{
// _activeUsersMin = 0;
//}
//if (range.Length < 2 || !int.TryParse(range[1], out _activeUsersMax))
//{
// _activeUsersMax = constants.MaxEveryoneCount;
//}
}
public Tariff GetTariff(int tenantId, bool withRequestToPaymentSystem = true)
public Tariff GetTariff(int tenantId, bool withRequestToPaymentSystem = true, bool refresh = false)
{
//single tariff for all portals
if (_coreBaseSettings.Standalone)
@ -164,8 +155,7 @@ public class TariffService : ITariffService
tenantId = -1;
}
var tariff = GetTariffFromCache(tenantId);
int? tariffId = null;
var tariff = refresh ? null : GetTariffFromCache(tenantId);
if (tariff == null)
{
@ -174,11 +164,12 @@ public class TariffService : ITariffService
if (string.IsNullOrEmpty(_cache.Get<string>(GetTariffNeedToUpdateCacheKey(tenantId))))
{
tariffId = tariff.Id;
UpdateCache(tariff.Id);
}
if (_billingClient.Configured && withRequestToPaymentSystem)
{
var paymentFound = false;
try
{
@ -190,10 +181,11 @@ public class TariffService : ITariffService
var asynctariff = CreateDefault(true);
string email = null;
var tenantQuotas = _quotaService.GetTenantQuotas();
foreach (var currentPayment in currentPayments)
foreach (var currentPayment in currentPayments.OrderBy(r => r.EndDate))
{
var quota = _quotaService.GetTenantQuotas().SingleOrDefault(q => q.ProductId == currentPayment.ProductId.ToString());
var quota = tenantQuotas.SingleOrDefault(q => q.ProductId == currentPayment.ProductId.ToString());
if (quota == null)
{
throw new InvalidOperationException($"Quota with id {currentPayment.ProductId} not found for portal {GetPortalId(tenantId)}.");
@ -204,10 +196,23 @@ public class TariffService : ITariffService
var paymentEndDate = 9999 <= currentPayment.EndDate.Year ? DateTime.MaxValue : currentPayment.EndDate;
asynctariff.DueDate = DateTime.Compare(asynctariff.DueDate, paymentEndDate) < 0 ? asynctariff.DueDate : paymentEndDate;
asynctariff.Quotas = asynctariff.Quotas.Where(r => r.Id != quota.Tenant).ToList();
asynctariff.Quotas.Add(new Quota(quota.Tenant, currentPayment.Quantity));
email = currentPayment.PaymentEmail;
}
TenantQuota updatedQuota = null;
foreach (var quota in asynctariff.Quotas)
{
var tenantQuota = tenantQuotas.SingleOrDefault(q => q.Tenant == quota.Id);
tenantQuota *= quota.Quantity;
updatedQuota += tenantQuota;
}
updatedQuota.Check(_serviceProvider).Wait();
if (!string.IsNullOrEmpty(email))
{
asynctariff.CustomerId = email;
@ -217,10 +222,21 @@ public class TariffService : ITariffService
{
asynctariff = CalculateTariff(tenantId, asynctariff);
tariff = asynctariff;
tariffId = asynctariff.Id;
}
UpdateCache(tariff.Id);
paymentFound = true;
}
catch (Exception error)
{
if (error is not BillingNotFoundException)
{
LogError(error, tenantId.ToString());
}
}
catch (BillingNotFoundException)
if (!paymentFound)
{
var freeTariff = tariff.Quotas.FirstOrDefault(tariffRow =>
{
@ -244,22 +260,23 @@ public class TariffService : ITariffService
{
asynctariff = CalculateTariff(tenantId, asynctariff);
tariff = asynctariff;
tariffId = asynctariff.Id;
}
}
catch (Exception error)
{
LogError(error, tenantId.ToString());
UpdateCache(tariff.Id);
}
}
}
if (tariffId.HasValue && tariffId.Value != 0)
else
{
_notify.Publish(new TariffCacheItem { TenantId = tenantId, TariffId = tariffId.Value }, CacheNotifyAction.Insert);
tariff = CalculateTariff(tenantId, tariff);
}
return tariff;
void UpdateCache(int tariffId)
{
_notify.Publish(new TariffCacheItem { TenantId = tenantId, TariffId = tariffId }, CacheNotifyAction.Insert);
}
}
public async Task<bool> PaymentChange(int tenantId, Dictionary<string, int> quantity)
@ -430,6 +447,27 @@ public class TariffService : ITariffService
public async Task<Uri> GetShoppingUri(int tenant, string currency = null, string language = null, string customerEmail = null, Dictionary<string, int> quantity = null, string backUrl = null)
{
List<TenantQuota> newQuotas = new();
if (_billingClient.Configured)
{
var allQuotas = _quotaService.GetTenantQuotas().Where(q => !string.IsNullOrEmpty(q.ProductId) && q.Visible).ToList();
newQuotas = quantity.Select(item => allQuotas.FirstOrDefault(q => q.Name == item.Key)).ToList();
TenantQuota updatedQuota = null;
foreach (var addedQuota in newQuotas)
{
var qty = quantity[addedQuota.Name];
var quota = addedQuota;
quota *= qty;
updatedQuota += quota;
}
await updatedQuota.Check(_serviceProvider);
}
var hasQuantity = quantity != null && quantity.Any();
var key = "shopingurl_" + (hasQuantity ? string.Join('_', quantity.Keys.ToArray()) : "all");
var url = _cache.Get<string>(key);
@ -438,22 +476,6 @@ public class TariffService : ITariffService
url = string.Empty;
if (_billingClient.Configured)
{
var allQuotas = _quotaService.GetTenantQuotas().Where(q => !string.IsNullOrEmpty(q.ProductId) && q.Visible);
var newQuotas = quantity.Select(item => allQuotas.FirstOrDefault(q => q.Name == item.Key));
TenantQuota updatedQuota = null;
foreach (var addedQuota in newQuotas)
{
var qty = quantity[addedQuota.Name];
var quota = addedQuota;
quota *= qty;
updatedQuota += quota;
}
await updatedQuota.Check(_serviceProvider);
var productIds = newQuotas.Select(q => q.ProductId);
try
@ -682,6 +704,7 @@ public class TariffService : ITariffService
}
var tariff = CreateDefault(true);
tariff.Id = r.Id;
tariff.DueDate = r.Stamp.Year < 9999 ? r.Stamp : DateTime.MaxValue;
tariff.CustomerId = r.CustomerId;
@ -727,6 +750,7 @@ public class TariffService : ITariffService
if (efTariff.Id == default)
{
efTariff.Id = (-tenant);
tariffInfo.Id = efTariff.Id;
}
if (efTariff.CustomerId == default)
@ -769,8 +793,9 @@ public class TariffService : ITariffService
// update tenant.LastModified to flush cache in documents
_tenantService.SaveTenant(_coreSettings, t);
}
ClearCache(tenant);
NotifyWebSocket(currentTariff, tariffInfo);
}
return inserted;
@ -941,6 +966,57 @@ public class TariffService : ITariffService
}
}
private void NotifyWebSocket(Tariff currenTariff, Tariff newTariff)
{
var quotaSocketManager = _serviceProvider.GetRequiredService<QuotaSocketManager>();
var updatedQuota = GetTenantQuotaFromTariff(newTariff);
var maxTotalSize = updatedQuota.MaxTotalSize;
var maxTotalSizeFeatureName = updatedQuota.GetFeature<MaxTotalSizeFeature>().Name;
_ = quotaSocketManager.ChangeQuotaFeatureValue(maxTotalSizeFeatureName, maxTotalSize);
var maxPaidUsers = updatedQuota.CountRoomAdmin;
var maxPaidUsersFeatureName = updatedQuota.GetFeature<CountPaidUserFeature>().Name;
_ = quotaSocketManager.ChangeQuotaFeatureValue(maxPaidUsersFeatureName, maxPaidUsers);
var maxRoomCount = updatedQuota.CountRoom == int.MaxValue ? -1 : updatedQuota.CountRoom;
var maxRoomCountFeatureName = updatedQuota.GetFeature<CountRoomFeature>().Name;
_ = quotaSocketManager.ChangeQuotaFeatureValue(maxRoomCountFeatureName, maxRoomCount);
if (currenTariff != null)
{
var currentQuota = GetTenantQuotaFromTariff(currenTariff);
var free = updatedQuota.Free;
if (currentQuota.Free != free)
{
var freeFeatureName = updatedQuota.GetFeature<FreeFeature>().Name;
_ = quotaSocketManager.ChangeQuotaFeatureValue(freeFeatureName, free);
}
}
}
private TenantQuota GetTenantQuotaFromTariff(Tariff tariff)
{
TenantQuota result = null;
foreach (var tariffRow in tariff.Quotas)
{
var qty = tariffRow.Quantity;
var quota = _quotaService.GetTenantQuota(tariffRow.Id);
quota *= qty;
result += quota;
}
return result;
}
public int GetPaymentDelay()
{
return _paymentDelay;

View File

@ -271,6 +271,7 @@ public class CachedUserService : IUserService, ICachedService
{
Service.SetUserPhoto(tenant, id, photo);
CacheUserPhotoItem.Publish(new UserPhotoCacheItem { Key = UserServiceCache.GetUserPhotoCacheKey(tenant, id) }, CacheNotifyAction.Remove);
CacheUserInfoItem.Publish(new UserInfoCacheItem { Id = id.ToString(), Tenant = tenant }, CacheNotifyAction.Any);
}
public DateTime GetUserPasswordStamp(int tenant, Guid id)

View File

@ -45,7 +45,7 @@ public class TenantManager
internal CoreBaseSettings CoreBaseSettings { get; set; }
internal CoreSettings CoreSettings { get; set; }
private readonly static object _lock = new object();
private static readonly object _lock = new object();
static TenantManager()
{
@ -283,17 +283,17 @@ public class TenantManager
return QuotaService.GetTenantQuotas().Where(q => q.Tenant < 0 && (all || q.Visible)).OrderByDescending(q => q.Tenant).ToList();
}
public TenantQuota GetCurrentTenantQuota()
public TenantQuota GetCurrentTenantQuota(bool refresh = false)
{
return GetTenantQuota(GetCurrentTenant().Id);
return GetTenantQuota(GetCurrentTenant().Id, refresh);
}
public TenantQuota GetTenantQuota(int tenant)
public TenantQuota GetTenantQuota(int tenant, bool refresh = false)
{
var defaultQuota = QuotaService.GetTenantQuota(tenant) ?? QuotaService.GetTenantQuota(Tenant.DefaultTenant) ?? TenantQuota.Default;
if (defaultQuota.Tenant != tenant && TariffService != null)
{
var tariff = TariffService.GetTariff(tenant);
var tariff = TariffService.GetTariff(tenant, refresh: refresh);
TenantQuota currentQuota = null;
foreach (var tariffRow in tariff.Quotas)

View File

@ -65,9 +65,9 @@ public class UserManager
private readonly TenantQuotaFeatureCheckerCount<CountUserFeature> _activeUsersFeatureChecker;
private readonly Constants _constants;
private readonly UserFormatter _userFormatter;
private Tenant _tenant;
private Tenant Tenant => _tenant ??= _tenantManager.GetCurrentTenant();
private readonly QuotaSocketManager _quotaSocketManager;
private readonly TenantQuotaFeatureStatHelper _tenantQuotaFeatureStatHelper;
private Tenant Tenant => _tenantManager.GetCurrentTenant();
public UserManager()
{
@ -88,7 +88,9 @@ public class UserManager
ICache cache,
TenantQuotaFeatureCheckerCount<CountPaidUserFeature> countPaidUserChecker,
TenantQuotaFeatureCheckerCount<CountUserFeature> activeUsersFeatureChecker,
UserFormatter userFormatter
UserFormatter userFormatter,
QuotaSocketManager quotaSocketManager,
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper
)
{
_userService = service;
@ -106,6 +108,8 @@ public class UserManager
_activeUsersFeatureChecker = activeUsersFeatureChecker;
_constants = _userManagerConstants.Constants;
_userFormatter = userFormatter;
_quotaSocketManager = quotaSocketManager;
_tenantQuotaFeatureStatHelper = tenantQuotaFeatureStatHelper;
}
public UserManager(
@ -123,8 +127,10 @@ public class UserManager
TenantQuotaFeatureCheckerCount<CountPaidUserFeature> tenantQuotaFeatureChecker,
TenantQuotaFeatureCheckerCount<CountUserFeature> activeUsersFeatureChecker,
IHttpContextAccessor httpContextAccessor,
UserFormatter userFormatter)
: this(service, tenantManager, permissionContext, userManagerConstants, coreBaseSettings, coreSettings, instanceCrypto, radicaleClient, cardDavAddressbook, log, cache, tenantQuotaFeatureChecker, activeUsersFeatureChecker, userFormatter)
UserFormatter userFormatter,
QuotaSocketManager quotaSocketManager,
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper)
: this(service, tenantManager, permissionContext, userManagerConstants, coreBaseSettings, coreSettings, instanceCrypto, radicaleClient, cardDavAddressbook, log, cache, tenantQuotaFeatureChecker, activeUsersFeatureChecker, userFormatter, quotaSocketManager, tenantQuotaFeatureStatHelper)
{
_accessor = httpContextAccessor;
}
@ -322,7 +328,7 @@ public class UserManager
return findUsers.ToArray();
}
public UserInfo UpdateUserInfo(UserInfo u)
public async Task<UserInfo> UpdateUserInfo(UserInfo u)
{
if (IsSystemUser(u.Id))
{
@ -343,15 +349,31 @@ public class UserManager
throw new InvalidOperationException("User not found.");
}
return _userService.SaveUser(_tenantManager.GetCurrentTenant().Id, u);
var (name, value) = ("", -1);
if (!IsUserInGroup(oldUserData.Id, Constants.GroupUser.ID) &&
oldUserData.Status != u.Status)
{
(name, value) = await _tenantQuotaFeatureStatHelper.GetStat<CountPaidUserFeature, int>();
value = oldUserData.Status > u.Status ? ++value : --value;//crutch: data race
}
var newUserData = _userService.SaveUser(_tenantManager.GetCurrentTenant().Id, u);
if (value > 0)
{
_ = _quotaSocketManager.ChangeQuotaUsedValue(name, value);
}
return newUserData;
}
public async Task<UserInfo> UpdateUserInfoWithSyncCardDavAsync(UserInfo u)
{
var oldUserData = _userService.GetUserByUserName(_tenantManager.GetCurrentTenant().Id, u.UserName);
var newUser = UpdateUserInfo(u);
var newUser = await UpdateUserInfo(u);
if (_coreBaseSettings.DisableDocSpace)
{
await SyncCardDavAsync(u, oldUserData, newUser);
@ -644,7 +666,7 @@ public class UserManager
return GetUsers(employeeStatus).Where(u => IsUserInGroupInternal(u.Id, groupId, refs)).ToArray();
}
public async Task AddUserIntoGroup(Guid userId, Guid groupId, bool dontClearAddressBook = false)
public async Task AddUserIntoGroup(Guid userId, Guid groupId, bool dontClearAddressBook = false, bool notifyWebSocket = true)
{
if (Constants.LostUser.Id == userId || Constants.LostGroupInfo.ID == groupId)
{
@ -652,6 +674,8 @@ public class UserManager
}
var user = GetUsers(userId);
var isUser = this.IsUser(user);
var isPaidUser = IsPaidUser(user);
_permissionContext.DemandPermissions(new UserGroupObject(new UserAccount(user, _tenantManager.GetCurrentTenant().Id, _userFormatter), groupId),
Constants.Action_EditGroups);
@ -659,6 +683,7 @@ public class UserManager
_userService.SaveUserGroupRef(Tenant.Id, new UserGroupRef(userId, groupId, UserGroupRefType.Contains));
ResetGroupCache(userId);
if (groupId == Constants.GroupUser.ID)
{
var tenant = _tenantManager.GetCurrentTenant();
@ -671,9 +696,21 @@ public class UserManager
await _cardDavAddressbook.Delete(myUri, user.Id, user.Email, tenant.Id);
}
}
if (!notifyWebSocket)
{
return;
}
if (isUser && groupId != Constants.GroupUser.ID ||
!isUser && !isPaidUser && groupId != Constants.GroupUser.ID)
{
var (name, value) = await _tenantQuotaFeatureStatHelper.GetStat<CountPaidUserFeature, int>();
_ = _quotaSocketManager.ChangeQuotaUsedValue(name, value);
}
}
public void RemoveUserFromGroup(Guid userId, Guid groupId)
public async Task RemoveUserFromGroup(Guid userId, Guid groupId)
{
if (Constants.LostUser.Id == userId || Constants.LostGroupInfo.ID == groupId)
{
@ -681,6 +718,8 @@ public class UserManager
}
var user = GetUsers(userId);
var isUserBefore = this.IsUser(user);
var isPaidUserBefore = IsPaidUser(user);
_permissionContext.DemandPermissions(new UserGroupObject(new UserAccount(user, _tenantManager.GetCurrentTenant().Id, _userFormatter), groupId),
Constants.Action_EditGroups);
@ -688,6 +727,16 @@ public class UserManager
_userService.RemoveUserGroupRef(Tenant.Id, userId, groupId, UserGroupRefType.Contains);
ResetGroupCache(userId);
var isUserAfter = this.IsUser(user);
var isPaidUserAfter = IsPaidUser(user);
if (isPaidUserBefore && !isPaidUserAfter && isUserAfter ||
isUserBefore && !isUserAfter)
{
var (name, value) = await _tenantQuotaFeatureStatHelper.GetStat<CountPaidUserFeature, int>();
_ = _quotaSocketManager.ChangeQuotaUsedValue(name, value);
}
}
internal void ResetGroupCache(Guid userID)
@ -904,7 +953,7 @@ public class UserManager
{
return isCollaborator;
}
if (groupId == Constants.GroupManager.ID)
{
return !isUser && !isCollaborator;
@ -930,4 +979,9 @@ public class UserManager
Sid = g.Sid
};
}
private bool IsPaidUser(UserInfo userInfo)
{
return this.IsCollaborator(userInfo) || this.IsDocSpaceAdmin(userInfo);
}
}

View File

@ -123,7 +123,7 @@ public class WorkContext
INotifySender emailSender = _notifyServiceSender;
INotifySender telegramSender = _telegramSender;
INotifySender pushSender = _pushSender;
var postman = _configuration["core:notify:postman"];
@ -161,6 +161,11 @@ public class WorkContext
}
}
public void RegisterSendMethod(Func<DateTime, Task> method, string cron)
{
NotifyEngine.RegisterSendMethod(method, cron);
}
public void RegisterSendMethod(Action<DateTime> method, string cron)
{
NotifyEngine.RegisterSendMethod(method, cron);

View File

@ -24,6 +24,10 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using ASC.Core.Common.EF;
using Microsoft.EntityFrameworkCore;
namespace ASC.Core.Data;
[Scope]
@ -227,7 +231,7 @@ public class EFUserService : IUserService
if (sortBy == "type")
{
var q1 = from user in q
join userGroup in userDbContext.UserGroups.Where(g => !g.Removed && (g.UserGroupId == Users.Constants.GroupAdmin.ID || g.UserGroupId == Users.Constants.GroupUser.ID
join userGroup in userDbContext.UserGroups.Where(g => !g.Removed && (g.UserGroupId == Users.Constants.GroupAdmin.ID || g.UserGroupId == Users.Constants.GroupUser.ID
|| g.UserGroupId == Users.Constants.GroupCollaborator.ID))
on user.Id equals userGroup.Userid into joinedGroup
from @group in joinedGroup.DefaultIfEmpty()
@ -317,8 +321,8 @@ public class EFUserService : IUserService
if (immediate)
{
await userGroups.ExecuteDeleteAsync();
await groups.ExecuteDeleteAsync();
await userGroups.ExecuteDeleteAsync();
await groups.ExecuteDeleteAsync();
}
else
{
@ -372,15 +376,15 @@ public class EFUserService : IUserService
.SetProperty(p => p.Removed, true)
.SetProperty(p => p.LastModified, DateTime.UtcNow));
await users.ExecuteUpdateAsync(ug => ug
.SetProperty(p => p.Removed, true)
.SetProperty(p => p.LastModified, DateTime.UtcNow)
.SetProperty(p => p.TerminatedDate, DateTime.UtcNow)
.SetProperty(p => p.Status, EmployeeStatus.Terminated)
);
await users.ExecuteUpdateAsync(ug => ug
.SetProperty(p => p.Removed, true)
.SetProperty(p => p.LastModified, DateTime.UtcNow)
.SetProperty(p => p.TerminatedDate, DateTime.UtcNow)
.SetProperty(p => p.Status, EmployeeStatus.Terminated)
);
}
await tr.CommitAsync();
await tr.CommitAsync();
}).GetAwaiter()
.GetResult();
}
@ -403,7 +407,7 @@ public class EFUserService : IUserService
var userGroups = userDbContext.UserGroups.Where(r => r.Tenant == tenant && r.Userid == userId && r.UserGroupId == groupId && r.RefType == refType);
if (immediate)
{
await userGroups.ExecuteDeleteAsync();
await userGroups.ExecuteDeleteAsync();
}
else
{
@ -517,7 +521,7 @@ public class EFUserService : IUserService
if (user != null)
{
user.LastModified = userGroupRef.LastModified;
await userDbContext.AddOrUpdateAsync(r => userDbContext.UserGroups, _mapper.Map<UserGroupRef, UserGroup>(userGroupRef));
await userDbContext.AddOrUpdateAsync(r => userDbContext.UserGroups, _mapper.Map<UserGroupRef, UserGroup>(userGroupRef));
}
await userDbContext.SaveChangesAsync();
@ -548,42 +552,45 @@ public class EFUserService : IUserService
public void SetUserPhoto(int tenant, Guid id, byte[] photo)
{
using var userDbContext = _dbContextFactory.CreateDbContext();
var strategy = userDbContext.Database.CreateExecutionStrategy();
strategy.Execute(async () =>
var userPhoto = userDbContext.Photos.FirstOrDefault(r => r.UserId == id && r.Tenant == tenant);
if (photo != null && photo.Length != 0)
{
using var userDbContext = _dbContextFactory.CreateDbContext();
using var tr = await userDbContext.Database.BeginTransactionAsync();
var userPhoto = await userDbContext.Photos.FirstOrDefaultAsync(r => r.UserId == id && r.Tenant == tenant);
if (photo != null && photo.Length != 0)
if (userPhoto == null)
{
if (userPhoto == null)
userPhoto = new UserPhoto
{
userPhoto = new UserPhoto
{
Tenant = tenant,
UserId = id,
Photo = photo
};
}
else
{
userPhoto.Photo = photo;
}
await userDbContext.AddOrUpdateAsync(r => userDbContext.Photos, userPhoto);
Tenant = tenant,
UserId = id,
Photo = photo
};
}
else if (userPhoto != null)
else
{
userDbContext.Photos.Remove(userPhoto);
userPhoto.Photo = photo;
}
await userDbContext.SaveChangesAsync();
await tr.CommitAsync();
}).GetAwaiter()
.GetResult();
userDbContext.AddOrUpdate(userDbContext.Photos, userPhoto);
var userEntity = new User
{
Id = id,
LastModified = DateTime.UtcNow,
Tenant = tenant
};
userDbContext.Users.Attach(userEntity);
userDbContext.Entry(userEntity).Property(x => x.LastModified).IsModified = true;
}
else if (userPhoto != null)
{
userDbContext.Photos.Remove(userPhoto);
}
userDbContext.SaveChanges();
}
private IQueryable<User> GetUserQuery(UserDbContext userDbContext, int tenant)

View File

@ -59,7 +59,7 @@ public static class DbQuotaExtension
Tenant = -1,
Name = "trial",
Description = null,
Features = "trial,audit,ldap,sso,whitelabel,thirdparty,restore,total_size:107374182400,file_size:100,manager:1",
Features = "trial,audit,ldap,sso,whitelabel,thirdparty,restore,oauth,total_size:107374182400,file_size:100,manager:1",
Price = 0,
ProductId = null,
Visible = false
@ -69,7 +69,7 @@ public static class DbQuotaExtension
Tenant = -2,
Name = "admin",
Description = null,
Features = "audit,ldap,sso,whitelabel,thirdparty,restore,contentsearch,total_size:107374182400,file_size:1024,manager:1",
Features = "audit,ldap,sso,whitelabel,thirdparty,restore,oauth,contentsearch,total_size:107374182400,file_size:1024,manager:1",
Price = 30,
ProductId = "1002",
Visible = true
@ -79,7 +79,7 @@ public static class DbQuotaExtension
Tenant = -3,
Name = "startup",
Description = null,
Features = "free,total_size:2147483648,manager:5,room:5",
Features = "free,total_size:2147483648,manager:3,room:12",
Price = 0,
ProductId = null,
Visible = false

View File

@ -100,6 +100,25 @@ public class NotifyEngine : INotifyEngine, IDisposable
_methodsEvent.Set();
}
internal void RegisterSendMethod(Func<DateTime, Task> method, string cron)
{
ArgumentNullException.ThrowIfNull(method);
ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(cron);
var w = new SendMethodWrapper(method, cron, _logger);
lock (_sendMethods)
{
if (!_notifyScheduler.IsAlive)
{
_notifyScheduler.Start();
}
_sendMethods.Remove(w);
_sendMethods.Add(w);
}
_methodsEvent.Set();
}
internal void UnregisterSendMethod(Action<DateTime> method)
{
ArgumentNullException.ThrowIfNull(method);
@ -124,7 +143,7 @@ public class NotifyEngine : INotifyEngine, IDisposable
copy = _sendMethods.ToList();
}
foreach(var w in copy)
foreach (var w in copy)
{
if (!w.ScheduleDate.HasValue)
{
@ -590,6 +609,7 @@ public class NotifyEngine : INotifyEngine, IDisposable
private readonly SemaphoreSlim _semaphore;
private readonly CronExpression _cronExpression;
private readonly Action<DateTime> _method;
private readonly Func<DateTime, Task> _asyncMethod;
private readonly ILogger _logger;
public DateTime? ScheduleDate { get; private set; }
@ -608,6 +628,11 @@ public class NotifyEngine : INotifyEngine, IDisposable
UpdateScheduleDate(DateTime.UtcNow);
}
public SendMethodWrapper(Func<DateTime, Task> method, string cron, ILogger log) : this((Action<DateTime>)null, cron, log)
{
_asyncMethod = method;
}
public void UpdateScheduleDate(DateTime d)
{
try
@ -626,11 +651,18 @@ public class NotifyEngine : INotifyEngine, IDisposable
public async Task InvokeSendMethod(DateTime d)
{
await _semaphore.WaitAsync();
await Task.Run(() =>
await Task.Run(async () =>
{
try
{
_method(d);
if (_method != null)
{
_method(d);
}
else if (_asyncMethod != null)
{
await _asyncMethod(d);
}
}
catch (Exception e)
{
@ -642,7 +674,7 @@ public class NotifyEngine : INotifyEngine, IDisposable
public override bool Equals(object obj)
{
return obj is SendMethodWrapper w && _method.Equals(w._method);
return obj is SendMethodWrapper w && ((_method != null && _method.Equals(w._method)) || (_asyncMethod != null && _asyncMethod.Equals(w._asyncMethod)));
}
public override int GetHashCode()

View File

@ -1,45 +1,34 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Files.Thirdparty.Box;
[Scope]
internal class BoxTagDao : BoxDaoBase, ITagDao<string>
{
public BoxTagDao(
IServiceProvider serviceProvider,
UserManager userManager,
TenantManager tenantManager,
TenantUtil tenantUtil,
IDbContextFactory<FilesDbContext> dbContextManager,
SetupInfo setupInfo,
ILogger<BoxTagDao> monitor,
FileUtility fileUtility,
TempPath tempPath,
AuthContext authContext) : base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility, tempPath, authContext)
{
}
}
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Core.Common.Quota.Features;
public class FreeFeature : TenantQuotaFeatureFlag
{
public override string Name { get => "free"; }
public FreeFeature(TenantQuota tenantQuota) : base(tenantQuota)
{
}
}

View File

@ -0,0 +1,64 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Core.Common.Quota;
public class QuotaSocketManager : SocketServiceClient
{
private readonly TenantManager _tenantManager;
public override string Hub => "files";
public QuotaSocketManager(
ILogger<SocketServiceClient> logger,
IHttpClientFactory clientFactory,
MachinePseudoKeys mashinePseudoKeys,
TenantManager tenantManager,
IConfiguration configuration) : base(logger, clientFactory, mashinePseudoKeys, configuration)
{
_tenantManager = tenantManager;
}
public async Task ChangeQuotaUsedValue(string featureId, object value)
{
var room = GetQuotaRoom();
await MakeRequest("change-quota-used-value", new { room, featureId, value });
}
public async Task ChangeQuotaFeatureValue(string featureId, object value)
{
var room = GetQuotaRoom();
await MakeRequest("change-quota-feature-value", new { room, featureId, value });
}
private string GetQuotaRoom()
{
var tenantId = _tenantManager.GetCurrentTenant().Id;
return $"{tenantId}-quota";
}
}

View File

@ -67,7 +67,7 @@ public class TenantQuotaFeature<T> : TenantQuotaFeature
}
}
protected virtual T Default { get; }
public virtual T Default { get; }
public TenantQuotaFeature(TenantQuota tenantQuota)
{
@ -83,7 +83,7 @@ public class TenantQuotaFeature<T> : TenantQuotaFeature
public class TenantQuotaFeatureCount : TenantQuotaFeature<int>
{
protected override int Default => int.MaxValue;
public override int Default => int.MaxValue;
public TenantQuotaFeatureCount(TenantQuota tenantQuota) : base(tenantQuota)
{
@ -105,7 +105,7 @@ public class TenantQuotaFeatureCount : TenantQuotaFeature<int>
public class TenantQuotaFeatureSize : TenantQuotaFeature<long>
{
protected override long Default => long.MaxValue;
public override long Default => long.MaxValue;
public TenantQuotaFeatureSize(TenantQuota tenantQuota) : base(tenantQuota)
{

View File

@ -1,46 +1,50 @@
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Files.Thirdparty.OneDrive;
[Scope]
internal class OneDriveTagDao : OneDriveDaoBase, ITagDao<string>
{
public OneDriveTagDao(
IServiceProvider serviceProvider,
UserManager userManager,
TenantManager tenantManager,
TenantUtil tenantUtil,
IDbContextFactory<FilesDbContext> dbContextManager,
SetupInfo setupInfo,
ILogger<OneDriveTagDao> monitor,
FileUtility fileUtility,
TempPath tempPath,
AuthContext authContext)
: base(serviceProvider, userManager, tenantManager, tenantUtil, dbContextManager, setupInfo, monitor, fileUtility, tempPath, authContext)
{
}
}
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
namespace ASC.Core.Common.Quota;
[Scope]
public class TenantQuotaFeatureStatHelper
{
private readonly IServiceProvider _serviceProvider;
public TenantQuotaFeatureStatHelper(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<(string Name, T1 Value)> GetStat<T, T1>() where T : TenantQuotaFeature
{
var statisticProvider = (ITenantQuotaFeatureStat<T1>)_serviceProvider.GetService(typeof(ITenantQuotaFeatureStat<,>).MakeGenericType(typeof(T), typeof(T1)));
var quota = new TenantQuota();
var name = quota.GetFeature<T>().Name;
if (statisticProvider != null)
{
return (name, await statisticProvider.GetValue());
}
return (name, default(T1));
}
}

View File

@ -222,13 +222,13 @@ public class TenantQuota : IMapFrom<DbQuota>
_countUserFeature = new CountUserFeature(this) { Order = 1 };
_countPaidUserFeature = new CountPaidUserFeature(this);
_usersInRoomFeature = new UsersInRoomFeature(this) { Order = 8 };
_usersInRoomFeature = new UsersInRoomFeature(this) { Order = 8, Visible = false };
_countRoomFeature = new CountRoomFeature(this) { Order = 2 };
_maxTotalSizeFeature = new MaxTotalSizeFeature(this);
_maxFileSizeFeature = new MaxFileSizeFeature(this);
_nonProfitFeature = new TenantQuotaFeatureFlag(this) { Name = "non-profit", Visible = false };
_trialFeature = new TenantQuotaFeatureFlag(this) { Name = "trial", Visible = false };
_freeFeature = new TenantQuotaFeatureFlag(this) { Name = "free", Visible = false };
_freeFeature = new FreeFeature(this) { Visible = false };
_updateFeature = new TenantQuotaFeatureFlag(this) { Name = "update", Visible = false };
_auditFeature = new TenantQuotaFeatureFlag(this) { Name = "audit", Order = 7 };
_docsEditionFeature = new TenantQuotaFeatureFlag(this) { Name = "docs", Visible = false };
@ -323,7 +323,7 @@ public class TenantQuota : IMapFrom<DbQuota>
return quota;
}
var newQuota = new TenantQuota(quota);
var newQuota = new TenantQuota(old);
newQuota.Price += quota.Price;
newQuota.Visible &= quota.Visible;
newQuota.ProductId = "";
@ -336,11 +336,31 @@ public class TenantQuota : IMapFrom<DbQuota>
}
else if (f is TenantQuotaFeatureCount count)
{
count.Value += quota.GetFeature<int>(f.Name).Value;
var currentValue = count.Value;
var newValue = quota.GetFeature<int>(f.Name).Value;
if (currentValue == count.Default && newValue != currentValue)
{
count.Value = newValue;
}
else if (currentValue != count.Default && newValue != count.Default)
{
count.Value += newValue;
}
}
else if (f is TenantQuotaFeatureSize length)
{
length.Value += quota.GetFeature<long>(f.Name).Value;
var currentValue = length.Value;
var newValue = quota.GetFeature<long>(f.Name).Value;
if (currentValue == length.Default && newValue != currentValue)
{
length.Value = newValue;
}
else
{
length.Value += newValue;
}
}
else if (f is TenantQuotaFeatureFlag flag)
{

View File

@ -60,7 +60,7 @@ public class BackupRepository : IBackupRepository
public List<BackupRecord> GetExpiredBackupRecords()
{
using var backupContext = _dbContextFactory.CreateDbContext();
return backupContext.Backups.AsNoTracking().Where(b => b.ExpiresOn != DateTime.MinValue && b.ExpiresOn <= DateTime.UtcNow).ToList();
return backupContext.Backups.AsNoTracking().Where(b => b.ExpiresOn != DateTime.MinValue && b.ExpiresOn <= DateTime.UtcNow && b.Removed == false).ToList();
}
public List<BackupRecord> GetScheduledBackupRecords()

View File

@ -709,14 +709,18 @@ public class BackupPortalTask : PortalTaskBase
foreach (var file in group)
{
var storage = StorageFactory.GetStorage(TenantId, group.Key);
var file1 = file;
var file1 = file;
Stream fileStream = null;
await ActionInvoker.Try(async state =>
{
var f = (BackupFileInfo)state;
using var fileStream = await storage.GetReadStreamAsync(f.Domain, f.Path);
await writer.WriteEntryAsync(file1.GetZipKey(), fileStream);
fileStream = await storage.GetReadStreamAsync(f.Domain, f.Path);
}, file, 5, error => _logger.WarningCanNotBackupFile(file1.Module, file1.Path, error));
if(fileStream != null)
{
await writer.WriteEntryAsync(file1.GetZipKey(), fileStream);
fileStream.Dispose();
}
SetCurrentStepProgress((int)(++filesProcessed * 100 / (double)filesCount));
}
}

View File

@ -24,6 +24,10 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using System.Linq;
using ASC.Files.Core.Core.Thirdparty.ProviderDao;
namespace ASC.Data.Backup.Tasks.Modules;
public class FilesModuleSpecifics : ModuleSpecificsBase
@ -172,8 +176,8 @@ public class FilesModuleSpecifics : ModuleSpecificsBase
preparedRow = new Dictionary<string, object>();
object folderId = null;
var sboxId = Regex.Replace(row[1].ToString(), @"(?<=(?:sbox-|box-|dropbox-|spoint-|drive-|onedrive-))\d+", match =>
var ids = string.Join("-|", Selectors.All.Select(s => s.Id));
var sboxId = Regex.Replace(row[1].ToString(), @"(?<=(?:" + $"{ids}-" + @"))\d+", match =>
{
folderId = columnMapper.GetMapping("files_thirdparty_account", "id", match.Value);

View File

@ -48,6 +48,9 @@ public abstract class BaseStorage : IDataStore
protected readonly ILoggerProvider _options;
protected readonly IHttpClientFactory _clientFactory;
private readonly TenantQuotaFeatureStatHelper _tenantQuotaFeatureStatHelper;
private readonly QuotaSocketManager _quotaSocketManager;
public BaseStorage(
TempStream tempStream,
TenantManager tenantManager,
@ -56,7 +59,9 @@ public abstract class BaseStorage : IDataStore
IHttpContextAccessor httpContextAccessor,
ILoggerProvider options,
ILogger logger,
IHttpClientFactory clientFactory)
IHttpClientFactory clientFactory,
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper,
QuotaSocketManager quotaSocketManager)
{
_tempStream = tempStream;
@ -67,6 +72,8 @@ public abstract class BaseStorage : IDataStore
_clientFactory = clientFactory;
Logger = logger;
_httpContextAccessor = httpContextAccessor;
_tenantQuotaFeatureStatHelper = tenantQuotaFeatureStatHelper;
_quotaSocketManager = quotaSocketManager;
}
public TimeSpan GetExpire(string domain)
@ -334,19 +341,23 @@ public abstract class BaseStorage : IDataStore
public abstract string GetPostParams(string domain, string directoryPath, long maxUploadSize, string contentType,
string contentDisposition);
internal void QuotaUsedAdd(string domain, long size, bool quotaCheckFileSize = true)
internal async Task QuotaUsedAdd(string domain, long size, bool quotaCheckFileSize = true)
{
if (QuotaController != null)
{
QuotaController.QuotaUsedAdd(Modulename, domain, DataList.GetData(domain), size, quotaCheckFileSize);
var(name, value) = await _tenantQuotaFeatureStatHelper.GetStat<MaxTotalSizeFeature, long>();
_ = _quotaSocketManager.ChangeQuotaUsedValue(name, value);
}
}
internal void QuotaUsedDelete(string domain, long size)
internal async Task QuotaUsedDelete(string domain, long size)
{
if (QuotaController != null)
{
QuotaController.QuotaUsedDelete(Modulename, domain, DataList.GetData(domain), size);
var (name, value) = await _tenantQuotaFeatureStatHelper.GetStat<MaxTotalSizeFeature, long>();
_ = _quotaSocketManager.ChangeQuotaUsedValue(name, value);
}
}

View File

@ -75,8 +75,10 @@ public class DiscDataStore : BaseStorage
ILogger<DiscDataStore> logger,
EncryptionSettingsHelper encryptionSettingsHelper,
EncryptionFactory encryptionFactory,
IHttpClientFactory clientFactory)
: base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, options, logger, clientFactory)
IHttpClientFactory clientFactory,
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper,
QuotaSocketManager quotaSocketManager)
: base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, options, logger, clientFactory, tenantQuotaFeatureStatHelper, quotaSocketManager)
{
_encryptionSettingsHelper = encryptionSettingsHelper;
_encryptionFactory = encryptionFactory;
@ -143,18 +145,18 @@ public class DiscDataStore : BaseStorage
{
return SaveAsync(domain, path, stream);
}
private bool EnableQuotaCheck(string domain)
{
return (QuotaController != null) && !domain.EndsWith("_temp");
}
private bool EnableQuotaCheck(string domain)
{
return (QuotaController != null) && !domain.EndsWith("_temp");
}
public override Task<Uri> SaveAsync(string domain, string path, Stream stream)
{
Logger.DebugSavePath(path);
var buffered = _tempStream.GetBuffered(stream);
if (EnableQuotaCheck(domain))
if (EnableQuotaCheck(domain))
{
QuotaController.QuotaUsedCheck(buffered.Length);
}
@ -193,7 +195,7 @@ public class DiscDataStore : BaseStorage
fslen = fs.Length;
}
QuotaUsedAdd(domain, fslen);
await QuotaUsedAdd(domain, fslen);
_crypt.EncryptFile(target);
@ -226,7 +228,7 @@ public class DiscDataStore : BaseStorage
return string.Format("{0}_{1}", chunkNumber, uploadId);
}
public override Task<Uri> FinalizeChunkedUploadAsync(string domain, string path, string uploadId, Dictionary<int, string> eTags)
public override async Task<Uri> FinalizeChunkedUploadAsync(string domain, string path, string uploadId, Dictionary<int, string> eTags)
{
var target = GetTarget(domain, path);
@ -238,12 +240,12 @@ public class DiscDataStore : BaseStorage
}
var size = _crypt.GetFileSize(target);
QuotaUsedAdd(domain, size);
await QuotaUsedAdd(domain, size);
}
_crypt.EncryptFile(target);
return GetUriAsync(domain, path);
return await GetUriAsync(domain, path);
}
public override Task AbortChunkedUploadAsync(string domain, string path, string uploadId)
@ -258,7 +260,7 @@ public class DiscDataStore : BaseStorage
#endregion
public override Task DeleteAsync(string domain, string path)
public override async Task DeleteAsync(string domain, string path)
{
ArgumentNullException.ThrowIfNull(path);
@ -269,8 +271,7 @@ public class DiscDataStore : BaseStorage
var size = _crypt.GetFileSize(target);
File.Delete(target);
QuotaUsedDelete(domain, size);
return Task.CompletedTask; ;
await QuotaUsedDelete(domain, size);
}
else
{
@ -278,7 +279,7 @@ public class DiscDataStore : BaseStorage
}
}
public override Task DeleteFilesAsync(string domain, List<string> paths)
public override async Task DeleteFilesAsync(string domain, List<string> paths)
{
ArgumentNullException.ThrowIfNull(paths);
@ -294,13 +295,11 @@ public class DiscDataStore : BaseStorage
var size = _crypt.GetFileSize(target);
File.Delete(target);
QuotaUsedDelete(domain, size);
await QuotaUsedDelete(domain, size);
}
return Task.CompletedTask;
}
public override Task DeleteFilesAsync(string domain, string folderPath, string pattern, bool recursive)
public override async Task DeleteFilesAsync(string domain, string folderPath, string pattern, bool recursive)
{
ArgumentNullException.ThrowIfNull(folderPath);
@ -313,9 +312,8 @@ public class DiscDataStore : BaseStorage
{
var size = _crypt.GetFileSize(entry);
File.Delete(entry);
QuotaUsedDelete(domain, size);
await QuotaUsedDelete(domain, size);
}
return Task.CompletedTask;
}
else
{
@ -323,7 +321,7 @@ public class DiscDataStore : BaseStorage
}
}
public override Task DeleteFilesAsync(string domain, string folderPath, DateTime fromDate, DateTime toDate)
public override async Task DeleteFilesAsync(string domain, string folderPath, DateTime fromDate, DateTime toDate)
{
ArgumentNullException.ThrowIfNull(folderPath);
@ -339,7 +337,7 @@ public class DiscDataStore : BaseStorage
{
var size = _crypt.GetFileSize(entry);
File.Delete(entry);
QuotaUsedDelete(domain, size);
await QuotaUsedDelete(domain, size);
}
}
}
@ -347,8 +345,6 @@ public class DiscDataStore : BaseStorage
{
throw new DirectoryNotFoundException($"Directory '{targetDir}' not found");
}
return Task.CompletedTask;
}
public override Task MoveDirectoryAsync(string srcdomain, string srcdir, string newdomain, string newdir)
@ -367,7 +363,7 @@ public class DiscDataStore : BaseStorage
return Task.CompletedTask;
}
public override Task<Uri> MoveAsync(string srcdomain, string srcpath, string newdomain, string newpath, bool quotaCheckFileSize = true)
public override async Task<Uri> MoveAsync(string srcdomain, string srcpath, string newdomain, string newpath, bool quotaCheckFileSize = true)
{
ArgumentNullException.ThrowIfNull(srcpath);
ArgumentNullException.ThrowIfNull(newpath);
@ -392,14 +388,14 @@ public class DiscDataStore : BaseStorage
File.Move(target, newtarget);
QuotaUsedDelete(srcdomain, flength);
QuotaUsedAdd(newdomain, flength, quotaCheckFileSize);
await QuotaUsedDelete(srcdomain, flength);
await QuotaUsedAdd(newdomain, flength, quotaCheckFileSize);
}
else
{
throw new FileNotFoundException("File not found", Path.GetFullPath(target));
}
return GetUriAsync(newdomain, newpath);
return await GetUriAsync(newdomain, newpath);
}
public override Task<bool> IsDirectoryAsync(string domain, string path)
@ -415,7 +411,7 @@ public class DiscDataStore : BaseStorage
return Task.FromResult(!string.IsNullOrEmpty(targetDir) && Directory.Exists(targetDir));
}
public override Task DeleteDirectoryAsync(string domain, string path)
public override async Task DeleteDirectoryAsync(string domain, string path)
{
ArgumentNullException.ThrowIfNull(path);
@ -434,7 +430,7 @@ public class DiscDataStore : BaseStorage
if (!Directory.Exists(targetDir))
{
return Task.CompletedTask;
return;
}
var entries = Directory.GetFiles(targetDir, "*.*", SearchOption.AllDirectories);
@ -446,8 +442,7 @@ public class DiscDataStore : BaseStorage
Directory.Delete(targetDir, true);
QuotaUsedDelete(domain, size);
return Task.CompletedTask;
await QuotaUsedDelete(domain, size);
}
public override Task<long> GetFileSizeAsync(string domain, string path)
@ -488,7 +483,7 @@ public class DiscDataStore : BaseStorage
return result.ToString();
}
public override Task DeleteExpiredAsync(string domain, string folderPath, TimeSpan oldThreshold)
public override async Task DeleteExpiredAsync(string domain, string folderPath, TimeSpan oldThreshold)
{
ArgumentNullException.ThrowIfNull(folderPath);
@ -496,7 +491,7 @@ public class DiscDataStore : BaseStorage
var targetDir = GetTarget(domain, folderPath);
if (!Directory.Exists(targetDir))
{
return Task.CompletedTask;
return;
}
var entries = Directory.GetFiles(targetDir, "*.*", SearchOption.TopDirectoryOnly);
@ -508,11 +503,9 @@ public class DiscDataStore : BaseStorage
var size = _crypt.GetFileSize(entry);
File.Delete(entry);
QuotaUsedDelete(domain, size);
await QuotaUsedDelete(domain, size);
}
}
return Task.CompletedTask;
}
public override string GetUploadForm(string domain, string directoryPath, string redirectTo, long maxUploadSize, string contentType, string contentDisposition, string submitLabel)
@ -608,7 +601,7 @@ public class DiscDataStore : BaseStorage
return Task.FromResult(size);
}
public override Task<Uri> CopyAsync(string srcdomain, string srcpath, string newdomain, string newpath)
public override async Task<Uri> CopyAsync(string srcdomain, string srcpath, string newdomain, string newpath)
{
ArgumentNullException.ThrowIfNull(srcpath);
ArgumentNullException.ThrowIfNull(newpath);
@ -626,16 +619,16 @@ public class DiscDataStore : BaseStorage
File.Copy(target, newtarget, true);
var flength = _crypt.GetFileSize(target);
QuotaUsedAdd(newdomain, flength);
await QuotaUsedAdd(newdomain, flength);
}
else
{
throw new FileNotFoundException("File not found", Path.GetFullPath(target));
}
return GetUriAsync(newdomain, newpath);
return await GetUriAsync(newdomain, newpath);
}
public override Task CopyDirectoryAsync(string srcdomain, string srcdir, string newdomain, string newdir)
public override async Task CopyDirectoryAsync(string srcdomain, string srcdir, string newdomain, string newdir)
{
var target = GetTarget(srcdomain, srcdir);
var newtarget = GetTarget(newdomain, newdir);
@ -643,8 +636,7 @@ public class DiscDataStore : BaseStorage
var diSource = new DirectoryInfo(target);
var diTarget = new DirectoryInfo(newtarget);
CopyAll(diSource, diTarget, newdomain);
return Task.CompletedTask;
await CopyAll(diSource, diTarget, newdomain);
}
@ -684,7 +676,7 @@ public class DiscDataStore : BaseStorage
return SaveAsync(domain, path, stream);
}
private void CopyAll(DirectoryInfo source, DirectoryInfo target, string newdomain)
private async Task CopyAll(DirectoryInfo source, DirectoryInfo target, string newdomain)
{
// Check if the target directory exists, if not, create it.
if (!Directory.Exists(target.FullName))
@ -698,14 +690,14 @@ public class DiscDataStore : BaseStorage
var fp = CrossPlatform.PathCombine(target.ToString(), fi.Name);
fi.CopyTo(fp, true);
var size = _crypt.GetFileSize(fp);
QuotaUsedAdd(newdomain, size);
await QuotaUsedAdd(newdomain, size);
}
// Copy each subdirectory using recursion.
foreach (var diSourceSubDir in source.GetDirectories())
{
var nextTargetSubDir = target.CreateSubdirectory(diSourceSubDir.Name);
CopyAll(diSourceSubDir, nextTargetSubDir, newdomain);
await CopyAll(diSourceSubDir, nextTargetSubDir, newdomain);
}
}
@ -768,7 +760,7 @@ public class DiscDataStore : BaseStorage
{
ArgumentNullException.ThrowIfNull(path);
var target = GetTarget(domain, path);
var target = GetTarget(domain, path);
var lastModificationDate = File.Exists(target) ? File.GetLastWriteTimeUtc(target) : throw new FileNotFoundException("File not found" + target);
var etag = '"' + lastModificationDate.Ticks.ToString("X8", CultureInfo.InvariantCulture) + '"';

View File

@ -48,8 +48,10 @@ public class GoogleCloudStorage : BaseStorage
IHttpContextAccessor httpContextAccessor,
ILoggerProvider factory,
ILogger<GoogleCloudStorage> options,
IHttpClientFactory clientFactory)
: base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, factory, options, clientFactory)
IHttpClientFactory clientFactory,
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper,
QuotaSocketManager quotaSocketManager)
: base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, factory, options, clientFactory, tenantQuotaFeatureStatHelper, quotaSocketManager)
{
}
@ -234,7 +236,7 @@ public class GoogleCloudStorage : BaseStorage
// InvalidateCloudFront(MakePath(domain, path));
QuotaUsedAdd(domain, buffered.Length);
await QuotaUsedAdd(domain, buffered.Length);
return await GetUriAsync(domain, path);
}
@ -248,7 +250,7 @@ public class GoogleCloudStorage : BaseStorage
await storage.DeleteObjectAsync(_bucket, key);
QuotaUsedDelete(domain, size);
await QuotaUsedDelete(domain, size);
}
public override async Task DeleteFilesAsync(string domain, string folderPath, string pattern, bool recursive)
@ -271,7 +273,7 @@ public class GoogleCloudStorage : BaseStorage
await foreach (var obj in objToDel)
{
await storage.DeleteObjectAsync(_bucket, obj.Name);
QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
await QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
}
}
@ -325,7 +327,7 @@ public class GoogleCloudStorage : BaseStorage
if (quotaUsed > 0)
{
QuotaUsedDelete(domain, quotaUsed);
await QuotaUsedDelete(domain, quotaUsed);
}
}
@ -339,7 +341,7 @@ public class GoogleCloudStorage : BaseStorage
await foreach (var obj in objToDel)
{
await storage.DeleteObjectAsync(_bucket, obj.Name);
QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
await QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
}
}
@ -378,8 +380,8 @@ public class GoogleCloudStorage : BaseStorage
await DeleteAsync(srcdomain, srcpath);
QuotaUsedDelete(srcdomain, size);
QuotaUsedAdd(newdomain, size, quotaCheckFileSize);
await QuotaUsedDelete(srcdomain, size);
await QuotaUsedAdd(newdomain, size, quotaCheckFileSize);
return await GetUriAsync(newdomain, newpath);
}
@ -455,7 +457,7 @@ public class GoogleCloudStorage : BaseStorage
await foreach (var obj in objToDel)
{
await storage.DeleteObjectAsync(_bucket, obj.Name);
QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
await QuotaUsedDelete(domain, Convert.ToInt64(obj.Size));
}
}
@ -549,7 +551,7 @@ public class GoogleCloudStorage : BaseStorage
await storage.CopyObjectAsync(_bucket, MakePath(srcdomain, srcpath), _bucket, MakePath(newdomain, newpath), options);
QuotaUsedAdd(newdomain, size);
await QuotaUsedAdd(newdomain, size);
return await GetUriAsync(newdomain, newpath);
}
@ -571,7 +573,7 @@ public class GoogleCloudStorage : BaseStorage
DestinationPredefinedAcl = GetDomainACL(newdomain)
});
QuotaUsedAdd(newdomain, Convert.ToInt64(obj.Size));
await QuotaUsedAdd(newdomain, Convert.ToInt64(obj.Size));
}
}
@ -734,7 +736,7 @@ public class GoogleCloudStorage : BaseStorage
if (QuotaController != null)
{
var size = await GetFileSizeAsync(domain, path);
QuotaUsedAdd(domain, size);
await QuotaUsedAdd(domain, size);
}
return await GetUriAsync(domain, path);

View File

@ -55,8 +55,10 @@ public class RackspaceCloudStorage : BaseStorage
IHttpContextAccessor httpContextAccessor,
ILoggerProvider options,
ILogger<RackspaceCloudStorage> logger,
IHttpClientFactory httpClient)
: base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, options, logger, httpClient)
IHttpClientFactory httpClient,
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper,
QuotaSocketManager quotaSocketManager)
: base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, options, logger, httpClient, tenantQuotaFeatureStatHelper, quotaSocketManager)
{
_logger = logger;
TempPath = tempPath;
@ -192,7 +194,7 @@ public class RackspaceCloudStorage : BaseStorage
return (QuotaController != null) && !domain.EndsWith("_temp");
}
public Task<Uri> SaveAsync(string domain, string path, Stream stream, string contentType,
public async Task<Uri> SaveAsync(string domain, string path, Stream stream, string contentType,
string contentDisposition, ACL acl, string contentEncoding = null, int cacheDays = 5,
DateTime? deleteAt = null, long? deleteAfter = null)
{
@ -282,9 +284,9 @@ public class RackspaceCloudStorage : BaseStorage
_region
);
QuotaUsedAdd(domain, buffered.Length);
await QuotaUsedAdd(domain, buffered.Length);
return GetUriAsync(domain, path);
return await GetUriAsync(domain, path);
}
@ -296,11 +298,11 @@ public class RackspaceCloudStorage : BaseStorage
client.DeleteObject(_private_container, MakePath(domain, path));
QuotaUsedDelete(domain, size);
await QuotaUsedDelete(domain, size);
}
public override Task DeleteFilesAsync(string domain, string folderPath, string pattern, bool recursive)
public override async Task DeleteFilesAsync(string domain, string folderPath, string pattern, bool recursive)
{
var client = GetClient();
@ -309,7 +311,7 @@ public class RackspaceCloudStorage : BaseStorage
if (!files.Any())
{
return Task.CompletedTask;
return;
}
foreach (var file in files)
@ -319,10 +321,8 @@ public class RackspaceCloudStorage : BaseStorage
if (QuotaController != null)
{
QuotaUsedDelete(domain, files.Select(x => x.Bytes).Sum());
await QuotaUsedDelete(domain, files.Select(x => x.Bytes).Sum());
}
return Task.CompletedTask;
}
public override Task DeleteFilesAsync(string domain, List<string> paths)
@ -371,11 +371,11 @@ public class RackspaceCloudStorage : BaseStorage
if (quotaUsed > 0)
{
QuotaUsedDelete(domain, quotaUsed);
await QuotaUsedDelete(domain, quotaUsed);
}
}
public override Task DeleteFilesAsync(string domain, string folderPath, DateTime fromDate, DateTime toDate)
public override async Task DeleteFilesAsync(string domain, string folderPath, DateTime fromDate, DateTime toDate)
{
var client = GetClient();
@ -384,7 +384,7 @@ public class RackspaceCloudStorage : BaseStorage
if (!files.Any())
{
return Task.CompletedTask;
return;
}
foreach (var file in files)
@ -394,10 +394,8 @@ public class RackspaceCloudStorage : BaseStorage
if (QuotaController != null)
{
QuotaUsedDelete(domain, files.Select(x => x.Bytes).Sum());
await QuotaUsedDelete(domain, files.Select(x => x.Bytes).Sum());
}
return Task.CompletedTask;
}
public override Task MoveDirectoryAsync(string srcdomain, string srcdir, string newdomain, string newdir)
@ -429,8 +427,8 @@ public class RackspaceCloudStorage : BaseStorage
await DeleteAsync(srcdomain, srcpath);
QuotaUsedDelete(srcdomain, size);
QuotaUsedAdd(newdomain, size, quotaCheckFileSize);
await QuotaUsedDelete(srcdomain, size);
await QuotaUsedAdd(newdomain, size, quotaCheckFileSize);
return await GetUriAsync(newdomain, newpath);
}
@ -475,7 +473,7 @@ public class RackspaceCloudStorage : BaseStorage
return IsFileAsync(domain, path);
}
public override Task DeleteDirectoryAsync(string domain, string path)
public override async Task DeleteDirectoryAsync(string domain, string path)
{
var client = GetClient();
@ -484,9 +482,8 @@ public class RackspaceCloudStorage : BaseStorage
foreach (var obj in objToDel)
{
client.DeleteObject(_private_container, obj.Name);
QuotaUsedDelete(domain, obj.Bytes);
await QuotaUsedDelete(domain, obj.Bytes);
}
return Task.CompletedTask;
}
public override Task<long> GetFileSizeAsync(string domain, string path)
@ -571,12 +568,12 @@ public class RackspaceCloudStorage : BaseStorage
client.CopyObject(_private_container, srcKey, _private_container, dstKey);
QuotaUsedAdd(newdomain, size);
await QuotaUsedAdd(newdomain, size);
return await GetUriAsync(newdomain, newpath);
}
public override Task CopyDirectoryAsync(string srcdomain, string dir, string newdomain, string newdir)
public override async Task CopyDirectoryAsync(string srcdomain, string dir, string newdomain, string newdir)
{
var srckey = MakePath(srcdomain, dir);
var dstkey = MakePath(newdomain, newdir);
@ -588,9 +585,8 @@ public class RackspaceCloudStorage : BaseStorage
{
client.CopyObject(_private_container, file.Name, _private_container, file.Name.Replace(srckey, dstkey));
QuotaUsedAdd(newdomain, file.Bytes);
await QuotaUsedAdd(newdomain, file.Bytes);
}
return Task.CompletedTask;
}
public override async Task<string> SavePrivateAsync(string domain, string path, Stream stream, DateTime expires)
@ -671,7 +667,7 @@ public class RackspaceCloudStorage : BaseStorage
{
var size = await GetFileSizeAsync(domain, path);
QuotaUsedAdd(domain, size);
await QuotaUsedAdd(domain, size);
}
return await GetUriAsync(domain, path);

View File

@ -67,8 +67,10 @@ public class S3Storage : BaseStorage
ILoggerProvider factory,
ILogger<S3Storage> options,
IHttpClientFactory clientFactory,
IConfiguration configuration)
: base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, factory, options, clientFactory)
IConfiguration configuration,
TenantQuotaFeatureStatHelper tenantQuotaFeatureStatHelper,
QuotaSocketManager quotaSocketManager)
: base(tempStream, tenantManager, pathUtils, emailValidationKeyProvider, httpContextAccessor, factory, options, clientFactory, tenantQuotaFeatureStatHelper, quotaSocketManager)
{
_configuration = configuration;
}
@ -259,7 +261,7 @@ public class S3Storage : BaseStorage
await InvalidateCloudFrontAsync(MakePath(domain, path));
QuotaUsedAdd(domain, buffered.Length);
await QuotaUsedAdd(domain, buffered.Length);
return await GetUriAsync(domain, path);
}
@ -352,7 +354,7 @@ public class S3Storage : BaseStorage
if (QuotaController != null)
{
var size = await GetFileSizeAsync(domain, path);
QuotaUsedAdd(domain, size);
await QuotaUsedAdd(domain, size);
}
return await GetUriAsync(domain, path);
@ -407,7 +409,7 @@ public class S3Storage : BaseStorage
await client.DeleteObjectAsync(request);
QuotaUsedDelete(domain, size);
await QuotaUsedDelete(domain, size);
}
public override Task DeleteFilesAsync(string domain, List<string> paths)
@ -467,7 +469,7 @@ public class S3Storage : BaseStorage
if (quotaUsed > 0)
{
QuotaUsedDelete(domain, quotaUsed);
await QuotaUsedDelete(domain, quotaUsed);
}
}
@ -493,7 +495,7 @@ public class S3Storage : BaseStorage
await client.DeleteObjectAsync(deleteRequest);
QuotaUsedDelete(domain, s3Object.Size);
await QuotaUsedDelete(domain, s3Object.Size);
}
}
@ -515,7 +517,7 @@ public class S3Storage : BaseStorage
await client.DeleteObjectAsync(deleteRequest);
QuotaUsedDelete(domain, s3Object.Size);
await QuotaUsedDelete(domain, s3Object.Size);
}
}
@ -554,8 +556,8 @@ public class S3Storage : BaseStorage
await CopyFileAsync(client, srcKey, dstKey, newdomain, S3MetadataDirective.REPLACE);
await DeleteAsync(srcdomain, srcpath);
QuotaUsedDelete(srcdomain, size);
QuotaUsedAdd(newdomain, size, quotaCheckFileSize);
await QuotaUsedDelete(srcdomain, size);
await QuotaUsedAdd(newdomain, size, quotaCheckFileSize);
return await GetUriAsync(newdomain, newpath);
}
@ -848,7 +850,7 @@ public class S3Storage : BaseStorage
using var client = GetClient();
await CopyFileAsync(client, srcKey, dstKey, newdomain, S3MetadataDirective.REPLACE);
QuotaUsedAdd(newdomain, size);
await QuotaUsedAdd(newdomain, size);
return await GetUriAsync(newdomain, newpath);
}
@ -866,7 +868,7 @@ public class S3Storage : BaseStorage
{
await CopyFileAsync(client, s3Object.Key, s3Object.Key.Replace(srckey, dstkey), newdomain);
QuotaUsedAdd(newdomain, s3Object.Size);
await QuotaUsedAdd(newdomain, s3Object.Size);
}
}

View File

@ -102,7 +102,7 @@ public class ChunkZipWriteOperator : IDataWriteOperator
theMemStream.Position = 0;
if (bytesRead == chunkUploadSize || last)
{
if (_fileStream.Position == _fileStream.Length)
if (_fileStream.Position == _fileStream.Length && last)
{
_chunkedUploadSession.LastChunk = true;
}

View File

@ -38,6 +38,5 @@ public class FeedApiFilter
public Guid Author { get; set; }
public string[] SearchKeys { get; set; }
public bool OnlyNew { get; set; }
public bool WithoutMe { get; set; }
public bool WithRelated { get; set; }
public bool History { get; set; }
}

View File

@ -202,23 +202,58 @@ public class FeedAggregateDataProvider
var q = feedDbContext.FeedAggregates.AsNoTracking()
.Where(r => r.Tenant == _tenantManager.GetCurrentTenant().Id);
var exp = GetIdSearchExpression(filter.Id, filter.Module, filter.WithRelated);
var feeds = filter.History ? GetFeedsAsHistoryQuery(q, filter) : GetFeedsDefaultQuery(feedDbContext, q, filter);
if (exp != null)
return _mapper.Map<IEnumerable<FeedAggregate>, List<FeedResultItem>>(feeds);
}
private static IQueryable<FeedAggregate> GetFeedsAsHistoryQuery(IQueryable<FeedAggregate> query, FeedApiFilter filter)
{
Expression<Func<FeedAggregate, bool>> exp = null;
ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(filter.Id);
ArgumentNullOrEmptyException.ThrowIfNullOrEmpty(filter.Module);
switch (filter.Module)
{
q = q.Where(exp);
case Constants.RoomsModule:
{
var roomId = $"{Constants.RoomItem}_{filter.Id}";
exp = f => f.Id == roomId || (f.Id.StartsWith(Constants.SharedRoomItem) && f.ContextId == roomId);
if (filter.History)
{
exp = f => f.Id == roomId || f.ContextId == roomId;
}
break;
}
case Constants.FilesModule:
exp = f => f.Id.StartsWith($"{Constants.FileItem}_{filter.Id}") || f.Id.StartsWith($"{Constants.SharedFileItem}_{filter.Id}");
break;
case Constants.FoldersModule:
exp = f => f.Id == $"{Constants.FolderItem}_{filter.Id}" || f.Id.StartsWith($"{Constants.SharedFolderItem}_{filter.Id}");
break;
}
var q1 = q.Join(feedDbContext.FeedUsers, a => a.Id, b => b.FeedId, (aggregates, users) => new { aggregates, users })
if (exp == null)
{
throw new InvalidOperationException();
}
return query.Where(exp);
}
private IQueryable<FeedAggregate> GetFeedsDefaultQuery(FeedDbContext feedDbContext, IQueryable<FeedAggregate> query, FeedApiFilter filter)
{
var q1 = query.Join(feedDbContext.FeedUsers, a => a.Id, b => b.FeedId, (aggregates, users) => new { aggregates, users })
.OrderByDescending(r => r.aggregates.ModifiedDate)
.Skip(filter.Offset)
.Take(filter.Max);
if (exp == null)
{
q1 = q1.Where(r => r.aggregates.ModifiedBy != _authContext.CurrentAccount.ID).
Where(r => r.users.UserId == _authContext.CurrentAccount.ID);
}
q1 = q1.Where(r => r.aggregates.ModifiedBy != _authContext.CurrentAccount.ID).
Where(r => r.users.UserId == _authContext.CurrentAccount.ID);
if (filter.OnlyNew)
{
@ -249,16 +284,14 @@ public class FeedAggregateDataProvider
if (filter.SearchKeys != null && filter.SearchKeys.Length > 0)
{
var keys = filter.SearchKeys
.Where(s => !string.IsNullOrEmpty(s))
.Select(s => s.Replace("\\", "\\\\").Replace("%", "\\%").Replace("_", "\\_"))
.ToList();
.Where(s => !string.IsNullOrEmpty(s))
.Select(s => s.Replace("\\", "\\\\").Replace("%", "\\%").Replace("_", "\\_"))
.ToList();
q1 = q1.Where(r => keys.Any(k => r.aggregates.Keywords.StartsWith(k)));
}
var news = q1.Select(r => r.aggregates).Distinct().AsEnumerable();
return _mapper.Map<IEnumerable<FeedAggregate>, List<FeedResultItem>>(news);
return q1.Select(r => r.aggregates).Distinct();
}
public int GetNewFeedsCount(DateTime lastReadedTime)
@ -323,30 +356,30 @@ public class FeedAggregateDataProvider
if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(module))
{
return exp;
return null;
}
if (module == Constants.RoomsModule)
switch (module)
{
var roomId = $"{Constants.RoomItem}_{id}";
var sharedRoomId = $"{Constants.SharedRoomItem}_{id}";
case Constants.RoomsModule:
{
var roomId = $"{Constants.RoomItem}_{id}";
exp = f => f.Id == roomId || f.Id.StartsWith(sharedRoomId);
exp = f => f.Id == roomId || (f.Id.StartsWith(Constants.SharedRoomItem) && f.ContextId == roomId);
if (withRelated)
{
exp = f => f.Id == roomId || f.Id.StartsWith(sharedRoomId) || f.ContextId == roomId;
}
}
if (withRelated)
{
exp = f => f.Id == roomId || f.ContextId == roomId;
}
if (module == Constants.FilesModule)
{
exp = f => f.Id.StartsWith($"{Constants.FileItem}_{id}") || f.Id.StartsWith($"{Constants.SharedFileItem}_{id}");
}
if (module == Constants.FoldersModule)
{
exp = f => f.Id == $"{Constants.FolderItem}_{id}" || f.Id.StartsWith($"{Constants.SharedFolderItem}_{id}");
break;
}
case Constants.FilesModule:
exp = f => f.Id.StartsWith($"{Constants.FileItem}_{id}") || f.Id.StartsWith($"{Constants.SharedFileItem}_{id}");
break;
case Constants.FoldersModule:
exp = f => f.Id == $"{Constants.FolderItem}_{id}" || f.Id.StartsWith($"{Constants.SharedFolderItem}_{id}");
break;
}
return exp;

View File

@ -46,7 +46,7 @@ public class GwsMigratingFiles : MigratingFiles
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly FileStorageService<int> _fileStorageService;
private readonly FileStorageService _fileStorageService;
private readonly string _rootFolder;
private int _foldersCount;
private int _filesCount;
@ -109,7 +109,7 @@ public class GwsMigratingFiles : MigratingFiles
_groups = groups.ToDictionary(group => group.GroupName, group => group);
}
public GwsMigratingFiles(GlobalFolderHelper globalFolderHelper, IDaoFactory daoFactory, FileSecurity fileSecurity, FileStorageService<int> fileStorageService, string rootFolder, GwsMigratingUser user, Action<string, Exception> log) : base(log)
public GwsMigratingFiles(GlobalFolderHelper globalFolderHelper, IDaoFactory daoFactory, FileSecurity fileSecurity, FileStorageService fileStorageService, string rootFolder, GwsMigratingUser user, Action<string, Exception> log) : base(log)
{
_globalFolderHelper = globalFolderHelper;
_daoFactory = daoFactory;

View File

@ -38,14 +38,14 @@ public class GwsMigratingUser : MigratingUser<GwsMigratingContacts, GwsMigrating
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly FileStorageService<int> _fileStorageService;
private readonly FileStorageService _fileStorageService;
private readonly UserManager _userManager;
public GwsMigratingUser(
GlobalFolderHelper globalFolderHelper,
IDaoFactory daoFactory,
FileSecurity fileSecurity,
FileStorageService<int> fileStorageService,
FileStorageService fileStorageService,
UserManager userManager)
{
_globalFolderHelper = globalFolderHelper;

View File

@ -42,7 +42,7 @@ public class NCMigratingFiles : MigratingFiles
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly FileStorageService<int> _fileStorageService;
private readonly FileStorageService _fileStorageService;
private readonly NCMigratingUser _user;
private readonly string _rootFolder;
private List<NCFileCache> _files;
@ -55,7 +55,7 @@ public class NCMigratingFiles : MigratingFiles
private Dictionary<string, NCMigratingGroups> _groups;
private Dictionary<object, int> _matchingFileId;
private string _folderCreation;
public NCMigratingFiles(GlobalFolderHelper globalFolderHelper, IDaoFactory daoFactory, FileSecurity fileSecurity, FileStorageService<int> fileStorageService, NCMigratingUser user, NCStorages storages, string rootFolder, Action<string, Exception> log) : base(log)
public NCMigratingFiles(GlobalFolderHelper globalFolderHelper, IDaoFactory daoFactory, FileSecurity fileSecurity, FileStorageService fileStorageService, NCMigratingUser user, NCStorages storages, string rootFolder, Action<string, Exception> log) : base(log)
{
_globalFolderHelper = globalFolderHelper;
_daoFactory = daoFactory;

View File

@ -46,7 +46,7 @@ public class NCMigratingUser : MigratingUser<NCMigratingContacts, NCMigratingCal
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly FileStorageService<int> _fileStorageService;
private readonly FileStorageService _fileStorageService;
private readonly TenantManager _tenantManager;
private readonly UserManager _userManager;
private readonly NCUser _user;
@ -57,7 +57,7 @@ public class NCMigratingUser : MigratingUser<NCMigratingContacts, NCMigratingCal
GlobalFolderHelper globalFolderHelper,
IDaoFactory daoFactory,
FileSecurity fileSecurity,
FileStorageService<int> fileStorageService,
FileStorageService fileStorageService,
TenantManager tenantManager,
UserManager userManager,
string key,

View File

@ -38,7 +38,7 @@ public class NextcloudWorkspaceMigration : AbstractMigration<NCMigrationInfo, NC
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly FileStorageService<int> _fileStorageService;
private readonly FileStorageService _fileStorageService;
private readonly SecurityContext _securityContext;
private readonly UserManager _userManager;
private readonly TenantManager _tenantManager;
@ -47,7 +47,7 @@ public class NextcloudWorkspaceMigration : AbstractMigration<NCMigrationInfo, NC
GlobalFolderHelper globalFolderHelper,
IDaoFactory daoFactory,
FileSecurity fileSecurity,
FileStorageService<int> fileStorageService,
FileStorageService fileStorageService,
SecurityContext securityContext,
UserManager userManager,
TenantManager tenantManager,

View File

@ -41,8 +41,7 @@ public class OCMigratingFiles : MigratingFiles
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly FileStorageService<int> _fileStorageService;
private readonly FileStorageService _fileStorageService;
private readonly OCMigratingUser _user;
private readonly string _rootFolder;
private List<OCFileCache> _files;
@ -55,11 +54,10 @@ public class OCMigratingFiles : MigratingFiles
private Dictionary<string, OCMigratingGroups> _groups;
private Dictionary<object, int> _matchingFileId;
private string _folderCreation;
public OCMigratingFiles(GlobalFolderHelper globalFolderHelper, IDaoFactory daoFactory, FileSecurity fileSecurity, FileStorageService<int> fileStorageService, OCMigratingUser user, OCStorages storages, string rootFolder, Action<string, Exception> log) : base(log)
public OCMigratingFiles(GlobalFolderHelper globalFolderHelper, IDaoFactory daoFactory, FileStorageService fileStorageService, OCMigratingUser user, OCStorages storages, string rootFolder, Action<string, Exception> log) : base(log)
{
_globalFolderHelper = globalFolderHelper;
_daoFactory = daoFactory;
_fileSecurity = fileSecurity;
_fileStorageService = fileStorageService;
_user = user;
_rootFolder = rootFolder;

View File

@ -45,8 +45,7 @@ public class OCMigratingUser : MigratingUser<OCMigratingContacts, OCMigratingCal
private UserInfo _userInfo;
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly FileStorageService<int> _fileStorageService;
private readonly FileStorageService _fileStorageService;
private readonly TenantManager _tenantManager;
private readonly UserManager _userManager;
private readonly OCUser _user;
@ -55,8 +54,7 @@ public class OCMigratingUser : MigratingUser<OCMigratingContacts, OCMigratingCal
public OCMigratingUser(
GlobalFolderHelper globalFolderHelper,
IDaoFactory daoFactory,
FileSecurity fileSecurity,
FileStorageService<int> fileStorageService,
FileStorageService fileStorageService,
TenantManager tenantManager,
UserManager userManager,
string key,
@ -67,7 +65,6 @@ public class OCMigratingUser : MigratingUser<OCMigratingContacts, OCMigratingCal
Key = key;
_globalFolderHelper = globalFolderHelper;
_daoFactory = daoFactory;
_fileSecurity = fileSecurity;
_fileStorageService = fileStorageService;
_tenantManager = tenantManager;
_userManager = userManager;
@ -126,7 +123,7 @@ public class OCMigratingUser : MigratingUser<OCMigratingContacts, OCMigratingCal
ModulesList.Add(new MigrationModules(MigratingCalendar.ModuleName, MigrationResource.OnlyofficeModuleNameCalendar));
}
MigratingFiles = new OCMigratingFiles(_globalFolderHelper, _daoFactory, _fileSecurity, _fileStorageService, this, _user.Storages, _rootFolder, log);
MigratingFiles = new OCMigratingFiles(_globalFolderHelper, _daoFactory, _fileStorageService, this, _user.Storages, _rootFolder, log);
MigratingFiles.Parse();
if (MigratingFiles.FoldersCount != 0 || MigratingFiles.FilesCount != 0)
{

View File

@ -37,7 +37,7 @@ public class OwnCloudMigration : AbstractMigration<OCMigrationInfo, OCMigratingU
private readonly GlobalFolderHelper _globalFolderHelper;
private readonly IDaoFactory _daoFactory;
private readonly FileSecurity _fileSecurity;
private readonly FileStorageService<int> _fileStorageService;
private readonly FileStorageService _fileStorageService;
private readonly SecurityContext _securityContext;
private readonly TenantManager _tenantManager;
private readonly UserManager _userManager;
@ -46,7 +46,7 @@ public class OwnCloudMigration : AbstractMigration<OCMigrationInfo, OCMigratingU
GlobalFolderHelper globalFolderHelper,
IDaoFactory daoFactory,
FileSecurity fileSecurity,
FileStorageService<int> fileStorageService,
FileStorageService fileStorageService,
SecurityContext securityContext,
TenantManager tenantManager,
UserManager userManager,

View File

@ -45,11 +45,21 @@
files.markAsNewFile(req.body);
res.end();
});
router.post("/markasnew-folder", (req, res) => {
files.markAsNewFolder(req.body);
res.end();
});
router.post("/change-quota-used-value", (req, res) => {
files.changeQuotaUsedValue(req.body);
res.end();
});
router.post("/change-quota-feature-value", (req, res) => {
files.changeQuotaFeatureValue(req.body);
res.end();
});
return router;
};

View File

@ -55,11 +55,11 @@
);
});
socket.on("subscribe", ({roomParts, individual}) => {
socket.on("subscribe", ({ roomParts, individual }) => {
changeSubscription(roomParts, individual, subscribe);
});
socket.on("unsubscribe", ({roomParts, individual}) => {
socket.on("unsubscribe", ({ roomParts, individual }) => {
changeSubscription(roomParts, individual, unsubscribe);
});
@ -80,7 +80,7 @@
changeFunc(roomParts);
if(individual){
if (individual) {
if (Array.isArray(roomParts)) {
changeFunc(roomParts.map((p) => `${p}-${userId}`));
} else {
@ -116,7 +116,6 @@
socket.leave(room);
}
}
});
function startEdit({ fileId, room } = {}) {
@ -167,11 +166,34 @@
logger.info(`markAsNewFile ${fileId} in room ${room}:${count}`);
filesIO.to(room).emit("s:markasnew-file", { fileId, count });
}
function markAsNewFolder({ folderId, count, room } = {}) {
logger.info(`markAsNewFolder ${folderId} in room ${room}:${count}`);
filesIO.to(room).emit("s:markasnew-folder", { folderId, count });
}
return { startEdit, stopEdit, createFile, createFolder, deleteFile, deleteFolder, updateFile, updateFolder, markAsNewFile, markAsNewFolder };
function changeQuotaUsedValue({ featureId, value, room } = {}) {
logger.info(`changeQuotaUsedValue in room ${room}`, { featureId, value });
filesIO.to(room).emit("s:change-quota-used-value", { featureId, value });
}
function changeQuotaFeatureValue({ featureId, value, room } = {}) {
logger.info(`changeQuotaFeatureValue in room ${room}`, { featureId, value });
filesIO.to(room).emit("s:change-quota-feature-value", { featureId, value });
}
return {
startEdit,
stopEdit,
createFile,
createFolder,
deleteFile,
deleteFolder,
updateFile,
updateFolder,
markAsNewFile,
markAsNewFolder,
changeQuotaUsedValue,
changeQuotaFeatureValue,
};
};

View File

@ -28,7 +28,10 @@ const accessKeyId = aws.accessKeyId;
const secretAccessKey = aws.secretAccessKey;
const awsRegion = aws.region;
const logGroupName = aws.logGroupName;
const logStreamName = aws.logStreamName;
const logStreamName = aws.logStreamName.replace("${hostname}", os.hostname())
.replace("${applicationContext}", "SocketIO")
.replace("${guid}", randomUUID())
.replace("${date}", date.format(new Date(), 'YYYY/MM/DDTHH.mm.ss'));
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName);
@ -54,17 +57,7 @@ var options = {
cloudWatch: {
name: 'aws',
level: "debug",
logStreamName: () => {
const hostname = os.hostname();
const now = new Date();
const guid = randomUUID();
const dateAsString = date.format(now, 'YYYY/MM/DDTHH.mm.ss');
return logStreamName.replace("${hostname}", hostname)
.replace("${applicationContext}", "SocketIO")
.replace("${guid}", guid)
.replace("${date}", dateAsString);
},
logStreamName: logStreamName,
logGroupName: logGroupName,
awsRegion: awsRegion,
jsonMessage: true,

View File

@ -58,7 +58,10 @@ const accessKeyId = aws.accessKeyId;
const secretAccessKey = aws.secretAccessKey;
const awsRegion = aws.region;
const logGroupName = aws.logGroupName;
const logStreamName = aws.logStreamName;
const logStreamName = aws.logStreamName.replace("${hostname}", os.hostname())
.replace("${applicationContext}", "SsoAuth")
.replace("${guid}", randomUUID())
.replace("${date}", date.format(new Date(), 'YYYY/MM/DDTHH.mm.ss'));
let transports = [];
@ -77,17 +80,7 @@ if (aws != null && aws.accessKeyId !== '')
transports.push(new WinstonCloudWatch({
name: 'aws',
level: "debug",
logStreamName: () => {
const hostname = os.hostname();
const now = new Date();
const guid = randomUUID();
const dateAsString = date.format(now, 'YYYY/MM/DDTHH.mm.ss');
return logStreamName.replace("${hostname}", hostname)
.replace("${applicationContext}", "SsoAuth")
.replace("${guid}", guid)
.replace("${date}", dateAsString);
},
logStreamName: logStreamName,
logGroupName: logGroupName,
awsRegion: awsRegion,
jsonMessage: true,

View File

@ -72,7 +72,10 @@ const accessKeyId = aws.accessKeyId;
const secretAccessKey = aws.secretAccessKey;
const awsRegion = aws.region;
const logGroupName = aws.logGroupName;
const logStreamName = aws.logStreamName;
const logStreamName = aws.logStreamName.replace("${hostname}", os.hostname())
.replace("${applicationContext}", "WebDav")
.replace("${guid}", randomUUID())
.replace("${date}", date.format(new Date(), 'YYYY/MM/DDTHH.mm.ss'));
let transports = [
new (winston.transports.Console)(),
@ -85,17 +88,7 @@ if (aws != null && aws.accessKeyId !== '')
transports.push(new WinstonCloudWatch({
name: 'aws',
level: "debug",
logStreamName: () => {
const hostname = os.hostname();
const now = new Date();
const guid = randomUUID();
const dateAsString = date.format(now, 'YYYY/MM/DDTHH.mm.ss');
return logStreamName.replace("${hostname}", hostname)
.replace("${applicationContext}", "WebDav")
.replace("${guid}", guid)
.replace("${date}", dateAsString);
},
logStreamName: logStreamName,
logGroupName: logGroupName,
awsRegion: awsRegion,
jsonMessage: true,

View File

@ -32,7 +32,10 @@ const accessKeyId = aws.accessKeyId;
const secretAccessKey = aws.secretAccessKey;
const awsRegion = aws.region;
const logGroupName = aws.logGroupName;
const logStreamName = aws.logStreamName;
const logStreamName = aws.logStreamName.replace("${hostname}", os.hostname())
.replace("${applicationContext}", "WebPlugins")
.replace("${guid}", randomUUID())
.replace("${date}", date.format(new Date(), 'YYYY/MM/DDTHH.mm.ss'));
const options = {
file: {
@ -54,18 +57,8 @@ const options = {
cloudWatch: {
name: 'aws',
level: "debug",
logStreamName: () => {
const hostname = os.hostname();
const now = new Date();
const guid = randomUUID();
const dateAsString = date.format(now, 'YYYY/MM/DDTHH.mm.ss');
return logStreamName.replace("${hostname}", hostname)
.replace("${applicationContext}", "WebPlugins")
.replace("${guid}", guid)
.replace("${date}", dateAsString);
},
logGroupName:logGroupName,
logStreamName: logStreamName,
logGroupName: logGroupName,
awsRegion: awsRegion,
jsonMessage: true,
awsOptions: {

View File

@ -56,11 +56,26 @@ public class LocalesTest
}
}
public static bool Save
{
get
{
bool save;
if (bool.TryParse(Environment.GetEnvironmentVariable("SAVE"), out save))
{
return save;
}
return false;
}
}
public List<string> Workspaces { get; set; }
public List<TranslationFile> TranslationFiles { get; set; }
public List<JavaScriptFile> JavaScriptFiles { get; set; }
public List<ModuleFolder> ModuleFolders { get; set; }
public List<KeyValuePair<string, string>> NotTranslatedToasts { get; set; }
public List<KeyValuePair<string, string>> NotTranslatedProps { get; set; }
public List<LanguageItem> CommonTranslations { get; set; }
public List<ParseJsonError> ParseJsonErrors { get; set; }
public static string ConvertPathToOS { get; private set; }
@ -68,13 +83,19 @@ public class LocalesTest
//public List<JsonEncodingError> WrongEncodingJsonErrors { get; set; }
private static readonly string _md5ExcludesPath = Path.GetFullPath(Utils.ConvertPathToOS("../../../md5-excludes.json"));
private static readonly string _spellCheckCommonExcludesPath = Path.GetFullPath(Utils.ConvertPathToOS("../../../spellcheck-excludes-common.json"));
private static readonly string _spellCheckExcludesPath = Path.GetFullPath(Utils.ConvertPathToOS("../../../spellcheck-excludes.json"));
//private static string _encodingExcludesPath = "../../../encoding-excludes.json";
private static readonly List<string> _md5Excludes = File.Exists(_md5ExcludesPath)
private static readonly List<string> Md5Excludes = File.Exists(_md5ExcludesPath)
? JsonConvert.DeserializeObject<List<string>>(File.ReadAllText(_md5ExcludesPath))
: new List<string>();
private static readonly List<string> SpellCheckCommonExcludes = File.Exists(_spellCheckCommonExcludesPath)
? JsonConvert.DeserializeObject<List<string>>(File.ReadAllText(_spellCheckCommonExcludesPath))
: new List<string>();
//private static List<string> encodingExcludes = File.Exists(_encodingExcludesPath)
// ? JsonConvert.DeserializeObject<List<string>>(File.ReadAllText(_encodingExcludesPath))
// : new List<string>();
@ -201,7 +222,10 @@ public class LocalesTest
"|(?<=toastr.success\\([\"`\'])(.*)(?=[\"\'`])" +
"|(?<=toastr.warn\\([\"`\'])(.*)(?=[\"\'`])", RegexOptions.Multiline | RegexOptions.ECMAScript);
var notTranslatedPropsRegex = new Regex("<[\\w\\n][^>]* (title|placeholder|label|text)={?[\\\"\\'](.*)[\\\"\\']}?", RegexOptions.Multiline | RegexOptions.ECMAScript);
NotTranslatedToasts = new List<KeyValuePair<string, string>>();
NotTranslatedProps = new List<KeyValuePair<string, string>>();
foreach (var path in javascriptFiles)
{
@ -219,6 +243,18 @@ public class LocalesTest
}
}
var propsMatches = notTranslatedPropsRegex.Matches(jsFileText).ToList();
if (propsMatches.Any())
{
foreach (var propsMatch in propsMatches)
{
var found = propsMatch.Value;
if (!string.IsNullOrEmpty(found) && !NotTranslatedProps.Exists(t => t.Value == found))
NotTranslatedProps.Add(new KeyValuePair<string, string>(path, found));
}
}
var matches = regexp.Matches(jsFileText);
var translationKeys = matches
@ -318,7 +354,11 @@ public class LocalesTest
TestContext.Progress.WriteLine($"Found CommonTranslations = {CommonTranslations.Count()}. First path is '{CommonTranslations.FirstOrDefault()?.Path}'");
TestContext.Progress.WriteLine($"Found _md5Excludes = {_md5Excludes.Count()} Path to file '{_md5ExcludesPath}'");
TestContext.Progress.WriteLine($"Found Md5Excludes = {Md5Excludes.Count} Path to file '{_md5ExcludesPath}'");
TestContext.Progress.WriteLine($"Found SpellCheckCommonExcludes = {SpellCheckCommonExcludes.Count} Path to file '{_spellCheckCommonExcludesPath}'");
TestContext.Progress.WriteLine($"Save spell check excludes = {Save} Path to file '{_spellCheckExcludesPath}'");
}
@ -359,7 +399,7 @@ public class LocalesTest
var errorsCount = 0;
var message = $"Next keys have spell check issues:\r\n\r\n";
//var list = new List<SpellCheckExclude>();
var list = new List<SpellCheckExclude>();
var groupByLng = TranslationFiles
.GroupBy(t => t.Language)
@ -376,7 +416,7 @@ public class LocalesTest
{
var dicPaths = SpellCheck.GetDictionaryPaths(group.Language);
//var spellCheckExclude = new SpellCheckExclude(group.Language);
var spellCheckExclude = new SpellCheckExclude(group.Language);
using (var dictionaryStream = File.OpenRead(dicPaths.DictionaryPath))
using (var affixStream = File.OpenRead(dicPaths.AffixPath))
@ -391,27 +431,44 @@ public class LocalesTest
if (result.HasProblems)
{
message += $"{++i}. lng='{group.Language}' file='{g.FilePath}'\r\nkey='{item.Key}' value='{item.Value}'\r\nIncorrect words:\r\n{string.Join("\r\n", result.SpellIssues.Select(issue => $"'{issue.Word}' Suggestion: '{issue.Suggestions.FirstOrDefault()}'"))}\r\n\r\n";
var incorrectWords = result.SpellIssues
.Where(t => !SpellCheckCommonExcludes
.Exists(e => e.Equals(t.Word, StringComparison.InvariantCultureIgnoreCase)))
.Select(issue => $"'{issue.Word}' " +
$"Suggestion: '{issue.Suggestions.FirstOrDefault()}'")
.ToList();
if (!incorrectWords.Any())
continue;
message += $"{++i}. lng='{group.Language}' file='{g.FilePath}'\r\nkey='{item.Key}' " +
$"value='{item.Value}'\r\nIncorrect words:\r\n" +
$"{string.Join("\r\n", incorrectWords)}\r\n\r\n";
errorsCount++;
/*foreach (var word in result.SpellIssues
if (Save)
{
foreach (var word in result.SpellIssues
.Where(issue => issue.Suggestions.Any())
.Select(issue => issue.Word))
{
if (!spellCheckExclude.Excludes.Contains(word))
{
spellCheckExclude.Excludes.Add(word);
}
}*/
.Select(issue => issue.Word))
{
if (!spellCheckExclude.Excludes.Contains(word))
{
spellCheckExclude.Excludes.Add(word);
}
}
}
}
}
}
}
//spellCheckExclude.Excludes.Sort();
//list.Add(spellCheckExclude);
if (Save)
{
spellCheckExclude.Excludes.Sort();
list.Add(spellCheckExclude);
}
}
catch (NotSupportedException)
{
@ -420,8 +477,12 @@ public class LocalesTest
}
}
//string json = JsonConvert.SerializeObject(list, Formatting.Indented);
//File.WriteAllText("../../../spellcheck-excludes.json", json, Encoding.UTF8);
if (Save)
{
string json = JsonConvert.SerializeObject(list, Formatting.Indented);
File.WriteAllText(_spellCheckExcludesPath, json, Encoding.UTF8);
TestContext.Progress.WriteLine($"File spellcheck-excludes.json has been saved to '{_spellCheckExcludesPath}'");
}
Assert.AreEqual(0, errorsCount, message);
}
@ -443,7 +504,7 @@ public class LocalesTest
{
var duplicatesByMD5 = TranslationFiles
.Where(t => t.Language != "pt-BR")
.Where(t => !_md5Excludes.Contains(t.Md5Hash))
.Where(t => !Md5Excludes.Contains(t.Md5Hash))
.GroupBy(t => t.Md5Hash)
.Where(grp => grp.Count() > 1)
.Select(grp => new { Key = grp.Key, Count = grp.Count(), Paths = grp.ToList().Select(f => f.FilePath) })
@ -747,7 +808,10 @@ public class LocalesTest
continue;
}
var exepts = new List<string> { "Error", "Done", "Warning", "Alert", "Info" };
var notCommonKeys = module.AppliedJsTranslationKeys
.Except(exepts)
.Where(k => !k.StartsWith("Common:"))
.OrderBy(t => t)
.ToList();
@ -1079,11 +1143,34 @@ public class LocalesTest
Assert.AreEqual(0, NotTranslatedToasts.Count, message);
}
[Test]
[Category("Locales")]
public void NotTranslatedPropsTest()
{
var message = $"Next text not translated props (title, placeholder, label, text):\r\n\r\n";
var i = 0;
NotTranslatedProps.GroupBy(t => t.Key)
.Select(g => new
{
FilePath = g.Key,
Values = g.ToList()
})
.ToList()
.ForEach(t =>
{
message += $"{++i}. Path='{t.FilePath}'\r\n\r\n{string.Join("\r\n", t.Values.Select(v => v.Value))}\r\n\r\n";
});
Assert.AreEqual(0, NotTranslatedProps.Count, message);
}
[Test]
[Category("Locales")]
public void WrongTranslationVariablesTest()
{
var message = $"Next keys have wrong variables:\r\n\r\n";
var message = $"Next keys have wrong or empty variables:\r\n\r\n";
var regVariables = new Regex("\\{\\{([^\\{].?[^\\}]+)\\}\\}", RegexOptions.Compiled | RegexOptions.Multiline);
var groupedByLng = TranslationFiles
@ -1092,8 +1179,8 @@ public class LocalesTest
{
Language = g.Key,
TranslationsWithVariables = g.ToList()
.SelectMany(t => t.Translations)
.Where(k => k.Value.IndexOf("{{") != -1)
.SelectMany(t => t.Translations)
//.Where(k => k.Value.IndexOf("{{") != -1)
.Select(t => new
{
t.Key,
@ -1109,6 +1196,7 @@ public class LocalesTest
var enWithVariables = groupedByLng
.Where(t => t.Language == "en")
.SelectMany(t => t.TranslationsWithVariables)
.Where(t => t.Variables.Count > 0)
.ToList();
var otherLanguagesWithVariables = groupedByLng
@ -1118,11 +1206,129 @@ public class LocalesTest
var i = 0;
var errorsCount = 0;
foreach (var lng in otherLanguagesWithVariables)
{
foreach (var t in lng.TranslationsWithVariables)
foreach (var enKeyWithVariables in enWithVariables)
{
foreach (var lng in otherLanguagesWithVariables)
{
var lngKey = lng.TranslationsWithVariables
.Where(t => t.Key == enKeyWithVariables.Key)
.FirstOrDefault();
if (lngKey == null)
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{enKeyWithVariables.Key}' not found\r\n\r\n";
errorsCount++;
continue;
}
if (enKeyWithVariables.Variables.Count != lngKey.Variables.Count)
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{lngKey.Key}' has less variables then 'en' language have " +
$"(en={enKeyWithVariables.Variables.Count}|{lng.Language}={lngKey.Variables.Count})\r\n" +
$"'en': '{enKeyWithVariables.Value}'\r\n'{lng.Language}': '{lngKey.Value}'\r\n\r\n";
errorsCount++;
}
if (!lngKey.Variables.All(v => enKeyWithVariables.Variables.Contains(v)))
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{lngKey.Key}' has not equals variables of 'en' language have \r\n" +
$"'{enKeyWithVariables.Value}' Variables=[{string.Join(",", enKeyWithVariables.Variables)}]\r\n" +
$"'{lngKey.Value}' Variables=[{string.Join(",", lngKey.Variables)}]\r\n\r\n";
errorsCount++;
}
}
}
Assert.AreEqual(0, errorsCount, message);
}
[Test]
[Category("Locales")]
public void WrongTranslationTagsTest()
{
var message = $"Next keys have wrong or empty translation's html tags:\r\n\r\n";
var regString = "<([^>]*)>(\\s*(.+?)\\s*)</([^>/]*)>";
var regTags = new Regex(regString, RegexOptions.Compiled | RegexOptions.Multiline);
var groupedByLng = TranslationFiles
.GroupBy(t => t.Language)
.Select(g => new
{
var enKey = enWithVariables
Language = g.Key,
TranslationsWithTags = g.ToList()
.SelectMany(t => t.Translations)
//.Where(k => k.Value.IndexOf("<") != -1)
.Select(t => new
{
t.Key,
t.Value,
Tags = regTags.Matches(t.Value)
.Select(m => m.Groups[1]?.Value?.Trim())
.ToList()
})
.ToList()
})
.ToList();
var enWithTags = groupedByLng
.Where(t => t.Language == "en")
.SelectMany(t => t.TranslationsWithTags)
.Where(t => t.Tags.Count > 0)
.ToList();
var otherLanguagesWithTags = groupedByLng
.Where(t => t.Language != "en")
.ToList();
var i = 0;
var errorsCount = 0;
foreach (var enKeyWithTags in enWithTags)
{
foreach (var lng in otherLanguagesWithTags)
{
var lngKey = lng.TranslationsWithTags
.Where(t => t.Key == enKeyWithTags.Key)
.FirstOrDefault();
if (lngKey == null)
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{enKeyWithTags.Key}' not found\r\n\r\n";
errorsCount++;
continue;
}
if (enKeyWithTags.Tags.Count != lngKey.Tags.Count)
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{lngKey.Key}' has less tags then 'en' language have " +
$"(en={enKeyWithTags.Tags.Count}|{lng.Language}={lngKey.Tags.Count})\r\n" +
$"'en': '{enKeyWithTags.Value}'\r\n'{lng.Language}': '{lngKey.Value}'\r\n\r\n";
errorsCount++;
}
if (!lngKey.Tags.All(v => enKeyWithTags.Tags.Contains(v)))
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{lngKey.Key}' has not equals tags of 'en' language have \r\n" +
$"'{enKeyWithTags.Value}' Tags=[{string.Join(",", enKeyWithTags.Tags)}]\r\n" +
$"'{lngKey.Value}' Tags=[{string.Join(",", lngKey.Tags)}]\r\n\r\n";
errorsCount++;
}
}
}
/*foreach (var lng in otherLanguagesWithTags)
{
foreach (var t in lng.TranslationsWithTags)
{
var enKey = enWithTags
.Where(en => en.Key == t.Key)
.FirstOrDefault();
@ -1134,25 +1340,25 @@ public class LocalesTest
continue;
}
if (enKey.Variables.Count != t.Variables.Count)
if (enKey.Tags.Count != t.Tags.Count)
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{t.Key}' has less variables then 'en' language have " +
$"(en={enKey.Variables.Count}|{lng.Language}={t.Variables.Count})\r\n" +
message += $"{++i}. lng='{lng.Language}' key='{t.Key}' has less tags then 'en' language have " +
$"(en={enKey.Tags.Count}|{lng.Language}={t.Tags.Count})\r\n" +
$"'en': '{enKey.Value}'\r\n'{lng.Language}': '{t.Value}'\r\n\r\n";
errorsCount++;
}
if (!t.Variables.All(v => enKey.Variables.Contains(v)))
if (!t.Tags.All(v => enKey.Tags.Contains(v)))
{
// wrong
errorsCount++;
message += $"{++i}. lng='{lng.Language}' key='{t.Key}' has not equals variables of 'en' language have\r\n\r\n" +
$"Have to be:\r\n'{enKey.Value}'\r\n\r\n{string.Join("\r\n", enKey.Variables)}\r\n\r\n" +
$"But in real:\r\n'{t.Value}'\r\n\r\n{string.Join("\r\n", t.Variables)} \r\n\r\n";
message += $"{++i}. lng='{lng.Language}' key='{t.Key}' has not equals tags of 'en' language have\r\n\r\n" +
$"Have to be:\r\n'{enKey.Value}'\r\n\r\n{string.Join("\r\n", enKey.Tags)}\r\n\r\n" +
$"But in real:\r\n'{t.Value}'\r\n\r\n{string.Join("\r\n", t.Tags)} \r\n\r\n";
}
}
}
}*/
Assert.AreEqual(0, errorsCount, message);
}

View File

@ -0,0 +1,20 @@
[
"Amazon",
"AWS",
"DocSpace",
"ONLYOFFICE",
"ID",
"IP",
"IPv4",
"IPv",
"DNS",
"SVG",
"PNG",
"NameId",
"IdP",
"Sp",
"macOS",
"SAML",
"Stripe",
"SDK"
]

View File

@ -102,15 +102,15 @@ public class PortalController : ControllerBase
[HttpPost("register")]
[AllowCrossSiteJson]
[Authorize(AuthenticationSchemes = "auth:allowskip:registerportal")]
public Task<IActionResult> RegisterAsync(TenantModel model)
public async Task<IActionResult> RegisterAsync(TenantModel model)
{
if (model == null)
{
return Task.FromResult<IActionResult>(BadRequest(new
return BadRequest(new
{
error = "portalNameEmpty",
message = "PortalName is required"
}));
});
}
if (!ModelState.IsValid)
@ -122,11 +122,11 @@ public class PortalController : ControllerBase
message.Add(ModelState[k].Errors.FirstOrDefault().ErrorMessage);
}
return Task.FromResult<IActionResult>(BadRequest(new
return BadRequest(new
{
error = "params",
message
}));
});
}
var sw = Stopwatch.StartNew();
@ -136,7 +136,7 @@ public class PortalController : ControllerBase
if (!CheckPasswordPolicy(model.Password, out var error1))
{
sw.Stop();
return Task.FromResult<IActionResult>(BadRequest(error1));
return BadRequest(error1);
}
if (!string.IsNullOrEmpty(model.Password))
@ -152,16 +152,11 @@ public class PortalController : ControllerBase
{
sw.Stop();
return Task.FromResult<IActionResult>(BadRequest(error));
return BadRequest(error);
}
return InternalRegisterAsync(model, error, sw);
}
private async Task<IActionResult> InternalRegisterAsync(TenantModel model, object error, Stopwatch sw)
{
model.PortalName = (model.PortalName ?? "").Trim();
var (exists, _) = await CheckExistingNamePortalAsync(model.PortalName);
(var exists, error) = await CheckExistingNamePortalAsync(model.PortalName);
if (!exists)
{

View File

@ -66,8 +66,8 @@ public class Startup : BaseStartup
services.AddHostedService<BackupCleanerTempFileService>();
services.AddHostedService<BackupWorkerService>();
services.AddActivePassiveHostedService<BackupCleanerService>();
services.AddActivePassiveHostedService<BackupSchedulerService>();
services.AddActivePassiveHostedService<BackupCleanerService>(DIHelper);
services.AddActivePassiveHostedService<BackupSchedulerService>(DIHelper);
services.AddBaseDbContextPool<BackupsContext>();
services.AddBaseDbContextPool<FilesDbContext>();

View File

@ -82,38 +82,20 @@ public abstract class FeedModule : IFeedModule
}
}
protected string GetGroupId(string item, Guid author, string rootId = null, int action = -1)
protected string GetGroupId(string item, Guid author, DateTime date, string rootId = null, int action = -1)
{
const int interval = 2;
var now = DateTime.UtcNow;
var hours = now.Hour;
var groupIdHours = hours - (hours % interval);
var time = date.ToString("g");
if (rootId == null)
{
// groupId = {item}_{author}_{date}
return string.Format("{0}_{1}_{2}",
item,
author,
now.ToString("yyyy.MM.dd.") + groupIdHours);
return $"{item}_{author}_{time}";
}
if (action == -1)
{
// groupId = {item}_{author}_{date}_{rootId}_{action}
return string.Format("{0}_{1}_{2}_{3}",
item,
author,
now.ToString("yyyy.MM.dd.") + groupIdHours,
rootId);
return $"{item}_{author}_{time}_{rootId}";
}
// groupId = {item}_{author}_{date}_{rootId}_{action}
return string.Format("{0}_{1}_{2}_{3}_{4}",
item,
author,
now.ToString("yyyy.MM.dd.") + groupIdHours,
rootId,
action);
return $"{item}_{author}_{time}_{rootId}_{action}";
}
}

View File

@ -53,8 +53,8 @@ public class Startup : BaseWorkerStartup
DIHelper.TryAdd<NotifyInvokeSendMethodRequestedIntegrationEventHandler>();
DIHelper.TryAdd<NotifySendMessageRequestedIntegrationEventHandler>();
services.AddActivePassiveHostedService<NotifySenderService>();
services.AddActivePassiveHostedService<NotifyCleanerService>();
services.AddActivePassiveHostedService<NotifySenderService>(DIHelper);
services.AddActivePassiveHostedService<NotifyCleanerService>(DIHelper);
services.AddBaseDbContextPool<NotifyDbContext>();
}

View File

@ -71,13 +71,13 @@
"docservice": {
"coauthor-docs": [ ".pptx", ".ppsx", ".xlsx", ".csv", ".docx", ".docxf", ".oform", ".txt" ],
"commented-docs": [ ".docx", ".docxf", ".xlsx", ".pptx" ],
"convert-docs": [ ".pptm", ".ppt", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".rtf" ],
"convert-docs": [ ".pptm", ".ppt", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".rtf", ".xml" ],
"edited-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".txt", ".rtf", ".mht", ".html", ".htm" ],
"encrypted-docs": [ ".docx", ".docxf", ".xlsx", ".pptx", ".oform" ],
"formfilling-docs": [ ".oform" ],
"customfilter-docs": [ ".xlsx" ],
"reviewed-docs": [ ".docx", ".docxf" ],
"viewed-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".gslides", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".gsheet", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".gdoc", ".txt", ".rtf", ".mht", ".html", ".htm", ".epub", ".pdf", ".djvu", ".xps" ],
"viewed-docs": [ ".pptx", ".pptm", ".ppt", ".ppsx", ".ppsm", ".pps", ".potx", ".potm", ".pot", ".odp", ".fodp", ".otp", ".gslides", ".xlsx", ".xlsm", ".xls", ".xltx", ".xltm", ".xlt", ".ods", ".fods", ".ots", ".gsheet", ".csv", ".docx", ".docxf", ".oform", ".docm", ".doc", ".dotx", ".dotm", ".dot", ".odt", ".fodt", ".ott", ".gdoc", ".txt", ".rtf", ".mht", ".html", ".htm", ".xml", ".fb2", ".epub", ".pdf", ".djvu", ".xps", ".oxps" ],
"secret": {
"value": "secret",
"header": "AuthorizationJwt"
@ -110,6 +110,8 @@
"min": 3,
"max": 50
},
"api-system":"",
"api-cache":"",
"images": "static/images",
"hide-settings": "Monitoring,LdapSettings,DocService,MailService,PublicPortal,ProxyHttpContent,SpamSubscription,FullTextSearch",
"hub": {
@ -120,10 +122,14 @@
"controlpanel": {
"url": ""
},
"legalterms": "https://www.onlyoffice.com/legalterms.aspx",
"support-feedback": "https://helpdesk.onlyoffice.com",
"teamlab-site": "http://www.onlyoffice.com",
"help-center": "https://helpcenter.onlyoffice.com/{ru|de|fr|es|it}",
"max-upload-size" : 5242880
"book-training-email": "training@onlyoffice.com",
"documentation-email": "documentation@onlyoffice.com",
"max-upload-size" : 5242880,
"zendesk-key" : ""
},
"ConnectionStrings": {
"default": {

View File

@ -150,38 +150,6 @@
"clickatellapiKey": ""
}
}
},
{
"type": "ASC.FederatedLogin.LoginProviders.DocuSignLoginProvider, ASC.FederatedLogin",
"services": [
{
"type": "ASC.Core.Common.Configuration.Consumer, ASC.Core.Common"
},
{
"type": "ASC.FederatedLogin.LoginProviders.DocuSignLoginProvider, ASC.FederatedLogin"
},
{
"key": "docuSign",
"type": "ASC.Core.Common.Configuration.Consumer, ASC.Core.Common"
},
{
"key": "docuSign",
"type": "ASC.FederatedLogin.LoginProviders.DocuSignLoginProvider, ASC.FederatedLogin"
}
],
"instanceScope": "perlifetimescope",
"parameters": {
"name": "docuSign",
"order": "1",
"props": {
"docuSignClientId": "",
"docuSignClientSecret": "",
"docuSignHost": ""
},
"additional": {
"docuSignRedirectUrl" : "https://service.teamlab.info/oauth2.aspx"
}
}
},
{
"type": "ASC.FederatedLogin.LoginProviders.DropboxLoginProvider, ASC.FederatedLogin",
@ -338,37 +306,6 @@
"linkedInRedirectUrl" : "https://service.teamlab.info/oauth2.aspx"
}
}
},
{
"type": "ASC.FederatedLogin.LoginProviders.MailRuLoginProvider, ASC.FederatedLogin",
"services": [
{
"type": "ASC.Core.Common.Configuration.Consumer, ASC.Core.Common"
},
{
"type": "ASC.FederatedLogin.LoginProviders.MailRuLoginProvider, ASC.FederatedLogin"
},
{
"key": "mailru",
"type": "ASC.Core.Common.Configuration.Consumer, ASC.Core.Common"
},
{
"key": "mailru",
"type": "ASC.FederatedLogin.LoginProviders.MailRuLoginProvider, ASC.FederatedLogin"
}
],
"instanceScope": "perlifetimescope",
"parameters": {
"name": "mailru",
"order": "4",
"props": {
"mailRuClientId": "",
"mailRuClientSecret": ""
},
"additional": {
"mailRuRedirectUrl" : "https://service.teamlab.info/oauth2.aspx"
}
}
},
{
"type": "ASC.FederatedLogin.LoginProviders.OneDriveLoginProvider, ASC.FederatedLogin",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -73,7 +73,7 @@ namespace ASC.Migrations.MySql.Migrations.CoreDb
new
{
Tenant = -1,
Features = "trial,audit,ldap,sso,whitelabel,thirdparty,restore,total_size:107374182400,file_size:100,manager:1",
Features = "trial,audit,ldap,sso,whitelabel,thirdparty,restore,oauth,total_size:107374182400,file_size:100,manager:1",
Name = "trial",
Price = 0m,
Visible = false
@ -81,7 +81,7 @@ namespace ASC.Migrations.MySql.Migrations.CoreDb
new
{
Tenant = -2,
Features = "audit,ldap,sso,whitelabel,thirdparty,restore,contentsearch,total_size:107374182400,file_size:1024,manager:1",
Features = "audit,ldap,sso,whitelabel,thirdparty,restore,oauth,contentsearch,total_size:107374182400,file_size:1024,manager:1",
Name = "admin",
Price = 30m,
ProductId = "1002",
@ -90,7 +90,7 @@ namespace ASC.Migrations.MySql.Migrations.CoreDb
new
{
Tenant = -3,
Features = "free,total_size:2147483648,manager:5,room:5",
Features = "free,total_size:2147483648,manager:3,room:12",
Name = "startup",
Price = 0m,
Visible = false

View File

@ -120,17 +120,17 @@ public partial class CoreDbContextMigrate : Migration
migrationBuilder.InsertData(
table: "tenants_quota",
columns: new[] { "tenant", "description", "features", "name", "product_id" },
values: new object[] { -3, null, "free,total_size:2147483648,manager:5,room:5", "startup", null });
values: new object[] { -3, null, "free,total_size:2147483648,manager:3,room:12", "startup", null });
migrationBuilder.InsertData(
table: "tenants_quota",
columns: new[] { "tenant", "description", "features", "name", "price", "product_id", "visible" },
values: new object[] { -2, null, "audit,ldap,sso,whitelabel,thirdparty,restore,contentsearch,total_size:107374182400,file_size:1024,manager:1", "admin", 30m, "1002", true });
values: new object[] { -2, null, "audit,ldap,sso,whitelabel,thirdparty,restore,oauth,contentsearch,total_size:107374182400,file_size:1024,manager:1", "admin", 30m, "1002", true });
migrationBuilder.InsertData(
table: "tenants_quota",
columns: new[] { "tenant", "description", "features", "name", "product_id" },
values: new object[] { -1, null, "trial,audit,ldap,sso,whitelabel,thirdparty,restore,total_size:107374182400,file_size:100,manager:1", "trial", null });
values: new object[] { -1, null, "trial,audit,ldap,sso,whitelabel,thirdparty,restore,oauth,total_size:107374182400,file_size:100,manager:1", "trial", null });
migrationBuilder.CreateIndex(
name: "last_modified",

View File

@ -70,7 +70,7 @@ namespace ASC.Migrations.MySql.Migrations.CoreDb
new
{
Tenant = -1,
Features = "trial,audit,ldap,sso,whitelabel,thirdparty,restore,total_size:107374182400,file_size:100,manager:1",
Features = "trial,audit,ldap,sso,whitelabel,thirdparty,restore,oauth,total_size:107374182400,file_size:100,manager:1",
Name = "trial",
Price = 0m,
Visible = false
@ -78,7 +78,7 @@ namespace ASC.Migrations.MySql.Migrations.CoreDb
new
{
Tenant = -2,
Features = "audit,ldap,sso,whitelabel,thirdparty,restore,contentsearch,total_size:107374182400,file_size:1024,manager:1",
Features = "audit,ldap,sso,whitelabel,thirdparty,restore,oauth,contentsearch,total_size:107374182400,file_size:1024,manager:1",
Name = "admin",
Price = 30m,
ProductId = "1002",
@ -87,7 +87,7 @@ namespace ASC.Migrations.MySql.Migrations.CoreDb
new
{
Tenant = -3,
Features = "free,total_size:2147483648,manager:5,room:5",
Features = "free,total_size:2147483648,manager:3,room:12",
Name = "startup",
Price = 0m,
Visible = false

View File

@ -68,7 +68,7 @@ namespace ASC.Migrations.PostgreSql.Migrations.CoreDb
new
{
Tenant = -1,
Features = "trial,audit,ldap,sso,whitelabel,thirdparty,restore,total_size:107374182400,file_size:100,manager:1",
Features = "trial,audit,ldap,sso,whitelabel,thirdparty,restore,oauth,total_size:107374182400,file_size:100,manager:1",
Name = "trial",
Price = 0m,
Visible = false
@ -76,7 +76,7 @@ namespace ASC.Migrations.PostgreSql.Migrations.CoreDb
new
{
Tenant = -2,
Features = "audit,ldap,sso,whitelabel,thirdparty,restore,contentsearch,total_size:107374182400,file_size:1024,manager:1",
Features = "audit,ldap,sso,whitelabel,thirdparty,restore,oauth,contentsearch,total_size:107374182400,file_size:1024,manager:1",
Name = "admin",
Price = 30m,
ProductId = "1002",
@ -85,7 +85,7 @@ namespace ASC.Migrations.PostgreSql.Migrations.CoreDb
new
{
Tenant = -3,
Features = "free,total_size:2147483648,manager:5,room:5",
Features = "free,total_size:2147483648,manager:3,room:12",
Name = "startup",
Price = 0m,
Visible = false

View File

@ -113,19 +113,19 @@ public partial class CoreDbContextMigrate : Migration
schema: "onlyoffice",
table: "tenants_quota",
columns: new[] { "tenant", "description", "features", "name", "visible" },
values: new object[] { -3, null, "free,total_size:2147483648,manager:5,room:5", "startup", false });
values: new object[] { -3, null, "free,total_size:2147483648,manager:3,room:12", "startup", false });
migrationBuilder.InsertData(
schema: "onlyoffice",
table: "tenants_quota",
columns: new[] { "tenant", "description", "features", "name", "price", "product_id", "visible" },
values: new object[] { -2, null, "audit,ldap,sso,whitelabel,thirdparty,restore,contentsearch,total_size:107374182400,file_size:1024,manager:1", "admin", 30m, "1002", true });
values: new object[] { -2, null, "audit,ldap,sso,whitelabel,thirdparty,restore,oauth,contentsearch,total_size:107374182400,file_size:1024,manager:1", "admin", 30m, "1002", true });
migrationBuilder.InsertData(
schema: "onlyoffice",
table: "tenants_quota",
columns: new[] { "tenant", "description", "features", "name", "visible" },
values: new object[] { -1, null, "trial,audit,ldap,sso,whitelabel,thirdparty,restore,total_size:107374182400,file_size:100,manager:1", "trial", false });
values: new object[] { -1, null, "trial,audit,ldap,sso,whitelabel,thirdparty,restore,oauth,total_size:107374182400,file_size:100,manager:1", "trial", false });
migrationBuilder.CreateIndex(
name: "last_modified_tenants_quotarow",

View File

@ -82,7 +82,7 @@ namespace ASC.Migrations.PostgreSql.Migrations.CoreDb
new
{
Tenant = -3,
Features = "free,total_size:2147483648,manager:5,room:5",
Features = "free,total_size:2147483648,manager:3,room:12",
Name = "startup",
Price = 0m,
Visible = false

View File

@ -36,8 +36,8 @@
"devDependencies": {
"auto-changelog": "file:./packages/auto-changelog-2.3.1.tgz",
"he": "^1.2.0",
"shx": "^0.3.3",
"terser": "^5.8.0"
"shx": "^0.3.4",
"terser": "^5.16.6"
},
"packageManager": "yarn@3.2.4"
}

View File

@ -41,49 +41,50 @@
"test:ui:mobile": "cross-env DEVICE_TYPE=mobile npx codecept-ui"
},
"dependencies": {
"copy-to-clipboard": "^3.3.1",
"copy-to-clipboard": "^3.3.3",
"element-resize-detector": "^1.2.4",
"file-saver": "^2.0.5",
"firebase": "^8.10.0",
"firebase": "^8.10.1",
"hex-to-rgba": "^2.0.1",
"react-avatar-editor": "^13.0.0",
"react-colorful": "^5.5.1",
"react-hotkeys-hook": "^3.4.4",
"react-markdown": "^7.0.1",
"react-colorful": "^5.6.1",
"react-hotkeys-hook": "^3.4.7",
"react-markdown": "^7.1.2",
"react-smartbanner": "^5.1.4",
"react-string-format": "^0.1.4",
"windows-iana": "^5.1.0"
},
"devDependencies": {
"@babel/core": "^7.15.5",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-export-default-from": "^7.14.5",
"@babel/plugin-transform-runtime": "^7.15.0",
"@babel/preset-env": "^7.15.6",
"@babel/preset-react": "^7.14.5",
"@babel/preset-typescript": "^7.18.6",
"@babel/core": "^7.21.3",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-export-default-from": "^7.18.10",
"@babel/plugin-transform-runtime": "^7.21.0",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@svgr/webpack": "^5.5.0",
"babel-loader": "^8.2.2",
"babel-loader": "^8.3.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^6.2.0",
"copy-webpack-plugin": "^9.1.0",
"css-loader": "^6.7.3",
"external-remotes-plugin": "^1.0.0",
"file-loader": "^6.2.0",
"html-loader": "^4.2.0",
"html-webpack-plugin": "5.3.2",
"html-webpack-plugin": "5.5.0",
"json-loader": "^0.5.7",
"playwright": "^1.18.1",
"sass": "^1.39.2",
"sass-loader": "^12.1.0",
"serve": "14.1.1",
"shx": "^0.3.3",
"source-map-loader": "^3.0.0",
"style-loader": "3.2.1",
"terser-webpack-plugin": "^5.2.4",
"typescript": "^4.7.4",
"webpack": "5.52.1",
"playwright": "^1.32.0",
"sass": "^1.59.3",
"sass-loader": "^12.6.0",
"serve": "14.2.0",
"shx": "^0.3.4",
"source-map-loader": "^3.0.2",
"style-loader": "3.3.2",
"terser-webpack-plugin": "^5.3.7",
"typescript": "^4.9.5",
"use-resize-observer": "^9.1.0",
"webpack": "5.76.3",
"webpack-cli": "4.10.0",
"webpack-dev-server": "4.3.1"
"webpack-dev-server": "4.13.1"
},
"title": "ONLYOFFICE"
}

View File

@ -1,8 +1,8 @@
{
"AboutCompanyAddressTitle": "Ünvan",
"AboutCompanyEmailTitle": "e-poçt",
"AboutHeader": "Bu proqram haqqında",
"DocumentManagement": "Sənədlərin idarə edilməsi",
"OnlineEditors": "Onlayn redaktorlar",
"Site": "Sayt",
"SoftwareLicense": "Proqram təminatı lisenziyası"
}

View File

@ -1 +1,8 @@
{}
{
"ArchiveHeader": "Arxivə köçürülsün?",
"ArchiveRoom": "Otağı arxivləşdirmək üzrəsiniz.",
"ArchiveRooms": "Otaqları arxivləşdirmək üzrəsiniz.",
"RestoreAllRooms": "Bütün otaqları bərpa etmək istədiyinizə əminsiniz?",
"RestoreRoom": "Otağı bərpa etmək istədiyinizə əminsiniz?",
"RestoreRooms": "Otaqları bərpa etmək istədiyinizə əminsiniz?"
}

View File

@ -1,5 +1,14 @@
{
"AppointAdmin": "Administratorları təyin edin",
"BackupPortal": "DocSpace məlumatlarını yedəkləyin",
"ManagePortal": " DocSpace konfiqurasiyasını idarə edin",
"ManageUser": "İstifadəçi hesablarını idarə edin"
"ChangeInstruction": "DocSpace sahibini dəyişmək üçün lütfən, aşağıda yeni sahibin adını seçin.",
"ChangeOwner": "DocSpace sahibini dəyişdirin",
"ChangeUser": "İstifadəçini dəyişdirin",
"DeactivateOrDeletePortal": "DocSpace-i deaktiv edin və ya silin",
"DoTheSame": "Administratorlarla da eyni şeyi edin",
"ManagePortal": "DocSpace konfiqurasiyasını idarə edin",
"ManageUser": "İstifadəçi hesablarını idarə edin",
"NewPortalOwner": "Yeni DocSpace sahibi",
"PortalOwnerCan": "DocSpace sahibi edə bilər:",
"SetAccessRights": "Giriş hüquqlarını təyin edin"
}

View File

@ -1,6 +1,7 @@
{
"ChangeUserStatusDialog": "{{ userStatus }} olan istifadəçilər {{ status }} olacaqlar.",
"ChangeUserStatusDialogHeader": "İstifadəçi statusunun dəyişdirilməsi",
"ChangeUserStatusDialogMessage": "DocSpace-in sahibi və özünüz üçün vəziyyəti dəyişdirə bilməzsiniz.",
"ChangeUsersActiveStatus": "iz ver",
"ChangeUsersDisableStatus": "söndürülüb"
}

View File

@ -2,6 +2,7 @@
"ChangeUserTypeButton": "Növün dəyişdirilməsi",
"ChangeUserTypeHeader": "İstifadəçi növünün dəyişdirilməsi",
"ChangeUserTypeMessage": "'{{ firstType }}' tipinə malik olan istifadəçilər '{{ secondType }}' istifadəçi tipinə dəyişdirildi.",
"ChangeUserTypeMessageMulti": "Seçilmiş istifadəçilər '{{ secondType }}' növünə köçürüləcək.",
"ChangeUserTypeMessageWarning": "DocSpace inzibatçının və özüvüzün istifadəçi tipini dəyişdirə bilməzsiniz.",
"SuccessChangeUserType": "İstifadəçi tipi müvəffəqiyyətlə dəyişdirildi"
}

View File

@ -5,9 +5,9 @@
"CurrentNumber": "Cari mobil telefon nömrəniz",
"DeleteProfileBtn": "Hesabımı silin",
"DeleteProfileConfirmation": "Diqqət! Hesabınızı silmək üzrəsiniz.",
"DeleteProfileConfirmationInfo": "\"Hesabımı silin\" seçiminə klikləməklə bizim Məxfilik siyasətimizlə razılaşırsınız.",
"DeleteProfileConfirmationInfo": "\"Hesabımı silin\" seçiminə klikləməklə bizim <1>Məxfilik siyasətimizlə razılaşırsınız.</1>",
"DeleteProfileSuccessMessage": "Hesabınız uğurla silinmişdir.",
"DeleteProfileSuccessMessageInfo": "Hesabınızın və onunla bağlı məlumatların silinməsi haqqında daha ətraflı öyrənmək üçün bizim Məxfilik Siyasətimizə nəzər yetirin.",
"DeleteProfileSuccessMessageInfo": "Hesabınızın və onunla bağlı məlumatların silinməsi haqqında daha ətraflı öyrənmək üçün bizim <1>Məxfilik Siyasətimizə</1> nəzər yetirin.",
"EmailAndPasswordCopiedToClipboard": "E-poçt ünvanı və şifrə mübadilə buferinə kopyalandı.",
"EnterAppCodeDescription": "Öz tətbiqinizdən əldə etdiyiniz 6 rəqəmli kodu daxil edin. Əgər telefonunuz əlçatan deyilsə, o zaman ehtiyat kodlarından istifadə edin.",
"EnterAppCodeTitle": "Doğrulama tətbiqindən kodu daxil edin",
@ -18,9 +18,16 @@
"LoginRegistryButton": "Qoşulun",
"PassworResetTitle": "İndi yeni şifrə yarada bilərsiniz.",
"PhoneSubtitle": "Əlavə təhlükəsizliyini təmin etmək üçün iki faktorlu autentifikasiya işə salınıb. DocSpace işə davam etmək üçün mobil telefon nömrənizi daxil edin. Mobil telefon nömrəsi ölkə kodu ilə beynəlxalq formatda daxil edilməlidir.",
"PortalContinueTitle": "Lütfən, DocSpace-i yenidən aktivləşdirmək istədiyinizi təsdiqləyin.",
"PortalDeactivateTitle": "Lütfən, DocSpace-i deaktiv etmək istədiyinizi təsdiqləyin.",
"PortalRemoveTitle": "Lütfən, DocSpace-i silmək istədiyinizi təsdiqləyin.",
"Reactivate": "Yenidən aktivləşdirin",
"SetAppButton": "Tətbiqə qoşulun",
"SetAppDescription": "İki faktorlu avtorizasiya kodu aktivləşdirildi. DocSpace-də işləməyə davam etmək üçün doğrulama tətbiqini quraşdırın. Doğrulama tətbiqini <1>Android</1> və <4>iOS</4> və ya <8>Windows Phone</8> üçün istifadə edə bilərsiniz.",
"SetAppInstallDescription": "Tətbiqə qoşulmaq üçün QR kodu skan edin və ya manual olaraq öz gizli açarınızı <1>{{ secretKey }}</1>, daha sonra isə aşağıdakı xanada tətbiqdən əldə etdiyiz 6 rəqəmli kodu daxil edin.",
"SetAppTitle": "Doğrulama tətbiqini quraşdırın",
"SuccessDeactivate": "Hesabınız uğurla deaktiv edildi. 10 saniyə ərzində <1>sayt</1>a yönləndiriləcəksiniz.",
"SuccessReactivate": "Hesabınız uğurla yenidən aktivləşdirildi. 10 saniyəyə <1>portala</1> yönləndiriləcəksiniz.",
"SuccessRemoved": "Hesabınız uğurla silindi. 10 saniyə ərzində <1>sayt</1>a yönləndiriləcəksiniz.",
"WelcomeUser": "DocSpace-ə xoş gəlmisiniz!\nBaşlamaq üçün qeydiyyatdan keçin və ya sosial şəbəkə vasitəsilə giriş edin."
}

View File

@ -1,6 +1,6 @@
{
"ConflictResolveDescription": "<1>{{file}}</1> adlı fayl artıq <1>{{folder}}</1> qovluğunda mövcuddur. ",
"ConflictResolveDescriptionFiles": "{{filesCount}} sənəd sayı artıq <1>{{folder}}<1> qovluğunda mövcuddur.",
"ConflictResolveDescriptionFiles": "{{filesCount}} sənəd sayı artıq <1>{{folder}}</1> qovluğunda mövcuddur.",
"ConflictResolveSelectAction": "Xahiş edirik əməliyyatı seçin:",
"ConflictResolveTitle": "Üzərinə yazmanın təsdiqlənməsi",
"CreateDescription": "Qovluqda iki fərqli fayl olacaq. ",

View File

@ -3,5 +3,6 @@
"ConnectFolderTitle": "Qovluq başlığı",
"ConnectionUrl": "Qoşulma üçün URL",
"Login": "Giriş edin",
"Reconnect": "Yenidən qoşul"
"Reconnect": "Yenidən qoşul",
"SuccessfulConnectionOfAThirdParty": "Üçüncü tərəf xidməti uğurla qoşuldu."
}

View File

@ -1,7 +1,11 @@
{
"ConversionMessage": "Bütün yüklənilmiş sənədlər sürətli redaktə etmək üçün Office Open XML formatına (docx, xlsx və ya pptx) konvertasiya olunur.",
"ConvertedFileDestination": "Faylın nüsxəsi <strong>{{folderTitle}}</strong> qovluğunda yaradılacaq.",
"DocumentConversionTitle": "Sənədin çevrilməsi",
"FailedToConvert": "Çevrilmə uğursuz oldu",
"FileUploadTitle": "Fayl yükləndi",
"HideMessage": "Bu bildirişi bir daha göstərmə",
"InfoCreateFileIn": "Yeni fayl '{{fileTitle}}' '{{folderTitle}}' qovluğunda yaradıldı",
"OpenFileMessage": "Açdığınız sənəd faylı tez baxmaq və redaktə etmək üçün Office Open XML formatına çevriləcək.",
"SaveOriginalFormatMessage": "Faylın nüsxəsini orijinal formatda yadda saxla"
}

View File

@ -1 +1,27 @@
{}
{
"ChooseRoomType": "Otaq növünü seçin",
"CollaborationRoomDescription": "Komandanızla bir və ya bir neçə sənəd üzərində əməkdaşlıq edin",
"CollaborationRoomTitle": "Əməkdaşlıq otağı",
"CreateRoomConfirmation": "Yaddaşa qoşulmadan davam edilsin?\nSiz hələ qoşulmamış üçüncü tərəfin yaddaş seçimini etmisiniz. Xidmətə qoşulmadan davam etsəniz, bu seçim əlavə edilməyəcək.",
"CreateTagOption": "Teq yaradın",
"CustomRoomDescription": "Bu otaqdan istənilən xüsusi məqsəd üçün istifadə etmək üçün öz parametrlərinizi tətbiq edin.",
"CustomRoomTitle": "Fərdi otaq",
"FillingFormsRoomDescription": "İstənilən sənəd növünü cəld yaratmaq üçün sənəd şablonlarını yaradın, paylaşın və doldurun və ya əvvəlcədən təyin edilmiş parametrlərlə işləyin.",
"FillingFormsRoomTitle": "Formaların doldurulması otağı",
"Icon": "Simvol",
"MakeRoomPrivateDescription": "Bu otaqdakı bütün fayllar şifrələnəcək.",
"MakeRoomPrivateLimitationsWarningDescription": "Bu funksiya ilə siz yalnız mövcud DocSpace istifadəçilərini dəvət edə bilərsiniz. Otaq yaratdıqdan sonra istifadəçi siyahısını dəyişə bilməzsiniz.",
"MakeRoomPrivateTitle": "Otağı Şəxsi edin",
"ReviewRoomDescription": "Sənədlərə baxış və ya şərh tələb edin",
"ReviewRoomTitle": "Baxış otağı",
"RoomEditing": "Otaq təşkili",
"RootLabel": "Mənbə",
"TagsPlaceholder": "Teq əlavə edin",
"ThirdPartyStorageComboBoxPlaceholder": "Yaddaş seçin",
"ThirdPartyStorageDescription": "Bu otaq üçün məlumat yaddaşı kimi üçüncü tərəf xidmətlərindən istifadə edin. Bu otağın məlumatlarını saxlamaq üçün əlaqələndirilmiş yaddaşda yeni qovluq yaradılacaq.",
"ThirdPartyStorageNoStorageAlert": "Əvvəllər \"İnteqrasiya\" bölməsində müvafiq xidməti birləşdirməlisiniz. Əks halda bağlantı mümkün olmayacaq.",
"ThirdPartyStoragePermanentSettingDescription": "Fayllar {{thirdpartyTitle}} qovluğunda üçüncü tərəf \"{{thirdpartyFolderName}}\" deposunda saxlanılır.\n<strong>{{thirdpartyPath}}</strong>",
"ThirdPartyStorageRoomAdminNoStorageAlert": "Üçüncü tərəfin yaddaşına qoşulmaq üçün DocSpace parametrlərinin İnteqrasiya bölməsində müvafiq xidməti əlavə etməlisiniz. İnteqrasiyanı aktivləşdirmək üçün DocSpace sahibi və ya administratorla əlaqə saxlayın.",
"ViewOnlyRoomDescription": "Hazır sənədlərə, hesabatlara, sənədləşməyə və digər fayllara baxmaq üçün paylaşın.",
"ViewOnlyRoomTitle": "Yalnız baxış otağı"
}

View File

@ -1,5 +1,15 @@
{
"DeleteFile": "Bu faylı silmək üzrəsiniz. Davam etmək istədiyinizə əminsiniz?",
"DeleteFolder": "Bu qovluğu silmək üzrəsiniz. Davam etmək istədiyinizə əminsiniz?",
"DeleteItems": "Bu elementləri silmək üzrəsiniz. Davam etmək istədiyinizə əminsiniz?",
"DeleteRoom": "Siz bu otağı silmək üzrəsiniz. Siz onu bərpa edə bilməzsiniz.",
"DeleteRooms": "Bu otaqları silmək üzrəsiniz. Onları bərpa edə bilməzsiniz.",
"MoveToTrashButton": "Zibil qutusuna yerləşdir",
"MoveToTrashFile": "Bu faylı silmək üzrəsiniz. Nəzərə alın ki, fayl kimsə ilə paylaşılıbsa, əlçatan olmayacaq. Fayl 30 gün ərzində həmişəlik silinəcək. Davam etmək istədiyinizə əminsiniz?",
"MoveToTrashFolder": "Siz bu qovluğu silmək üzrəsiniz. Nəzərə alın ki, bunu kimsə ilə paylaşmısınızsa, istifadə edilə bilməz. Davam etmək istədiyinizə əminsiniz?",
"MoveToTrashFolderFromPersonal": "Bu qovluğu silmək üzrəsiniz. Davam etmək istədiyinizə əminsiniz?",
"MoveToTrashItems": "Bu elementləri silmək üzrəsiniz. Nəzərə alın ki, elementləri kimsə ilə paylaşmısınızsa, onlar əlçatan olmayacaq. Davam etmək istədiyinizə əminsiniz?",
"MoveToTrashTitle": "Zibil qutusuna atılsın?",
"UnsubscribeButton": "Abunəlikdən çıx",
"UnsubscribeNote": "Siyahıda göstərilən elementlərdən abunəliyi dayandırmağı əminsinizmi?",
"UnsubscribeTitle": "Abunəlikdən çıxma təsdiqi"

View File

@ -1,4 +1,5 @@
{
"DeleteUser": "İstifadəçini sil",
"DeleteUserMessage": "{{userCaption}} <strong>{{user}}</strong> silinəcək. Başqalarına açıq olan istifadəçinin şəxsi sənədləri silinəcək. Davam etmək istədiyinizə əminsiniz?",
"SuccessfullyDeleteUserInfoMessage": "İstifadəçi müvəffəqiyyətlə silinmişdir"
}

View File

@ -1,3 +1,5 @@
{
"DisconnectCloudMessage": "{{service}} hizmetini kaldırmak istediğinizden emin misiniz? Bu, {{account}} hesabınızı hiçbir şekilde etkilemeyecektir.",
"DisconnectCloudTitle": "Buludla bağlantını dayandırın",
"SuccessDeleteThirdParty": "Üçüncü tərəf {{service}} silinir "
}

View File

@ -1,3 +1,5 @@
{
"DeleteGroupUsersSuccessMessage": "İstifadəçilər müvəffəqiyyətlə silinmişdir."
"DeleteGroupUsersSuccessMessage": "İstifadəçilər müvəffəqiyyətlə silinmişdir.",
"DeleteUsers": "İstifadəçiləri silin",
"DeleteUsersMessage": "Seçilmiş əngəllənmiş istifadəçilər DocSpace-dən silinəcək. Bu istifadəçilərin başqaları üçün əlçatan olan şəxsi sənədləri silinəcək."
}

View File

@ -1 +1,3 @@
{}
{
"SaveOrChange": "Tələb olunan parametri azaldın və ya cari qiymət planınızı saxlayın."
}

View File

@ -1,7 +1,7 @@
{
"ChooseFormatText": "Yüklənəcək hər bir faylın formatını seçin ",
"ConvertMessage": "Əgər faylın konvertasiyasını seçsəz, bəzi məlumatlar itə bilər.",
"ConvertToZip": "Fayllar .zip formatına sıxlaşdırılacaq",
"ConvertToZip": "<strong>Fayllar .zip</strong> formatına sıxlaşdırılacaq",
"CustomFormat": "Fərdi format",
"OriginalFormat": "Orijinal format"
}

View File

@ -1,6 +1,8 @@
{
"DeleteForeverButton": "Həmişəlik sil",
"DeleteForeverNote": "Zibil qutusundan elementlər həmişəlik silinəcək. Siz onları bərpa edə bilməyəcəksiniz.",
"DeleteForeverNoteArchive": "Arxivlənmiş bütün elementlər həmişəlik silinəcək. Onları bərpa edə bilməyəcəksiniz.",
"DeleteForeverTitle": "Həmişəlik silinsin?",
"SuccessEmptyArchived": "Arxivi boşaldıldı",
"SuccessEmptyTrash": "Zibil qutusu təmizləndi"
}

View File

@ -3,5 +3,6 @@
"Error403Text": "Təəssüf ki, giriş rədd edilmişdir.",
"Error404Text": "Təəssüf ki, resurs tapıla bilmir.",
"ErrorEmptyResponse": "Boş cavab",
"ErrorOfflineText": "İnternet bağlantısı tapılmadı"
"ErrorOfflineText": "İnternet bağlantısı tapılmadı",
"ErrorUnavailableText": "DocSpace əlçatan deyil"
}

View File

@ -1,33 +1,58 @@
{
"AddMembersDescription": "Yeni komanda üzvlərini əl ilə əlavə edə və ya onları keçid vasitəsilə dəvət edə bilərsiniz.",
"All": "Bütün",
"AllFiles": "Bütün fayllar",
"ArchiveAction": "Boş arxiv",
"ArchiveEmptyScreen": "Siz DocSpace-də istifadə etmədiyiniz otaqları arxivləşdirə və bərpa edə və ya istənilən vaxt onları həmişəlik silə bilərsiniz. Bu otaqlar burada görünəcək.",
"ArchiveEmptyScreenHeader": "Burada hələ arxivləşdirilmiş otaqlar yoxdur",
"ArchiveEmptyScreenUser": "Arxivlənmiş otaqlar burada görünəcək.",
"ArchivedRoomAction": "'{{name}}' otağı arxivləşdirildi",
"ArchivedRoomsAction": "Otaqlar arxivləşdirildi",
"Archives": "Arxivlər",
"BackToParentFolderButton": "Ana qovluğa qayıt",
"ByAuthor": "Müəllif",
"ByCreation": "Yaradıldı",
"ByErasure": "Təmizləmə",
"ByLastModified": "Dəyişdirilib",
"CollaborationRooms": "Əməkdaşlıq",
"ContainsSpecCharacter": "Başlıqda aşağıdakı simvollardan heç biri ola bilməz: *+: \"<>? |/ ",
"Convert": "Konvertasiya",
"CopyItem": "<strong>{{title}}</strong> köçürüldü",
"CopyItems": "<strong>{{qty}}</strong> elementlər köçürüldü",
"CreateRoom": "Otaq yaradın",
"CustomRooms": "Fərdi",
"DaysRemaining": "Qalan günlər: {{daysRemaining}}",
"DisableNotifications": "Bildirişləri söndürün",
"Document": "Sənəd",
"EditRoom": "Otağı redaktə edin",
"EmptyFile": "Boş fayl",
"EmptyFilterDescriptionText": "Heç bir fayl və ya qovluq bu süzgəcə uyğun deyil. Xahiş edirik digər süzgəc parametrindən istifadə edin və ya bütün faylların göstərilməsi üçün süzgəci sıfırlayın.",
"EmptyFilterSubheadingText": "Bu süzgəc üçün heç bir fayl tapılmadı",
"EmptyFolderDecription": "Faylları buraya çəkin və ya yenilərini yaradın",
"EmptyFolderDescriptionUser": "Adminlər tərəfindən yüklənmiş fayl və qovluqlar burada görünəcək.",
"EmptyFolderHeader": "Bu qovluqda heç bir fayl yoxdur",
"EmptyRecycleBin": "Boş zibil qutusu",
"EmptyScreenFolder": "Burada hələ ki, heç bir sənəd yoxdur",
"EnableNotifications": "Bildirişləri aktivləşdirin",
"ExcludeSubfolders": "Alt qovluqları istisna edin",
"FavoritesEmptyContainerDescription": "Faylları favorit kimi qeyd etmək və ya bu siyahıdan çıxarmaq üçün kontekst menyusundan istifadə edin. ",
"FileContents": "Fayl məzmunları",
"FileRemoved": "Fayl zibil qutusuna göndərildi",
"FileRenamed": "'{{oldTitle}}' sənəd adı '{{newTitle}}'adına dəyişdirildi",
"FillingFormRooms": "Anket doldurmaq",
"Filter": "Süzgəc",
"FinalizeVersion": "Versiyanı formalaşdır",
"Folder": "Qovluq",
"FolderRemoved": "Qovluq zibil qutusuna göndərildi",
"FolderRenamed": "'{{folderTitle}}' qovluğunun adı '{{newFoldedTitle}}' olaraq dəyişdirildi ",
"Forms": "Anketlər",
"FormsTemplates": "Anket şablonları",
"GoToMyRooms": "Otaqlarım bölməsinə keçin",
"GoToPersonal": "Sənədlərim bölməsinə keçin",
"Images": "Şəkillər",
"InviteUsersInRoom": "İstifadəçiləri otağa dəvət edin",
"LinkForPortalUsers": "Portal istifadəçiləri üçün link",
"LinkForRoomMembers": "Otaq üzvləri üçün keçid",
"MarkAsFavorite": "Favorit kimi işarələ",
"MarkRead": "Oxunmuş kimi işarələ",
"MarkedAsFavorite": "Çox istifadə olunan siyahısına əlavə olundu",
@ -36,10 +61,17 @@
"MoveItems": "<strong>{{qty}}</strong> elementlərin yeri dəyişdirildi",
"MoveOrCopy": "Köçür və ya Kopyala",
"MoveTo": "Yerini dəyiş",
"MoveToArchive": "Arxivə köçürün",
"MoveToFolderMessage": "Qovluğu alt qovluğuna köçürə bilməzsiniz ",
"New": "Yeni",
"NewRoom": "Yeni otaq",
"NoAccessRoomDescription": "5 saniyə ərzində avtomatik olaraq Otaqlarım bölməsinə yönləndiriləcəksiniz.",
"NoAccessRoomTitle": "Təəssüf ki, bu otağa girişiniz yoxdur.",
"NoFilesHereYet": "Burada hələ heç bir fayl yoxdur",
"Open": "Açmaq",
"OpenLocation": "Qovluğu açmaq",
"Pin": "Sabitləyin",
"PinToTop": "Yuxarıya sabitləyin",
"Presentation": "Təqdimat",
"PrivateRoomDescriptionEncrypted": "Şifrlənmiş redaktə və real zaman rejimində kollektiv iş.",
"PrivateRoomDescriptionSafest": "Docx, xlsx və pptx üçün ən təhlükəsiz saxlama.",
@ -47,10 +79,23 @@
"PrivateRoomDescriptionUnbreakable": "Sındırılmamış AES-256 alqoritmi.",
"PrivateRoomHeader": "Yazdığınız hər bir simvolun şifrələndiyi ONLYOFFICE şəxsi otağına xoş gəldiniz",
"PrivateRoomSupport": "Şəxsi Otaqda işləmək {{organizationName}} masa üstü tətbiqi ilə mümkündür. <3> Təlimatlar </3> ",
"RecentEmptyContainerDescription": "Bu bölmədə bu yaxınlarda baxdığınız və ya redaktə etdiyiniz sənədlər göstəriləcək.",
"RecycleBinAction": "Boş zibil qutusu",
"RemoveFromFavorites": "Favoritlərdən sil",
"RemoveFromList": "Siyahıdan sil",
"RemovedFromFavorites": "Favoritlərdən sil",
"Rename": "Adını dəyiş",
"RestoreAll": "Hər şeyi bərpa edin",
"RoomCreated": "Otaq yaradıldı",
"RoomEmptyContainerDescription": "Lütfən, ilk otağı yaradın.",
"RoomEmptyContainerDescriptionUser": "Sizinlə paylaşılan otaqlar burada görünəcək",
"RoomNotificationsDisabled": "Otaq bildirişləri deaktiv edildi",
"RoomNotificationsEnabled": "Otaq bildirişləri aktiv edildi",
"RoomPinned": "Otaq sabitləndi",
"RoomRemoved": "Otaq silindi",
"RoomUnpinned": "Otaq sabitləmədən çıxarıldı",
"RoomsRemoved": "Otaqlar silindi",
"SearchByContent": "Fayl məzmununa görə axtarın",
"SendByEmail": "Elektron poçt vasitəsi ilə göndər",
"Share": "Paylaş",
"ShowVersionHistory": "Versiya tarixçəsinə bax",
@ -59,9 +104,15 @@
"TooltipElementsCopyMessage": "{{element}} elementlərini köçürmək",
"TooltipElementsMoveMessage": "{{element}} elementinin yerdəyişməsi",
"TrashEmptyDescription": "'Səbət' bölməsinə bütün silinmiş fayllar yerləşdirilirlər. Əgər onlar səhvən siliniblərsə, Siz onları bərpa edə bilərsiniz, və ya onları ömürlük silə bilərsiniz. Lütfən, nəzərə alın ki, 'Səbətdən' silinən fayllar bir də bərpa oluna bilməyəcəklər.",
"TrashErasureWarning": "Zibil qutusundakı elementlər 30 gündən sonra avtomatik silinir.",
"UnarchivedRoomAction": "'{{name}}' otağı arxivdən çıxarıldı.",
"UnarchivedRoomsAction": "Otaqlar arxivdən çıxarıldı.",
"UnblockVersion": "Blokdan çıxar",
"Unpin": "Sabitləmədən çıxarın",
"VersionBadge": "V.{{version}}",
"VersionHistory": "Versiya tarixçəsi",
"ViewList": "Siyahı",
"ViewTiles": "Plitkalar"
"ViewOnlyRooms": "Yalnız baxın",
"ViewTiles": "Plitkalar",
"WithSubfolders": "Alt qovluqlarla"
}

View File

@ -1,4 +1,5 @@
{
"AdditionalSections": "Əlavə bölmələr",
"ConnectEmpty": "Burada heç nə yoxdur",
"DisplayFavorites": "Ən çox istifadə olunanları göstər",
"DisplayNotification": "Zibil qutusuna atılan zaman bildirişi göstər",
@ -8,5 +9,8 @@
"KeepIntermediateVersion": "Aralıq versiyaları redaktə edəndə yadda saxla",
"OriginalCopy": "Faylın nüsxəsini orijinal formatda da yadda saxla",
"StoringFileVersion": "Fayl versiyaları saxla",
"UpdateOrCreate": "Eyni adı olan mövcud faylın fayl versiyasını yeniləyin. Əks halda, faylın bir nüsxəsi yaradılacaq. "
"ThirdPartyAccounts": "Üçüncü tərəf hesabları",
"ThirdPartyBtn": "Üçüncü tərəf yaddaşının quraşdırılmasına icazə verin",
"UpdateOrCreate": "Eyni adı olan mövcud faylın fayl versiyasını yeniləyin. Əks halda, faylın bir nüsxəsi yaradılacaq. ",
"UploadPluginsHere": "Pluginləri buradan yükləyin"
}

View File

@ -1,8 +1,41 @@
{
"AccountsEmptyScreenText": "İstifadəçi detallarına burada baxın",
"AndMoreLabel": "və <strong>{{count}} daha çox</strong>",
"CreationDate": "Yaradılma tarixi",
"Data": "Məlumat",
"DateModified": "Değiştirilme tarihi",
"FeedCreateFileSeveral": "Fayllar əlavə edildi",
"FeedCreateFileSingle": "Fayl yaradıldı",
"FeedCreateFolderSeveral": "Qovluqlar əlavə edildi",
"FeedCreateFolderSingle": "Qovluq yaradıldı",
"FeedCreateRoom": "<strong>«{{roomTitle}}»</strong> otaq yaradıldı",
"FeedCreateRoomTag": "Teqlər əlavə edildi",
"FeedCreateUser": "İstifadəçilər əlavə edildi",
"FeedDeleteFile": "Fayllar silindi",
"FeedDeleteFolder": "Qovluqlar silindi",
"FeedDeleteRoomTag": "Teqlər silindi",
"FeedDeleteUser": "İstifadəçi silindi",
"FeedLocationLabel": "Qovluq «{{folderTitle}}»",
"FeedMoveFile": "Fayllar köçürüldü",
"FeedMoveFolder": "Qovluqlar köçürüldü",
"FeedRenameFile": "Faylın adı dəyişdirildi",
"FeedRenameFolder": "Qovluğun adı dəyişdirildi",
"FeedRenameRoom": "<strong>{{oldRoomTitle}}»</strong> olan otağın adı <strong>«{{roomTitle}}»</strong> olaraq dəyişdirildi.",
"FeedUpdateFile": "Fayl yeniləndi",
"FeedUpdateRoom": "Simvol dəyişdirildi",
"FeedUpdateUser": "{{role}} rolu təyin edildi",
"FileExtension": "Fayl uzantısı",
"FilesEmptyScreenText": "Fayl və qovluq detallarına burada baxın",
"ItemsSelected": "Seçilmiş elementlər",
"LastModifiedBy": "tərəfindən Son Dəyişiklik",
"PendingInvitations": "Gözləyən dəvətlər",
"Properties": "Xüsusiyyətlər",
"RoomsEmptyScreenTent": "Otaq detallarına burada baxın",
"SelectedUsers": "Seçilmiş hesablar",
"StorageType": "Yaddaş növü",
"SubmenuDetails": "Detallar",
"SubmenuHistory": "Tarixçə",
"SystemProperties": "Sistem xüsusiyyətləri",
"UsersInRoom": "Otaqdakı istifadəçilər",
"Versions": "Versiyalar"
}

View File

@ -1,3 +1,14 @@
{
"LinkCopySuccess": "Link köçürüldü"
"AddManually": "Əl ilə əlavə edin",
"AddManuallyDescriptionAccounts": "Yeni istifadəçiləri e-poçt vasitəsilə şəxsən DocSpace-ə dəvət edin",
"AddManuallyDescriptionRoom": "Mövcud DocSpace istifadəçilərini adlardan istifadə edərək otağa əlavə edin və ya yeni istifadəçiləri e-poçt vasitəsilə şəxsən dəvət edin",
"EmailErrorMessage": "E-poçt ünvanı etibarlı deyil. Siz e-poçtu klikləməklə redaktə edə bilərsiniz.",
"InviteAccountSearchPlaceholder": "İnsanları e-məktubla dəvət edin",
"InviteRoomSearchPlaceholder": "İnsanları ad və ya e-məktubla dəvət edin",
"InviteViaLink": "Keçidlə dəvət edin",
"InviteViaLinkDescriptionAccounts": "DocSpace-də avtorizasiya üçün universal keçid yaradın",
"InviteViaLinkDescriptionRoom": "Otağın avtorizasiyası üçün universal keçid yaradın",
"Invited": "Dəvət edildi",
"LinkCopySuccess": "Link köçürüldü",
"SendInvitation": "Dəvət göndərin"
}

View File

@ -0,0 +1,22 @@
{
"Ascending": "Artan",
"CopyWindowCode": "Pəncərə yerləşdirmə kodunu kopyalayın",
"DataDisplay": "Məlumatların göstərilməsi parametrləri",
"Descending": "Azalan",
"Destroy": "Məhv etmək",
"EnterCount": "Sayını daxil edin",
"EnterHeight": "Hündürlüyü daxil edin",
"EnterId": "ID daxil edin",
"EnterPage": "Nömrə səhifəsini daxil edin",
"EnterWidth": "Genişliyi daxil edin",
"FolderId": "Qovluq id",
"FrameId": "Çərçivə id",
"Header": "Başlıq",
"ItemsCount": "Maddələr sayılır",
"JavascriptSdk": "Javascript SDK",
"Menu": "Menyu",
"Page": "Səhifə",
"SearchTerm": "Axtarış termini",
"SortOrder": "Sırala",
"WindowParameters": "Pəncərə parametrləri"
}

View File

@ -1 +1,14 @@
{}
{
"ClickHere": "Buraya klikləyin",
"ConfirmEmailDescription": "Aktivləşdirmə e-məktubunda göndərilən keçiddən istifadə edin. Aktivləşdirmə linki olan e-məktub almamısınız?",
"ConfirmEmailHeader": "Lütfən, DocSpace xüsusiyyətlərinə daxil olmaq üçün e-poçtunuzu ({{ email }}) aktiv edin.",
"RequestActivation": "Yenidən aktivləşdirmə tələb edin",
"RoomQuotaDescription": "DocSpace üçün daha uyğun qiymət planı tapmaq üçün lazımsız otaqları arxivləşdirə və ya <1>{{clickHere}}</1> edə bilərsiniz.",
"RoomQuotaHeader": "Otaqlar keçmək üzrədir: {{currentValue}} / {{maxValue}}",
"StorageAndRoomHeader": "Yaddaş və otaq limitləri aşmaq üzrədir.",
"StorageAndUserHeader": "Yaddaş və administratorlar/ekspert istifadəçilər limitləri aşmaq üzrədir.",
"StorageQuotaDescription": "Siz lazımsız faylları silə və ya DocSpace üçün daha uyğun qiymət planı tapmaq üçün <1>{{clickHere}}</1> edə bilərsiniz.",
"StorageQuotaHeader": "Yaddaş sahəsinin həcmi keçmək üzrədir : {{currentValue}} / {{maxValue}}",
"UserQuotaDescription": "Portalınız üçün daha yaxşı qiymət planı tapmaq üçün <1>{{clickHere}}</1>.",
"UserQuotaHeader": "Admin/ekspert istifadəçilərin sayı keçmək üzrədir: {{currentValue}} / {{maxValue}}."
}

View File

@ -1 +1,13 @@
{}
{
"ActionsWithFilesDescription": "Nişanlar sizə fayl yükləmələri, yeni yaradılanlar və dəyişikliklər haqqında məlumat verir.",
"Badges": "Beyclər",
"DailyFeed": "Gündəlik DocSpace axını",
"DailyFeedDescription": "DocSpace-dəki xəbərləri və hadisələri gündəlik xülasədə oxuyun.",
"ManageNotifications": "İdarə etmək",
"Notifications": "Bildirişlər",
"RoomsActions": "Otaqlarda fayllarla əməliyyatlar",
"RoomsActivity": "Otaqların fəaliyyəti",
"RoomsActivityDescription": "Saatlıq bildirişlər. Otaqlarınızdakı bütün hərəkətlərdən xəbərdar olun",
"UsefulTips": "Faydalı DocSpace məsləhətləri",
"UsefulTipsDescription": "DocSpace-də faydalı təlimatlar əldə edin"
}

Some files were not shown because too many files have changed in this diff Show More