Compare commits

..

47 Commits

Author SHA1 Message Date
raller1028
45a5567978 Update push_test_server.yml
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-02-21 15:45:43 +08:00
link
3190421fd9 Start sequence (#904)
Signed-off-by: link <a624669980@163.com>
2023-02-17 19:31:22 +08:00
link
a7126cac63 Rotational training registration to message-bus (#903) 2023-02-17 18:40:19 +08:00
raller1028
151aa037bb Update push_test_server.yml
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-02-17 11:46:30 +08:00
raller1028
08ed9933ee Update push_test_server.yml
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-02-16 18:32:47 +08:00
raller1028
8425011e73 Update push_test_server.yml
Signed-off-by: raller1028 <57336867+raller1028@users.noreply.github.com>
2023-02-16 18:20:40 +08:00
link
96ff550d61 Fix permission change cannot be unmount (#902) 2023-02-16 16:55:51 +08:00
link
85a803d352 Update service (#900) 2023-02-16 14:23:49 +08:00
link
5def3e5856 Add error log (#899) 2023-02-16 09:08:35 +08:00
Tiger Wang
c768260b1b add CODE_OF_CONDUCT.md (#898)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-02-15 16:14:47 -05:00
link
46e146e633 Fix the download file name error problem (#896) 2023-02-15 17:31:20 +08:00
link
1a2f917b30 Exception handling when adding a mount to the same device (#890) 2023-02-13 19:42:34 +08:00
link
86adfbaef8 Updated demo snapshot (#889) 2023-02-13 18:58:21 +08:00
link
143af78745 Add 0.4.2 changelog (#885) 2023-02-10 19:11:36 +08:00
link
7b07f22685 Fix sharing failure issue (#884) 2023-02-10 17:27:13 +08:00
Tiger Wang
c8b6a1c228 fix installation issue for Mint Linux (#881)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-02-09 19:51:00 -05:00
link
827fba2164 Add error logs (#879) 2023-02-09 17:29:33 +08:00
link
6217009caf The same driver and the same account can only be hooked up once (#878)
Signed-off-by: link <a624669980@163.com>
2023-02-09 17:23:33 +08:00
link
fbfcc2c43a Remount if the configuration file already exists (#877) 2023-02-09 16:10:16 +08:00
link
b331c484f5 Add uninstall error judgment (#875) 2023-02-09 10:31:15 +08:00
link
a1fbbf9584 Modify the configuration file (#872) 2023-02-08 18:11:46 +08:00
link
3611ec7d09 Return directly if name does not exist (#871) 2023-02-07 21:58:01 +08:00
link
7dedbafc8a Modify naming rules 2023-02-07 10:32:12 +00:00
link
b7634402bd Merge branch 'main' of https://github.com/IceWhaleTech/CasaOS 2023-02-07 06:39:31 +00:00
link
8e025a9836 Mounted folders are inoperable 2023-02-07 06:38:21 +00:00
Tiger Wang
8bfc8178cf remove obsolete functions for notification of app installation/uninstallation (#867)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-02-06 21:53:09 -05:00
link
8343f52137 Return of error when modifying mount (#864) 2023-02-06 17:44:37 +08:00
link
7501833cf9 Modify file permissions (#860) 2023-02-06 17:43:40 +08:00
link
4c917a33a4 Remove client judgment (#862) 2023-02-06 17:10:05 +08:00
link
173997c44b Send notification changes to message bus (#861) 2023-02-06 15:47:29 +08:00
link
76bc6e68ae Modify file list return data 2023-02-02 15:59:33 +00:00
link
d0fc2cc8cb Conflict resolution 2023-02-02 03:52:17 +00:00
link
32980f4033 Merge branch 'add_file_drive' 2023-02-02 03:51:41 +00:00
link
28d3ca0ca6 added google drive and dropbox driver 2023-02-02 03:36:59 +00:00
link
3d31ad6689 Create push_test_server.yml
Signed-off-by: link <a624669980@163.com>
2023-01-31 18:19:36 +08:00
Tiger Wang
0bb138e39e add API for checking casaos-* service status (#852)
Signed-off-by: Tiger Wang <tigerwang@outlook.com>
2023-01-30 17:01:47 -05:00
link
e8db1767e5 Update CHANGELOG.md
Signed-off-by: link <a624669980@163.com>
2023-01-19 21:42:10 +08:00
link
6b01263252 Update CHANGELOG.md
Signed-off-by: link <a624669980@163.com>
2023-01-19 10:16:55 +08:00
Tiger Wang
b2a4fafdb4 add race detection for tests 2023-01-17 12:49:49 -05:00
Tiger Wang
fcfb48d88e enable code coverage (#825) 2023-01-16 17:58:25 -05:00
link
86380d912d Allow specific special characters (#823) 2023-01-16 14:10:39 +08:00
link
87d8be8c61 added google drive 2023-01-16 05:54:44 +00:00
link
9123974811 Update CHANGELOG.md
Signed-off-by: link <a624669980@163.com>
2023-01-09 18:08:47 +08:00
link
be50579544 add update type (#812) 2023-01-09 15:16:04 +08:00
link
91bb0cba6f Update CHANGELOG.md
Signed-off-by: link <a624669980@163.com>
2023-01-08 15:33:48 +08:00
link
b9946db854 repair the error of version number display after successful update (#805) 2023-01-05 15:16:54 +08:00
老竭力
9eb650b444 Add download triage for download migration-tool (#793)
Co-authored-by: Tiger Wang <tigerwang@outlook.com>
2023-01-05 10:47:44 +08:00
110 changed files with 9364 additions and 509 deletions

22
.github/workflows/codecov.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Collect Code Coverage
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Run coverage
run: go test -race -failfast -coverprofile=coverage.txt -covermode=atomic -v ./...
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3

View File

@@ -33,7 +33,7 @@ jobs:
- name: Get old instance and snapshot name, create new instance name
run: |
echo "OLD_INSTANCE_SNAPSHOT_NAME=$(aws lightsail get-instance-snapshots | grep '"name": "casaos-0.3.6-1666150291' | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g')" >> $GITHUB_ENV
echo "OLD_INSTANCE_SNAPSHOT_NAME=$(aws lightsail get-instance-snapshots | grep '"name": "updateto_to_0.4.1-1676285322' | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g')" >> $GITHUB_ENV
echo "OLD_INSTANCE_NAME=$(aws lightsail get-instances | grep '"name": "CasaOS-Demo-[0-9]' | sed 's/ //g' | sed 's/"//g' | sed 's/,//g' | sed 's/name://g')" >> $GITHUB_ENV
echo "NEW_INSTANCE_NAME=CasaOS-Demo-$(date +%s)" >> $GITHUB_ENV

84
.github/workflows/push_test_server.yml vendored Normal file
View File

@@ -0,0 +1,84 @@
name: Auto Publish Website
on:
push:
branches:
- main
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-22.04
steps:
-
name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: isntall git
run: sudo apt install --yes git
- name: git global
run: sudo git config --global --add safe.directory '*'
- name: set version
run: sudo git tag v99.99.99-alpha
- name: Fetch all tags
run: sudo git fetch --force --tags
- name: Get version
id: get_version
# run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
run: echo "VERSION=$(git describe --abbrev=0 --tags | awk -F- '{print $1}')" >> $GITHUB_ENV
- name: show version
id: show_version
# run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
run: echo ${{env.VERSION}}
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.19
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
version: latest
args: release --rm-dist --snapshot
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution
# GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- name: remove migration file
run: sudo find . -type f \( -name '*migration*' \) -delete
- name: install sshpass
run: sudo apt install sshpass --yes
- name: ZeroTier
uses: zerotier/github-action@v1.0.1
with:
network_id: ${{ secrets.ZEROTIER_NETWORK_ID }}
auth_token: ${{ secrets.ZEROTIER_CENTRAL_TOKEN }}
- name: ping host
shell: bash
run: |
count=10
while ! ping -c 1 10.147.18.11 ; do
echo "waiting..." ;
sleep 1 ;
let count=count-1
done
echo "ping success"
- name: copy tar to target host
shell: bash
run: |
sshpass -p "${{ secrets.ssh_password }}" scp -r -o StrictHostKeyChecking=no -P 22 ./dist/*.gz root@10.147.18.11:/var/www/download
echo "ping success"
- name: send message
run: |
curl -X POST -H "Content-Type: application/json" -d '{"msg_type":"text","content":{"text":"CasaOS updated"}}' ${{ secrets.SSH_ROBOT_URL }}

View File

@@ -6,7 +6,7 @@ before:
- go generate
- go run github.com/google/go-licenses@latest check . --disallowed_types=restricted
- go mod tidy
- go test -v ./...
- go test -race -v ./...
builds:
- id: casaos-amd64

View File

@@ -6,7 +6,7 @@ before:
- go generate
- go run github.com/google/go-licenses@latest check . --disallowed_types=restricted
- go mod tidy
- go test -v ./...
- go test -race -v ./...
builds:
- id: casaos-amd64

View File

@@ -16,6 +16,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Security
## [0.4.2]
### Added
- [App] Increase the display of progress during the installation process
- [App] Label whether the current app supports x86 or Pi devices
- [App] Support single app version upgrade
- [File] Support mounting of Google Drive and Dropbox cloud drives
- [System] Support Mint Linux
### Changed
- [File] Optimize the download speed of a single file
### Fixed
- [Share] Fix the samba permission issue
- [Disk] Fix the problem of disk mount point plus 1 after upgrade ([#770](https://github.com/IceWhaleTech/CasaOS/issues/770))
- [File] Fix the problem of file permission change caused by modifying files in casaos ([#829](https://github.com/IceWhaleTech/CasaOS/issues/829))
- [Share] Fix the problem of files being deleted due to samba uninstallation failure ([#843](https://github.com/IceWhaleTech/CasaOS/issues/843))
## [0.4.1] - 2023-1-19
### Added
- [Disk] Added disk merging feature in storage management (beta) that allows for multiple disks to be merged into a single storage space
- [System] Added option for startpage.com search engine
- [APP] Added app cloning feature in the app's context menu.
### Changed
- [APP] Improved app installation process, including display of the installation process, checks for successful installation, and prompts
- [System] Binary sizes are 40%~60% smaller (thanks to upx)
- [App] Optimization of install and update for certain country.
- [All] Lots of bug fixes
## [0.4.0] - 2022-12-13
### Added

128
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
wiki@casaos.io.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@@ -28,6 +28,9 @@
<a href="https://github.com/IceWhaleTech/CasaOS/issues" target="_blank">
<img alt="CasaOS Issues" src="https://img.shields.io/github/issues/IceWhaleTech/CasaOS?color=162453&style=flat-square&label=Issues" />
</a>
<a href="https://codecov.io/gh/IceWhaleTech/CasaOS" >
<img src="https://codecov.io/gh/IceWhaleTech/CasaOS/branch/main/graph/badge.svg?token=l9uMKGlkxM"/>
</a>
<a href="https://github.com/IceWhaleTech/CasaOS/stargazers" target="_blank">
<img alt="CasaOS Stargazers" src="https://img.shields.io/github/stars/IceWhaleTech/CasaOS?color=162453&style=flat-square&label=Stars" />
</a>

116
api/casaos/openapi.yaml Normal file
View File

@@ -0,0 +1,116 @@
openapi: 3.0.3
info:
title: CasaOS API
version: v2
description: |
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_dark_night_800px.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_twilight_blue_800px.png">
<img alt="CasaOS" src="https://raw.githubusercontent.com/IceWhaleTech/logo/main/casaos/casaos_banner_twilight_blue_800px.png">
</picture>
CasaOS API provides miscellaneous methods for different scenarios.
For issues and discussions, please visit the [GitHub repository](https://github.com/IceWhaleTech/CasaOS) or join [our Discord](https://discord.gg/knqAbbBbeX).
servers:
- url: /v2/casaos
tags:
- name: Health methods
description: |-
(TODO)
- name: File methods
description: |-
(TODO)
x-tagGroups:
- name: Methods
tags:
- Health methods
security:
- access_token: []
paths:
/health/services:
get:
tags:
- Health methods
summary: Get service status
description: |-
Get running status of each `casaos-*` service.
operationId: getHealthServices
responses:
"200":
$ref: "#/components/responses/GetHealthServicesOK"
"500":
$ref: "#/components/responses/ResponseInternalServerError"
/file/test:
get:
tags:
- File methods
summary: Test file methods
description: |-
Test file methods.
operationId: getFileTest
responses:
"200":
$ref: "#/components/responses/ResponseOK"
"500":
$ref: "#/components/responses/ResponseInternalServerError"
components:
securitySchemes:
access_token:
type: apiKey
in: header
name: Authorization
responses:
ResponseOK:
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/BaseResponse"
ResponseInternalServerError:
description: Internal Server Error
content:
application/json:
schema:
$ref: "#/components/schemas/BaseResponse"
GetHealthServicesOK:
description: OK
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/BaseResponse"
- properties:
data:
$ref: "#/components/schemas/HealthServices"
schemas:
BaseResponse:
properties:
message:
readOnly: true
description: message returned by server side if there is any
type: string
example: ""
HealthServices:
properties:
running:
type: array
items:
type: string
example: "casaos-gateway.service"
not_running:
type: array
items:
type: string
example: "casaos.service"

24
api/index.html Normal file
View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>CasaOS | Developers</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='casaos/openapi.yaml' expandResponses='all' jsonSampleExpandLevel='all'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>

View File

@@ -56,6 +56,22 @@ __is_migration_needed() {
__is_version_gt "${version2}" "${version1}"
}
__get_download_domain(){
local region
# Use ipconfig.io/country and https://ifconfig.io/country_code to get the country code
region=$(curl --connect-timeout 2 -s ipconfig.io/country || echo "")
if [ "${region}" = "" ]; then
region=$(curl --connect-timeout 2 -s https://ifconfig.io/country_code || echo "")
fi
if [[ "${region}" = "China" ]] || [[ "${region}" = "CN" ]]; then
echo "https://casaos.oss-cn-shanghai.aliyuncs.com/"
else
echo "https://github.com/"
fi
}
DOWNLOAD_DOMAIN=$(__get_download_domain)
BUILD_PATH=$(dirname "${BASH_SOURCE[0]}")/../../..
SOURCE_ROOT=${BUILD_PATH}/sysroot
@@ -71,7 +87,7 @@ CURRENT_BIN_FILE=${CURRENT_BIN_PATH}/${APP_NAME}
CURRENT_BIN_FILE_LEGACY=$(realpath -e ${CURRENT_BIN_PATH_LEGACY}/${APP_NAME} || which ${APP_NAME} || echo CURRENT_BIN_FILE_LEGACY_NOT_FOUND)
SOURCE_VERSION="$(${SOURCE_BIN_FILE} -v)"
CURRENT_VERSION="$(${CURRENT_BIN_FILE} -v || ${CURRENT_BIN_FILE_LEGACY} -v || (stat "${CURRENT_BIN_FILE_LEGACY}" > /dev/null && echo LEGACY_WITHOUT_VERSION) || echo CURRENT_VERSION_NOT_FOUND)"
CURRENT_VERSION="$(${CURRENT_BIN_FILE} -v || ${CURRENT_BIN_FILE_LEGACY} -v || (stat "${CURRENT_BIN_FILE_LEGACY}" >/dev/null && echo LEGACY_WITHOUT_VERSION) || echo CURRENT_VERSION_NOT_FOUND)"
__info_done "CURRENT_VERSION: ${CURRENT_VERSION}"
__info_done "SOURCE_VERSION: ${SOURCE_VERSION}"
@@ -86,18 +102,18 @@ fi
ARCH="unknown"
case $(uname -m) in
x86_64)
ARCH="amd64"
;;
aarch64)
ARCH="arm64"
;;
armv7l)
ARCH="arm-7"
;;
*)
__error "Unsupported architecture"
;;
x86_64)
ARCH="amd64"
;;
aarch64)
ARCH="arm64"
;;
armv7l)
ARCH="arm-7"
;;
*)
__error "Unsupported architecture"
;;
esac
__info "ARCH: ${ARCH}"
@@ -134,7 +150,7 @@ while read -r VERSION_PAIR; do
if [ "${CURRENT_VERSION_FOUND}" = "true" ]; then
MIGRATION_PATH+=("${URL// /}")
fi
done < "${MIGRATION_LIST_FILE}"
done <"${MIGRATION_LIST_FILE}"
if [ ${#MIGRATION_PATH[@]} -eq 0 ]; then
__warning "No migration path found from ${CURRENT_VERSION} to ${SOURCE_VERSION}"
@@ -143,7 +159,8 @@ fi
pushd "${MIGRATION_SERVICE_DIR}"
{ for URL in "${MIGRATION_PATH[@]}"; do
{
for URL in "${MIGRATION_PATH[@]}"; do
MIGRATION_TOOL_FILE=$(basename "${URL}")
if [ -f "${MIGRATION_TOOL_FILE}" ]; then

View File

@@ -1,3 +1,3 @@
LEGACY_WITHOUT_VERSION https://github.com/IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
v0.3.5 https://github.com/IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
v0.3.5.1 https://github.com/IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
LEGACY_WITHOUT_VERSION ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
v0.3.5 ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz
v0.3.5.1 ${DOWNLOAD_DOMAIN}IceWhaleTech/CasaOS/releases/download/v0.3.6/linux-${ARCH}-casaos-migration-tool-v0.3.6.tar.gz

View File

@@ -18,7 +18,9 @@ __get_setup_script_directory_by_os_release() {
} || {
pushd "${ID}" >/dev/null
} || {
pushd "${ID_LIKE}" >/dev/null
[[ -n ${ID_LIKE} ]] && for ID in ${ID_LIKE}; do
pushd "${ID}" >/dev/null && break
done
} || {
echo "Unsupported OS: ${ID} ${VERSION_CODENAME} (${ID_LIKE})"
exit 1

View File

@@ -1,5 +1,6 @@
[Unit]
After=casaos-gateway.service
After=casaos-message-bus.service
After=rclone.service
ConditionFileNotEmpty=/etc/casaos/casaos.conf
Description=CasaOS Main Service

View File

@@ -0,0 +1,12 @@
[Unit]
Description=rclone
[Service]
ExecStartPre=/usr/bin/mkdir -p /var/run/rclone
ExecStartPre=/usr/bin/rm -f /var/run/rclone/rclone.sock
ExecStart=/usr/bin/rclone rcd --rc-addr unix:///var/run/rclone/rclone.sock --rc-no-auth --rc-allow-origin "*"
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target

View File

@@ -26,7 +26,9 @@ __get_setup_script_directory_by_os_release() {
} || {
pushd "${ID}" &>/dev/null
} || {
pushd "${ID_LIKE}" &>/dev/null
[[ -n ${ID_LIKE} ]] && for ID in ${ID_LIKE}; do
pushd "${ID}" >/dev/null && break
done
} || {
echo "Unsupported OS: ${ID} ${VERSION_CODENAME} (${ID_LIKE})"
exit 1

View File

@@ -66,18 +66,6 @@ GetLocalJoinNetworks() {
zerotier-cli listnetworks -j
}
#移除挂载点,删除已挂在的文件夹
UMountPorintAndRemoveDir() {
DEVICE=$1
MOUNT_POINT=$(mount | grep ${DEVICE} | awk '{ print $3 }')
if [[ -z ${MOUNT_POINT} ]]; then
${log} "Warning: ${DEVICE} is not mounted"
else
umount -lf ${DEVICE}
/bin/rmdir "${MOUNT_POINT}"
fi
}
#格式化fat32磁盘
#param 需要格式化的目录 /dev/sda1
#param 格式
@@ -133,11 +121,7 @@ GetPlugInDisk() {
fdisk -l | grep 'Disk' | grep 'sd' | awk -F , '{print substr($1,11,3)}'
}
#获取磁盘状态
#param 磁盘路径
GetDiskHealthState() {
smartctl -H $1 | grep "SMART Health Status" | awk -F ":" '{print$2}'
}
#获取磁盘字节数量和扇区数量
#param 磁盘路径 /dev/sda
@@ -370,19 +354,6 @@ MountCIFS(){
$sudo_cmd mount -t cifs -o username=$1,password=$6,port=$4 //$2/$3 $5
}
# $1:service name
CheckServiceStatus(){
rs="`systemctl status $1 |grep -E 'Active|PID'`"
#echo "$rs"
run="`echo "$rs" |grep -B 2 'running'`"
fai="`echo "$rs" |grep -E -B 2 'failed|inactive|dead'`"
if [ "$run" == "" ]
then
echo "failed"
else
echo "running"
fi
}
UDEVILUmount(){
$sudo_cmd udevil umount -f $1
}

View File

@@ -17,10 +17,10 @@ import (
interfaces "github.com/IceWhaleTech/CasaOS-Common"
"github.com/IceWhaleTech/CasaOS-Common/utils/systemctl"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/sqlite"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/IceWhaleTech/CasaOS/types"
"gorm.io/gorm"
)
@@ -47,7 +47,7 @@ func init() {
flag.Parse()
if *versionFlag {
fmt.Println("v" + types.CURRENTVERSION)
fmt.Println("v" + common.VERSION)
os.Exit(0)
}
@@ -86,7 +86,7 @@ func init() {
sqliteDB = sqlite.GetDb(dbFlag)
// gredis.GetRedisConn(config.RedisInfo),
service.MyService = service.NewService(sqliteDB, "", nil)
service.MyService = service.NewService(sqliteDB, "")
}
func main() {

210
codegen/casaos_api.go Normal file
View File

@@ -0,0 +1,210 @@
// Package codegen provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/deepmap/oapi-codegen version v1.12.4 DO NOT EDIT.
package codegen
import (
"bytes"
"compress/gzip"
"encoding/base64"
"fmt"
"net/url"
"path"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo/v4"
)
const (
Access_tokenScopes = "access_token.Scopes"
)
// BaseResponse defines model for BaseResponse.
type BaseResponse struct {
// Message message returned by server side if there is any
Message *string `json:"message,omitempty"`
}
// HealthServices defines model for HealthServices.
type HealthServices struct {
NotRunning *[]string `json:"not_running,omitempty"`
Running *[]string `json:"running,omitempty"`
}
// GetHealthServicesOK defines model for GetHealthServicesOK.
type GetHealthServicesOK struct {
Data *HealthServices `json:"data,omitempty"`
// Message message returned by server side if there is any
Message *string `json:"message,omitempty"`
}
// ResponseInternalServerError defines model for ResponseInternalServerError.
type ResponseInternalServerError = BaseResponse
// ResponseOK defines model for ResponseOK.
type ResponseOK = BaseResponse
// ServerInterface represents all server handlers.
type ServerInterface interface {
// Test file methods
// (GET /file/test)
GetFileTest(ctx echo.Context) error
// Get service status
// (GET /health/services)
GetHealthServices(ctx echo.Context) error
}
// ServerInterfaceWrapper converts echo contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
}
// GetFileTest converts echo context to params.
func (w *ServerInterfaceWrapper) GetFileTest(ctx echo.Context) error {
var err error
ctx.Set(Access_tokenScopes, []string{""})
// Invoke the callback with all the unmarshalled arguments
err = w.Handler.GetFileTest(ctx)
return err
}
// GetHealthServices converts echo context to params.
func (w *ServerInterfaceWrapper) GetHealthServices(ctx echo.Context) error {
var err error
ctx.Set(Access_tokenScopes, []string{""})
// Invoke the callback with all the unmarshalled arguments
err = w.Handler.GetHealthServices(ctx)
return err
}
// This is a simple interface which specifies echo.Route addition functions which
// are present on both echo.Echo and echo.Group, since we want to allow using
// either of them for path registration
type EchoRouter interface {
CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
}
// RegisterHandlers adds each server route to the EchoRouter.
func RegisterHandlers(router EchoRouter, si ServerInterface) {
RegisterHandlersWithBaseURL(router, si, "")
}
// Registers handlers, and prepends BaseURL to the paths, so that the paths
// can be served under a prefix.
func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) {
wrapper := ServerInterfaceWrapper{
Handler: si,
}
router.GET(baseURL+"/file/test", wrapper.GetFileTest)
router.GET(baseURL+"/health/services", wrapper.GetHealthServices)
}
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"H4sIAAAAAAAC/7xW70/jRhD9V1bTfoDKxBGoUmXpPnC9wiFUpSpIrUSi3GY9sfewd92ZcSBF/t+rXZtL",
"SCiC649PiffHe2/e7szsAxhfN96hE4bsAQi58Y4xfpyjfERdSXmFtLIGeXIZho13gk7CX900lTVarHfp",
"Z/YujLEpsdZxtqomS8huHuBbwiVk8E26YUv7dZy+14y/DrTQJQ/QkG+QxPYici0R7CWIpyqh67pZ13UJ",
"5MiGbBPkQQaTS+gSeKS6cILkdBV2If1E5OlNwb0+pH0lj9yqJ1c9+5a4Nxr9T7QEV7pkAIuOP9mR7Z5H",
"jcy6iBNPgYYJRSgtOczVYq24j49tjsoulZRIqCwr7daQAN7ruqkQMoAECHU+cdUaMqEWE5B1E2ZYyLqi",
"F75zzHvSnJc5tc6FDdkDWME6jm94jGbtecQ9BOyxfBnQRHodvl+Dd1RowTu9fj1uDIfRtGRlfRWs7yPQ",
"xiDzXPwtxiO2wdgSdY4ECThdB4zTVkpP9s94GzZcurGXuO6dsm7p909o2o7HJ6axRlrC+IFTp5RS/QT7",
"lgyqGnOr303hoCFcIvGR8ZWno3hBMFO5ptvDKSgmwyjvplCKNJylKem7UWGlbBctIw13d2R8nV4Y/K3U",
"FV6jKdPKFz6ttXVpb97wM19o55DmAX7ubFHK/IfxuLkfNa6YwteKrQLQf6hW7mykmC+qFl8WbOtC6SpI",
"+FGznlz1ov5/Rb2adOcWTF2vSp3+cqEa8iubI6vassGq0g59y6pGKX3OaulJ5Xa5REInig06TdbzKKCc",
"eVKWucWQ47nKLZuW2XrHiWoq1IxqZdlKKAXq5tzKx3ahCBvPVjytZwePbvRO7IffyzxUntRnb5268S2p",
"D5aNp3yzO+8HRkWR3ro/TheL9wv8/XA0jeliJebuJmBIYIXEfZKsjkO6+gadbixkcDIaj04ggUZLGXM0",
"XdoKU0GOhblA2U+0a2RRYdmjZyOIkBRT9iKHLPTWMxtiYonFb6vtHo/Hf1fUv6xLtzpFl8D3b9nyXOeL",
"9aita03r5/QH23TBkN3A2fbwLOxLy1iXU94qzM/aco6ihnqqWLS0rPxSoTal+jRU0u8+qQHmWct2OsDX",
"GPfco+bfdzCEOgQyhLplYc+/beJWN4jvpad94GbWzcKCQMZxvqUKMkhXx0P2Q1gwwO+6fnA9+TA53LSP",
"Hfbw4np5w5MTD0T3R6KLc/Jt0/MN637euyt7gc66vwIAAP//o5zNVnEKAAA=",
}
// GetSwagger returns the content of the embedded swagger specification file
// or error if failed to decode
func decodeSpec() ([]byte, error) {
zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
if err != nil {
return nil, fmt.Errorf("error base64 decoding spec: %s", err)
}
zr, err := gzip.NewReader(bytes.NewReader(zipped))
if err != nil {
return nil, fmt.Errorf("error decompressing spec: %s", err)
}
var buf bytes.Buffer
_, err = buf.ReadFrom(zr)
if err != nil {
return nil, fmt.Errorf("error decompressing spec: %s", err)
}
return buf.Bytes(), nil
}
var rawSpec = decodeSpecCached()
// a naive cached of a decoded swagger spec
func decodeSpecCached() func() ([]byte, error) {
data, err := decodeSpec()
return func() ([]byte, error) {
return data, err
}
}
// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
var res = make(map[string]func() ([]byte, error))
if len(pathToFile) > 0 {
res[pathToFile] = rawSpec
}
return res
}
// GetSwagger returns the Swagger specification corresponding to the generated code
// in this file. The external references of Swagger specification are resolved.
// The logic of resolving external references is tightly connected to "import-mapping" feature.
// Externally referenced files must be embedded in the corresponding golang packages.
// Urls can be supported but this task was out of the scope.
func GetSwagger() (swagger *openapi3.T, err error) {
var resolvePath = PathToRawSpec("")
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
var pathToFile = url.String()
pathToFile = path.Clean(pathToFile)
getSpec, ok := resolvePath[pathToFile]
if !ok {
err1 := fmt.Errorf("path not found: %s", pathToFile)
return nil, err1
}
return getSpec()
}
var specData []byte
specData, err = rawSpec()
if err != nil {
return
}
swagger, err = loader.LoadFromData(specData)
if err != nil {
return
}
return
}

2254
codegen/message_bus/api.go Normal file

File diff suppressed because it is too large Load Diff

7
common/constants.go Normal file
View File

@@ -0,0 +1,7 @@
package common
const (
SERVICENAME = "casaos"
VERSION = "0.4.2"
BODY = " "
)

23
common/message.go Normal file
View File

@@ -0,0 +1,23 @@
package common
import (
"fmt"
"github.com/IceWhaleTech/CasaOS/codegen/message_bus"
)
var (
// devtype -> action -> event
EventTypes map[string]map[string]message_bus.EventType
PropertyNameLookupMaps = map[string]map[string]string{
"system": {
fmt.Sprintf("%s:%s", SERVICENAME, "utilization"): "ID_BUS",
},
}
ActionPastTense = map[string]string{
"add": "added",
"remove": "removed",
}
)

12
drivers/all.go Normal file
View File

@@ -0,0 +1,12 @@
package drivers
import (
_ "github.com/IceWhaleTech/CasaOS/drivers/dropbox"
_ "github.com/IceWhaleTech/CasaOS/drivers/google_drive"
)
// All do nothing,just for import
// same as _ import
func All() {
}

30
drivers/base/client.go Normal file
View File

@@ -0,0 +1,30 @@
package base
import (
"net/http"
"time"
"github.com/go-resty/resty/v2"
)
var NoRedirectClient *resty.Client
var RestyClient = NewRestyClient()
var HttpClient = &http.Client{}
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
var DefaultTimeout = time.Second * 30
func init() {
NoRedirectClient = resty.New().SetRedirectPolicy(
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}),
)
NoRedirectClient.SetHeader("user-agent", UserAgent)
}
func NewRestyClient() *resty.Client {
return resty.New().
SetHeader("user-agent", UserAgent).
SetRetryCount(3).
SetTimeout(DefaultTimeout)
}

12
drivers/base/types.go Normal file
View File

@@ -0,0 +1,12 @@
package base
import "github.com/go-resty/resty/v2"
type Json map[string]interface{}
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
type ReqCallback func(req *resty.Request)

100
drivers/dropbox/drive.go Normal file
View File

@@ -0,0 +1,100 @@
package dropbox
import (
"context"
"errors"
"net/http"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
type Dropbox struct {
model.Storage
Addition
AccessToken string
}
func (d *Dropbox) Config() driver.Config {
return config
}
func (d *Dropbox) GetAddition() driver.Additional {
return &d.Addition
}
func (d *Dropbox) Init(ctx context.Context) error {
if len(d.RefreshToken) == 0 {
d.getRefreshToken()
}
return d.refreshToken()
}
func (d *Dropbox) Drop(ctx context.Context) error {
return nil
}
func (d *Dropbox) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
files, err := d.getFiles(dir.GetID())
if err != nil {
return nil, err
}
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
return fileToObj(src), nil
})
}
func (d *Dropbox) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
url := "https://content.dropboxapi.com/2/files/download"
link := model.Link{
URL: url,
Method: http.MethodPost,
Header: http.Header{
"Authorization": []string{"Bearer " + d.AccessToken},
"Dropbox-API-Arg": []string{`{"path": "` + file.GetPath() + `"}`},
},
}
return &link, nil
}
func (d *Dropbox) GetUserInfo(ctx context.Context) (string, error) {
url := "https://api.dropboxapi.com/2/users/get_current_account"
user := UserInfo{}
resp, err := d.request(url, http.MethodPost, func(req *resty.Request) {
req.SetHeader("Content-Type", "")
}, &user)
if err != nil {
return "", err
}
logger.Info("resp", zap.Any("resp", string(resp)))
return user.Email, nil
}
func (d *Dropbox) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
return nil
}
func (d *Dropbox) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
return nil
}
func (d *Dropbox) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
return nil
}
func (d *Dropbox) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
return errors.New("not support")
}
func (d *Dropbox) Remove(ctx context.Context, obj model.Obj) error {
return nil
}
func (d *Dropbox) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
return nil
}
var _ driver.Driver = (*Dropbox)(nil)

33
drivers/dropbox/meta.go Normal file
View File

@@ -0,0 +1,33 @@
package dropbox
import (
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/internal/op"
)
const ICONURL = "./img/driver/Dropbox.svg"
const APPKEY = "tciqajyazzdygt9"
const APPSECRET = "e7gtmv441cwdf0n"
type Addition struct {
driver.RootID
RefreshToken string `json:"refresh_token" required:"true" omit:"true"`
AppKey string `json:"app_key" type:"string" default:"tciqajyazzdygt9" omit:"true"`
AppSecret string `json:"app_secret" type:"string" default:"e7gtmv441cwdf0n" omit:"true"`
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
AuthUrl string `json:"auth_url" type:"string" default:"https://www.dropbox.com/oauth2/authorize?client_id=tciqajyazzdygt9&redirect_uri=https://cloudoauth.files.casaos.app&response_type=code&token_access_type=offline&state=${HOST}%2Fv1%2Frecover%2FDropbox&&force_reapprove=true&force_reauthentication=true"`
Icon string `json:"icon" type:"string" default:"./img/driver/Dropbox.svg"`
Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
}
var config = driver.Config{
Name: "Dropbox",
OnlyProxy: true,
DefaultRoot: "root",
}
func init() {
op.RegisterDriver(func() driver.Driver {
return &Dropbox{}
})
}

88
drivers/dropbox/types.go Normal file
View File

@@ -0,0 +1,88 @@
package dropbox
import (
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/model"
"go.uber.org/zap"
)
type UserInfo struct {
AccountID string `json:"account_id"`
Name struct {
GivenName string `json:"given_name"`
Surname string `json:"surname"`
FamiliarName string `json:"familiar_name"`
DisplayName string `json:"display_name"`
AbbreviatedName string `json:"abbreviated_name"`
} `json:"name"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
Disabled bool `json:"disabled"`
Country string `json:"country"`
Locale string `json:"locale"`
ReferralLink string `json:"referral_link"`
IsPaired bool `json:"is_paired"`
AccountType struct {
Tag string `json:".tag"`
} `json:"account_type"`
RootInfo struct {
Tag string `json:".tag"`
RootNamespaceID string `json:"root_namespace_id"`
HomeNamespaceID string `json:"home_namespace_id"`
} `json:"root_info"`
}
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
type File struct {
Tag string `json:".tag"`
Name string `json:"name"`
PathLower string `json:"path_lower"`
PathDisplay string `json:"path_display"`
ID string `json:"id"`
ClientModified time.Time `json:"client_modified,omitempty"`
ServerModified time.Time `json:"server_modified,omitempty"`
Rev string `json:"rev,omitempty"`
Size int `json:"size,omitempty"`
IsDownloadable bool `json:"is_downloadable,omitempty"`
ContentHash string `json:"content_hash,omitempty"`
}
type Files struct {
Files []File `json:"entries"`
Cursor string `json:"cursor"`
HasMore bool `json:"has_more"`
}
type Error struct {
Error struct {
Errors []struct {
Domain string `json:"domain"`
Reason string `json:"reason"`
Message string `json:"message"`
LocationType string `json:"location_type"`
Location string `json:"location"`
}
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func fileToObj(f File) *model.ObjThumb {
logger.Info("dropbox file", zap.Any("file", f))
obj := &model.ObjThumb{
Object: model.Object{
ID: f.ID,
Name: f.Name,
Size: int64(f.Size),
Modified: f.ClientModified,
IsFolder: f.Tag == "folder",
Path: f.PathDisplay,
},
Thumbnail: model.Thumbnail{},
}
return obj
}

102
drivers/dropbox/util.go Normal file
View File

@@ -0,0 +1,102 @@
package dropbox
import (
"fmt"
"net/http"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/drivers/base"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
func (d *Dropbox) getRefreshToken() error {
url := "https://api.dropbox.com/oauth2/token"
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"code": d.Code,
"grant_type": "authorization_code",
"redirect_uri": "https://cloudoauth.files.casaos.app",
}).SetBasicAuth(d.Addition.AppKey, d.Addition.AppSecret).SetHeader("Content-Type", "application/x-www-form-urlencoded").Post(url)
if err != nil {
return err
}
logger.Info("get refresh token", zap.String("res", res.String()))
if e.Error != "" {
return fmt.Errorf(e.Error)
}
d.RefreshToken = resp.RefreshToken
return nil
}
func (d *Dropbox) refreshToken() error {
url := "https://api.dropbox.com/oauth2/token"
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"refresh_token": d.RefreshToken,
"grant_type": "refresh_token",
}).SetBasicAuth(d.Addition.AppKey, d.Addition.AppSecret).SetHeader("Content-Type", "application/x-www-form-urlencoded").Post(url)
if err != nil {
return err
}
logger.Info("get refresh token", zap.String("res", res.String()))
if e.Error != "" {
return fmt.Errorf(e.Error)
}
d.AccessToken = resp.AccessToken
return nil
}
func (d *Dropbox) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
req.SetHeader("Content-Type", "application/json")
if callback != nil {
callback(req)
}
if resp != nil {
req.SetResult(resp)
}
var e Error
req.SetError(&e)
res, err := req.Execute(method, url)
if err != nil {
return nil, err
}
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = d.refreshToken()
if err != nil {
return nil, err
}
return d.request(url, method, callback, resp)
}
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
return res.Body(), nil
}
func (d *Dropbox) getFiles(path string) ([]File, error) {
res := make([]File, 0)
var resp Files
body := base.Json{
"limit": 2000,
"path": path,
}
_, err := d.request("https://api.dropboxapi.com/2/files/list_folder", http.MethodPost, func(req *resty.Request) {
req.SetBody(body)
}, &resp)
if err != nil {
return nil, err
}
res = append(res, resp.Files...)
return res, nil
}

View File

@@ -0,0 +1,183 @@
package google_drive
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/drivers/base"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
type GoogleDrive struct {
model.Storage
Addition
AccessToken string
}
func (d *GoogleDrive) Config() driver.Config {
return config
}
func (d *GoogleDrive) GetAddition() driver.Additional {
return &d.Addition
}
func (d *GoogleDrive) Init(ctx context.Context) error {
if d.ChunkSize == 0 {
d.ChunkSize = 5
}
if len(d.RefreshToken) == 0 {
d.getRefreshToken()
}
return d.refreshToken()
}
func (d *GoogleDrive) Drop(ctx context.Context) error {
return nil
}
func (d *GoogleDrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
files, err := d.getFiles(dir.GetID())
if err != nil {
return nil, err
}
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
return fileToObj(src), nil
})
}
func (d *GoogleDrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.GetID())
_, err := d.request(url, http.MethodGet, nil, nil)
if err != nil {
return nil, err
}
link := model.Link{
Method: http.MethodGet,
URL: url + "&alt=media",
Header: http.Header{
"Authorization": []string{"Bearer " + d.AccessToken},
},
}
return &link, nil
}
func (d *GoogleDrive) GetUserInfo(ctx context.Context) (string, error) {
url := "https://content.googleapis.com/drive/v3/about?fields=user"
user := UserInfo{}
resp, err := d.request(url, http.MethodGet, nil, &user)
if err != nil {
return "", err
}
logger.Info("resp", zap.Any("resp", resp))
return user.User.EmailAddress, nil
}
func (d *GoogleDrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
data := base.Json{
"name": dirName,
"parents": []string{parentDir.GetID()},
"mimeType": "application/vnd.google-apps.folder",
}
_, err := d.request("https://www.googleapis.com/drive/v3/files", http.MethodPost, func(req *resty.Request) {
req.SetBody(data)
}, nil)
return err
}
func (d *GoogleDrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
query := map[string]string{
"addParents": dstDir.GetID(),
"removeParents": "root",
}
url := "https://www.googleapis.com/drive/v3/files/" + srcObj.GetID()
_, err := d.request(url, http.MethodPatch, func(req *resty.Request) {
req.SetQueryParams(query)
}, nil)
return err
}
func (d *GoogleDrive) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
data := base.Json{
"name": newName,
}
url := "https://www.googleapis.com/drive/v3/files/" + srcObj.GetID()
_, err := d.request(url, http.MethodPatch, func(req *resty.Request) {
req.SetBody(data)
}, nil)
return err
}
func (d *GoogleDrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
return errors.New("not support")
}
func (d *GoogleDrive) Remove(ctx context.Context, obj model.Obj) error {
url := "https://www.googleapis.com/drive/v3/files/" + obj.GetID()
_, err := d.request(url, http.MethodDelete, nil, nil)
return err
}
func (d *GoogleDrive) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
obj := stream.GetOld()
var (
e Error
url string
data base.Json
res *resty.Response
err error
)
if obj != nil {
url = fmt.Sprintf("https://www.googleapis.com/upload/drive/v3/files/%s?uploadType=resumable&supportsAllDrives=true", obj.GetID())
data = base.Json{}
} else {
data = base.Json{
"name": stream.GetName(),
"parents": []string{dstDir.GetID()},
}
url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&supportsAllDrives=true"
}
req := base.NoRedirectClient.R().
SetHeaders(map[string]string{
"Authorization": "Bearer " + d.AccessToken,
"X-Upload-Content-Type": stream.GetMimetype(),
"X-Upload-Content-Length": strconv.FormatInt(stream.GetSize(), 10),
}).
SetError(&e).SetBody(data).SetContext(ctx)
if obj != nil {
res, err = req.Patch(url)
} else {
res, err = req.Post(url)
}
if err != nil {
return err
}
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = d.refreshToken()
if err != nil {
return err
}
return d.Put(ctx, dstDir, stream, up)
}
return fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
putUrl := res.Header().Get("location")
if stream.GetSize() < d.ChunkSize*1024*1024 {
_, err = d.request(putUrl, http.MethodPut, func(req *resty.Request) {
req.SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)).SetBody(stream.GetReadCloser())
}, nil)
} else {
err = d.chunkUpload(ctx, stream, putUrl)
}
return err
}
var _ driver.Driver = (*GoogleDrive)(nil)

View File

@@ -0,0 +1,35 @@
package google_drive
import (
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/internal/op"
)
const ICONURL = "./img/driver/GoogleDrive.svg"
const CLIENTID = "921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com"
const CLIENTSECRET = "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"
type Addition struct {
driver.RootID
RefreshToken string `json:"refresh_token" required:"true" omit:"true"`
OrderBy string `json:"order_by" type:"string" help:"such as: folder,name,modifiedTime" omit:"true"`
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" omit:"true"`
ClientID string `json:"client_id" required:"true" default:"921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com" omit:"true"`
ClientSecret string `json:"client_secret" required:"true" default:"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" omit:"true"`
ChunkSize int64 `json:"chunk_size" type:"number" help:"chunk size while uploading (unit: MB)" omit:"true"`
AuthUrl string `json:"auth_url" type:"string" default:"https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?response_type=code&client_id=921743327851-urr4f7jjfp4ts639evqb3i4m4qb4u4cc.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fcloudoauth.files.casaos.app&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&approval_prompt=force&state=${HOST}%2Fv1%2Frecover%2FGoogleDrive&service=lso&o2v=1&flowName=GeneralOAuthFlow"`
Icon string `json:"icon" type:"string" default:"./img/driver/GoogleDrive.svg"`
Code string `json:"code" type:"string" help:"code from auth_url" omit:"true"`
}
var config = driver.Config{
Name: "GoogleDrive",
OnlyProxy: true,
DefaultRoot: "root",
}
func init() {
op.RegisterDriver(func() driver.Driver {
return &GoogleDrive{}
})
}

View File

@@ -0,0 +1,77 @@
package google_drive
import (
"strconv"
"time"
"github.com/IceWhaleTech/CasaOS/model"
log "github.com/sirupsen/logrus"
)
type UserInfo struct {
User struct {
Kind string `json:"kind"`
DisplayName string `json:"displayName"`
PhotoLink string `json:"photoLink"`
Me bool `json:"me"`
PermissionID string `json:"permissionId"`
EmailAddress string `json:"emailAddress"`
} `json:"user"`
}
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
type Files struct {
NextPageToken string `json:"nextPageToken"`
Files []File `json:"files"`
}
type File struct {
Id string `json:"id"`
Name string `json:"name"`
MimeType string `json:"mimeType"`
ModifiedTime time.Time `json:"modifiedTime"`
Size string `json:"size"`
ThumbnailLink string `json:"thumbnailLink"`
ShortcutDetails struct {
TargetId string `json:"targetId"`
TargetMimeType string `json:"targetMimeType"`
} `json:"shortcutDetails"`
}
func fileToObj(f File) *model.ObjThumb {
log.Debugf("google file: %+v", f)
size, _ := strconv.ParseInt(f.Size, 10, 64)
obj := &model.ObjThumb{
Object: model.Object{
ID: f.Id,
Name: f.Name,
Size: size,
Modified: f.ModifiedTime,
IsFolder: f.MimeType == "application/vnd.google-apps.folder",
},
Thumbnail: model.Thumbnail{},
}
if f.MimeType == "application/vnd.google-apps.shortcut" {
obj.ID = f.ShortcutDetails.TargetId
obj.IsFolder = f.ShortcutDetails.TargetMimeType == "application/vnd.google-apps.folder"
}
return obj
}
type Error struct {
Error struct {
Errors []struct {
Domain string `json:"domain"`
Reason string `json:"reason"`
Message string `json:"message"`
LocationType string `json:"location_type"`
Location string `json:"location"`
}
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}

View File

@@ -0,0 +1,152 @@
package google_drive
import (
"context"
"fmt"
"io"
"net/http"
"strconv"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/drivers/base"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"go.uber.org/zap"
)
// do others that not defined in Driver interface
func (d *GoogleDrive) getRefreshToken() error {
url := "https://www.googleapis.com/oauth2/v4/token"
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"client_id": d.ClientID,
"client_secret": d.ClientSecret,
"code": d.Code,
"grant_type": "authorization_code",
"redirect_uri": "https://cloudoauth.files.casaos.app",
}).Post(url)
if err != nil {
return err
}
logger.Info("get refresh token", zap.String("res", res.String()))
if e.Error != "" {
return fmt.Errorf(e.Error)
}
d.RefreshToken = resp.RefreshToken
return nil
}
func (d *GoogleDrive) refreshToken() error {
url := "https://www.googleapis.com/oauth2/v4/token"
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"client_id": d.ClientID,
"client_secret": d.ClientSecret,
"refresh_token": d.RefreshToken,
"grant_type": "refresh_token",
}).Post(url)
if err != nil {
return err
}
log.Debug(res.String())
if e.Error != "" {
return fmt.Errorf(e.Error)
}
d.AccessToken = resp.AccessToken
return nil
}
func (d *GoogleDrive) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
req.SetQueryParam("includeItemsFromAllDrives", "true")
req.SetQueryParam("supportsAllDrives", "true")
if callback != nil {
callback(req)
}
if resp != nil {
req.SetResult(resp)
}
var e Error
req.SetError(&e)
res, err := req.Execute(method, url)
if err != nil {
return nil, err
}
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = d.refreshToken()
if err != nil {
return nil, err
}
return d.request(url, method, callback, resp)
}
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
return res.Body(), nil
}
func (d *GoogleDrive) getFiles(id string) ([]File, error) {
pageToken := "first"
res := make([]File, 0)
for pageToken != "" {
if pageToken == "first" {
pageToken = ""
}
var resp Files
orderBy := "folder,name,modifiedTime desc"
if d.OrderBy != "" {
orderBy = d.OrderBy + " " + d.OrderDirection
}
query := map[string]string{
"orderBy": orderBy,
"fields": "files(id,name,mimeType,size,modifiedTime,thumbnailLink,shortcutDetails),nextPageToken",
"pageSize": "1000",
"q": fmt.Sprintf("'%s' in parents and trashed = false", id),
//"includeItemsFromAllDrives": "true",
//"supportsAllDrives": "true",
"pageToken": pageToken,
}
_, err := d.request("https://www.googleapis.com/drive/v3/files", http.MethodGet, func(req *resty.Request) {
req.SetQueryParams(query)
}, &resp)
if err != nil {
return nil, err
}
pageToken = resp.NextPageToken
res = append(res, resp.Files...)
}
return res, nil
}
func (d *GoogleDrive) chunkUpload(ctx context.Context, stream model.FileStreamer, url string) error {
var defaultChunkSize = d.ChunkSize * 1024 * 1024
var finish int64 = 0
for finish < stream.GetSize() {
if utils.IsCanceled(ctx) {
return ctx.Err()
}
chunkSize := stream.GetSize() - finish
if chunkSize > defaultChunkSize {
chunkSize = defaultChunkSize
}
_, err := d.request(url, http.MethodPut, func(req *resty.Request) {
req.SetHeaders(map[string]string{
"Content-Length": strconv.FormatInt(chunkSize, 10),
"Content-Range": fmt.Sprintf("bytes %d-%d/%d", finish, finish+chunkSize-1, stream.GetSize()),
}).SetBody(io.LimitReader(stream.GetReadCloser(), chunkSize)).SetContext(ctx)
}, nil)
if err != nil {
return err
}
finish += chunkSize
}
return nil
}

108
go.mod
View File

@@ -1,36 +1,130 @@
module github.com/IceWhaleTech/CasaOS
go 1.16
go 1.19
require (
github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d
github.com/IceWhaleTech/CasaOS-Common v0.4.1-alpha2
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/IceWhaleTech/CasaOS-Common v0.4.2-alpha3
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/deckarep/golang-set/v2 v2.1.0
github.com/deepmap/oapi-codegen v1.12.4
github.com/disintegration/imaging v1.6.2
github.com/dsoprea/go-exif/v3 v3.0.0-20221012082141-d21ac8e2de85
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd
github.com/getkin/kin-openapi v0.113.0
github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.8.2
github.com/glebarez/sqlite v1.6.0
github.com/go-ini/ini v1.67.0
github.com/go-resty/resty/v2 v2.7.0
github.com/golang/mock v1.6.0
github.com/golang/snappy v0.0.4 // indirect
github.com/gomodule/redigo v1.8.9
github.com/google/go-github/v36 v36.0.0
github.com/googollee/go-socket.io v1.6.2
github.com/gorilla/websocket v1.5.0
github.com/h2non/filetype v1.1.3
github.com/hirochachacha/go-smb2 v1.1.0
github.com/klauspost/compress v1.15.13 // indirect
github.com/json-iterator/go v1.1.12
github.com/labstack/echo/v4 v4.10.0
github.com/maruel/natural v1.1.0
github.com/mholt/archiver/v3 v3.5.1
github.com/moby/sys/mount v0.3.3
github.com/moby/sys/mountinfo v0.6.2
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
github.com/robfig/cron v1.2.0
github.com/satori/go.uuid v1.2.0
github.com/shirou/gopsutil/v3 v3.22.11
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1
github.com/tidwall/gjson v1.14.4
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.4.0
golang.org/x/crypto v0.5.0
golang.org/x/oauth2 v0.3.0
gorm.io/gorm v1.24.2
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gorm.io/gorm v1.24.3
gotest.tools v2.2.0+incompatible
)
require (
github.com/andybalholm/brotli v1.0.1 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
github.com/geoffgarside/ber v1.1.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.20.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/gofrs/uuid v4.0.0+incompatible // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/invopop/yaml v0.1.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.15.13 // indirect
github.com/klauspost/pgzip v1.2.5 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/perimeterx/marshmallow v1.1.4 // indirect
github.com/pierrec/lz4/v4 v4.1.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tklauser/go-sysconf v0.3.11 // indirect
github.com/tklauser/numcpus v0.6.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/ulikunitz/xz v0.5.9 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/time v0.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.21.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.4.0 // indirect
modernc.org/sqlite v1.20.0 // indirect
)

124
go.sum
View File

@@ -1,15 +1,18 @@
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d h1:62lEBImTxZ83pgzywgDNIrPPuQ+j4ep9QjqrWBn1hrU=
github.com/Curtis-Milo/nat-type-identifier-go v0.0.0-20220215191915-18d42168c63d/go.mod h1:lW9x+yEjqKdPbE3+cf2fGPJXCw/hChX3Omi9QHTLFsQ=
github.com/IceWhaleTech/CasaOS-Common v0.4.1-alpha2 h1:O6vDvIy+wFBj6DuUN4ZDLa6M7Cg80SXXpMsM9t2EqpA=
github.com/IceWhaleTech/CasaOS-Common v0.4.1-alpha2/go.mod h1:xcemiRsXcs1zrmQxYMyExDjZ7UHYwkJqYE71IDIV0xA=
github.com/IceWhaleTech/CasaOS-Common v0.4.2-alpha3 h1:WJUYo+hJpLmza7mQngoJVeUJOfnrZevNrX5wzTuOJo0=
github.com/IceWhaleTech/CasaOS-Common v0.4.2-alpha3/go.mod h1:xcemiRsXcs1zrmQxYMyExDjZ7UHYwkJqYE71IDIV0xA=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a/go.mod h1:sSBbaOg90XwWKtpT56kVujF0bIeVITnPlssLclogS04=
github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -21,6 +24,10 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI=
github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s=
github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
@@ -37,7 +44,6 @@ github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU=
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LVjRU0RNUuMDqkPTxcALio0LWPFPXxxFCvVGVAwEpFc=
@@ -47,6 +53,8 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4G
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
github.com/getkin/kin-openapi v0.113.0 h1:t9aNS/q5Agr7a55Jp1AuZ3sR2WzHESv3Dd2ys4UphsM=
github.com/getkin/kin-openapi v0.113.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -67,6 +75,11 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
@@ -76,6 +89,10 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@@ -83,6 +100,8 @@ github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
@@ -105,7 +124,6 @@ github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs0
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v36 v36.0.0 h1:ndCzM616/oijwufI7nBRa+5eZHLldT+4yIB68ib5ogs=
@@ -118,12 +136,18 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googollee/go-socket.io v1.6.2 h1:olKLLHJtHz1IkL/OrTyNriZZvVQYEORNkJAqsOwPask=
github.com/googollee/go-socket.io v1.6.2/go.mod h1:0vGP8/dXR9SZUMMD4+xxaGo/lohOw3YWMh2WRiWeKxg=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI=
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -131,8 +155,11 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
@@ -149,23 +176,43 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA=
github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs=
github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0=
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
@@ -173,10 +220,11 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw=
github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -196,7 +244,7 @@ github.com/shirou/gopsutil/v3 v3.22.11 h1:kxsPKS+Eeo+VnEQ2XCaGJepeP6KY53QoRTETx3
github.com/shirou/gopsutil/v3 v3.22.11/go.mod h1:xl0EeL4vXJ+hQMAGN8B9VFpxukEMA0XdevQOe5MZ1oY=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -218,27 +266,28 @@ github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+Kd
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -246,17 +295,13 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -268,20 +313,18 @@ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -289,7 +332,6 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -297,36 +339,33 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -341,6 +380,7 @@ google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -351,21 +391,21 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.24.2 h1:9wR6CFD+G8nOusLdvkZelOEhpJVwwHzpQOUM+REd6U0=
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.3 h1:WL2ifUmzR/SLp85CSURAfybcHnGZ+yLSGSxgYXlFBHg=
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20=

43
internal/conf/config.go Normal file
View File

@@ -0,0 +1,43 @@
package conf
type Database struct {
Type string `json:"type" env:"DB_TYPE"`
Host string `json:"host" env:"DB_HOST"`
Port int `json:"port" env:"DB_PORT"`
User string `json:"user" env:"DB_USER"`
Password string `json:"password" env:"DB_PASS"`
Name string `json:"name" env:"DB_NAME"`
DBFile string `json:"db_file" env:"DB_FILE"`
TablePrefix string `json:"table_prefix" env:"DB_TABLE_PREFIX"`
SSLMode string `json:"ssl_mode" env:"DB_SSL_MODE"`
}
type Scheme struct {
Https bool `json:"https" env:"HTTPS"`
CertFile string `json:"cert_file" env:"CERT_FILE"`
KeyFile string `json:"key_file" env:"KEY_FILE"`
}
type LogConfig struct {
Enable bool `json:"enable" env:"LOG_ENABLE"`
Name string `json:"name" env:"LOG_NAME"`
MaxSize int `json:"max_size" env:"MAX_SIZE"`
MaxBackups int `json:"max_backups" env:"MAX_BACKUPS"`
MaxAge int `json:"max_age" env:"MAX_AGE"`
Compress bool `json:"compress" env:"COMPRESS"`
}
type Config struct {
Force bool `json:"force" env:"FORCE"`
Address string `json:"address" env:"ADDR"`
Port int `json:"port" env:"PORT"`
SiteURL string `json:"site_url" env:"SITE_URL"`
Cdn string `json:"cdn" env:"CDN"`
JwtSecret string `json:"jwt_secret" env:"JWT_SECRET"`
TokenExpiresIn int `json:"token_expires_in" env:"TOKEN_EXPIRES_IN"`
Database Database `json:"database"`
Scheme Scheme `json:"scheme"`
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
BleveDir string `json:"bleve_dir" env:"BLEVE_DIR"`
Log LogConfig `json:"log"`
}

72
internal/conf/const.go Normal file
View File

@@ -0,0 +1,72 @@
package conf
const (
TypeString = "string"
TypeSelect = "select"
TypeBool = "bool"
TypeText = "text"
TypeNumber = "number"
)
const (
// site
VERSION = "version"
ApiUrl = "api_url"
BasePath = "base_path"
SiteTitle = "site_title"
Announcement = "announcement"
AllowIndexed = "allow_indexed"
Logo = "logo"
Favicon = "favicon"
MainColor = "main_color"
// preview
TextTypes = "text_types"
AudioTypes = "audio_types"
VideoTypes = "video_types"
ImageTypes = "image_types"
ProxyTypes = "proxy_types"
ProxyIgnoreHeaders = "proxy_ignore_headers"
AudioAutoplay = "audio_autoplay"
VideoAutoplay = "video_autoplay"
// global
HideFiles = "hide_files"
CustomizeHead = "customize_head"
CustomizeBody = "customize_body"
LinkExpiration = "link_expiration"
SignAll = "sign_all"
PrivacyRegs = "privacy_regs"
OcrApi = "ocr_api"
FilenameCharMapping = "filename_char_mapping"
// index
SearchIndex = "search_index"
AutoUpdateIndex = "auto_update_index"
IndexPaths = "index_paths"
IgnorePaths = "ignore_paths"
// aria2
Aria2Uri = "aria2_uri"
Aria2Secret = "aria2_secret"
// single
Token = "token"
IndexProgress = "index_progress"
//Github
GithubClientId = "github_client_id"
GithubClientSecrets = "github_client_secrets"
GithubLoginEnabled = "github_login_enabled"
)
const (
UNKNOWN = iota
FOLDER
//OFFICE
VIDEO
AUDIO
TEXT
IMAGE
)

30
internal/conf/var.go Normal file
View File

@@ -0,0 +1,30 @@
package conf
import "regexp"
var (
BuiltAt string
GoVersion string
GitAuthor string
GitCommit string
Version string = "dev"
WebVersion string
)
var (
Conf *Config
)
var SlicesMap = make(map[string][]string)
var FilenameCharMap = make(map[string]string)
var PrivacyReg []*regexp.Regexp
var (
// StoragesLoaded loaded success if empty
StoragesLoaded = false
)
var (
RawIndexHtml string
ManageHtml string
IndexHtml string
)

25
internal/driver/config.go Normal file
View File

@@ -0,0 +1,25 @@
/*
* @Author: a624669980@163.com a624669980@163.com
* @Date: 2022-12-13 11:05:05
* @LastEditors: a624669980@163.com a624669980@163.com
* @LastEditTime: 2022-12-13 11:05:13
* @FilePath: /drive/internal/driver/config.go
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
package driver
type Config struct {
Name string `json:"name"`
LocalSort bool `json:"local_sort"`
OnlyLocal bool `json:"only_local"`
OnlyProxy bool `json:"only_proxy"`
NoCache bool `json:"no_cache"`
NoUpload bool `json:"no_upload"`
NeedMs bool `json:"need_ms"` // if need get message from user, such as validate code
DefaultRoot string `json:"default_root"`
CheckStatus bool
}
func (c Config) MustProxy() bool {
return c.OnlyProxy || c.OnlyLocal
}

131
internal/driver/driver.go Normal file
View File

@@ -0,0 +1,131 @@
package driver
import (
"context"
"github.com/IceWhaleTech/CasaOS/model"
)
type Driver interface {
Meta
Reader
User
//Writer
//Other
}
type Meta interface {
Config() Config
// GetStorage just get raw storage, no need to implement, because model.Storage have implemented
GetStorage() *model.Storage
SetStorage(model.Storage)
// GetAddition Additional is used for unmarshal of JSON, so need return pointer
GetAddition() Additional
// Init If already initialized, drop first
Init(ctx context.Context) error
Drop(ctx context.Context) error
}
type Other interface {
Other(ctx context.Context, args model.OtherArgs) (interface{}, error)
}
type Reader interface {
// List files in the path
// if identify files by path, need to set ID with path,like path.Join(dir.GetID(), obj.GetName())
// if identify files by id, need to set ID with corresponding id
List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error)
// Link get url/filepath/reader of file
Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error)
}
type User interface {
// GetRoot get root directory of user
GetUserInfo(ctx context.Context) (string, error)
}
type Getter interface {
GetRoot(ctx context.Context) (model.Obj, error)
}
//type Writer interface {
// Mkdir
// Move
// Rename
// Copy
// Remove
// Put
//}
type Mkdir interface {
MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error
}
type Move interface {
Move(ctx context.Context, srcObj, dstDir model.Obj) error
}
type Rename interface {
Rename(ctx context.Context, srcObj model.Obj, newName string) error
}
type Copy interface {
Copy(ctx context.Context, srcObj, dstDir model.Obj) error
}
type Remove interface {
Remove(ctx context.Context, obj model.Obj) error
}
type Put interface {
Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up UpdateProgress) error
}
//type WriteResult interface {
// MkdirResult
// MoveResult
// RenameResult
// CopyResult
// PutResult
// Remove
//}
type MkdirResult interface {
MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error)
}
type MoveResult interface {
Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error)
}
type RenameResult interface {
Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error)
}
type CopyResult interface {
Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error)
}
type PutResult interface {
Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up UpdateProgress) (model.Obj, error)
}
type UpdateProgress func(percentage int)
type Progress struct {
Total int64
Done int64
up UpdateProgress
}
func (p *Progress) Write(b []byte) (n int, err error) {
n = len(b)
p.Done += int64(n)
p.up(int(float64(p.Done) / float64(p.Total) * 100))
return
}
func NewProgress(total int64, up UpdateProgress) *Progress {
return &Progress{
Total: total,
up: up,
}
}

56
internal/driver/item.go Normal file
View File

@@ -0,0 +1,56 @@
/*
* @Author: a624669980@163.com a624669980@163.com
* @Date: 2022-12-13 11:05:47
* @LastEditors: a624669980@163.com a624669980@163.com
* @LastEditTime: 2022-12-13 11:05:54
* @FilePath: /drive/internal/driver/item.go
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
*/
package driver
type Additional interface{}
type Select string
type Item struct {
Name string `json:"name"`
Type string `json:"type"`
Default string `json:"default"`
Options string `json:"options"`
Required bool `json:"required"`
Help string `json:"help"`
}
type Info struct {
Common []Item `json:"common"`
Additional []Item `json:"additional"`
Config Config `json:"config"`
}
type IRootPath interface {
GetRootPath() string
}
type IRootId interface {
GetRootId() string
}
type RootPath struct {
RootFolderPath string `json:"root_folder_path"`
}
type RootID struct {
RootFolderID string `json:"root_folder_id" omit:"true"`
}
func (r RootPath) GetRootPath() string {
return r.RootFolderPath
}
func (r *RootPath) SetRootPath(path string) {
r.RootFolderPath = path
}
func (r RootID) GetRootId() string {
return r.RootFolderID
}

6
internal/op/const.go Normal file
View File

@@ -0,0 +1,6 @@
package op
const (
WORK = "work"
RootName = "root"
)

173
internal/op/driver.go Normal file
View File

@@ -0,0 +1,173 @@
package op
import (
"reflect"
"strings"
"github.com/IceWhaleTech/CasaOS/internal/conf"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/pkg/errors"
)
type New func() driver.Driver
var driverNewMap = map[string]New{}
var driverInfoMap = map[string][]driver.Item{} //driver.Info{}
func RegisterDriver(driver New) {
// log.Infof("register driver: [%s]", config.Name)
tempDriver := driver()
tempConfig := tempDriver.Config()
registerDriverItems(tempConfig, tempDriver.GetAddition())
driverNewMap[tempConfig.Name] = driver
}
func GetDriverNew(name string) (New, error) {
n, ok := driverNewMap[name]
if !ok {
return nil, errors.Errorf("no driver named: %s", name)
}
return n, nil
}
func GetDriverNames() []string {
var driverNames []string
for k := range driverInfoMap {
driverNames = append(driverNames, k)
}
return driverNames
}
// func GetDriverInfoMap() map[string]driver.Info {
// return driverInfoMap
// }
func GetDriverInfoMap() map[string][]driver.Item {
return driverInfoMap
}
func registerDriverItems(config driver.Config, addition driver.Additional) {
// log.Debugf("addition of %s: %+v", config.Name, addition)
tAddition := reflect.TypeOf(addition)
for tAddition.Kind() == reflect.Pointer {
tAddition = tAddition.Elem()
}
//mainItems := getMainItems(config)
additionalItems := getAdditionalItems(tAddition, config.DefaultRoot)
driverInfoMap[config.Name] = additionalItems
// driver.Info{
// Common: mainItems,
// Additional: additionalItems,
// Config: config,
// }
}
func getMainItems(config driver.Config) []driver.Item {
items := []driver.Item{{
Name: "mount_path",
Type: conf.TypeString,
Required: true,
Help: "",
}, {
Name: "order",
Type: conf.TypeNumber,
Help: "use to sort",
}, {
Name: "remark",
Type: conf.TypeText,
}}
if !config.NoCache {
items = append(items, driver.Item{
Name: "cache_expiration",
Type: conf.TypeNumber,
Default: "30",
Required: true,
Help: "The cache expiration time for this storage",
})
}
if !config.OnlyProxy && !config.OnlyLocal {
items = append(items, []driver.Item{{
Name: "web_proxy",
Type: conf.TypeBool,
}, {
Name: "webdav_policy",
Type: conf.TypeSelect,
Options: "302_redirect,use_proxy_url,native_proxy",
Default: "302_redirect",
Required: true,
},
}...)
} else {
items = append(items, driver.Item{
Name: "webdav_policy",
Type: conf.TypeSelect,
Default: "native_proxy",
Options: "use_proxy_url,native_proxy",
Required: true,
})
}
items = append(items, driver.Item{
Name: "down_proxy_url",
Type: conf.TypeText,
})
if config.LocalSort {
items = append(items, []driver.Item{{
Name: "order_by",
Type: conf.TypeSelect,
Options: "name,size,modified",
}, {
Name: "order_direction",
Type: conf.TypeSelect,
Options: "asc,desc",
}}...)
}
items = append(items, driver.Item{
Name: "extract_folder",
Type: conf.TypeSelect,
Options: "front,back",
})
return items
}
func getAdditionalItems(t reflect.Type, defaultRoot string) []driver.Item {
var items []driver.Item
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
if field.Type.Kind() == reflect.Struct {
items = append(items, getAdditionalItems(field.Type, defaultRoot)...)
continue
}
tag := field.Tag
ignore, ok1 := tag.Lookup("ignore")
name, ok2 := tag.Lookup("json")
if (ok1 && ignore == "true") || !ok2 {
continue
}
if tag.Get("omit") == "true" {
continue
}
item := driver.Item{
Name: name,
Type: strings.ToLower(field.Type.Name()),
Default: tag.Get("default"),
Options: tag.Get("options"),
Required: tag.Get("required") == "true",
Help: tag.Get("help"),
}
if tag.Get("type") != "" {
item.Type = tag.Get("type")
}
if item.Name == "root_folder_id" || item.Name == "root_folder_path" {
if item.Default == "" {
item.Default = defaultRoot
}
item.Required = item.Default != ""
}
// set default type to string
if item.Type == "" {
item.Type = "string"
}
items = append(items, item)
}
return items
}

545
internal/op/fs.go Normal file
View File

@@ -0,0 +1,545 @@
package op
import (
"context"
"os"
stdpath "path"
"time"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/generic_sync"
"github.com/IceWhaleTech/CasaOS/pkg/singleflight"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"github.com/Xhofe/go-cache"
"github.com/pkg/errors"
pkgerr "github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
// In order to facilitate adding some other things before and after file op
var listCache = cache.NewMemCache(cache.WithShards[[]model.Obj](64))
var listG singleflight.Group[[]model.Obj]
func updateCacheObj(storage driver.Driver, path string, oldObj model.Obj, newObj model.Obj) {
key := Key(storage, path)
objs, ok := listCache.Get(key)
if ok {
for i, obj := range objs {
if obj.GetName() == oldObj.GetName() {
objs[i] = newObj
break
}
}
listCache.Set(key, objs, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
}
}
func delCacheObj(storage driver.Driver, path string, obj model.Obj) {
key := Key(storage, path)
objs, ok := listCache.Get(key)
if ok {
for i, oldObj := range objs {
if oldObj.GetName() == obj.GetName() {
objs = append(objs[:i], objs[i+1:]...)
break
}
}
listCache.Set(key, objs, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
}
}
var addSortDebounceMap generic_sync.MapOf[string, func(func())]
func addCacheObj(storage driver.Driver, path string, newObj model.Obj) {
key := Key(storage, path)
objs, ok := listCache.Get(key)
if ok {
for i, obj := range objs {
if obj.GetName() == newObj.GetName() {
objs[i] = newObj
return
}
}
// Simple separation of files and folders
if len(objs) > 0 && objs[len(objs)-1].IsDir() == newObj.IsDir() {
objs = append(objs, newObj)
} else {
objs = append([]model.Obj{newObj}, objs...)
}
if storage.Config().LocalSort {
debounce, _ := addSortDebounceMap.LoadOrStore(key, utils.NewDebounce(time.Minute))
log.Debug("addCacheObj: wait start sort")
debounce(func() {
log.Debug("addCacheObj: start sort")
model.SortFiles(objs, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
addSortDebounceMap.Delete(key)
})
}
listCache.Set(key, objs, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
}
}
func ClearCache(storage driver.Driver, path string) {
listCache.Del(Key(storage, path))
}
func Key(storage driver.Driver, path string) string {
return stdpath.Join(storage.GetStorage().MountPath, utils.FixAndCleanPath(path))
}
// List files in storage, not contains virtual file
func List(ctx context.Context, storage driver.Driver, path string, args model.ListArgs, refresh ...bool) ([]model.Obj, error) {
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
return nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
}
path = utils.FixAndCleanPath(path)
log.Debugf("op.List %s", path)
key := Key(storage, path)
if !utils.IsBool(refresh...) {
if files, ok := listCache.Get(key); ok {
log.Debugf("use cache when list %s", path)
return files, nil
}
}
dir, err := GetUnwrap(ctx, storage, path)
if err != nil {
return nil, errors.WithMessage(err, "failed get dir")
}
log.Debugf("list dir: %+v", dir)
if !dir.IsDir() {
return nil, errors.WithStack(errors.New("not a folder"))
}
objs, err, _ := listG.Do(key, func() ([]model.Obj, error) {
files, err := storage.List(ctx, dir, args)
if err != nil {
return nil, errors.Wrapf(err, "failed to list objs")
}
// set path
for _, f := range files {
if s, ok := f.(model.SetPath); ok && f.GetPath() == "" && dir.GetPath() != "" {
s.SetPath(stdpath.Join(dir.GetPath(), f.GetName()))
}
}
// warp obj name
model.WrapObjsName(files)
// call hooks
go func(reqPath string, files []model.Obj) {
for _, hook := range ObjsUpdateHooks {
hook(args.ReqPath, files)
}
}(args.ReqPath, files)
// sort objs
if storage.Config().LocalSort {
model.SortFiles(files, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
}
model.ExtractFolder(files, storage.GetStorage().ExtractFolder)
if !storage.Config().NoCache {
if len(files) > 0 {
log.Debugf("set cache: %s => %+v", key, files)
listCache.Set(key, files, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
} else {
log.Debugf("del cache: %s", key)
listCache.Del(key)
}
}
return files, nil
})
return objs, err
}
// Get object from list of files
func Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
path = utils.FixAndCleanPath(path)
log.Debugf("op.Get %s", path)
// is root folder
if utils.PathEqual(path, "/") {
var rootObj model.Obj
switch r := storage.GetAddition().(type) {
case driver.IRootId:
rootObj = &model.Object{
ID: r.GetRootId(),
Name: RootName,
Size: 0,
Modified: storage.GetStorage().Modified,
IsFolder: true,
Path: path,
}
case driver.IRootPath:
rootObj = &model.Object{
Path: r.GetRootPath(),
Name: RootName,
Size: 0,
Modified: storage.GetStorage().Modified,
IsFolder: true,
}
default:
if storage, ok := storage.(driver.Getter); ok {
obj, err := storage.GetRoot(ctx)
if err != nil {
return nil, errors.WithMessage(err, "failed get root obj")
}
rootObj = obj
}
}
if rootObj == nil {
return nil, errors.Errorf("please implement IRootPath or IRootId or Getter method")
}
return &model.ObjWrapName{
Name: RootName,
Obj: rootObj,
}, nil
}
// not root folder
dir, name := stdpath.Split(path)
files, err := List(ctx, storage, dir, model.ListArgs{})
if err != nil {
return nil, errors.WithMessage(err, "failed get parent list")
}
for _, f := range files {
// TODO maybe copy obj here
if f.GetName() == name {
return f, nil
}
}
log.Debugf("cant find obj with name: %s", name)
return nil, errors.WithStack(errors.New("object not found"))
}
func GetUnwrap(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
obj, err := Get(ctx, storage, path)
if err != nil {
return nil, err
}
return model.UnwrapObjs(obj), err
}
var linkCache = cache.NewMemCache(cache.WithShards[*model.Link](16))
var linkG singleflight.Group[*model.Link]
// Link get link, if is an url. should have an expiry time
func Link(ctx context.Context, storage driver.Driver, path string, args model.LinkArgs) (*model.Link, model.Obj, error) {
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
return nil, nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
}
file, err := GetUnwrap(ctx, storage, path)
if err != nil {
return nil, nil, errors.WithMessage(err, "failed to get file")
}
if file.IsDir() {
return nil, nil, errors.WithStack(errors.New("not a file"))
}
key := Key(storage, path) + ":" + args.IP
if link, ok := linkCache.Get(key); ok {
return link, file, nil
}
fn := func() (*model.Link, error) {
link, err := storage.Link(ctx, file, args)
if err != nil {
return nil, errors.Wrapf(err, "failed get link")
}
if link.Expiration != nil {
linkCache.Set(key, link, cache.WithEx[*model.Link](*link.Expiration))
}
return link, nil
}
link, err, _ := linkG.Do(key, fn)
return link, file, err
}
// Other api
func Other(ctx context.Context, storage driver.Driver, args model.FsOtherArgs) (interface{}, error) {
obj, err := GetUnwrap(ctx, storage, args.Path)
if err != nil {
return nil, errors.WithMessagef(err, "failed to get obj")
}
if o, ok := storage.(driver.Other); ok {
return o.Other(ctx, model.OtherArgs{
Obj: obj,
Method: args.Method,
Data: args.Data,
})
} else {
return nil, errors.New("not implement")
}
}
var mkdirG singleflight.Group[interface{}]
func MakeDir(ctx context.Context, storage driver.Driver, path string, lazyCache ...bool) error {
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
}
path = utils.FixAndCleanPath(path)
key := Key(storage, path)
_, err, _ := mkdirG.Do(key, func() (interface{}, error) {
// check if dir exists
f, err := GetUnwrap(ctx, storage, path)
if err != nil {
if errors.Is(pkgerr.Cause(err), errors.New("object not found")) {
parentPath, dirName := stdpath.Split(path)
err = MakeDir(ctx, storage, parentPath)
if err != nil {
return nil, errors.WithMessagef(err, "failed to make parent dir [%s]", parentPath)
}
parentDir, err := GetUnwrap(ctx, storage, parentPath)
// this should not happen
if err != nil {
return nil, errors.WithMessagef(err, "failed to get parent dir [%s]", parentPath)
}
switch s := storage.(type) {
case driver.MkdirResult:
var newObj model.Obj
newObj, err = s.MakeDir(ctx, parentDir, dirName)
if err == nil {
if newObj != nil {
addCacheObj(storage, parentPath, model.WrapObjName(newObj))
} else if !utils.IsBool(lazyCache...) {
ClearCache(storage, parentPath)
}
}
case driver.Mkdir:
err = s.MakeDir(ctx, parentDir, dirName)
if err == nil && !utils.IsBool(lazyCache...) {
ClearCache(storage, parentPath)
}
default:
return nil, errors.New("not implement")
}
return nil, errors.WithStack(err)
}
return nil, errors.WithMessage(err, "failed to check if dir exists")
}
// dir exists
if f.IsDir() {
return nil, nil
}
// dir to make is a file
return nil, errors.New("file exists")
})
return err
}
func Move(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string, lazyCache ...bool) error {
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
}
srcPath = utils.FixAndCleanPath(srcPath)
dstDirPath = utils.FixAndCleanPath(dstDirPath)
srcRawObj, err := Get(ctx, storage, srcPath)
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
srcObj := model.UnwrapObjs(srcRawObj)
dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
if err != nil {
return errors.WithMessage(err, "failed to get dst dir")
}
srcDirPath := stdpath.Dir(srcPath)
switch s := storage.(type) {
case driver.MoveResult:
var newObj model.Obj
newObj, err = s.Move(ctx, srcObj, dstDir)
if err == nil {
delCacheObj(storage, srcDirPath, srcRawObj)
if newObj != nil {
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
} else if !utils.IsBool(lazyCache...) {
ClearCache(storage, dstDirPath)
}
}
case driver.Move:
err = s.Move(ctx, srcObj, dstDir)
if err == nil {
delCacheObj(storage, srcDirPath, srcRawObj)
if !utils.IsBool(lazyCache...) {
ClearCache(storage, dstDirPath)
}
}
default:
return errors.New("not implement")
}
return errors.WithStack(err)
}
func Rename(ctx context.Context, storage driver.Driver, srcPath, dstName string, lazyCache ...bool) error {
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
}
srcPath = utils.FixAndCleanPath(srcPath)
srcRawObj, err := Get(ctx, storage, srcPath)
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
srcObj := model.UnwrapObjs(srcRawObj)
srcDirPath := stdpath.Dir(srcPath)
switch s := storage.(type) {
case driver.RenameResult:
var newObj model.Obj
newObj, err = s.Rename(ctx, srcObj, dstName)
if err == nil {
if newObj != nil {
updateCacheObj(storage, srcDirPath, srcRawObj, model.WrapObjName(newObj))
} else if !utils.IsBool(lazyCache...) {
ClearCache(storage, srcDirPath)
}
}
case driver.Rename:
err = s.Rename(ctx, srcObj, dstName)
if err == nil && !utils.IsBool(lazyCache...) {
ClearCache(storage, srcDirPath)
}
default:
return errors.New("not implement")
}
return errors.WithStack(err)
}
// Copy Just copy file[s] in a storage
func Copy(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string, lazyCache ...bool) error {
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
}
srcPath = utils.FixAndCleanPath(srcPath)
dstDirPath = utils.FixAndCleanPath(dstDirPath)
srcObj, err := GetUnwrap(ctx, storage, srcPath)
if err != nil {
return errors.WithMessage(err, "failed to get src object")
}
dstDir, err := GetUnwrap(ctx, storage, dstDirPath)
if err != nil {
return errors.WithMessage(err, "failed to get dst dir")
}
switch s := storage.(type) {
case driver.CopyResult:
var newObj model.Obj
newObj, err = s.Copy(ctx, srcObj, dstDir)
if err == nil {
if newObj != nil {
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
} else if !utils.IsBool(lazyCache...) {
ClearCache(storage, dstDirPath)
}
}
case driver.Copy:
err = s.Copy(ctx, srcObj, dstDir)
if err == nil && !utils.IsBool(lazyCache...) {
ClearCache(storage, dstDirPath)
}
default:
return errors.New("not implement")
}
return errors.WithStack(err)
}
func Remove(ctx context.Context, storage driver.Driver, path string) error {
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
}
path = utils.FixAndCleanPath(path)
rawObj, err := Get(ctx, storage, path)
if err != nil {
// if object not found, it's ok
if errors.Is(pkgerr.Cause(err), errors.New("object not found")) {
return nil
}
return errors.WithMessage(err, "failed to get object")
}
dirPath := stdpath.Dir(path)
switch s := storage.(type) {
case driver.Remove:
err = s.Remove(ctx, model.UnwrapObjs(rawObj))
if err == nil {
delCacheObj(storage, dirPath, rawObj)
}
default:
return errors.New("not implement")
}
return errors.WithStack(err)
}
func Put(ctx context.Context, storage driver.Driver, dstDirPath string, file *model.FileStream, up driver.UpdateProgress, lazyCache ...bool) error {
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
return errors.Errorf("storage not init: %s", storage.GetStorage().Status)
}
defer func() {
if f, ok := file.GetReadCloser().(*os.File); ok {
err := os.RemoveAll(f.Name())
if err != nil {
log.Errorf("failed to remove file [%s]", f.Name())
}
}
}()
defer func() {
if err := file.Close(); err != nil {
log.Errorf("failed to close file streamer, %v", err)
}
}()
// if file exist and size = 0, delete it
dstDirPath = utils.FixAndCleanPath(dstDirPath)
dstPath := stdpath.Join(dstDirPath, file.GetName())
fi, err := GetUnwrap(ctx, storage, dstPath)
if err == nil {
if fi.GetSize() == 0 {
err = Remove(ctx, storage, dstPath)
if err != nil {
return errors.WithMessagef(err, "failed remove file that exist and have size 0")
}
} else {
file.Old = fi
}
}
err = MakeDir(ctx, storage, dstDirPath)
if err != nil {
return errors.WithMessagef(err, "failed to make dir [%s]", dstDirPath)
}
parentDir, err := GetUnwrap(ctx, storage, dstDirPath)
// this should not happen
if err != nil {
return errors.WithMessagef(err, "failed to get dir [%s]", dstDirPath)
}
// if up is nil, set a default to prevent panic
if up == nil {
up = func(p int) {}
}
switch s := storage.(type) {
case driver.PutResult:
var newObj model.Obj
newObj, err = s.Put(ctx, parentDir, file, up)
if err == nil {
if newObj != nil {
addCacheObj(storage, dstDirPath, model.WrapObjName(newObj))
} else if !utils.IsBool(lazyCache...) {
ClearCache(storage, dstDirPath)
}
}
case driver.Put:
err = s.Put(ctx, parentDir, file, up)
if err == nil && !utils.IsBool(lazyCache...) {
ClearCache(storage, dstDirPath)
}
default:
return errors.New("not implement")
}
log.Debugf("put file [%s] done", file.GetName())
//if err == nil {
// //clear cache
// key := stdpath.Join(storage.GetStorage().MountPath, dstDirPath)
// listCache.Del(key)
//}
return errors.WithStack(err)
}

109
internal/op/hook.go Normal file
View File

@@ -0,0 +1,109 @@
package op
import (
"regexp"
"strings"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/internal/conf"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"go.uber.org/zap"
)
// Obj
type ObjsUpdateHook = func(parent string, objs []model.Obj)
var (
ObjsUpdateHooks = make([]ObjsUpdateHook, 0)
)
func RegisterObjsUpdateHook(hook ObjsUpdateHook) {
ObjsUpdateHooks = append(ObjsUpdateHooks, hook)
}
func HandleObjsUpdateHook(parent string, objs []model.Obj) {
for _, hook := range ObjsUpdateHooks {
hook(parent, objs)
}
}
// Setting
type SettingItemHook func(item *model.SettingItem) error
var settingItemHooks = map[string]SettingItemHook{
conf.VideoTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.VideoTypes] = strings.Split(item.Value, ",")
return nil
},
conf.AudioTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.AudioTypes] = strings.Split(item.Value, ",")
return nil
},
conf.ImageTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.ImageTypes] = strings.Split(item.Value, ",")
return nil
},
conf.TextTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.TextTypes] = strings.Split(item.Value, ",")
return nil
},
conf.ProxyTypes: func(item *model.SettingItem) error {
conf.SlicesMap[conf.ProxyTypes] = strings.Split(item.Value, ",")
return nil
},
conf.ProxyIgnoreHeaders: func(item *model.SettingItem) error {
conf.SlicesMap[conf.ProxyIgnoreHeaders] = strings.Split(item.Value, ",")
return nil
},
conf.PrivacyRegs: func(item *model.SettingItem) error {
regStrs := strings.Split(item.Value, "\n")
regs := make([]*regexp.Regexp, 0, len(regStrs))
for _, regStr := range regStrs {
reg, err := regexp.Compile(regStr)
if err != nil {
return errors.WithStack(err)
}
regs = append(regs, reg)
}
conf.PrivacyReg = regs
return nil
},
conf.FilenameCharMapping: func(item *model.SettingItem) error {
var json = jsoniter.ConfigCompatibleWithStandardLibrary
err := json.UnmarshalFromString(item.Value, &conf.FilenameCharMap)
if err != nil {
return err
}
logger.Info("filename char mapping", zap.Any("FilenameCharMap", conf.FilenameCharMap))
return nil
},
}
func RegisterSettingItemHook(key string, hook SettingItemHook) {
settingItemHooks[key] = hook
}
func HandleSettingItemHook(item *model.SettingItem) (hasHook bool, err error) {
if hook, ok := settingItemHooks[item.Key]; ok {
return true, hook(item)
}
return false, nil
}
// Storage
type StorageHook func(typ string, storage driver.Driver)
var storageHooks = make([]StorageHook, 0)
func CallStorageHooks(typ string, storage driver.Driver) {
for _, hook := range storageHooks {
hook(typ, storage)
}
}
func RegisterStorageHook(hook StorageHook) {
storageHooks = append(storageHooks, hook)
}

36
internal/sign/sign.go Normal file
View File

@@ -0,0 +1,36 @@
package sign
import (
"sync"
"time"
"github.com/IceWhaleTech/CasaOS/pkg/sign"
)
var once sync.Once
var instance sign.Sign
func Sign(data string) string {
return NotExpired(data)
}
func WithDuration(data string, d time.Duration) string {
once.Do(Instance)
return instance.Sign(data, time.Now().Add(d).Unix())
}
func NotExpired(data string) string {
once.Do(Instance)
return instance.Sign(data, 0)
}
func Verify(data string, sign string) error {
once.Do(Instance)
return instance.Verify(data, sign)
}
func Instance() {
instance = sign.NewHMACSign([]byte("token"))
}

104
main.go
View File

@@ -1,6 +1,10 @@
//go:generate bash -c "mkdir -p codegen && go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 -generate types,server,spec -package codegen api/casaos/openapi.yaml > codegen/casaos_api.go"
//go:generate bash -c "mkdir -p codegen/message_bus && go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 -generate types,client -package message_bus https://raw.githubusercontent.com/IceWhaleTech/CasaOS-MessageBus/main/api/message_bus/openapi.yaml > codegen/message_bus/api.go"
package main
import (
"context"
_ "embed"
"flag"
"fmt"
"net"
@@ -11,6 +15,11 @@ import (
"github.com/IceWhaleTech/CasaOS-Common/model"
"github.com/IceWhaleTech/CasaOS-Common/utils/constants"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
util_http "github.com/IceWhaleTech/CasaOS-Common/utils/http"
"github.com/IceWhaleTech/CasaOS/codegen/message_bus"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/IceWhaleTech/CasaOS/pkg/cache"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/sqlite"
@@ -18,11 +27,10 @@ import (
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/route"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/IceWhaleTech/CasaOS/types"
"github.com/coreos/go-systemd/daemon"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
_ "github.com/IceWhaleTech/CasaOS/drivers"
"github.com/robfig/cron"
"gorm.io/gorm"
)
@@ -35,6 +43,12 @@ var (
commit = "private build"
date = "private build"
//go:embed api/index.html
_docHTML string
//go:embed api/casaos/openapi.yaml
_docYAML string
configFlag = flag.String("c", "", "config address")
dbFlag = flag.String("db", "", "db path")
versionFlag = flag.Bool("v", false, "version")
@@ -43,7 +57,7 @@ var (
func init() {
flag.Parse()
if *versionFlag {
fmt.Println("v" + types.CURRENTVERSION)
fmt.Println("v" + common.VERSION)
return
}
@@ -60,12 +74,12 @@ func init() {
sqliteDB = sqlite.GetDb(*dbFlag)
// gredis.GetRedisConn(config.RedisInfo),
service.MyService = service.NewService(sqliteDB, config.CommonInfo.RuntimePath, route.SocketIo())
service.MyService = service.NewService(sqliteDB, config.CommonInfo.RuntimePath)
service.Cache = cache.Init()
service.GetCPUThermalZone()
service.MyService.Storages().InitStorages()
route.InitFunction()
}
@@ -84,27 +98,28 @@ func main() {
if *versionFlag {
return
}
// model.Setup()
// gredis.Setup()
r := route.InitRouter()
defer service.SocketServer.Close()
r.GET("/v1/socketio/*any", gin.WrapH(service.SocketServer))
r.POST("/v1/socketio/*any", gin.WrapH(service.SocketServer))
v1Router := route.InitV1Router()
v2Router := route.InitV2Router()
v2DocRouter := route.InitV2DocRouter(_docHTML, _docYAML)
v3file := route.InitFile()
v4dir := route.InitDir()
mux := &util_http.HandlerMultiplexer{
HandlerMap: map[string]http.Handler{
"v1": v1Router,
"v2": v2Router,
"doc": v2DocRouter,
"v3": v3file,
"v4": v4dir,
},
}
// service.SyncTask(sqliteDB)
cron2 := cron.New()
// every day execution
err := cron2.AddFunc("0/5 * * * * *", func() {
if service.ClientCount > 0 {
// route.SendNetINfoBySocket()
// route.SendCPUBySocket()
// route.SendMemBySocket()
// route.SendDiskBySocket()
// route.SendUSBBySocket()
route.SendAllHardwareStatusBySocket()
}
route.SendAllHardwareStatusBySocket()
})
if err != nil {
fmt.Println(err)
@@ -117,10 +132,26 @@ func main() {
if err != nil {
panic(err)
}
routers := []string{"sys", "port", "file", "folder", "batch", "image", "samba", "notify", "socketio"}
for _, v := range routers {
routers := []string{
"/v1/sys",
"/v1/port",
"/v1/file",
"/v1/folder",
"/v1/batch",
"/v1/image",
"/v1/samba",
"/v1/notify",
"/v1/driver",
"/v1/cloud",
"/v1/recover",
"/v1/other",
route.V2APIPath,
route.V2DocPath,
route.V3FilePath,
}
for _, apiPath := range routers {
err = service.MyService.Gateway().CreateRoute(&model.Route{
Path: "/v1/" + v,
Path: apiPath,
Target: "http://" + listener.Addr().String(),
})
@@ -129,6 +160,25 @@ func main() {
panic(err)
}
}
var events []message_bus.EventType
events = append(events, message_bus.EventType{Name: "casaos:system:utilization", SourceID: common.SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}})
events = append(events, message_bus.EventType{Name: "casaos:file:recover", SourceID: common.SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}})
events = append(events, message_bus.EventType{Name: "casaos:file:operate", SourceID: common.SERVICENAME, PropertyTypeList: []message_bus.PropertyType{}})
// register at message bus
for i := 0; i < 10; i++ {
response, err := service.MyService.MessageBus().RegisterEventTypesWithResponse(context.Background(), events)
if err != nil {
logger.Error("error when trying to register one or more event types - some event type will not be discoverable", zap.Error(err))
}
if response != nil && response.StatusCode() != http.StatusOK {
logger.Error("error when trying to register one or more event types - some event type will not be discoverable", zap.String("status", response.Status()), zap.String("body", string(response.Body)))
}
if response.StatusCode() == http.StatusOK {
break
}
time.Sleep(time.Second)
}
go func() {
time.Sleep(time.Second * 2)
// v0.3.6
@@ -162,9 +212,15 @@ func main() {
} else {
logger.Info("This process is not running as a systemd service.")
}
// http.HandleFunc("/v1/file/test", func(w http.ResponseWriter, r *http.Request) {
// //http.ServeFile(w, r, r.URL.Path[1:])
// http.ServeFile(w, r, "/DATA/test.img")
// })
// go http.ListenAndServe(":8081", nil)
s := &http.Server{
Handler: r,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second, // fix G112: Potential slowloris attack (see https://github.com/securego/gosec)
}

39
model/args.go Normal file
View File

@@ -0,0 +1,39 @@
package model
import (
"io"
"net/http"
"time"
)
type ListArgs struct {
ReqPath string
}
type LinkArgs struct {
IP string
Header http.Header
Type string
}
type Link struct {
URL string `json:"url"`
Header http.Header `json:"header"` // needed header
Data io.ReadCloser // return file reader directly
Status int // status maybe 200 or 206, etc
FilePath *string // local file, return the filepath
Expiration *time.Duration // url expiration time
Method string `json:"method"` // http method
}
type OtherArgs struct {
Obj Obj
Method string
Data interface{}
}
type FsOtherArgs struct {
Path string `json:"path" form:"path"`
Method string `json:"method" form:"method"`
Data interface{} `json:"data" form:"data"`
}

6
model/common.go Normal file
View File

@@ -0,0 +1,6 @@
package model
type PageResp struct {
Content interface{} `json:"content"`
Total int64 `json:"total"`
}

186
model/obj.go Normal file
View File

@@ -0,0 +1,186 @@
package model
import (
"io"
"regexp"
"sort"
"strings"
"time"
mapset "github.com/deckarep/golang-set/v2"
"github.com/maruel/natural"
)
type UnwrapObj interface {
Unwrap() Obj
}
type Obj interface {
GetSize() int64
GetName() string
ModTime() time.Time
IsDir() bool
// The internal information of the driver.
// If you want to use it, please understand what it means
GetID() string
GetPath() string
}
type FileStreamer interface {
io.ReadCloser
Obj
GetMimetype() string
SetReadCloser(io.ReadCloser)
NeedStore() bool
GetReadCloser() io.ReadCloser
GetOld() Obj
}
type URL interface {
URL() string
}
type Thumb interface {
Thumb() string
}
type SetPath interface {
SetPath(path string)
}
func SortFiles(objs []Obj, orderBy, orderDirection string) {
if orderBy == "" {
return
}
sort.Slice(objs, func(i, j int) bool {
switch orderBy {
case "name":
{
c := natural.Less(objs[i].GetName(), objs[j].GetName())
if orderDirection == "desc" {
return !c
}
return c
}
case "size":
{
if orderDirection == "desc" {
return objs[i].GetSize() >= objs[j].GetSize()
}
return objs[i].GetSize() <= objs[j].GetSize()
}
case "modified":
if orderDirection == "desc" {
return objs[i].ModTime().After(objs[j].ModTime())
}
return objs[i].ModTime().Before(objs[j].ModTime())
}
return false
})
}
func ExtractFolder(objs []Obj, extractFolder string) {
if extractFolder == "" {
return
}
front := extractFolder == "front"
sort.SliceStable(objs, func(i, j int) bool {
if objs[i].IsDir() || objs[j].IsDir() {
if !objs[i].IsDir() {
return !front
}
if !objs[j].IsDir() {
return front
}
}
return false
})
}
// Wrap
func WrapObjName(objs Obj) Obj {
return &ObjWrapName{Obj: objs}
}
func WrapObjsName(objs []Obj) {
for i := 0; i < len(objs); i++ {
objs[i] = &ObjWrapName{Obj: objs[i]}
}
}
func UnwrapObjs(obj Obj) Obj {
if unwrap, ok := obj.(UnwrapObj); ok {
obj = unwrap.Unwrap()
}
return obj
}
func GetThumb(obj Obj) (thumb string, ok bool) {
if obj, ok := obj.(Thumb); ok {
return obj.Thumb(), true
}
if unwrap, ok := obj.(UnwrapObj); ok {
return GetThumb(unwrap.Unwrap())
}
return thumb, false
}
func GetUrl(obj Obj) (url string, ok bool) {
if obj, ok := obj.(URL); ok {
return obj.URL(), true
}
if unwrap, ok := obj.(UnwrapObj); ok {
return GetUrl(unwrap.Unwrap())
}
return url, false
}
// Merge
func NewObjMerge() *ObjMerge {
return &ObjMerge{
set: mapset.NewSet[string](),
}
}
type ObjMerge struct {
regs []*regexp.Regexp
set mapset.Set[string]
}
func (om *ObjMerge) Merge(objs []Obj, objs_ ...Obj) []Obj {
newObjs := make([]Obj, 0, len(objs)+len(objs_))
newObjs = om.insertObjs(om.insertObjs(newObjs, objs...), objs_...)
return newObjs
}
func (om *ObjMerge) insertObjs(objs []Obj, objs_ ...Obj) []Obj {
for _, obj := range objs_ {
if om.clickObj(obj) {
objs = append(objs, obj)
}
}
return objs
}
func (om *ObjMerge) clickObj(obj Obj) bool {
for _, reg := range om.regs {
if reg.MatchString(obj.GetName()) {
return false
}
}
return om.set.Add(obj.GetName())
}
func (om *ObjMerge) InitHideReg(hides string) {
rs := strings.Split(hides, "\n")
om.regs = make([]*regexp.Regexp, 0, len(rs))
for _, r := range rs {
om.regs = append(om.regs, regexp.MustCompile(r))
}
}
func (om *ObjMerge) Reset() {
om.set.Clear()
}

90
model/object.go Normal file
View File

@@ -0,0 +1,90 @@
package model
import (
"time"
)
type ObjWrapName struct {
Name string
Obj
}
func (o *ObjWrapName) Unwrap() Obj {
return o.Obj
}
func (o *ObjWrapName) GetName() string {
if o.Name == "" {
o.Name = o.Obj.GetName()
}
return o.Name
}
type Object struct {
ID string
Path string
Name string
Size int64
Modified time.Time
IsFolder bool
}
func (o *Object) GetName() string {
return o.Name
}
func (o *Object) GetSize() int64 {
return o.Size
}
func (o *Object) ModTime() time.Time {
return o.Modified
}
func (o *Object) IsDir() bool {
return o.IsFolder
}
func (o *Object) GetID() string {
return o.ID
}
func (o *Object) GetPath() string {
return o.Path
}
func (o *Object) SetPath(id string) {
o.Path = id
}
type Thumbnail struct {
Thumbnail string
}
type Url struct {
Url string
}
func (w Url) URL() string {
return w.Url
}
func (t Thumbnail) Thumb() string {
return t.Thumbnail
}
type ObjThumb struct {
Object
Thumbnail
}
type ObjectURL struct {
Object
Url
}
type ObjThumbURL struct {
Object
Thumbnail
Url
}

23
model/req.go Normal file
View File

@@ -0,0 +1,23 @@
package model
type PageReq struct {
Index int `json:"page" form:"index"`
Size int `json:"size" form:"size"`
}
const MaxUint = ^uint(0)
const MinUint = 0
const MaxInt = int(MaxUint >> 1)
const MinInt = -MaxInt - 1
func (p *PageReq) Validate() {
if p.Index < 1 {
p.Index = 1
}
if p.Size < 1 {
p.Size = 100000
}
// if p.PerPage < 1 {
// p.PerPage = MaxInt
// }
}

View File

@@ -1,7 +1,9 @@
package model
type SearchFileInfo struct {
Path string `json:"path"`
Name string `json:"name"`
Type int `json:"type"`
type SearchEngine struct {
Name string `json:"name"`
Icon string `json:"icon"`
SearchUrl string `json:"search_url"`
RecoUrl string `json:"reco_url"`
Data []string `json:"data"`
}

33
model/setting.go Normal file
View File

@@ -0,0 +1,33 @@
package model
const (
SINGLE = iota
SITE
STYLE
PREVIEW
GLOBAL
ARIA2
INDEX
GITHUB
)
const (
PUBLIC = iota
PRIVATE
READONLY
DEPRECATED
)
type SettingItem struct {
Key string `json:"key" gorm:"primaryKey" binding:"required"` // unique key
Value string `json:"value"` // value
Help string `json:"help"` // help message
Type string `json:"type"` // string, number, bool, select
Options string `json:"options"` // values for select
Group int `json:"group"` // use to group setting in frontend
Flag int `json:"flag"` // 0 = public, 1 = private, 2 = readonly, 3 = deprecated, etc.
}
func (s SettingItem) IsDeprecated() bool {
return s.Flag == DEPRECATED
}

View File

@@ -1,69 +0,0 @@
package model
//
type SmartctlA struct {
Smartctl struct {
Version []int `json:"version"`
SvnRevision string `json:"svn_revision"`
PlatformInfo string `json:"platform_info"`
BuildInfo string `json:"build_info"`
Argv []string `json:"argv"`
ExitStatus int `json:"exit_status"`
} `json:"smartctl"`
Device struct {
Name string `json:"name"`
InfoName string `json:"info_name"`
Type string `json:"type"`
Protocol string `json:"protocol"`
} `json:"device"`
ModelName string `json:"model_name"`
SerialNumber string `json:"serial_number"`
FirmwareVersion string `json:"firmware_version"`
UserCapacity struct {
Blocks int `json:"blocks"`
Bytes int64 `json:"bytes"`
} `json:"user_capacity"`
SmartStatus struct {
Passed bool `json:"passed"`
} `json:"smart_status"`
AtaSmartData struct {
OfflineDataCollection struct {
Status struct {
Value int `json:"value"`
String string `json:"string"`
} `json:"status"`
CompletionSeconds int `json:"completion_seconds"`
} `json:"offline_data_collection"`
SelfTest struct {
Status struct {
Value int `json:"value"`
String string `json:"string"`
Passed bool `json:"passed"`
} `json:"status"`
PollingMinutes struct {
Short int `json:"short"`
Extended int `json:"extended"`
Conveyance int `json:"conveyance"`
} `json:"polling_minutes"`
} `json:"self_test"`
Capabilities struct {
Values []int `json:"values"`
ExecOfflineImmediateSupported bool `json:"exec_offline_immediate_supported"`
OfflineIsAbortedUponNewCmd bool `json:"offline_is_aborted_upon_new_cmd"`
OfflineSurfaceScanSupported bool `json:"offline_surface_scan_supported"`
SelfTestsSupported bool `json:"self_tests_supported"`
ConveyanceSelfTestSupported bool `json:"conveyance_self_test_supported"`
SelectiveSelfTestSupported bool `json:"selective_self_test_supported"`
AttributeAutosaveEnabled bool `json:"attribute_autosave_enabled"`
ErrorLoggingSupported bool `json:"error_logging_supported"`
GpLoggingSupported bool `json:"gp_logging_supported"`
} `json:"capabilities"`
} `json:"ata_smart_data"`
PowerOnTime struct {
Hours int `json:"hours"`
} `json:"power_on_time"`
PowerCycleCount int `json:"power_cycle_count"`
Temperature struct {
Current int `json:"current"`
} `json:"temperature"`
}

54
model/storage.go Normal file
View File

@@ -0,0 +1,54 @@
package model
import "time"
type Storage struct {
ID uint `json:"id" gorm:"primaryKey"` // unique key
MountPath string `json:"mount_path" gorm:"unique" binding:"required"` // must be standardized
Order int `json:"order"` // use to sort
Driver string `json:"driver"` // driver used
CacheExpiration int `json:"cache_expiration"` // cache expire time
Status string `json:"status"`
Addition string `json:"addition" gorm:"type:text"` // Additional information, defined in the corresponding driver
Remark string `json:"remark"`
Modified time.Time `json:"modified"`
Disabled bool `json:"disabled"` // if disabled
Sort
Proxy
}
type Sort struct {
OrderBy string `json:"order_by"`
OrderDirection string `json:"order_direction"`
ExtractFolder string `json:"extract_folder"`
}
type Proxy struct {
WebProxy bool `json:"web_proxy"`
WebdavPolicy string `json:"webdav_policy"`
DownProxyUrl string `json:"down_proxy_url"`
}
func (s *Storage) GetStorage() *Storage {
return s
}
func (s *Storage) SetStorage(storage Storage) {
*s = storage
}
func (s *Storage) SetStatus(status string) {
s.Status = status
}
func (p Proxy) Webdav302() bool {
return p.WebdavPolicy == "302_redirect"
}
func (p Proxy) WebdavProxy() bool {
return p.WebdavPolicy == "use_proxy_url"
}
func (p Proxy) WebdavNative() bool {
return !p.Webdav302() && !p.WebdavProxy()
}

33
model/stream.go Normal file
View File

@@ -0,0 +1,33 @@
package model
import (
"io"
)
type FileStream struct {
Obj
io.ReadCloser
Mimetype string
WebPutAsTask bool
Old Obj
}
func (f *FileStream) GetMimetype() string {
return f.Mimetype
}
func (f *FileStream) NeedStore() bool {
return f.WebPutAsTask
}
func (f *FileStream) GetReadCloser() io.ReadCloser {
return f.ReadCloser
}
func (f *FileStream) SetReadCloser(rc io.ReadCloser) {
f.ReadCloser = rc
}
func (f *FileStream) GetOld() Obj {
return f.Old
}

12
pkg/fs/fs.go Normal file
View File

@@ -0,0 +1,12 @@
package fs
import "io"
// CheckClose is a utility function used to check the return from
// Close in a defer statement.
func CheckClose(c io.Closer, err *error) {
cerr := c.Close()
if *err == nil {
*err = cerr
}
}

View File

@@ -0,0 +1,412 @@
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package generic_sync
import (
"sync"
"sync/atomic"
"unsafe"
)
// MapOf is like a Go map[interface{}]interface{} but is safe for concurrent use
// by multiple goroutines without additional locking or coordination.
// Loads, stores, and deletes run in amortized constant time.
//
// The MapOf type is specialized. Most code should use a plain Go map instead,
// with separate locking or coordination, for better type safety and to make it
// easier to maintain other invariants along with the map content.
//
// The MapOf type is optimized for two common use cases: (1) when the entry for a given
// key is only ever written once but read many times, as in caches that only grow,
// or (2) when multiple goroutines read, write, and overwrite entries for disjoint
// sets of keys. In these two cases, use of a MapOf may significantly reduce lock
// contention compared to a Go map paired with a separate Mutex or RWMutex.
//
// The zero MapOf is empty and ready for use. A MapOf must not be copied after first use.
type MapOf[K comparable, V any] struct {
mu sync.Mutex
// read contains the portion of the map's contents that are safe for
// concurrent access (with or without mu held).
//
// The read field itself is always safe to load, but must only be stored with
// mu held.
//
// Entries stored in read may be updated concurrently without mu, but updating
// a previously-expunged entry requires that the entry be copied to the dirty
// map and unexpunged with mu held.
read atomic.Value // readOnly
// dirty contains the portion of the map's contents that require mu to be
// held. To ensure that the dirty map can be promoted to the read map quickly,
// it also includes all of the non-expunged entries in the read map.
//
// Expunged entries are not stored in the dirty map. An expunged entry in the
// clean map must be unexpunged and added to the dirty map before a new value
// can be stored to it.
//
// If the dirty map is nil, the next write to the map will initialize it by
// making a shallow copy of the clean map, omitting stale entries.
dirty map[K]*entry[V]
// misses counts the number of loads since the read map was last updated that
// needed to lock mu to determine whether the key was present.
//
// Once enough misses have occurred to cover the cost of copying the dirty
// map, the dirty map will be promoted to the read map (in the unamended
// state) and the next store to the map will make a new dirty copy.
misses int
}
// readOnly is an immutable struct stored atomically in the MapOf.read field.
type readOnly[K comparable, V any] struct {
m map[K]*entry[V]
amended bool // true if the dirty map contains some key not in m.
}
// expunged is an arbitrary pointer that marks entries which have been deleted
// from the dirty map.
var expunged = unsafe.Pointer(new(interface{}))
// An entry is a slot in the map corresponding to a particular key.
type entry[V any] struct {
// p points to the interface{} value stored for the entry.
//
// If p == nil, the entry has been deleted and m.dirty == nil.
//
// If p == expunged, the entry has been deleted, m.dirty != nil, and the entry
// is missing from m.dirty.
//
// Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty
// != nil, in m.dirty[key].
//
// An entry can be deleted by atomic replacement with nil: when m.dirty is
// next created, it will atomically replace nil with expunged and leave
// m.dirty[key] unset.
//
// An entry's associated value can be updated by atomic replacement, provided
// p != expunged. If p == expunged, an entry's associated value can be updated
// only after first setting m.dirty[key] = e so that lookups using the dirty
// map find the entry.
p unsafe.Pointer // *interface{}
}
func newEntry[V any](i V) *entry[V] {
return &entry[V]{p: unsafe.Pointer(&i)}
}
// Load returns the value stored in the map for a key, or nil if no
// value is present.
// The ok result indicates whether value was found in the map.
func (m *MapOf[K, V]) Load(key K) (value V, ok bool) {
read, _ := m.read.Load().(readOnly[K, V])
e, ok := read.m[key]
if !ok && read.amended {
m.mu.Lock()
// Avoid reporting a spurious miss if m.dirty got promoted while we were
// blocked on m.mu. (If further loads of the same key will not miss, it's
// not worth copying the dirty map for this key.)
read, _ = m.read.Load().(readOnly[K, V])
e, ok = read.m[key]
if !ok && read.amended {
e, ok = m.dirty[key]
// Regardless of whether the entry was present, record a miss: this key
// will take the slow path until the dirty map is promoted to the read
// map.
m.missLocked()
}
m.mu.Unlock()
}
if !ok {
return value, false
}
return e.load()
}
func (m *MapOf[K, V]) Has(key K) bool {
_, ok := m.Load(key)
return ok
}
func (e *entry[V]) load() (value V, ok bool) {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return value, false
}
return *(*V)(p), true
}
// Store sets the value for a key.
func (m *MapOf[K, V]) Store(key K, value V) {
read, _ := m.read.Load().(readOnly[K, V])
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
m.mu.Lock()
read, _ = m.read.Load().(readOnly[K, V])
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {
// The entry was previously expunged, which implies that there is a
// non-nil dirty map and this entry is not in it.
m.dirty[key] = e
}
e.storeLocked(&value)
} else if e, ok := m.dirty[key]; ok {
e.storeLocked(&value)
} else {
if !read.amended {
// We're adding the first new key to the dirty map.
// Make sure it is allocated and mark the read-only map as incomplete.
m.dirtyLocked()
m.read.Store(readOnly[K, V]{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
}
m.mu.Unlock()
}
// tryStore stores a value if the entry has not been expunged.
//
// If the entry is expunged, tryStore returns false and leaves the entry
// unchanged.
func (e *entry[V]) tryStore(i *V) bool {
for {
p := atomic.LoadPointer(&e.p)
if p == expunged {
return false
}
if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) {
return true
}
}
}
// unexpungeLocked ensures that the entry is not marked as expunged.
//
// If the entry was previously expunged, it must be added to the dirty map
// before m.mu is unlocked.
func (e *entry[V]) unexpungeLocked() (wasExpunged bool) {
return atomic.CompareAndSwapPointer(&e.p, expunged, nil)
}
// storeLocked unconditionally stores a value to the entry.
//
// The entry must be known not to be expunged.
func (e *entry[V]) storeLocked(i *V) {
atomic.StorePointer(&e.p, unsafe.Pointer(i))
}
// LoadOrStore returns the existing value for the key if present.
// Otherwise, it stores and returns the given value.
// The loaded result is true if the value was loaded, false if stored.
func (m *MapOf[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
// Avoid locking if it's a clean hit.
read, _ := m.read.Load().(readOnly[K, V])
if e, ok := read.m[key]; ok {
actual, loaded, ok := e.tryLoadOrStore(value)
if ok {
return actual, loaded
}
}
m.mu.Lock()
read, _ = m.read.Load().(readOnly[K, V])
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {
m.dirty[key] = e
}
actual, loaded, _ = e.tryLoadOrStore(value)
} else if e, ok := m.dirty[key]; ok {
actual, loaded, _ = e.tryLoadOrStore(value)
m.missLocked()
} else {
if !read.amended {
// We're adding the first new key to the dirty map.
// Make sure it is allocated and mark the read-only map as incomplete.
m.dirtyLocked()
m.read.Store(readOnly[K, V]{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
actual, loaded = value, false
}
m.mu.Unlock()
return actual, loaded
}
// tryLoadOrStore atomically loads or stores a value if the entry is not
// expunged.
//
// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and
// returns with ok==false.
func (e *entry[V]) tryLoadOrStore(i V) (actual V, loaded, ok bool) {
p := atomic.LoadPointer(&e.p)
if p == expunged {
return actual, false, false
}
if p != nil {
return *(*V)(p), true, true
}
// Copy the interface after the first load to make this method more amenable
// to escape analysis: if we hit the "load" path or the entry is expunged, we
// shouldn'V bother heap-allocating.
ic := i
for {
if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) {
return i, false, true
}
p = atomic.LoadPointer(&e.p)
if p == expunged {
return actual, false, false
}
if p != nil {
return *(*V)(p), true, true
}
}
}
// Delete deletes the value for a key.
func (m *MapOf[K, V]) Delete(key K) {
read, _ := m.read.Load().(readOnly[K, V])
e, ok := read.m[key]
if !ok && read.amended {
m.mu.Lock()
read, _ = m.read.Load().(readOnly[K, V])
e, ok = read.m[key]
if !ok && read.amended {
delete(m.dirty, key)
}
m.mu.Unlock()
}
if ok {
e.delete()
}
}
func (e *entry[V]) delete() (hadValue bool) {
for {
p := atomic.LoadPointer(&e.p)
if p == nil || p == expunged {
return false
}
if atomic.CompareAndSwapPointer(&e.p, p, nil) {
return true
}
}
}
// Range calls f sequentially for each key and value present in the map.
// If f returns false, range stops the iteration.
//
// Range does not necessarily correspond to any consistent snapshot of the MapOf's
// contents: no key will be visited more than once, but if the value for any key
// is stored or deleted concurrently, Range may reflect any mapping for that key
// from any point during the Range call.
//
// Range may be O(N) with the number of elements in the map even if f returns
// false after a constant number of calls.
func (m *MapOf[K, V]) Range(f func(key K, value V) bool) {
// We need to be able to iterate over all of the keys that were already
// present at the start of the call to Range.
// If read.amended is false, then read.m satisfies that property without
// requiring us to hold m.mu for a long time.
read, _ := m.read.Load().(readOnly[K, V])
if read.amended {
// m.dirty contains keys not in read.m. Fortunately, Range is already O(N)
// (assuming the caller does not break out early), so a call to Range
// amortizes an entire copy of the map: we can promote the dirty copy
// immediately!
m.mu.Lock()
read, _ = m.read.Load().(readOnly[K, V])
if read.amended {
read = readOnly[K, V]{m: m.dirty}
m.read.Store(read)
m.dirty = nil
m.misses = 0
}
m.mu.Unlock()
}
for k, e := range read.m {
v, ok := e.load()
if !ok {
continue
}
if !f(k, v) {
break
}
}
}
// Values returns a slice of the values in the map.
func (m *MapOf[K, V]) Values() []V {
var values []V
m.Range(func(key K, value V) bool {
values = append(values, value)
return true
})
return values
}
func (m *MapOf[K, V]) Count() int {
return len(m.dirty)
}
func (m *MapOf[K, V]) Empty() bool {
return m.Count() == 0
}
func (m *MapOf[K, V]) ToMap() map[K]V {
ans := make(map[K]V)
m.Range(func(key K, value V) bool {
ans[key] = value
return true
})
return ans
}
func (m *MapOf[K, V]) Clear() {
m.Range(func(key K, value V) bool {
m.Delete(key)
return true
})
}
func (m *MapOf[K, V]) missLocked() {
m.misses++
if m.misses < len(m.dirty) {
return
}
m.read.Store(readOnly[K, V]{m: m.dirty})
m.dirty = nil
m.misses = 0
}
func (m *MapOf[K, V]) dirtyLocked() {
if m.dirty != nil {
return
}
read, _ := m.read.Load().(readOnly[K, V])
m.dirty = make(map[K]*entry[V], len(read.m))
for k, e := range read.m {
if !e.tryExpungeLocked() {
m.dirty[k] = e
}
}
}
func (e *entry[V]) tryExpungeLocked() (isExpunged bool) {
p := atomic.LoadPointer(&e.p)
for p == nil {
if atomic.CompareAndSwapPointer(&e.p, nil, expunged) {
return true
}
p = atomic.LoadPointer(&e.p)
}
return p == expunged
}

52
pkg/sign/hmac.go Normal file
View File

@@ -0,0 +1,52 @@
package sign
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"io"
"strconv"
"strings"
"time"
)
type HMACSign struct {
SecretKey []byte
}
func (s HMACSign) Sign(data string, expire int64) string {
h := hmac.New(sha256.New, s.SecretKey)
expireTimeStamp := strconv.FormatInt(expire, 10)
_, err := io.WriteString(h, data+":"+expireTimeStamp)
if err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(h.Sum(nil)) + ":" + expireTimeStamp
}
func (s HMACSign) Verify(data, sign string) error {
signSlice := strings.Split(sign, ":")
// check whether contains expire time
if signSlice[len(signSlice)-1] == "" {
return ErrExpireMissing
}
// check whether expire time is expired
expires, err := strconv.ParseInt(signSlice[len(signSlice)-1], 10, 64)
if err != nil {
return ErrExpireInvalid
}
// if expire time is expired, return error
if expires < time.Now().Unix() && expires != 0 {
return ErrSignExpired
}
// verify sign
if s.Sign(data, expires) != sign {
return ErrSignInvalid
}
return nil
}
func NewHMACSign(secret []byte) Sign {
return HMACSign{SecretKey: secret}
}

15
pkg/sign/sign.go Normal file
View File

@@ -0,0 +1,15 @@
package sign
import "errors"
type Sign interface {
Sign(data string, expire int64) string
Verify(data, sign string) error
}
var (
ErrSignExpired = errors.New("sign expired")
ErrSignInvalid = errors.New("sign invalid")
ErrExpireInvalid = errors.New("expire invalid")
ErrExpireMissing = errors.New("expire missing")
)

View File

@@ -0,0 +1,212 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package singleflight provides a duplicate function call suppression
// mechanism.
package singleflight
import (
"bytes"
"errors"
"fmt"
"runtime"
"runtime/debug"
"sync"
)
// errGoexit indicates the runtime.Goexit was called in
// the user given function.
var errGoexit = errors.New("runtime.Goexit was called")
// A panicError is an arbitrary value recovered from a panic
// with the stack trace during the execution of given function.
type panicError struct {
value any
stack []byte
}
// Error implements error interface.
func (p *panicError) Error() string {
return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
}
func newPanicError(v any) error {
stack := debug.Stack()
// The first line of the stack trace is of the form "goroutine N [status]:"
// but by the time the panic reaches Do the goroutine may no longer exist
// and its status will have changed. Trim out the misleading line.
if line := bytes.IndexByte(stack[:], '\n'); line >= 0 {
stack = stack[line+1:]
}
return &panicError{value: v, stack: stack}
}
// call is an in-flight or completed singleflight.Do call
type call[T any] struct {
wg sync.WaitGroup
// These fields are written once before the WaitGroup is done
// and are only read after the WaitGroup is done.
val T
err error
// forgotten indicates whether Forget was called with this call's key
// while the call was still in flight.
forgotten bool
// These fields are read and written with the singleflight
// mutex held before the WaitGroup is done, and are read but
// not written after the WaitGroup is done.
dups int
chans []chan<- Result[T]
}
// Group represents a class of work and forms a namespace in
// which units of work can be executed with duplicate suppression.
type Group[T any] struct {
mu sync.Mutex // protects m
m map[string]*call[T] // lazily initialized
}
// Result holds the results of Do, so they can be passed
// on a channel.
type Result[T any] struct {
Val T
Err error
Shared bool
}
// Do executes and returns the results of the given function, making
// sure that only one execution is in-flight for a given key at a
// time. If a duplicate comes in, the duplicate caller waits for the
// original to complete and receives the same results.
// The return value shared indicates whether v was given to multiple callers.
func (g *Group[T]) Do(key string, fn func() (T, error)) (v T, err error, shared bool) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call[T])
}
if c, ok := g.m[key]; ok {
c.dups++
g.mu.Unlock()
c.wg.Wait()
if e, ok := c.err.(*panicError); ok {
panic(e)
} else if c.err == errGoexit {
runtime.Goexit()
}
return c.val, c.err, true
}
c := new(call[T])
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
g.doCall(c, key, fn)
return c.val, c.err, c.dups > 0
}
// DoChan is like Do but returns a channel that will receive the
// results when they are ready.
//
// The returned channel will not be closed.
func (g *Group[T]) DoChan(key string, fn func() (T, error)) <-chan Result[T] {
ch := make(chan Result[T], 1)
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call[T])
}
if c, ok := g.m[key]; ok {
c.dups++
c.chans = append(c.chans, ch)
g.mu.Unlock()
return ch
}
c := &call[T]{chans: []chan<- Result[T]{ch}}
c.wg.Add(1)
g.m[key] = c
g.mu.Unlock()
go g.doCall(c, key, fn)
return ch
}
// doCall handles the single call for a key.
func (g *Group[T]) doCall(c *call[T], key string, fn func() (T, error)) {
normalReturn := false
recovered := false
// use double-defer to distinguish panic from runtime.Goexit,
// more details see https://golang.org/cl/134395
defer func() {
// the given function invoked runtime.Goexit
if !normalReturn && !recovered {
c.err = errGoexit
}
c.wg.Done()
g.mu.Lock()
defer g.mu.Unlock()
if !c.forgotten {
delete(g.m, key)
}
if e, ok := c.err.(*panicError); ok {
// In order to prevent the waiting channels from being blocked forever,
// needs to ensure that this panic cannot be recovered.
if len(c.chans) > 0 {
go panic(e)
select {} // Keep this goroutine around so that it will appear in the crash dump.
} else {
panic(e)
}
} else if c.err == errGoexit {
// Already in the process of goexit, no need to call again
} else {
// Normal return
for _, ch := range c.chans {
ch <- Result[T]{c.val, c.err, c.dups > 0}
}
}
}()
func() {
defer func() {
if !normalReturn {
// Ideally, we would wait to take a stack trace until we've determined
// whether this is a panic or a runtime.Goexit.
//
// Unfortunately, the only way we can distinguish the two is to see
// whether the recover stopped the goroutine from terminating, and by
// the time we know that, the part of the stack trace relevant to the
// panic has been discarded.
if r := recover(); r != nil {
c.err = newPanicError(r)
}
}
}()
c.val, c.err = fn()
normalReturn = true
}()
if !normalReturn {
recovered = true
}
}
// Forget tells the singleflight to forget about a key. Future calls
// to Do for this key will call the function rather than waiting for
// an earlier call to complete.
func (g *Group[T]) Forget(key string) {
g.mu.Lock()
if c, ok := g.m[key]; ok {
c.forgotten = true
}
delete(g.m, key)
g.mu.Unlock()
}

View File

@@ -14,6 +14,7 @@ import (
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
"github.com/glebarez/sqlite"
@@ -42,7 +43,7 @@ func GetDb(dbPath string) *gorm.DB {
}
gdb = db
err = db.AutoMigrate(&model2.AppNotify{}, model2.SharesDBModel{}, model2.ConnectionsDBModel{})
err = db.AutoMigrate(&model2.AppNotify{}, model2.SharesDBModel{}, model2.ConnectionsDBModel{}, model.Storage{})
db.Exec("DROP TABLE IF EXISTS o_application")
db.Exec("DROP TABLE IF EXISTS o_friend")
db.Exec("DROP TABLE IF EXISTS o_person_download")

18
pkg/utils/balance.go Normal file
View File

@@ -0,0 +1,18 @@
package utils
import "strings"
var balance = ".balance"
func IsBalance(str string) bool {
return strings.Contains(str, balance)
}
// GetActualMountPath remove balance suffix
func GetActualMountPath(virtualPath string) string {
bIndex := strings.LastIndex(virtualPath, ".balance")
if bIndex != -1 {
virtualPath = virtualPath[:bIndex]
}
return virtualPath
}

5
pkg/utils/bool.go Normal file
View File

@@ -0,0 +1,5 @@
package utils
func IsBool(bs ...bool) bool {
return len(bs) > 0 && bs[0]
}

View File

@@ -2,14 +2,12 @@ package command
import (
"bufio"
"context"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
func OnlyExec(cmdStr string) {
@@ -98,23 +96,6 @@ func ExecLSBLKByPath(path string) []byte {
return output
}
// exec smart
func ExecSmartCTLByPath(path string) []byte {
timeout := 3
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
output, err := exec.CommandContext(ctx, "smartctl", "-a", path, "-j").Output()
if err != nil {
fmt.Println("smartctl", err)
return nil
}
return output
}
func ExecEnabledSMART(path string) {
exec.Command("smartctl", "-s on", path).Output()
}
func ExecuteScripts(scriptDirectory string) {
if _, err := os.Stat(scriptDirectory); os.IsNotExist(err) {
fmt.Printf("No post-start scripts at %s\n", scriptDirectory)

View File

@@ -46,11 +46,12 @@ const (
ERROR_APP_NAME_EXIST = 50004
//file
FILE_DOES_NOT_EXIST = 60001
FILE_READ_ERROR = 60002
FILE_DELETE_ERROR = 60003
DIR_NOT_EXISTS = 60004
SOURCE_DES_SAME = 60005
FILE_DOES_NOT_EXIST = 60001
FILE_READ_ERROR = 60002
FILE_DELETE_ERROR = 60003
DIR_NOT_EXISTS = 60004
SOURCE_DES_SAME = 60005
MOUNTED_DIRECTIORIES = 60006
//share
SHARE_ALREADY_EXISTS = 70001
@@ -109,8 +110,9 @@ var MsgFlags = map[int]string{
DIR_NOT_EXISTS: "Directory does not exist",
FILE_READ_ERROR: "File read error",
FILE_DELETE_ERROR: "Delete error",
FILE_READ_ERROR: "File read error",
FILE_DELETE_ERROR: "Delete error",
MOUNTED_DIRECTIORIES: "The directory is mounted, please unmount it first.",
COMMAND_ERROR_INVALID_OPERATION: "invalid operation",
}

14
pkg/utils/ctx.go Normal file
View File

@@ -0,0 +1,14 @@
package utils
import (
"context"
)
func IsCanceled(ctx context.Context) bool {
select {
case <-ctx.Done():
return true
default:
return false
}
}

View File

@@ -577,3 +577,22 @@ func ReadLine(lineNumber int, path string) string {
defer file.Close()
return ""
}
func NameAccumulation(name string, dir string) string {
path := filepath.Join(dir, name)
if _, err := os.Stat(path); os.IsNotExist(err) {
return name
}
base := name
strings.Split(base, "_")
index := strings.LastIndex(base, "_")
if index < 0 {
index = len(base)
}
for i := 1; ; i++ {
newPath := filepath.Join(dir, fmt.Sprintf("%s_%d", base[:index], i))
if _, err := os.Stat(newPath); os.IsNotExist(err) {
return fmt.Sprintf("%s_%d", base[:index], i)
}
}
}

View File

@@ -0,0 +1,12 @@
package file
import (
"fmt"
"testing"
)
func TestNameAccumulation(t *testing.T) {
fmt.Println("aaa")
a := NameAccumulation("/mnt/test_1_1", "/")
fmt.Println(a)
}

167
pkg/utils/httper/drive.go Normal file
View File

@@ -0,0 +1,167 @@
package httper
import (
"encoding/json"
"fmt"
"net"
"net/http"
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/go-resty/resty/v2"
"go.uber.org/zap"
)
type MountList struct {
MountPoints []struct {
MountPoint string `json:"MountPoint"`
Fs string `json:"Fs"`
Icon string `json:"Icon"`
Name string `json:"Name"`
} `json:"mountPoints"`
}
type MountPoint struct {
MountPoint string `json:"mount_point"`
Fs string `json:"fs"`
Icon string `json:"icon"`
Name string `json:"name"`
}
type MountResult struct {
Error string `json:"error"`
Input struct {
Fs string `json:"fs"`
MountPoint string `json:"mountPoint"`
} `json:"input"`
Path string `json:"path"`
Status int `json:"status"`
}
type RemotesResult struct {
Remotes []string `json:"remotes"`
}
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
var DefaultTimeout = time.Second * 30
func NewRestyClient() *resty.Client {
unixSocket := "/var/run/rclone/rclone.sock"
transport := http.Transport{
Dial: func(_, _ string) (net.Conn, error) {
return net.Dial("unix", unixSocket)
},
}
client := resty.New()
client.SetTransport(&transport).SetBaseURL("http://localhost")
client.SetRetryCount(3).SetRetryWaitTime(5*time.Second).SetTimeout(DefaultTimeout).SetHeader("User-Agent", UserAgent)
return client
}
func GetMountList() (MountList, error) {
var result MountList
res, err := NewRestyClient().R().Post("/mount/listmounts")
if err != nil {
return result, err
}
if res.StatusCode() != 200 {
return result, fmt.Errorf("get mount list failed")
}
json.Unmarshal(res.Body(), &result)
for i := 0; i < len(result.MountPoints); i++ {
result.MountPoints[i].Fs = result.MountPoints[i].Fs[:len(result.MountPoints[i].Fs)-1]
}
return result, err
}
func Mount(mountPoint string, fs string) error {
res, err := NewRestyClient().R().SetFormData(map[string]string{
"mountPoint": mountPoint,
"fs": fs,
"mountOpt": `{"AllowOther": true}`,
}).Post("/mount/mount")
if err != nil {
return err
}
if res.StatusCode() != 200 {
return fmt.Errorf("mount failed")
}
logger.Info("mount then", zap.Any("res", res.Body()))
return nil
}
func Unmount(mountPoint string) error {
res, err := NewRestyClient().R().SetFormData(map[string]string{
"mountPoint": mountPoint,
}).Post("/mount/unmount")
if err != nil {
logger.Error("when unmount", zap.Error(err))
return err
}
if res.StatusCode() != 200 {
logger.Error("then unmount failed", zap.Any("res", res.Body()))
return fmt.Errorf("unmount failed")
}
logger.Info("unmount then", zap.Any("res", res.Body()))
return nil
}
func CreateConfig(data map[string]string, name, t string) error {
data["config_is_local"] = "false"
dataStr, _ := json.Marshal(data)
res, err := NewRestyClient().R().SetFormData(map[string]string{
"name": name,
"parameters": string(dataStr),
"type": t,
}).Post("/config/create")
logger.Info("when create config then", zap.Any("res", res.Body()))
if err != nil {
return err
}
if res.StatusCode() != 200 {
return fmt.Errorf("create config failed")
}
return nil
}
func GetConfigByName(name string) (map[string]string, error) {
res, err := NewRestyClient().R().SetFormData(map[string]string{
"name": name,
}).Post("/config/get")
if err != nil {
return nil, err
}
if res.StatusCode() != 200 {
return nil, fmt.Errorf("create config failed")
}
var result map[string]string
json.Unmarshal(res.Body(), &result)
return result, nil
}
func GetAllConfigName() (RemotesResult, error) {
var result RemotesResult
res, err := NewRestyClient().R().SetFormData(map[string]string{}).Post("/config/listremotes")
if err != nil {
return result, err
}
if res.StatusCode() != 200 {
return result, fmt.Errorf("get config failed")
}
json.Unmarshal(res.Body(), &result)
return result, nil
}
func DeleteConfigByName(name string) error {
res, err := NewRestyClient().R().SetFormData(map[string]string{
"name": name,
}).Post("/config/delete")
if err != nil {
return err
}
if res.StatusCode() != 200 {
return fmt.Errorf("delete config failed")
}
return nil
}

81
pkg/utils/path.go Normal file
View File

@@ -0,0 +1,81 @@
package utils
import (
"errors"
"net/url"
stdpath "path"
"strings"
)
// FixAndCleanPath
// The upper layer of the root directory is still the root directory.
// So ".." And "." will be cleared
// for example
// 1. ".." or "." => "/"
// 2. "../..." or "./..." => "/..."
// 3. "../.x." or "./.x." => "/.x."
// 4. "x//\\y" = > "/z/x"
func FixAndCleanPath(path string) string {
path = strings.ReplaceAll(path, "\\", "/")
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
return stdpath.Clean(path)
}
// PathAddSeparatorSuffix Add path '/' suffix
// for example /root => /root/
func PathAddSeparatorSuffix(path string) string {
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
return path
}
// PathEqual judge path is equal
func PathEqual(path1, path2 string) bool {
return FixAndCleanPath(path1) == FixAndCleanPath(path2)
}
func IsSubPath(path string, subPath string) bool {
path, subPath = FixAndCleanPath(path), FixAndCleanPath(subPath)
return path == subPath || strings.HasPrefix(subPath, PathAddSeparatorSuffix(path))
}
func Ext(path string) string {
ext := stdpath.Ext(path)
if strings.HasPrefix(ext, ".") {
return ext[1:]
}
return ext
}
func EncodePath(path string, all ...bool) string {
seg := strings.Split(path, "/")
toReplace := []struct {
Src string
Dst string
}{
{Src: "%", Dst: "%25"},
{"%", "%25"},
{"?", "%3F"},
{"#", "%23"},
}
for i := range seg {
if len(all) > 0 && all[0] {
seg[i] = url.PathEscape(seg[i])
} else {
for j := range toReplace {
seg[i] = strings.ReplaceAll(seg[i], toReplace[j].Src, toReplace[j].Dst)
}
}
}
return strings.Join(seg, "/")
}
func JoinBasePath(basePath, reqPath string) (string, error) {
if strings.HasSuffix(reqPath, "..") || strings.Contains(reqPath, "../") {
return "", errors.New("access using relative path is not allowed")
}
return stdpath.Join(FixAndCleanPath(basePath), FixAndCleanPath(reqPath)), nil
}

46
pkg/utils/slice.go Normal file
View File

@@ -0,0 +1,46 @@
package utils
// SliceEqual check if two slices are equal
func SliceEqual[T comparable](a, b []T) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// SliceContains check if slice contains element
func SliceContains[T comparable](arr []T, v T) bool {
for _, vv := range arr {
if vv == v {
return true
}
}
return false
}
// SliceConvert convert slice to another type slice
func SliceConvert[S any, D any](srcS []S, convert func(src S) (D, error)) ([]D, error) {
var res []D
for i := range srcS {
dst, err := convert(srcS[i])
if err != nil {
return nil, err
}
res = append(res, dst)
}
return res, nil
}
func MustSliceConvert[S any, D any](srcS []S, convert func(src S) D) []D {
var res []D
for i := range srcS {
dst := convert(srcS[i])
res = append(res, dst)
}
return res
}

37
pkg/utils/time.go Normal file
View File

@@ -0,0 +1,37 @@
package utils
import (
"sync"
"time"
)
func MustParseCNTime(str string) time.Time {
lastOpTime, _ := time.ParseInLocation("2006-01-02 15:04:05 -07", str+" +08", time.Local)
return lastOpTime
}
func NewDebounce(interval time.Duration) func(f func()) {
var timer *time.Timer
var lock sync.Mutex
return func(f func()) {
lock.Lock()
defer lock.Unlock()
if timer != nil {
timer.Stop()
}
timer = time.AfterFunc(interval, f)
}
}
func NewDebounce2(interval time.Duration, f func()) func() {
var timer *time.Timer
var lock sync.Mutex
return func() {
lock.Lock()
defer lock.Unlock()
if timer == nil {
timer = time.AfterFunc(interval, f)
}
(*time.Timer)(timer).Reset(interval)
}
}

View File

@@ -14,15 +14,15 @@ import (
"strconv"
"strings"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/types"
)
func IsNeedUpdate(version model.Version) (bool, model.Version) {
v1 := strings.Split(version.Version, ".")
v2 := strings.Split(types.CURRENTVERSION, ".")
v2 := strings.Split(common.VERSION, ".")
for len(v1) < len(v2) {
v1 = append(v1, "0")

View File

@@ -1,25 +0,0 @@
//go:build darwin
// +build darwin
/*
* @Author: LinkLeong link@icewhale.org
* @Date: 2022-08-12 14:22:28
* @LastEditors: LinkLeong
* @LastEditTime: 2022-09-05 16:27:55
* @FilePath: /CasaOS/route/darwin.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package route
func MonitoryUSB() {
}
func SendAllHardwareStatusBySocket() {
}
func SendUSBBySocket() {
}

View File

@@ -18,13 +18,13 @@ import (
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/samba"
"github.com/IceWhaleTech/CasaOS/pkg/utils/encryption"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/IceWhaleTech/CasaOS/types"
"go.uber.org/zap"
)
@@ -50,7 +50,7 @@ func InitInfo() {
logger.Error("GetMacAddress", zap.String("error", err.Error()))
}
mb.Hash = encryption.GetMD5ByStr(mac)
mb.Version = types.CURRENTVERSION
mb.Version = common.VERSION
os.Remove(config.AppInfo.DBPath + "/baseinfo.conf")
by, err := json.Marshal(mb)
if err != nil {
@@ -73,7 +73,11 @@ func InitNetworkMount() {
}
baseHostPath := "/mnt/" + connection.Host
mountPointList := service.MyService.System().GetDirPath(baseHostPath)
mountPointList, err := service.MyService.System().GetDirPath(baseHostPath)
if err != nil {
logger.Error("get mount point err", zap.Any("err", err))
continue
}
for _, v := range mountPointList {
service.MyService.Connections().UnmountSmaba(v.Path)
}
@@ -89,4 +93,9 @@ func InitNetworkMount() {
connection.Directories = strings.Join(directories, ",")
service.MyService.Connections().UpdateConnection(&connection)
}
err := service.MyService.Storage().CheckAndMountAll()
if err != nil {
logger.Error("mount storage err", zap.Any("err", err))
}
}

View File

@@ -66,10 +66,11 @@ func SendAllHardwareStatusBySocket() {
body["sys_net"] = newNet
systemTempMap := service.MyService.Notify().GetSystemTempMap()
for k, v := range systemTempMap {
body[k] = v
}
service.MyService.Notify().SendNotify("sys_hardware_status", body)
systemTempMap.Range(func(key, value interface{}) bool {
body[key.(string)] = value
return true
})
service.MyService.Notify().SendNotify("casaos:system:utilization", body)
}
// func MonitoryUSB() {

View File

@@ -1,62 +0,0 @@
/*
* @Author: LinkLeong link@icewhale.com
* @Date: 2022-05-23 17:18:56
* @LastEditors: LinkLeong
* @LastEditTime: 2022-06-09 21:48:10
* @FilePath: /CasaOS/route/socket.go
* @Description:
* @Website: https://www.casaos.io
* Copyright (c) 2022 by icewhale, All Rights Reserved.
*/
package route
import (
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/service"
socketio "github.com/googollee/go-socket.io"
"go.uber.org/zap"
)
func SocketIo() *socketio.Server {
server := socketio.NewServer(nil)
server.OnConnect("/", func(s socketio.Conn) error {
s.SetContext("")
logger.Info("connected", zap.Any("id", s.ID()))
s.Join("public")
service.ClientCount += 1
return nil
})
server.OnEvent("/", "notice", func(s socketio.Conn, msg string) {
logger.Info("notice", zap.Any("msg", msg))
s.Emit("reply", "have "+msg)
})
// server.OnEvent("/chat", "msg", func(s socketio.Conn, msg string) string {
// s.SetContext(msg)
// return "recv " + msg
// })
// server.OnEvent("/", "bye", func(s socketio.Conn) string {
// last := s.Context().(string)
// s.Emit("bye", last)
// s.Close()
// return last
// })
server.OnError("/", func(s socketio.Conn, e error) {
logger.Error("meet error", zap.Any("error", e))
})
server.OnDisconnect("/", func(s socketio.Conn, reason string) {
service.ClientCount -= 1
logger.Info("closed", zap.Any("reason", reason))
})
go func() {
if err := server.Serve(); err != nil {
logger.Error("error when trying to listen socketio ", zap.Any("error", err))
}
}()
return server
}

View File

@@ -12,7 +12,7 @@ import (
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
func InitV1Router() *gin.Engine {
ginMode := gin.ReleaseMode
if config.ServerInfo.RunMode != "" {
ginMode = config.ServerInfo.RunMode
@@ -30,29 +30,13 @@ func InitRouter() *gin.Engine {
r.Use(middleware.WriteLog())
}
// r.StaticFS("/ui", http.FS(web.Static))
// r.GET("/", WebUIHome)
// r.StaticFS("/assets", http.Dir("./static/assets"))
// r.StaticFile("/favicon.ico", "./static/favicon.ico")
//r.GET("/", func(c *gin.Context) {
// c.Redirect(http.StatusMovedPermanently, "ui/")
//})
// r.POST("/v1/users/register", v1.PostUserRegister)
// r.POST("/v1/users/login", v1.PostUserLogin)
// r.GET("/v1/users/name", v1.GetUserAllUsername) //all/name
// r.POST("/v1/users/refresh", v1.PostUserRefreshToken)
// // No short-term modifications
// r.GET("/v1/users/image", v1.GetUserImage)
// r.GET("/v1/users/status", v1.GetUserStatus) //init/check
// r.GET("/v1/guide/check", v1.GetGuideCheck) // /v1/sys/guide_check
r.GET("/v1/sys/debug", v1.GetSystemConfigDebug) // //debug
r.GET("/v1/sys/version/check", v1.GetSystemCheckVersion)
r.GET("/ping", func(ctx *gin.Context) {
ctx.String(200, "pong")
})
r.GET("/v1/recover/:type", v1.GetRecoverStorage)
v1Group := r.Group("/v1")
v1Group.Use(jwt.ExceptLocalhost())
@@ -111,6 +95,18 @@ func InitRouter() *gin.Engine {
v1FileGroup.POST("/upload", v1.PostFileUpload)
v1FileGroup.GET("/upload", v1.GetFileUpload)
// v1FileGroup.GET("/download", v1.UserFileDownloadCommonService)
}
v1CloudGroup := v1Group.Group("/cloud")
v1CloudGroup.Use()
{
v1CloudGroup.GET("", v1.ListStorages)
v1CloudGroup.DELETE("", v1.DeleteStorage)
}
v1DriverGroup := v1Group.Group("/driver")
v1DriverGroup.Use()
{
v1DriverGroup.GET("", v1.ListDriverInfo)
}
v1FolderGroup := v1Group.Group("/folder")
v1FolderGroup.Use()
@@ -159,9 +155,14 @@ func InitRouter() *gin.Engine {
v1NotifyGroup.POST("/:path", v1.PostNotifyMessage)
// merge to system
v1NotifyGroup.POST("/system_status", v1.PostSystemStatusNotify)
v1NotifyGroup.POST("/install_app", v1.PostInstallAppNotify)
v1NotifyGroup.POST("/uninstall_app", v1.PostUninstallAppNotify)
}
v1OtherGroup := v1Group.Group("/other")
v1OtherGroup.Use()
{
v1OtherGroup.GET("/search", v1.GetSearchResult)
}
}
return r
}

12
route/v1/driver.go Normal file
View File

@@ -0,0 +1,12 @@
package v1
import (
"github.com/IceWhaleTech/CasaOS/internal/op"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/gin-gonic/gin"
)
func ListDriverInfo(c *gin.Context) {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: op.GetDriverInfoMap()})
}

View File

@@ -1,6 +1,7 @@
package v1
import (
"errors"
"fmt"
"io"
"io/ioutil"
@@ -16,13 +17,21 @@ import (
"sync"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/internal/conf"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/IceWhaleTech/CasaOS/internal/sign"
"github.com/gin-gonic/gin"
uuid "github.com/satori/go.uuid"
"go.uber.org/zap"
"github.com/h2non/filetype"
)
// @Summary 读取文件
@@ -190,6 +199,36 @@ func GetDownloadSingleFile(c *gin.Context) {
})
return
}
fileName := path.Base(filePath)
// c.Header("Content-Disposition", "inline")
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url2.PathEscape(fileName))
fi, err := os.Open(filePath)
if err != nil {
panic(err)
}
// We only have to pass the file header = first 261 bytes
buffer := make([]byte, 261)
_, _ = fi.Read(buffer)
kind, _ := filetype.Match(buffer)
if kind != filetype.Unknown {
c.Header("Content-Type", kind.MIME.Value)
}
node, err := os.Stat(filePath)
// Set the Last-Modified header to the timestamp
c.Header("Last-Modified", node.ModTime().UTC().Format(http.TimeFormat))
knownSize := node.Size() >= 0
if knownSize {
c.Header("Content-Length", strconv.FormatInt(node.Size(), 10))
}
http.ServeContent(c.Writer, c.Request, fileName, node.ModTime(), fi)
//http.ServeFile(c.Writer, c.Request, filePath)
defer fi.Close()
return
fileTmp, err := os.Open(filePath)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{
@@ -200,9 +239,6 @@ func GetDownloadSingleFile(c *gin.Context) {
}
defer fileTmp.Close()
fileName := path.Base(filePath)
// c.Header("Content-Disposition", "inline")
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url2.PathEscape(fileName))
c.File(filePath)
}
@@ -215,22 +251,49 @@ func GetDownloadSingleFile(c *gin.Context) {
// @Success 200 {string} string "ok"
// @Router /file/dirpath [get]
func DirPath(c *gin.Context) {
path := c.DefaultQuery("path", "")
info := service.MyService.System().GetDirPath(path)
var req ListReq
if err := c.ShouldBind(&req); err != nil {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
return
}
req.Validate()
info, err := service.MyService.System().GetDirPath(req.Path)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
shares := service.MyService.Shares().GetSharesList()
sharesMap := make(map[string]string)
for _, v := range shares {
sharesMap[v.Path] = fmt.Sprint(v.ID)
}
for i := 0; i < len(info); i++ {
// if len(info) <= (req.Page-1)*req.Size {
// c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: "page out of range"})
// return
// }
forEnd := req.Index * req.Size
if forEnd > len(info) {
forEnd = len(info)
}
for i := (req.Index - 1) * req.Size; i < forEnd; i++ {
if v, ok := sharesMap[info[i].Path]; ok {
ex := make(map[string]interface{})
shareEx := make(map[string]string)
shareEx["shared"] = "true"
shareEx["id"] = v
ex["share"] = shareEx
ex["mounted"] = false
info[i].Extensions = ex
}
}
if strings.HasPrefix(req.Path, "/mnt") || strings.HasPrefix(req.Path, "/media") {
for i := (req.Index - 1) * req.Size; i < forEnd; i++ {
ex := info[i].Extensions
if ex == nil {
ex = make(map[string]interface{})
}
mounted := service.IsMounted(info[i].Path)
ex["mounted"] = mounted
info[i].Extensions = ex
}
}
@@ -250,17 +313,34 @@ func DirPath(c *gin.Context) {
}
}
pathList := []model.Path{}
for i := 0; i < len(info); i++ {
pathList := []ObjResp{}
for i := (req.Index - 1) * req.Size; i < forEnd; i++ {
if info[i].Name == ".temp" && info[i].IsDir {
continue
}
if _, ok := fileQueue[info[i].Path]; !ok {
pathList = append(pathList, info[i])
t := ObjResp{}
t.IsDir = info[i].IsDir
t.Name = info[i].Name
t.Modified = info[i].Date
t.Date = info[i].Date
t.Size = info[i].Size
t.Path = info[i].Path
t.Extensions = info[i].Extensions
pathList = append(pathList, t)
}
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: pathList})
flist := FsListResp{
Content: pathList,
Total: int64(len(info)),
// Readme: "",
// Write: true,
// Provider: "local",
Index: req.Index,
Size: req.Size,
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: flist})
}
// @Summary rename file or dir
@@ -281,6 +361,12 @@ func RenamePath(c *gin.Context) {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
mounted := service.IsMounted(op)
if mounted {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.MOUNTED_DIRECTIORIES, Message: common_err.GetMsg(common_err.MOUNTED_DIRECTIORIES), Data: common_err.GetMsg(common_err.MOUNTED_DIRECTIORIES)})
return
}
success, err := service.MyService.System().RenameFile(op, np)
c.JSON(common_err.SUCCESS, model.Result{Success: success, Message: common_err.GetMsg(success), Data: err})
}
@@ -507,6 +593,13 @@ func PostOperateFileOrDir(c *gin.Context) {
}
list.Item[i].Size = size
total += size
if list.Type == "move" {
mounted := service.IsMounted(list.Item[i].From)
if mounted {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.MOUNTED_DIRECTIORIES, Message: common_err.GetMsg(common_err.MOUNTED_DIRECTIORIES), Data: common_err.GetMsg(common_err.MOUNTED_DIRECTIORIES)})
return
}
}
}
list.TotalSize = total
@@ -544,6 +637,13 @@ func DeleteFile(c *gin.Context) {
// path := c.Query("path")
// paths := strings.Split(path, ",")
for _, v := range paths {
mounted := service.IsMounted(v)
if mounted {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.MOUNTED_DIRECTIORIES, Message: common_err.GetMsg(common_err.MOUNTED_DIRECTIORIES), Data: common_err.GetMsg(common_err.MOUNTED_DIRECTIORIES)})
return
}
}
for _, v := range paths {
err := os.RemoveAll(v)
@@ -576,12 +676,18 @@ func PutFileContent(c *gin.Context) {
return
}
// err := os.Remove(path)
err := os.RemoveAll(fi.FilePath)
f, err := os.Stat(fi.FilePath)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.FILE_ALREADY_EXISTS, Message: common_err.GetMsg(common_err.FILE_ALREADY_EXISTS)})
return
}
fm := f.Mode()
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.FILE_DELETE_ERROR, Message: common_err.GetMsg(common_err.FILE_DELETE_ERROR), Data: err})
return
}
err = file.CreateFileAndWriteContent(fi.FilePath, fi.FileContent)
os.OpenFile(fi.FilePath, os.O_CREATE, fm)
err = file.WriteToFullPath([]byte(fi.FileContent), fi.FilePath, fm)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
@@ -660,3 +766,168 @@ func GetSize(c *gin.Context) {
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: size})
}
func Proxy(c *gin.Context) {
rawPath := c.Query("path")
filename := filepath.Base(rawPath)
storage, err := service.MyService.FsService().GetStorage(rawPath)
if err != nil {
c.JSON(500, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
if canProxy(storage, filename) {
downProxyUrl := storage.GetStorage().DownProxyUrl
if downProxyUrl != "" {
_, ok := c.GetQuery("d")
if !ok {
URL := fmt.Sprintf("%s%s?sign=%s",
strings.Split(downProxyUrl, "\n")[0],
utils.EncodePath(rawPath, true),
sign.Sign(rawPath))
c.Redirect(302, URL)
return
}
}
link, file, err := service.MyService.FsService().Link(c, rawPath, model.LinkArgs{
Header: c.Request.Header,
Type: c.Query("type"),
})
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
err = CommonProxy(c.Writer, c.Request, link, file)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
} else {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: "proxy not allowed"})
return
}
}
// TODO need optimize
// when should be proxy?
// 1. config.MustProxy()
// 2. storage.WebProxy
// 3. proxy_types
func shouldProxy(storage driver.Driver, filename string) bool {
if storage.Config().MustProxy() || storage.GetStorage().WebProxy {
return true
}
if utils.SliceContains(conf.SlicesMap[conf.ProxyTypes], utils.Ext(filename)) {
return true
}
return false
}
// TODO need optimize
// when can be proxy?
// 1. text file
// 2. config.MustProxy()
// 3. storage.WebProxy
// 4. proxy_types
// solution: text_file + shouldProxy()
func canProxy(storage driver.Driver, filename string) bool {
if storage.Config().MustProxy() || storage.GetStorage().WebProxy || storage.GetStorage().WebdavProxy() {
return true
}
if utils.SliceContains(conf.SlicesMap[conf.ProxyTypes], utils.Ext(filename)) {
return true
}
if utils.SliceContains(conf.SlicesMap[conf.TextTypes], utils.Ext(filename)) {
return true
}
return false
}
var HttpClient = &http.Client{}
func CommonProxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.Obj) error {
// read data with native
var err error
if link.Data != nil {
defer func() {
_ = link.Data.Close()
}()
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
if link.Header != nil {
// TODO clean header with blacklist or whitelist
link.Header.Del("set-cookie")
for h, val := range link.Header {
w.Header()[h] = val
}
}
if link.Status == 0 {
w.WriteHeader(http.StatusOK)
} else {
w.WriteHeader(link.Status)
}
_, err = io.Copy(w, link.Data)
if err != nil {
return err
}
return nil
}
// local file
if link.FilePath != nil && *link.FilePath != "" {
f, err := os.Open(*link.FilePath)
if err != nil {
return err
}
defer func() {
_ = f.Close()
}()
fileStat, err := os.Stat(*link.FilePath)
if err != nil {
return err
}
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
return nil
} else {
req, err := http.NewRequest(link.Method, link.URL, nil)
if err != nil {
return err
}
for h, val := range r.Header {
if utils.SliceContains(conf.SlicesMap[conf.ProxyIgnoreHeaders], strings.ToLower(h)) {
continue
}
req.Header[h] = val
}
for h, val := range link.Header {
req.Header[h] = val
}
res, err := HttpClient.Do(req)
if err != nil {
return err
}
defer func() {
_ = res.Body.Close()
}()
logger.Info("proxy status", zap.Any("status", res.StatusCode))
// TODO clean header with blacklist or whitelist
res.Header.Del("set-cookie")
for h, v := range res.Header {
w.Header()[h] = v
}
w.WriteHeader(res.StatusCode)
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.QueryEscape(file.GetName())))
if res.StatusCode >= 400 {
all, _ := ioutil.ReadAll(res.Body)
msg := string(all)
logger.Info("msg", zap.Any("msg", msg))
return errors.New(msg)
}
_, err = io.Copy(w, res.Body)
if err != nil {
return err
}
return nil
}
}

97
route/v1/file_read.go Normal file
View File

@@ -0,0 +1,97 @@
package v1
import (
"path/filepath"
"time"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/gin-gonic/gin"
)
type ListReq struct {
model.PageReq
Path string `json:"path" form:"path"`
//Refresh bool `json:"refresh"`
}
type ObjResp struct {
Name string `json:"name"`
Size int64 `json:"size"`
IsDir bool `json:"is_dir"`
Modified time.Time `json:"modified"`
Sign string `json:"sign"`
Thumb string `json:"thumb"`
Type int `json:"type"`
Path string `json:"path"`
Date time.Time `json:"date"`
Extensions map[string]interface{} `json:"extensions"`
}
type FsListResp struct {
Content []ObjResp `json:"content"`
Total int64 `json:"total"`
Readme string `json:"readme,omitempty"`
Write bool `json:"write,omitempty"`
Provider string `json:"provider,omitempty"`
Index int `json:"index"`
Size int `json:"size"`
}
func FsList(c *gin.Context) {
var req ListReq
if err := c.ShouldBind(&req); err != nil {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
return
}
req.Validate()
objs, err := service.MyService.FsService().FList(c, req.Path, false)
if err != nil {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
total, objs := pagination(objs, &req.PageReq)
provider := "unknown"
storage, err := service.MyService.FsService().GetStorage(req.Path)
if err == nil {
provider = storage.GetStorage().Driver
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: FsListResp{
Content: toObjsResp(objs, req.Path, false),
Total: int64(total),
Readme: "",
Write: false,
Provider: provider,
}})
}
func pagination(objs []model.Obj, req *model.PageReq) (int, []model.Obj) {
pageIndex, pageSize := req.Index, req.Size
total := len(objs)
start := (pageIndex - 1) * pageSize
if start > total {
return total, []model.Obj{}
}
end := start + pageSize
if end > total {
end = total
}
return total, objs[start:end]
}
func toObjsResp(objs []model.Obj, parent string, encrypt bool) []ObjResp {
var resp []ObjResp
for _, obj := range objs {
thumb, _ := model.GetThumb(obj)
resp = append(resp, ObjResp{
Name: obj.GetName(),
Size: obj.GetSize(),
IsDir: obj.IsDir(),
Modified: obj.ModTime(),
Sign: "",
Path: filepath.Join(parent, obj.GetName()),
Thumb: thumb,
Type: 0,
})
}
return resp
}

View File

@@ -3,7 +3,6 @@ package v1
import (
"net/http"
"github.com/IceWhaleTech/CasaOS-Common/model/notify"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/service"
@@ -11,14 +10,14 @@ import (
)
func PostNotifyMessage(c *gin.Context) {
path := c.Param("path")
name := c.Param("name")
message := make(map[string]interface{})
if err := c.ShouldBind(&message); err != nil {
c.JSON(http.StatusBadRequest, model.Result{Success: common_err.INVALID_PARAMS, Message: err.Error()})
return
}
service.MyService.Notify().SendNotify(path, message)
service.MyService.Notify().SendNotify(name, message)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
@@ -32,25 +31,3 @@ func PostSystemStatusNotify(c *gin.Context) {
service.MyService.Notify().SettingSystemTempData(message)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
func PostInstallAppNotify(c *gin.Context) {
app := notify.Application{}
if err := c.ShouldBind(&app); err != nil {
c.JSON(http.StatusBadRequest, model.Result{Success: common_err.INVALID_PARAMS, Message: err.Error()})
return
}
service.MyService.Notify().SendInstallAppBySocket(app)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}
func PostUninstallAppNotify(c *gin.Context) {
app := notify.Application{}
if err := c.ShouldBind(&app); err != nil {
c.JSON(http.StatusBadRequest, model.Result{Success: common_err.INVALID_PARAMS, Message: err.Error()})
return
}
service.MyService.Notify().SendUninstallAppBySocket(app)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)})
}

26
route/v1/other.go Normal file
View File

@@ -0,0 +1,26 @@
package v1
import (
"fmt"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/gin-gonic/gin"
)
func GetSearchResult(c *gin.Context) {
key := c.Query("key")
if key == "" {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS), Data: "key is empty"})
return
}
data, err := service.MyService.Other().Search(key)
if err != nil {
fmt.Println(err)
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
}

203
route/v1/recover.go Normal file
View File

@@ -0,0 +1,203 @@
package v1
import (
"strconv"
"strings"
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/drivers/dropbox"
"github.com/IceWhaleTech/CasaOS/drivers/google_drive"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func GetRecoverStorage(c *gin.Context) {
c.Header("Content-Type", "text/html; charset=utf-8")
t := c.Param("type")
currentTime := time.Now().UTC()
currentDate := time.Now().UTC().Format("2006-01-02")
notify := make(map[string]interface{})
if t == "GoogleDrive" {
add := google_drive.Addition{}
add.Code = c.Query("code")
if len(add.Code) == 0 {
c.String(200, `<p>Code cannot be empty</p><script>window.close()</script>`)
notify["status"] = "fail"
notify["message"] = "Code cannot be empty"
logger.Error("Then code is empty: ", zap.String("code", add.Code), zap.Any("name", "google_drive"))
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
add.RootFolderID = "root"
add.ClientID = google_drive.CLIENTID
add.ClientSecret = google_drive.CLIENTSECRET
var google_drive google_drive.GoogleDrive
google_drive.Addition = add
err := google_drive.Init(c)
if err != nil {
c.String(200, `<p>Initialization failure:`+err.Error()+`</p><script>window.close()</script>`)
notify["status"] = "fail"
notify["message"] = "Initialization failure"
logger.Error("Then init error: ", zap.Error(err), zap.Any("name", "google_drive"))
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
username, err := google_drive.GetUserInfo(c)
if err != nil {
c.String(200, `<p>Failed to get user information:`+err.Error()+`</p><script>window.close()</script>`)
notify["status"] = "fail"
notify["message"] = "Failed to get user information"
logger.Error("Then get user info error: ", zap.Error(err), zap.Any("name", "google_drive"))
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
dmap := make(map[string]string)
dmap["username"] = username
configs, err := service.MyService.Storage().GetConfig()
if err != nil {
c.String(200, `<p>Failed to get rclone config:`+err.Error()+`</p><script>window.close()</script>`)
notify["status"] = "fail"
notify["message"] = "Failed to get rclone config"
logger.Error("Then get config error: ", zap.Error(err), zap.Any("name", "google_drive"))
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
for _, v := range configs.Remotes {
cf, err := service.MyService.Storage().GetConfigByName(v)
if err != nil {
logger.Error("then get config by name error: ", zap.Error(err), zap.Any("name", v))
continue
}
if cf["type"] == "drive" && cf["username"] == dmap["username"] {
c.String(200, `<p>The same configuration has been added</p><script>window.close()</script>`)
err := service.MyService.Storage().CheckAndMountByName(v)
if err != nil {
logger.Error("check and mount by name error: ", zap.Error(err), zap.Any("name", cf["username"]))
}
notify["status"] = "warn"
notify["message"] = "The same configuration has been added"
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
}
if len(username) > 0 {
a := strings.Split(username, "@")
username = a[0]
}
//username = fileutil.NameAccumulation(username, "/mnt")
username += "_google_drive_" + strconv.FormatInt(time.Now().Unix(), 10)
dmap["client_id"] = add.ClientID
dmap["client_secret"] = add.ClientSecret
dmap["scope"] = "drive"
dmap["mount_point"] = "/mnt/" + username
dmap["token"] = `{"access_token":"` + google_drive.AccessToken + `","token_type":"Bearer","refresh_token":"` + google_drive.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*1).Add(time.Minute*50).Format("15:04:05") + `Z"}`
service.MyService.Storage().CreateConfig(dmap, username, "drive")
service.MyService.Storage().MountStorage("/mnt/"+username, username+":")
notify := make(map[string]interface{})
notify["status"] = "success"
notify["message"] = "Success"
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
} else if t == "Dropbox" {
add := dropbox.Addition{}
add.Code = c.Query("code")
if len(add.Code) == 0 {
c.String(200, `<p>Code cannot be empty</p><script>window.close()</script>`)
notify["status"] = "fail"
notify["message"] = "Code cannot be empty"
logger.Error("Then code is empty error: ", zap.String("code", add.Code), zap.Any("name", "dropbox"))
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
add.RootFolderID = ""
add.AppKey = dropbox.APPKEY
add.AppSecret = dropbox.APPSECRET
var dropbox dropbox.Dropbox
dropbox.Addition = add
err := dropbox.Init(c)
if err != nil {
c.String(200, `<p>Initialization failure:`+err.Error()+`</p><script>window.close()</script>`)
notify["status"] = "fail"
notify["message"] = "Initialization failure"
logger.Error("Then init error: ", zap.Error(err), zap.Any("name", "dropbox"))
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
username, err := dropbox.GetUserInfo(c)
if err != nil {
c.String(200, `<p>Failed to get user information:`+err.Error()+`</p><script>window.close()</script>`)
notify["status"] = "fail"
notify["message"] = "Failed to get user information"
logger.Error("Then get user information: ", zap.Error(err), zap.Any("name", "dropbox"))
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
dmap := make(map[string]string)
dmap["username"] = username
configs, err := service.MyService.Storage().GetConfig()
if err != nil {
c.String(200, `<p>Failed to get rclone config:`+err.Error()+`</p><script>window.close()</script>`)
notify["status"] = "fail"
notify["message"] = "Failed to get rclone config"
logger.Error("Then get config error: ", zap.Error(err), zap.Any("name", "dropbox"))
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
for _, v := range configs.Remotes {
cf, err := service.MyService.Storage().GetConfigByName(v)
if err != nil {
logger.Error("then get config by name error: ", zap.Error(err), zap.Any("name", v))
continue
}
if cf["type"] == "dropbox" && cf["username"] == dmap["username"] {
c.String(200, `<p>The same configuration has been added</p><script>window.close()</script>`)
err := service.MyService.Storage().CheckAndMountByName(v)
if err != nil {
logger.Error("check and mount by name error: ", zap.Error(err), zap.Any("name", cf["username"]))
}
notify["status"] = "warn"
notify["message"] = "The same configuration has been added"
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
return
}
}
if len(username) > 0 {
a := strings.Split(username, "@")
username = a[0]
}
username += "_dropbox_" + strconv.FormatInt(time.Now().Unix(), 10)
dmap["client_id"] = add.AppKey
dmap["client_secret"] = add.AppSecret
dmap["token"] = `{"access_token":"` + dropbox.AccessToken + `","token_type":"bearer","refresh_token":"` + dropbox.Addition.RefreshToken + `","expiry":"` + currentDate + `T` + currentTime.Add(time.Hour*3).Add(time.Minute*50).Format("15:04:05") + `.780385354Z"}`
dmap["mount_point"] = "/mnt/" + username
// data.SetValue(username, "type", "dropbox")
// data.SetValue(username, "client_id", add.AppKey)
// data.SetValue(username, "client_secret", add.AppSecret)
// data.SetValue(username, "mount_point", "/mnt/"+username)
// data.SetValue(username, "token", `{"access_token":"`+dropbox.AccessToken+`","token_type":"bearer","refresh_token":"`+dropbox.Addition.RefreshToken+`","expiry":"`+currentDate+`T`+currentTime.Add(time.Hour*3).Format("15:04:05")+`.780385354Z"}`)
// e = data.Save()
// if e != nil {
// c.String(200, `<p>保存配置失败:`+e.Error()+`</p>`)
// return
// }
service.MyService.Storage().CreateConfig(dmap, username, "dropbox")
service.MyService.Storage().MountStorage("/mnt/"+username, username+":")
notify["status"] = "success"
notify["message"] = "Success"
service.MyService.Notify().SendNotify("casaos:file:recover", notify)
}
c.String(200, `<p>Just close the page</p><script>window.close()</script>`)
}

View File

@@ -12,11 +12,17 @@ package v1
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS-Common/utils/systemctl"
"go.uber.org/zap"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/samba"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
@@ -30,12 +36,14 @@ import (
// service
func GetSambaStatus(c *gin.Context) {
status := service.MyService.System().IsServiceRunning("smbd")
if !status {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_NOT_RUNNING, Message: common_err.GetMsg(common_err.SERVICE_NOT_RUNNING)})
if status, err := systemctl.IsServiceRunning("smbd"); err != nil || !status {
c.JSON(http.StatusInternalServerError, model.Result{
Success: common_err.SERVICE_NOT_RUNNING,
Message: common_err.GetMsg(common_err.SERVICE_NOT_RUNNING),
})
return
}
needInit := true
if file.Exists("/etc/samba/smb.conf") {
str := file.ReadLine(1, "/etc/samba/smb.conf")
@@ -87,12 +95,13 @@ func PostSambaSharesCreate(c *gin.Context) {
shareDBModel.Anonymous = true
shareDBModel.Path = v.Path
shareDBModel.Name = filepath.Base(v.Path)
os.Chmod(v.Path, 0777)
os.Chmod(v.Path, 0o777)
service.MyService.Shares().CreateShare(shareDBModel)
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: shares})
}
func DeleteSambaShares(c *gin.Context) {
id := c.Param("id")
if id == "" {
@@ -103,8 +112,7 @@ func DeleteSambaShares(c *gin.Context) {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id})
}
//client
// client
func GetSambaConnectionsList(c *gin.Context) {
connections := service.MyService.Connections().GetConnectionsList()
connectionList := []model.Connections{}
@@ -131,11 +139,11 @@ func PostSambaConnectionsCreate(c *gin.Context) {
return
}
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,30}$", connection.Password); !ok {
if ok, _ := regexp.MatchString(`^[\w@#*.]{4,30}$`, connection.Password); !ok {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.CHARACTER_LIMIT, Message: common_err.GetMsg(common_err.CHARACTER_LIMIT)})
return
}
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,30}$", connection.Username); !ok {
if ok, _ := regexp.MatchString(`^[\w@#*.]{4,30}$`, connection.Username); !ok {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)})
return
}
@@ -191,11 +199,25 @@ func DeleteSambaConnections(c *gin.Context) {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.Record_NOT_EXIST, Message: common_err.GetMsg(common_err.Record_NOT_EXIST)})
return
}
mountPointList := service.MyService.System().GetDirPath(connection.MountPoint)
for _, v := range mountPointList {
service.MyService.Connections().UnmountSmaba(v.Path)
mountPointList, err := samba.GetSambaSharesList(connection.Host, connection.Port, connection.Username, connection.Password)
//mountPointList, err := service.MyService.System().GetDirPath(connection.MountPoint)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
baseHostPath := "/mnt/" + connection.Host
for _, v := range mountPointList {
err := service.MyService.Connections().UnmountSmaba(baseHostPath + "/" + v)
if err != nil {
logger.Error("unmount smaba error", zap.Error(err), zap.Any("path", baseHostPath+"/"+v))
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
}
dir, _ := ioutil.ReadDir(connection.MountPoint)
if len(dir) == 0 {
os.RemoveAll(connection.MountPoint)
}
os.RemoveAll(connection.MountPoint)
service.MyService.Connections().DeleteConnection(id)
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id})
}

143
route/v1/storage.go Normal file
View File

@@ -0,0 +1,143 @@
package v1
import (
"strconv"
"strings"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/drivers/dropbox"
"github.com/IceWhaleTech/CasaOS/drivers/google_drive"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
"github.com/IceWhaleTech/CasaOS/pkg/utils/httper"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
func ListStorages(c *gin.Context) {
// var req model.PageReq
// if err := c.ShouldBind(&req); err != nil {
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
// return
// }
// req.Validate()
//logger.Info("ListStorages", zap.Any("req", req))
//storages, total, err := service.MyService.Storage().GetStorages(req.Page, req.PerPage)
// if err != nil {
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
// return
// }
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: model.PageResp{
// Content: storages,
// Total: total,
// }})
r, err := service.MyService.Storage().GetStorages()
if err != nil {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
for i := 0; i < len(r.MountPoints); i++ {
dataMap, err := service.MyService.Storage().GetConfigByName(r.MountPoints[i].Fs)
if err != nil {
logger.Error("GetConfigByName", zap.Any("err", err))
continue
}
if dataMap["type"] == "drive" {
r.MountPoints[i].Icon = google_drive.ICONURL
}
if dataMap["type"] == "dropbox" {
r.MountPoints[i].Icon = dropbox.ICONURL
}
r.MountPoints[i].Name = dataMap["username"]
}
list := []httper.MountPoint{}
for _, v := range r.MountPoints {
list = append(list, httper.MountPoint{
Fs: v.Fs,
Icon: v.Icon,
MountPoint: v.MountPoint,
Name: v.Name,
})
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: list})
}
func UpdateStorage(c *gin.Context) {
var req model.Storage
if err := c.ShouldBind(&req); err != nil {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
return
}
if err := service.MyService.Storages().UpdateStorage(c, req); err != nil {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
} else {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: "success"})
}
}
func DeleteStorage(c *gin.Context) {
json := make(map[string]string)
c.ShouldBind(&json)
mountPoint := json["mount_point"]
if mountPoint == "" {
c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: "mount_point is empty"})
return
}
err := service.MyService.Storage().UnmountStorage(mountPoint)
if err != nil {
c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
service.MyService.Storage().DeleteConfigByName(strings.ReplaceAll(mountPoint, "/mnt/", ""))
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: "success"})
}
func DisableStorage(c *gin.Context) {
idStr := c.Query("id")
id, err := strconv.Atoi(idStr)
if err != nil {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
return
}
if err := service.MyService.Storages().DisableStorage(c, uint(id)); err != nil {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: "success"})
}
func EnableStorage(c *gin.Context) {
idStr := c.Query("id")
id, err := strconv.Atoi(idStr)
if err != nil {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
return
}
if err := service.MyService.Storages().EnableStorage(c, uint(id)); err != nil {
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
return
}
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: "success"})
}
func GetStorage(c *gin.Context) {
// idStr := c.Query("id")
// id, err := strconv.Atoi(idStr)
// if err != nil {
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()})
// return
// }
// storage, err := service.MyService.Storage().GetStorageById(uint(id))
// if err != nil {
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR), Data: err.Error()})
// return
// }
// c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: storage})
}

