1
0
mirror of https://github.com/haiwen/seafile-server.git synced 2025-08-17 14:27:32 +00:00
seafile-server/fileserver/fileserver.go
Jiaqiang Xu 7420b8d738
Go fileserver (#437)
* Initial commit for fileserver written in golang.

[gofileserver] Fix some syntaxt errors.

Add fs backend and objstore test (#352)

* Add fs backend and objstore test

* modify test case and optimize fs backend

* Modify function name and first write temporary files

* Don't need to reopen the temp files

Add comment for objstore (#354)

* Add comment for objstore

* Modify comment

Add commitmgr and test case (#356)

* Add commitmgr and test case

* Redefine the interface

* Modify comment and interface

* Modify parameter and del unused method

* Add comment for FromData and ToData

Add blockmgr and test case (#357)

* Add blockmgr and test case

* Modify comment and interface

Add fsmgr and test case (#358)

* Add fsmgr and test case

* Add save interface and error details

* Modify errors and comments

Add searpc package and test case (#360)

* Add searpc package

* Add searpc test case

* Add return error and add Request struct

* Modify returned error

* Modify comments

add checkPerm (#369)

Add file and block download (#363)

* Add file and block download

* Modify init and use aes algorithm

* Get block by offset and add stat method

* Modify objID's type

* Fix reset pos after add start

* Add http error handing and record log when failed to read block or write block to response

* Modify http return code and value names

* Modify http return code and add log info

* Block read add comment and only repeat once

load ccnetdb and support sqlite (#371)

Add zip download (#372)

* Add zip download

* Modify pack dir and log info

* Modify http return code and use Deflate zip compression methods

add /repo/<repo-id>/permission-check (#375)

add /<repo-id>/commit/HEAD (#377)

add  /repo/<repo-id>/commit/<id> (#379)

add /repo/<repo-id>/block/<id> (#380)

add /repo/<repo-id>/fs-id-list (#383)

add /repo/head-commits-multi (#388)

Add file upload api (#378)

* Add file upload api

* Upload api implements post multi files and create relative path

* Modify handle error and save files directly

* Fix rebase conflict

* index block use channel and optimize mkdir with parents

* Handle jobs and results in a loop

* Mkdir with parents use postMultiFiles and use pointer of SeafDirent

* Del diff_simple size_sched virtual_repo

* Need to check the path with and without slash

* Modify merge trees and add merge test case

* Del postFile and don't close results channel

* Close the file and remove multipart temp file

* Modify merge test case and compare the first name of path

* Use pointer of Entries for SeafDir

* Add test cases for different situations

add /repo/<repo-id>/pack-fs (#389)

add POST /<repo-id>/check-fs and /<repo-id>/check-blocks (#396)

Merge compute repo (#397)

* Add update repo size and merge virtual repo

* Eliminate lint warnings

* Uncomment merge virtual repo and compute repo size

* Need init the dents

* Use interface{} param and modify removeElems

* Move update dir to file.go and modify logs

* Del sync pkg

add PUT /<repo-id>/commit/<commit-id> (#400)

add PUT /<repo-id>/block/<id> (#401)

add POST /<repo-id>/recv-fs (#398)

add PUT /<repo-id>/commit/HEAD (#402)

Add http return code (#403)

Add file update API (#399)

* Add file update API

* Add GetObjIDByPath and fix change size error

* Add traffic statistics for update api

add diffTrees unit test (#391)

add GET /accessible-repos (#406)

add GET /<repo-id>/block-map/<file-id> (#405)

Add test update repo size and merge virtual repo (#409)

* Update dir need update repo size

* Add test update repo size and merge virtual repo

* Add delay for test ajax

* Add delay before get repo size and modify comment

Use go fileserver for unit test (#410)

* Use go fileserver for unit test

* Blocking scheduling update repo size

* Add delay because of sqlite doesn't support concurrency

* Post use multipart form encode

* Del mysql database when test finished

* Fix merge virtual repo failed when use sqlite3

Add upload block API (#412)

fixed error

Add quota-check API (#426)

use diff package

* Use central conf for go fileserver (#428)

* Use central conf for go fileserver

* Fix log error

* use store id and remove share get repo owner (#430)

* Fix permission error (#432)

Co-authored-by: feiniks <36756310+feiniks@users.noreply.github.com>
Co-authored-by: Xiangyue Cai <caixiangyue007@gmail.com>
2021-01-04 11:41:53 +08:00

433 lines
12 KiB
Go

// Main package for Seafile file server.
package main
import (
"database/sql"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
"github.com/haiwen/seafile-server/fileserver/blockmgr"
"github.com/haiwen/seafile-server/fileserver/commitmgr"
"github.com/haiwen/seafile-server/fileserver/fsmgr"
"github.com/haiwen/seafile-server/fileserver/repomgr"
"github.com/haiwen/seafile-server/fileserver/searpc"
"github.com/haiwen/seafile-server/fileserver/share"
_ "github.com/mattn/go-sqlite3"
"gopkg.in/ini.v1"
)
var dataDir, absDataDir string
var centralDir string
var logFile, absLogFile string
var rpcPipePath string
var dbType string
var groupTableName string
var cloudMode bool
var seafileDB, ccnetDB *sql.DB
// when SQLite is used, user and group db are separated.
var userDB, groupDB *sql.DB
type fileServerOptions struct {
host string
port uint32
maxUploadSize uint64
maxDownloadDirSize uint64
// Block size for indexing uploaded files
fixedBlockSize uint64
// Maximum number of goroutines to index uploaded files
maxIndexingThreads uint32
webTokenExpireTime uint32
// File mode for temp files
clusterSharedTempFileMode uint32
windowsEncoding string
// Timeout for fs-id-list requests.
fsIDListRequestTimeout uint32
}
var options fileServerOptions
func init() {
flag.StringVar(&centralDir, "F", "", "central config directory")
flag.StringVar(&dataDir, "d", "", "seafile data directory")
flag.StringVar(&logFile, "l", "", "log file path")
flag.StringVar(&rpcPipePath, "p", "", "rpc pipe path")
}
func loadCcnetDB() {
ccnetConfPath := filepath.Join(centralDir, "ccnet.conf")
config, err := ini.Load(ccnetConfPath)
if err != nil {
log.Fatalf("Failed to load ccnet.conf: %v", err)
}
section, err := config.GetSection("Database")
if err != nil {
log.Fatal("No database section in ccnet.conf.")
}
var dbEngine string = "sqlite"
key, err := section.GetKey("ENGINE")
if err == nil {
dbEngine = key.String()
}
if strings.EqualFold(dbEngine, "mysql") {
if key, err = section.GetKey("HOST"); err != nil {
log.Fatal("No database host in ccnet.conf.")
}
host := key.String()
if key, err = section.GetKey("USER"); err != nil {
log.Fatal("No database user in ccnet.conf.")
}
user := key.String()
if key, err = section.GetKey("PASSWD"); err != nil {
log.Fatal("No database password in ccnet.conf.")
}
password := key.String()
if key, err = section.GetKey("DB"); err != nil {
log.Fatal("No database db_name in ccnet.conf.")
}
dbName := key.String()
port := 3306
if key, err = section.GetKey("PORT"); err == nil {
port, _ = key.Int()
}
unixSocket := ""
if key, err = section.GetKey("UNIX_SOCKET"); err == nil {
unixSocket = key.String()
}
useTLS := false
if key, err = section.GetKey("USE_SSL"); err == nil {
useTLS, _ = key.Bool()
}
var dsn string
if unixSocket == "" {
dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=%t", user, password, host, port, dbName, useTLS)
} else {
dsn = fmt.Sprintf("%s:%s@unix(%s)/%s", user, password, unixSocket, dbName)
}
ccnetDB, err = sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("Failed to open database: %v", err)
}
} else if strings.EqualFold(dbEngine, "sqlite") {
ccnetDBPath := filepath.Join(centralDir, "groupmgr.db")
ccnetDB, err = sql.Open("sqlite3", ccnetDBPath)
if err != nil {
log.Fatalf("Failed to open database %s: %v", ccnetDBPath, err)
}
} else {
log.Fatalf("Unsupported database %s.", dbEngine)
}
}
func loadSeafileDB() {
var seafileConfPath string
seafileConfPath = filepath.Join(centralDir, "seafile.conf")
config, err := ini.Load(seafileConfPath)
if err != nil {
log.Fatalf("Failed to load seafile.conf: %v", err)
}
section, err := config.GetSection("database")
if err != nil {
log.Fatal("No database section in seafile.conf.")
}
var dbEngine string = "sqlite"
key, err := section.GetKey("type")
if err == nil {
dbEngine = key.String()
}
if strings.EqualFold(dbEngine, "mysql") {
if key, err = section.GetKey("host"); err != nil {
log.Fatal("No database host in seafile.conf.")
}
host := key.String()
if key, err = section.GetKey("user"); err != nil {
log.Fatal("No database user in seafile.conf.")
}
user := key.String()
if key, err = section.GetKey("password"); err != nil {
log.Fatal("No database password in seafile.conf.")
}
password := key.String()
if key, err = section.GetKey("db_name"); err != nil {
log.Fatal("No database db_name in seafile.conf.")
}
dbName := key.String()
port := 3306
if key, err = section.GetKey("port"); err == nil {
port, _ = key.Int()
}
unixSocket := ""
if key, err = section.GetKey("unix_socket"); err == nil {
unixSocket = key.String()
}
useTLS := false
if key, err = section.GetKey("use_ssl"); err == nil {
useTLS, _ = key.Bool()
}
var dsn string
if unixSocket == "" {
dsn = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?tls=%t", user, password, host, port, dbName, useTLS)
} else {
dsn = fmt.Sprintf("%s:%s@unix(%s)/%s", user, password, unixSocket, dbName)
}
seafileDB, err = sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("Failed to open database: %v", err)
}
} else if strings.EqualFold(dbEngine, "sqlite") {
seafileDBPath := filepath.Join(absDataDir, "seafile.db")
seafileDB, err = sql.Open("sqlite3", seafileDBPath)
if err != nil {
log.Fatalf("Failed to open database %s: %v", seafileDBPath, err)
}
} else {
log.Fatalf("Unsupported database %s.", dbEngine)
}
dbType = dbEngine
}
func loadFileServerOptions() {
var seafileConfPath string
seafileConfPath = filepath.Join(centralDir, "seafile.conf")
config, err := ini.Load(seafileConfPath)
if err != nil {
log.Fatalf("Failed to load seafile.conf: %v", err)
}
cloudMode = false
if section, err := config.GetSection("general"); err == nil {
if key, err := section.GetKey("cloud_mode"); err == nil {
cloudMode, _ = key.Bool()
}
}
initDefaultOptions()
if section, err := config.GetSection("fileserver"); err == nil {
if key, err := section.GetKey("host"); err == nil {
options.host = key.String()
}
if key, err := section.GetKey("port"); err == nil {
port, err := key.Uint()
if err == nil {
options.port = uint32(port)
}
}
if key, err := section.GetKey("max_indexing_threads"); err == nil {
threads, err := key.Uint()
if err == nil {
options.maxIndexingThreads = uint32(threads)
}
}
if key, err := section.GetKey("fixed_block_size"); err == nil {
blkSize, err := key.Uint64()
if err == nil {
options.fixedBlockSize = blkSize
}
}
if key, err := section.GetKey("web_token_expire_time"); err == nil {
expire, err := key.Uint()
if err == nil {
options.webTokenExpireTime = uint32(expire)
}
}
if key, err := section.GetKey("cluster_shared_temp_file_mode"); err == nil {
fileMode, err := key.Uint()
if err == nil {
options.clusterSharedTempFileMode = uint32(fileMode)
}
}
}
ccnetConfPath := filepath.Join(centralDir, "ccnet.conf")
config, err = ini.Load(ccnetConfPath)
if err != nil {
log.Fatalf("Failed to load ccnet.conf: %v", err)
}
groupTableName = "Group"
if section, err := config.GetSection("GROUP"); err == nil {
if key, err := section.GetKey("TABLE_NAME"); err == nil {
groupTableName = key.String()
}
}
}
func initDefaultOptions() {
options.host = "0.0.0.0"
options.port = 8082
options.maxDownloadDirSize = 100 * (1 << 20)
options.fixedBlockSize = 1 << 23
options.maxIndexingThreads = 1
options.webTokenExpireTime = 7200
options.clusterSharedTempFileMode = 0600
}
func main() {
flag.Parse()
if centralDir == "" {
log.Fatal("central config directory must be specified.")
}
_, err := os.Stat(centralDir)
if os.IsNotExist(err) {
log.Fatalf("central config directory %s doesn't exist: %v.", centralDir, err)
}
loadCcnetDB()
if dataDir == "" {
log.Fatal("seafile data directory must be specified.")
}
_, err = os.Stat(dataDir)
if os.IsNotExist(err) {
log.Fatalf("seafile data directory %s doesn't exist: %v.", dataDir, err)
}
absDataDir, err = filepath.Abs(dataDir)
if err != nil {
log.Fatalf("Failed to convert seafile data dir to absolute path: %v.", err)
}
loadSeafileDB()
loadFileServerOptions()
if logFile == "" {
absLogFile = filepath.Join(absDataDir, "seafile.log")
fp, err := os.OpenFile(absLogFile, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
log.Fatalf("Failed to open or create log file: %v", err)
}
log.SetOutput(fp)
} else if logFile != "-" {
absLogFile, err = filepath.Abs(logFile)
if err != nil {
log.Fatalf("Failed to convert log file path to absolute path: %v", err)
}
fp, err := os.OpenFile(absLogFile, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
log.Fatalf("Failed to open or create log file: %v", err)
}
log.SetOutput(fp)
}
// When logFile is "-", use default output (StdOut)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
repomgr.Init(seafileDB)
fsmgr.Init(centralDir, dataDir)
blockmgr.Init(centralDir, dataDir)
commitmgr.Init(centralDir, dataDir)
share.Init(ccnetDB, seafileDB, groupTableName, cloudMode)
rpcClientInit()
syncAPIInit()
sizeSchedulerInit()
initUpload()
router := newHTTPRouter()
log.Print("Seafile file server started.")
addr := fmt.Sprintf("%s:%d", options.host, options.port)
err = http.ListenAndServe(addr, router)
if err != nil {
log.Printf("File server exiting: %v", err)
}
}
var rpcclient *searpc.Client
func rpcClientInit() {
var pipePath string
if rpcPipePath != "" {
pipePath = filepath.Join(rpcPipePath, "seafile.sock")
} else {
pipePath = filepath.Join(absDataDir, "seafile.sock")
}
rpcclient = searpc.Init(pipePath, "seafserv-threaded-rpcserver")
}
func newHTTPRouter() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/protocol-version", handleProtocolVersion)
r.Handle("/files/{.*}/{.*}", appHandler(accessCB))
r.Handle("/blks/{.*}/{.*}", appHandler(accessBlksCB))
r.Handle("/zip/{.*}", appHandler(accessZipCB))
r.Handle("/upload-api/{.*}", appHandler(uploadAPICB))
r.Handle("/upload-aj/{.*}", appHandler(uploadAjaxCB))
r.Handle("/update-api/{.*}", appHandler(updateAPICB))
r.Handle("/update-aj/{.*}", appHandler(updateAjaxCB))
r.Handle("/upload-blks-api/{.*}", appHandler(uploadBlksAPICB))
r.Handle("/upload-raw-blks-api/{.*}", appHandler(uploadRawBlksAPICB))
// file syncing api
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/permission-check/",
appHandler(permissionCheckCB))
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/commit/{HEAD:HEAD\\/?}",
appHandler(headCommitOperCB))
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/commit/{id:[\\da-z]{40}}",
appHandler(commitOperCB))
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/block/{id:[\\da-z]{40}}",
appHandler(blockOperCB))
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/fs-id-list/",
appHandler(getFsObjIDCB))
r.Handle("/repo/head-commits-multi/",
appHandler(headCommitsMultiCB))
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/pack-fs/",
appHandler(packFSCB))
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/check-fs/",
appHandler(checkFSCB))
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/check-blocks/",
appHandler(checkBlockCB))
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/recv-fs/",
appHandler(recvFSCB))
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/quota-check/",
appHandler(getCheckQuotaCB))
// seadrive api
r.Handle("/repo/{repoid:[\\da-z]{8}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{4}-[\\da-z]{12}}/block-map/{id:[\\da-z]{40}}",
appHandler(getBlockMapCB))
r.Handle("/accessible-repos", appHandler(getAccessibleRepoListCB))
return r
}
func handleProtocolVersion(rsp http.ResponseWriter, r *http.Request) {
io.WriteString(rsp, "{\"version\": 2}")
}
type appError struct {
Error error
Message string
Code int
}
type appHandler func(http.ResponseWriter, *http.Request) *appError
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil {
if e.Error != nil && e.Code == http.StatusInternalServerError {
log.Printf("path %s internal server error: %v\n", r.URL.Path, e.Error)
}
http.Error(w, e.Message, e.Code)
}
}