View File

@@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"time"
@@ -14,6 +15,7 @@ import (
http2 "github.com/IceWhaleTech/CasaOS-Common/utils/http"
"github.com/IceWhaleTech/CasaOS-Common/utils/port"
"github.com/IceWhaleTech/CasaOS/common"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/config"
"github.com/IceWhaleTech/CasaOS/pkg/utils/common_err"
@@ -46,7 +48,7 @@ func GetSystemCheckVersion(c *gin.Context) {
data := make(map[string]interface{}, 3)
data["need_update"] = need
data["version"] = version
data["current_version"] = types.CURRENTVERSION
data["current_version"] = common.VERSION
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
}
@@ -92,7 +94,7 @@ func GetSystemConfigDebug(c *gin.Context) {
- Remote Version: %s
- Browser: $Browser$
- Version: $Version$
`, sys.OS, types.CURRENTVERSION, disk.Total>>20, disk.Used>>20, array, version.Version)
`, sys.OS, common.VERSION, disk.Total>>20, disk.Used>>20, array, version.Version)
// array = append(array, fmt.Sprintf("disk,total:%v,used:%v,UsedPercent:%v", disk.Total>>20, disk.Used>>20, disk.UsedPercent))
@@ -175,6 +177,7 @@ func PostKillCasaOS(c *gin.Context) {
func GetSystemHardwareInfo(c *gin.Context) {
data := make(map[string]string, 1)
data["drive_model"] = service.MyService.System().GetDeviceTree()
data["arch"] = runtime.GOARCH
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
@@ -229,9 +232,11 @@ func GetSystemUtilization(c *gin.Context) {
}
data["net"] = newNet
for k, v := range service.MyService.Notify().GetSystemTempMap() {
data[k] = v
}
systemMap := service.MyService.Notify().GetSystemTempMap()
systemMap.Range(func(key, value interface{}) bool {
data[key.(string)] = value
return true
})
c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data})
}

222
route/v2.go Normal file
View File

@@ -0,0 +1,222 @@
package route
import (
"log"
"net/http"
"net/url"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/IceWhaleTech/CasaOS/codegen"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/IceWhaleTech/CasaOS-Common/utils/common_err"
"github.com/IceWhaleTech/CasaOS-Common/utils/jwt"
v2Route "github.com/IceWhaleTech/CasaOS/route/v2"
"github.com/deepmap/oapi-codegen/pkg/middleware"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/labstack/echo/v4"
echo_middleware "github.com/labstack/echo/v4/middleware"
)
var (
_swagger *openapi3.T
V2APIPath string
V2DocPath string
V3FilePath string
V4DirPath string
)
func init() {
swagger, err := codegen.GetSwagger()
if err != nil {
panic(err)
}
_swagger = swagger
u, err := url.Parse(_swagger.Servers[0].URL)
if err != nil {
panic(err)
}
V2APIPath = strings.TrimRight(u.Path, "/")
V2DocPath = "/doc" + V2APIPath
V3FilePath = "/v3/file"
V4DirPath = "/v4/dir"
}
func InitV2Router() http.Handler {
appManagement := v2Route.NewCasaOS()
e := echo.New()
e.Use((echo_middleware.CORSWithConfig(echo_middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.POST, echo.GET, echo.OPTIONS, echo.PUT, echo.DELETE},
AllowHeaders: []string{echo.HeaderAuthorization, echo.HeaderContentLength, echo.HeaderXCSRFToken, echo.HeaderContentType, echo.HeaderAccessControlAllowOrigin, echo.HeaderAccessControlAllowHeaders, echo.HeaderAccessControlAllowMethods, echo.HeaderConnection, echo.HeaderOrigin, echo.HeaderXRequestedWith},
ExposeHeaders: []string{echo.HeaderContentLength, echo.HeaderAccessControlAllowOrigin, echo.HeaderAccessControlAllowHeaders},
MaxAge: 172800,
AllowCredentials: true,
})))
e.Use(echo_middleware.Gzip())
e.Use(echo_middleware.Logger())
e.Use(echo_middleware.JWTWithConfig(echo_middleware.JWTConfig{
Skipper: func(c echo.Context) bool {
return c.RealIP() == "::1" || c.RealIP() == "127.0.0.1"
//return true
},
ParseTokenFunc: func(token string, c echo.Context) (interface{}, error) {
claims, code := jwt.Validate(token)
if code != common_err.SUCCESS {
return nil, echo.ErrUnauthorized
}
c.Request().Header.Set("user_id", strconv.Itoa(claims.ID))
return claims, nil
},
TokenLookupFuncs: []echo_middleware.ValuesExtractor{
func(c echo.Context) ([]string, error) {
return []string{c.Request().Header.Get(echo.HeaderAuthorization)}, nil
},
},
}))
// e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
// return func(c echo.Context) error {
// switch c.Request().Header.Get(echo.HeaderContentType) {
// case common.MIMEApplicationYAML: // in case request contains a compose content in YAML
// return middleware.OapiRequestValidatorWithOptions(_swagger, &middleware.Options{
// Options: openapi3filter.Options{
// AuthenticationFunc: openapi3filter.NoopAuthenticationFunc,
// // ExcludeRequestBody: true,
// // ExcludeResponseBody: true,
// },
// })(next)(c)
// default:
// return middleware.OapiRequestValidatorWithOptions(_swagger, &middleware.Options{
// Options: openapi3filter.Options{
// AuthenticationFunc: openapi3filter.NoopAuthenticationFunc,
// },
// })(next)(c)
// }
// }
// })
e.Use(middleware.OapiRequestValidatorWithOptions(_swagger, &middleware.Options{
Options: openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},
}))
codegen.RegisterHandlersWithBaseURL(e, appManagement, V2APIPath)
return e
}
func InitV2DocRouter(docHTML string, docYAML string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == V2DocPath {
if _, err := w.Write([]byte(docHTML)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
return
}
if r.URL.Path == V2DocPath+"/openapi.yaml" {
if _, err := w.Write([]byte(docYAML)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
})
}
func InitFile() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
filePath := r.URL.Query().Get("path")
fileName := path.Base(filePath)
w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(fileName))
http.ServeFile(w, r, filePath)
})
}
func InitDir() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t := r.URL.Query().Get("format")
files := r.URL.Query().Get("files")
if len(files) == 0 {
// w.JSON(common_err.CLIENT_ERROR, model.Result{
// Success: common_err.INVALID_PARAMS,
// Message: common_err.GetMsg(common_err.INVALID_PARAMS),
// })
return
}
list := strings.Split(files, ",")
for _, v := range list {
if !file.Exists(v) {
// c.JSON(common_err.SERVICE_ERROR, model.Result{
// Success: common_err.FILE_DOES_NOT_EXIST,
// Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST),
// })
return
}
}
w.Header().Add("Content-Type", "application/octet-stream")
w.Header().Add("Content-Transfer-Encoding", "binary")
w.Header().Add("Cache-Control", "no-cache")
// handles only single files not folders and multiple files
// if len(list) == 1 {
//filePath := list[0]
// info, err := os.Stat(filePath)
// if err != nil {
// w.JSON(http.StatusOK, model.Result{
// Success: common_err.FILE_DOES_NOT_EXIST,
// Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST),
// })
//return
// }
//}
extension, ar, err := file.GetCompressionAlgorithm(t)
if err != nil {
// w.JSON(common_err.CLIENT_ERROR, model.Result{
// Success: common_err.INVALID_PARAMS,
// Message: common_err.GetMsg(common_err.INVALID_PARAMS),
// })
return
}
err = ar.Create(w)
if err != nil {
// c.JSON(common_err.SERVICE_ERROR, model.Result{
// Success: common_err.SERVICE_ERROR,
// Message: common_err.GetMsg(common_err.SERVICE_ERROR),
// Data: err.Error(),
// })
return
}
defer ar.Close()
commonDir := file.CommonPrefix(filepath.Separator, list...)
currentPath := filepath.Base(commonDir)
name := "_" + currentPath
name += extension
w.Header().Add("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(name))
for _, fname := range list {
err = file.AddFile(ar, fname, commonDir)
if err != nil {
log.Printf("Failed to archive %s: %v", fname, err)
}
}
})
}

17
route/v2/file.go Normal file
View File

@@ -0,0 +1,17 @@
package v2
import (
"net/http"
"github.com/labstack/echo/v4"
)
// Path: route/v2/file.go
func (s *CasaOS) GetFileTest(ctx echo.Context) error {
//http.ServeFile(w, r, r.URL.Path[1:])
http.ServeFile(ctx.Response().Writer, ctx.Request(), "/DATA/test.img")
return ctx.String(200, "pong")
}

26
route/v2/health.go Normal file
View File

@@ -0,0 +1,26 @@
package v2
import (
"net/http"
"github.com/IceWhaleTech/CasaOS/codegen"
"github.com/IceWhaleTech/CasaOS/service"
"github.com/labstack/echo/v4"
)
func (s *CasaOS) GetHealthServices(ctx echo.Context) error {
services, err := service.MyService.Health().Services()
if err != nil {
message := err.Error()
return ctx.JSON(http.StatusInternalServerError, codegen.ResponseInternalServerError{
Message: &message,
})
}
return ctx.JSON(http.StatusOK, codegen.GetHealthServicesOK{
Data: &codegen.HealthServices{
Running: services[true],
NotRunning: services[false],
},
})
}

11
route/v2/route.go Normal file
View File

@@ -0,0 +1,11 @@
package v2
import (
"github.com/IceWhaleTech/CasaOS/codegen"
)
type CasaOS struct{}
func NewCasaOS() codegen.ServerInterface {
return &CasaOS{}
}

View File

@@ -15,6 +15,7 @@ import (
command2 "github.com/IceWhaleTech/CasaOS/pkg/utils/command"
"github.com/IceWhaleTech/CasaOS/service/model"
model2 "github.com/IceWhaleTech/CasaOS/service/model"
"github.com/moby/sys/mount"
"gorm.io/gorm"
)
@@ -26,7 +27,7 @@ type ConnectionsService interface {
DeleteConnection(id string)
UpdateConnection(connection *model2.ConnectionsDBModel)
MountSmaba(username, host, directory, port, mountPoint, password string) string
UnmountSmaba(mountPoint string) string
UnmountSmaba(mountPoint string) error
}
type connectionsStruct struct {
@@ -59,9 +60,8 @@ func (s *connectionsStruct) MountSmaba(username, host, directory, port, mountPoi
str := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;MountCIFS " + username + " " + host + " " + directory + " " + port + " " + mountPoint + " " + password)
return str
}
func (s *connectionsStruct) UnmountSmaba(mountPoint string) string {
str := command2.ExecResultStr("source " + config.AppInfo.ShellPath + "/helper.sh ;UMountPorintAndRemoveDir " + mountPoint)
return str
func (s *connectionsStruct) UnmountSmaba(mountPoint string) error {
return mount.Unmount(mountPoint)
}
func NewConnectionsService(db *gorm.DB) ConnectionsService {

View File

@@ -22,6 +22,7 @@ import (
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/utils/file"
"github.com/moby/sys/mountinfo"
"go.uber.org/zap"
)
@@ -172,3 +173,16 @@ func CheckFileStatus() {
time.Sleep(time.Second * 3)
}
}
func IsMounted(path string) bool {
mounted, _ := mountinfo.Mounted(path)
if mounted {
return true
}
connections := MyService.Connections().GetConnectionsList()
for _, v := range connections {
if v.MountPoint == path {
return true
}
}
return false
}

154
service/fs.go Normal file
View File

@@ -0,0 +1,154 @@
package service
import (
"context"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/model"
log "github.com/dsoprea/go-logging"
"go.uber.org/zap"
)
type FsService interface {
FList(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error)
GetStorage(path string) (driver.Driver, error)
Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, model.Obj, error)
}
type fsService struct {
}
// the param named path of functions in this package is a mount path
// So, the purpose of this package is to convert mount path to actual path
// then pass the actual path to the op package
func (f *fsService) FList(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
res, err := MyService.FsListService().FsList(ctx, path, refresh...)
if err != nil {
logger.Info("failed list", zap.Any("path", path), zap.Any("err", err))
return nil, err
}
return res, nil
}
// func (f *fsService) Get(ctx context.Context, path string) (model.Obj, error) {
// res, err := get(ctx, path)
// if err != nil {
// log.Errorf("failed get %s: %+v", path, err)
// return nil, err
// }
// return res, nil
// }
func (f *fsService) Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, model.Obj, error) {
res, file, err := MyService.FsLinkService().Link(ctx, path, args)
if err != nil {
log.Errorf("failed link %s: %+v", path, err)
return nil, nil, err
}
return res, file, nil
}
// func (f *fsService) MakeDir(ctx context.Context, path string, lazyCache ...bool) error {
// err := makeDir(ctx, path, lazyCache...)
// if err != nil {
// log.Errorf("failed make dir %s: %+v", path, err)
// }
// return err
// }
// func (f *fsService) Move(ctx context.Context, srcPath, dstDirPath string, lazyCache ...bool) error {
// err := move(ctx, srcPath, dstDirPath, lazyCache...)
// if err != nil {
// log.Errorf("failed move %s to %s: %+v", srcPath, dstDirPath, err)
// }
// return err
// }
// func (f *fsService) Copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool) (bool, error) {
// res, err := _copy(ctx, srcObjPath, dstDirPath, lazyCache...)
// if err != nil {
// log.Errorf("failed copy %s to %s: %+v", srcObjPath, dstDirPath, err)
// }
// return res, err
// }
// func (f *fsService) Rename(ctx context.Context, srcPath, dstName string, lazyCache ...bool) error {
// err := rename(ctx, srcPath, dstName, lazyCache...)
// if err != nil {
// log.Errorf("failed rename %s to %s: %+v", srcPath, dstName, err)
// }
// return err
// }
// func (f *fsService) Remove(ctx context.Context, path string) error {
// err := remove(ctx, path)
// if err != nil {
// log.Errorf("failed remove %s: %+v", path, err)
// }
// return err
// }
// func PutDirectly(ctx context.Context, dstDirPath string, file *model.FileStream, lazyCache ...bool) error {
// err := putDirectly(ctx, dstDirPath, file, lazyCache...)
// if err != nil {
// log.Errorf("failed put %s: %+v", dstDirPath, err)
// }
// return err
// }
// func (f *fsService) PutAsTask(dstDirPath string, file *model.FileStream) error {
// err := putAsTask(dstDirPath, file)
// if err != nil {
// log.Errorf("failed put %s: %+v", dstDirPath, err)
// }
// return err
// }
func (f *fsService) GetStorage(path string) (driver.Driver, error) {
storageDriver, _, err := MyService.StoragePath().GetStorageAndActualPath(path)
if err != nil {
return nil, err
}
return storageDriver, nil
}
// func (f *fsService) Other(ctx context.Context, args model.FsOtherArgs) (interface{}, error) {
// res, err := other(ctx, args)
// if err != nil {
// log.Errorf("failed remove %s: %+v", args.Path, err)
// }
// return res, err
// }
// func get(ctx context.Context, path string) (model.Obj, error) {
// path = utils.FixAndCleanPath(path)
// // maybe a virtual file
// if path != "/" {
// virtualFiles := op.GetStorageVirtualFilesByPath(stdpath.Dir(path))
// for _, f := range virtualFiles {
// if f.GetName() == stdpath.Base(path) {
// return f, nil
// }
// }
// }
// storage, actualPath, err := op.GetStorageAndActualPath(path)
// if err != nil {
// // if there are no storage prefix with path, maybe root folder
// if path == "/" {
// return &model.Object{
// Name: "root",
// Size: 0,
// Modified: time.Time{},
// IsFolder: true,
// }, nil
// }
// return nil, errors.WithMessage(err, "failed get storage")
// }
// return op.Get(ctx, storage, actualPath)
// }
func NewFsService() FsService {
return &fsService{}
}

27
service/fs_link.go Normal file
View File

@@ -0,0 +1,27 @@
package service
import (
"context"
"github.com/IceWhaleTech/CasaOS/internal/op"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/pkg/errors"
)
type FsLinkService interface {
Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, model.Obj, error)
}
type fsLinkService struct {
}
func (f *fsLinkService) Link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, model.Obj, error) {
storage, actualPath, err := MyService.StoragePath().GetStorageAndActualPath(path)
if err != nil {
return nil, nil, errors.WithMessage(err, "failed get storage")
}
return op.Link(ctx, storage, actualPath, args)
}
func NewFsLinkService() FsLinkService {
return &fsLinkService{}
}

198
service/fs_list.go Normal file
View File

@@ -0,0 +1,198 @@
package service
import (
"context"
stdpath "path"
"time"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/internal/driver"
"github.com/IceWhaleTech/CasaOS/internal/op"
"github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/pkg/singleflight"
"github.com/IceWhaleTech/CasaOS/pkg/utils"
"github.com/Xhofe/go-cache"
log "github.com/dsoprea/go-logging"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type FsListService interface {
FsList(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error)
Key(storage driver.Driver, path string) string
Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, error)
GetUnwrap(ctx context.Context, storage driver.Driver, path string) (model.Obj, error)
List(ctx context.Context, storage driver.Driver, path string, args model.ListArgs, refresh ...bool) ([]model.Obj, error)
}
type fsListService struct {
}
var listCache = cache.NewMemCache(cache.WithShards[[]model.Obj](64))
var listG singleflight.Group[[]model.Obj]
// List files
func (fl *fsListService) FsList(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
virtualFiles := MyService.Storages().GetStorageVirtualFilesByPath(path)
storage, actualPath, err := MyService.StoragePath().GetStorageAndActualPath(path)
if err != nil && len(virtualFiles) == 0 {
return nil, errors.WithMessage(err, "failed get storage")
}
var _objs []model.Obj
if storage != nil {
_objs, err = fl.List(ctx, storage, actualPath, model.ListArgs{
ReqPath: path,
}, refresh...)
if err != nil {
log.Errorf("%+v", err)
if len(virtualFiles) == 0 {
return nil, errors.WithMessage(err, "failed get objs")
}
}
}
om := model.NewObjMerge()
objs := om.Merge(virtualFiles, _objs...)
return objs, nil
}
func (fl *fsListService) Key(storage driver.Driver, path string) string {
return stdpath.Join(storage.GetStorage().MountPath, utils.FixAndCleanPath(path))
}
// Get object from list of files
func (fl *fsListService) Get(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
path = utils.FixAndCleanPath(path)
logger.Info("get", zap.String("path", path))
// is root folder
if utils.PathEqual(path, "/") {
var rootObj model.Obj
switch r := storage.GetAddition().(type) {
case driver.IRootId:
rootObj = &model.Object{
ID: r.GetRootId(),
Name: op.RootName,
Size: 0,
Modified: storage.GetStorage().Modified,
IsFolder: true,
}
case driver.IRootPath:
rootObj = &model.Object{
Path: r.GetRootPath(),
Name: op.RootName,
Size: 0,
Modified: storage.GetStorage().Modified,
IsFolder: true,
}
default:
if storage, ok := storage.(driver.Getter); ok {
obj, err := storage.GetRoot(ctx)
if err != nil {
return nil, errors.WithMessage(err, "failed get root obj")
}
rootObj = obj
}
}
if rootObj == nil {
return nil, errors.Errorf("please implement IRootPath or IRootId or Getter method")
}
return &model.ObjWrapName{
Name: op.RootName,
Obj: rootObj,
}, nil
}
// not root folder
dir, name := stdpath.Split(path)
files, err := fl.List(ctx, storage, dir, model.ListArgs{})
if err != nil {
return nil, errors.WithMessage(err, "failed get parent list")
}
for _, f := range files {
// TODO maybe copy obj here
if f.GetName() == name {
return f, nil
}
}
logger.Info("cant find obj with name", zap.Any("name", name))
return nil, errors.WithStack(errors.New("object not found"))
}
func (fl *fsListService) GetUnwrap(ctx context.Context, storage driver.Driver, path string) (model.Obj, error) {
obj, err := fl.Get(ctx, storage, path)
if err != nil {
return nil, err
}
return model.UnwrapObjs(obj), err
}
// List files in storage, not contains virtual file
func (fl *fsListService) List(ctx context.Context, storage driver.Driver, path string, args model.ListArgs, refresh ...bool) ([]model.Obj, error) {
if storage.Config().CheckStatus && storage.GetStorage().Status != op.WORK {
return nil, errors.Errorf("storage not init: %s", storage.GetStorage().Status)
}
path = utils.FixAndCleanPath(path)
logger.Info("op.List", zap.Any("path", path))
key := fl.Key(storage, path)
if !utils.IsBool(refresh...) {
if files, ok := listCache.Get(key); ok {
logger.Info("op.List", zap.Any("use cache", path))
return files, nil
}
}
dir, err := fl.GetUnwrap(ctx, storage, path)
if err != nil {
return nil, errors.WithMessage(err, "failed get dir")
}
logger.Info("op.List", zap.Any("dir", dir))
if !dir.IsDir() {
return nil, errors.WithStack(errors.New("not a folder"))
}
objs, err, _ := listG.Do(key, func() ([]model.Obj, error) {
files, err := storage.List(ctx, dir, args)
if err != nil {
return nil, errors.Wrapf(err, "failed to list objs")
}
// set path
for _, f := range files {
if s, ok := f.(model.SetPath); ok && f.GetPath() == "" && dir.GetPath() != "" {
s.SetPath(stdpath.Join(dir.GetPath(), f.GetName()))
}
}
// warp obj name
model.WrapObjsName(files)
// call hooks
go func(reqPath string, files []model.Obj) {
for _, hook := range op.ObjsUpdateHooks {
hook(args.ReqPath, files)
}
}(args.ReqPath, files)
// sort objs
if storage.Config().LocalSort {
model.SortFiles(files, storage.GetStorage().OrderBy, storage.GetStorage().OrderDirection)
}
model.ExtractFolder(files, storage.GetStorage().ExtractFolder)
if !storage.Config().NoCache {
if len(files) > 0 {
logger.Info("set cache", zap.Any("key", key), zap.Any("files", files))
listCache.Set(key, files, cache.WithEx[[]model.Obj](time.Minute*time.Duration(storage.GetStorage().CacheExpiration)))
} else {
logger.Info("del cache", zap.Any("key", key))
listCache.Del(key)
}
}
return files, nil
})
return objs, err
}
func NewFsListService() FsListService {
return &fsListService{}
}

39
service/health.go Normal file
View File

@@ -0,0 +1,39 @@
package service
import (
"github.com/IceWhaleTech/CasaOS-Common/utils/systemctl"
)
type HealthService interface {
Services() (map[bool]*[]string, error)
}
type service struct{}
func (s *service) Services() (map[bool]*[]string, error) {
services, err := systemctl.ListServices("casaos*")
if err != nil {
return nil, err
}
var running, notRunning []string
for _, service := range services {
if service.Running {
running = append(running, service.Name)
} else {
notRunning = append(notRunning, service.Name)
}
}
result := map[bool]*[]string{
true: &running,
false: &notRunning,
}
return result, nil
}
func NewHealthService() HealthService {
return &service{}
}

View File

@@ -1,26 +1,27 @@
package service
import (
"context"
"encoding/json"
json2 "encoding/json"
"fmt"
"net/http"
"time"
notifyCommon "github.com/IceWhaleTech/CasaOS-Common/model/notify"
"github.com/IceWhaleTech/CasaOS-Common/utils/logger"
"github.com/IceWhaleTech/CasaOS/common"
model2 "github.com/IceWhaleTech/CasaOS/model"
"github.com/IceWhaleTech/CasaOS/model/notify"
"github.com/IceWhaleTech/CasaOS/service/model"
"github.com/IceWhaleTech/CasaOS/types"
"go.uber.org/zap"
"golang.org/x/sync/syncmap"
socketio "github.com/googollee/go-socket.io"
"github.com/gorilla/websocket"
"gorm.io/gorm"
)
var (
//NotifyMsg chan notify.Message
ClientCount int
)
type NotifyServer interface {
GetLog(id string) model.AppNotify
AddLog(log model.AppNotify)
@@ -30,39 +31,47 @@ type NotifyServer interface {
GetList(c int) (list []model.AppNotify)
MarkRead(id string, state int)
// SendText(m model.AppNotify)
SendUninstallAppBySocket(app notifyCommon.Application)
// SendUninstallAppBySocket(app notifyCommon.Application)
SendFileOperateNotify(nowSend bool)
SendInstallAppBySocket(app notifyCommon.Application)
SendStorageBySocket(message notify.StorageMessage)
SendNotify(path string, message map[string]interface{})
//SendInstallAppBySocket(app notifyCommon.Application)
SendNotify(name string, message map[string]interface{})
SettingSystemTempData(message map[string]interface{})
GetSystemTempMap() map[string]interface{}
GetSystemTempMap() syncmap.Map
}
type notifyServer struct {
db *gorm.DB
SystemTempMap map[string]interface{}
SystemTempMap syncmap.Map //[string]interface{}
}
func (i *notifyServer) SettingSystemTempData(message map[string]interface{}) {
for k, v := range message {
i.SystemTempMap[k] = v
i.SystemTempMap.Store(k, v)
//i.SystemTempMap[k] = v
}
}
func (i *notifyServer) SendNotify(path string, message map[string]interface{}) {
SocketServer.BroadcastToRoom("/", "public", path, message)
}
func (i *notifyServer) SendStorageBySocket(message notify.StorageMessage) {
SocketServer.BroadcastToRoom("/", "public", "storage_status", message)
func (i *notifyServer) SendNotify(name string, message map[string]interface{}) {
msg := make(map[string]string)
for k, v := range message {
bt, _ := json.Marshal(v)
msg[k] = string(bt)
}
response, err := MyService.MessageBus().PublishEventWithResponse(context.Background(), common.SERVICENAME, name, msg)
if err != nil {
logger.Error("failed to publish event to message bus", zap.Error(err), zap.Any("event", msg))
return
}
if response.StatusCode() != http.StatusOK {
logger.Error("failed to publish event to message bus", zap.String("status", response.Status()), zap.Any("response", response))
}
// SocketServer.BroadcastToRoom("/", "public", path, message)
}
// Send periodic broadcast messages
func (i *notifyServer) SendFileOperateNotify(nowSend bool) {
if nowSend {
len := 0
FileQueue.Range(func(k, v interface{}) bool {
len++
@@ -74,7 +83,18 @@ func (i *notifyServer) SendFileOperateNotify(nowSend bool) {
if len == 0 {
model.Data = []string{}
listMsg["file_operate"] = model
SocketServer.BroadcastToRoom("/", "public", "file_operate", listMsg)
msg := make(map[string]string)
for k, v := range listMsg {
bt, _ := json.Marshal(v)
msg[k] = string(bt)
}
response, err := MyService.MessageBus().PublishEventWithResponse(context.Background(), common.SERVICENAME, "casaos:file:operate", msg)
if err != nil {
logger.Error("failed to publish event to message bus", zap.Error(err), zap.Any("event", msg))
}
if response.StatusCode() != http.StatusOK {
logger.Error("failed to publish event to message bus", zap.String("status", response.Status()), zap.Any("response", response))
}
return
}
@@ -122,7 +142,19 @@ func (i *notifyServer) SendFileOperateNotify(nowSend bool) {
model.Data = list
listMsg["file_operate"] = model
SocketServer.BroadcastToRoom("/", "public", "file_operate", listMsg)
msg := make(map[string]string)
for k, v := range listMsg {
bt, _ := json.Marshal(v)
msg[k] = string(bt)
}
response, err := MyService.MessageBus().PublishEventWithResponse(context.Background(), common.SERVICENAME, "casaos:file:operate", msg)
if err != nil {
logger.Error("failed to publish event to message bus", zap.Error(err), zap.Any("event", msg))
}
if response.StatusCode() != http.StatusOK {
logger.Error("failed to publish event to message bus", zap.String("status", response.Status()), zap.Any("response", response))
}
} else {
for {
@@ -179,20 +211,30 @@ func (i *notifyServer) SendFileOperateNotify(nowSend bool) {
model.Data = list
listMsg["file_operate"] = model
SocketServer.BroadcastToRoom("/", "public", "file_operate", listMsg)
msg := make(map[string]string)
for k, v := range listMsg {
bt, _ := json.Marshal(v)
msg[k] = string(bt)
}
response, err := MyService.MessageBus().PublishEventWithResponse(context.Background(), common.SERVICENAME, "casaos:file:operate", msg)
if err != nil {
logger.Error("failed to publish event to message bus", zap.Error(err), zap.Any("event", msg))
}
if response.StatusCode() != http.StatusOK {
logger.Error("failed to publish event to message bus", zap.String("status", response.Status()), zap.Any("response", response))
}
time.Sleep(time.Second * 3)
}
}
}
func (i *notifyServer) SendInstallAppBySocket(app notifyCommon.Application) {
SocketServer.BroadcastToRoom("/", "public", "app_install", app)
// func (i *notifyServer) SendInstallAppBySocket(app notifyCommon.Application) {
// SocketServer.BroadcastToRoom("/", "public", "app_install", app)
// }
}
func (i *notifyServer) SendUninstallAppBySocket(app notifyCommon.Application) {
SocketServer.BroadcastToRoom("/", "public", "app_uninstall", app)
}
// func (i *notifyServer) SendUninstallAppBySocket(app notifyCommon.Application) {
// SocketServer.BroadcastToRoom("/", "public", "app_uninstall", app)
// }
func (i *notifyServer) SSR() {
server := socketio.NewServer(nil)
@@ -300,10 +342,10 @@ func SendMeg() {
// }
// }
func (i *notifyServer) GetSystemTempMap() map[string]interface{} {
func (i *notifyServer) GetSystemTempMap() syncmap.Map {
return i.SystemTempMap
}
func NewNotifyService(db *gorm.DB) NotifyServer {
return &notifyServer{db: db, SystemTempMap: make(map[string]interface{})}
return &notifyServer{db: db, SystemTempMap: syncmap.Map{}}
}

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