Skip to content

Commit b702cc5

Browse files
Artem TitovArtem Titov
authored andcommitted
Merge remote-tracking branch 'origin/main' into dev/storage
1 parent aa19956 commit b702cc5

File tree

10 files changed

+252
-126
lines changed

10 files changed

+252
-126
lines changed

common/connectors/storageconn/connector.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ func NewConnector(connection *config.Connection) *Connector {
2525

2626
func (s *Connector) Download(request *Request) *FileResponse {
2727
response := NewFileResponse(*request)
28-
if request.StorageFilename == "" {
29-
response.Error = fmt.Errorf("storage filename not specified")
30-
}
3128

3229
if err := os.MkdirAll(request.BaseFolder, 0775); err != nil {
3330
response.Error = fmt.Errorf("failed to create base folder: %v", err)
@@ -99,10 +96,6 @@ func (s *Connector) Download(request *Request) *FileResponse {
9996

10097
func (s *Connector) Upload(request *Request) *Response {
10198
response := &Response{R: *request}
102-
if request.StorageFilename == "" {
103-
response.Error = fmt.Errorf("storage filename not specified")
104-
return response
105-
}
10699

107100
if request.File == nil {
108101
response.Error = fmt.Errorf("file for upload is not specified")
@@ -121,6 +114,8 @@ func (s *Connector) Upload(request *Request) *Response {
121114
r.SetFormData(map[string]string{
122115
"request": string(requestJSON),
123116
})
117+
118+
// request.StorageFilename can be empty
124119
r.SetFileReader("file", request.StorageFilename, request.File)
125120

126121
resp, err := r.Post(path)
@@ -139,10 +134,6 @@ func (s *Connector) Upload(request *Request) *Response {
139134

140135
func (s *Connector) Delete(request *Request) *Response {
141136
response := &Response{R: *request}
142-
if request.StorageFilename == "" {
143-
response.Error = fmt.Errorf("storage filename not specified")
144-
return response
145-
}
146137

147138
path := "/storage/remove"
148139
r := s.connection.R()
@@ -153,7 +144,7 @@ func (s *Connector) Delete(request *Request) *Response {
153144
return response
154145
}
155146

156-
r.SetQueryParams(map[string]string{
147+
r.SetFormData(map[string]string{
157148
"request": string(requestJSON),
158149
})
159150

common/connectors/storageconn/structs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type Request struct {
3434
// For uploads, File should be specified
3535
File io.Reader `json:"-"`
3636

37-
// StorageFilename should be always specified
37+
// If StorageFilename is not specified, Storage tries to get the filename automatically
3838
StorageFilename string `json:"storageFilename"`
3939
}
4040

common/constants/resource/resource_type.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,37 @@ const (
1717
CheckerOutput
1818
Interactor
1919
// Will be increased
20+
// Don't forget to add a new type to storage/filesystem/resource_info.go
2021
)
2122

23+
// FilepathFolderMapping should contain all types
24+
var FilepathFolderMapping = map[Type]string{
25+
SourceCode: "source",
26+
CompiledBinary: "",
27+
CompileOutput: "",
28+
TestInput: "tests",
29+
TestAnswer: "tests",
30+
TestOutput: "tests",
31+
TestStderr: "tests",
32+
Checker: "checker",
33+
CheckerOutput: "tests",
34+
Interactor: "interactor",
35+
}
36+
37+
// FilepathFilenameMapping should contain all types
38+
var FilepathFilenameMapping = map[Type]string{
39+
SourceCode: "source",
40+
CompiledBinary: "solution",
41+
CompileOutput: "compile.out",
42+
TestInput: "%02d",
43+
TestAnswer: "%02d.a",
44+
TestOutput: "%02d.out",
45+
TestStderr: "%02d.err",
46+
Checker: "checker",
47+
CheckerOutput: "%02d.check",
48+
Interactor: "interactor",
49+
}
50+
2251
type DataType int
2352

2453
const (

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func main() {
3434
if ts.Config.Storage != nil {
3535
err := storage.SetupStorage(ts)
3636
if err != nil {
37-
logger.Panic(err.Error())
37+
logger.Panic("Can not setup storage, error: %v", err.Error())
3838
}
3939
} else {
4040
logger.Info("storage is not configured, skipping storage component")

storage/filesystem/filesystem.go

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"mime/multipart"
66
"os"
77
"path/filepath"
8+
"strconv"
89
"strings"
910
"testing_system/common/config"
1011
"testing_system/lib/logger"
@@ -22,24 +23,34 @@ func NewFilesystem(storageConfig *config.StorageConfig) IFilesystem {
2223
}
2324

2425
func (filesystem *Filesystem) generatePathFromID(prefix string, id string) string {
25-
var parts []string
26-
parts = append(parts, prefix)
26+
parts := []string{prefix}
2727
for i := 0; i < len(id); i += int(filesystem.BlockSize) {
2828
end := min(i+int(filesystem.BlockSize), len(id))
2929
parts = append(parts, id[i:end])
3030
}
3131
return filepath.Join(parts...)
3232
}
3333

34-
func (filesystem *Filesystem) SaveFile(c *gin.Context, prefix string, id string, filename string, file *multipart.FileHeader) error {
35-
fullPath, err := filesystem.GetFilePath(prefix, id, filename)
34+
func (filesystem *Filesystem) SaveFile(c *gin.Context, resourceInfo *ResourceInfo, file *multipart.FileHeader) error {
35+
fullPath, err := filesystem.BuildFilePath(resourceInfo)
3636
if err != nil {
3737
return err
3838
}
3939

40-
fullDirPath := filepath.Dir(fullPath)
41-
if err := os.MkdirAll(fullDirPath, 0755); err != nil {
42-
return fmt.Errorf("failed to create directory: %w", err)
40+
dir := filepath.Dir(fullPath)
41+
if err := os.MkdirAll(dir, 0755); err != nil {
42+
return fmt.Errorf("failed to create directory %s: %w", dir, err)
43+
}
44+
45+
entries, err := filesystem.GetDirEntriesNames(dir)
46+
if err != nil {
47+
return err
48+
}
49+
50+
if resourceInfo.EmptyStorageFilename {
51+
if len(entries) > 1 || (len(entries) == 1 && entries[0] != filepath.Base(resourceInfo.Filepath)) {
52+
return fmt.Errorf("failed to save file: directory %s already contains some files different from default filename, but StorageFilename is not specified", dir)
53+
}
4354
}
4455

4556
if _, err := os.Stat(fullPath); err == nil {
@@ -49,17 +60,28 @@ func (filesystem *Filesystem) SaveFile(c *gin.Context, prefix string, id string,
4960
return c.SaveUploadedFile(file, fullPath)
5061
}
5162

52-
func (filesystem *Filesystem) RemoveFile(prefix string, id string, filename string) error {
53-
fullPath, err := filesystem.GetFilePath(prefix, id, filename)
63+
func (filesystem *Filesystem) RemoveFile(resourceInfo *ResourceInfo) error {
64+
fullPath, err := filesystem.BuildFilePath(resourceInfo)
5465
if err != nil {
5566
return err
5667
}
5768

69+
dir := filepath.Dir(fullPath)
70+
entries, err := filesystem.GetDirEntriesNames(dir)
71+
if err != nil {
72+
return err
73+
}
74+
75+
if resourceInfo.EmptyStorageFilename {
76+
if len(entries) > 1 || (len(entries) == 1 && entries[0] != filepath.Base(resourceInfo.Filepath)) {
77+
return fmt.Errorf("failed to remove file: directory %s contains some files different from default filename, but StorageFilename is not specified", dir)
78+
}
79+
}
80+
5881
if err := os.Remove(fullPath); err != nil {
5982
return fmt.Errorf("failed to remove file %s: %w", fullPath, err)
6083
}
6184

62-
dir := filepath.Dir(fullPath)
6385
basePath, _ := filepath.Abs(filesystem.Basepath)
6486

6587
for {
@@ -82,15 +104,32 @@ func (filesystem *Filesystem) RemoveFile(prefix string, id string, filename stri
82104
return nil
83105
}
84106

85-
func (filesystem *Filesystem) GetFilePath(prefix, id, filename string) (string, error) {
86-
if prefix == "" || id == "" || filename == "" {
87-
return "", fmt.Errorf("prefix, id, and filename cannot be empty")
107+
func (filesystem *Filesystem) GetFile(resourceInfo *ResourceInfo) (string, error) {
108+
fullPath, err := filesystem.BuildFilePath(resourceInfo)
109+
if err != nil {
110+
return "", err
111+
}
112+
113+
dir := filepath.Dir(fullPath)
114+
entries, err := filesystem.GetDirEntriesNames(dir)
115+
if err != nil {
116+
return "", err
117+
}
118+
119+
if resourceInfo.EmptyStorageFilename {
120+
if len(entries) > 1 || (len(entries) == 1 && entries[0] != filepath.Base(resourceInfo.Filepath)) {
121+
return "", fmt.Errorf("failed to get file: directory %s contains some files different from default filename, but StorageFilename is not specified", dir)
122+
}
88123
}
89124

90-
cleanFilename := filepath.Base(filename)
125+
return fullPath, nil
126+
}
91127

92-
subpath := filesystem.generatePathFromID(prefix, id)
93-
fullPath := filepath.Join(filesystem.Basepath, subpath, cleanFilename)
128+
func (filesystem *Filesystem) BuildFilePath(resourceInfo *ResourceInfo) (string, error) {
129+
cleanFilename := filepath.Base(resourceInfo.Filepath)
130+
131+
subpathID := filesystem.generatePathFromID(resourceInfo.DataType.String(), strconv.FormatUint(resourceInfo.ID, 10))
132+
fullPath := filepath.Join(filesystem.Basepath, subpathID, cleanFilename)
94133

95134
absBasepath, err := filepath.Abs(filesystem.Basepath)
96135
if err != nil {
@@ -103,8 +142,23 @@ func (filesystem *Filesystem) GetFilePath(prefix, id, filename string) (string,
103142
}
104143

105144
if !strings.HasPrefix(absFullPath, absBasepath) {
106-
return "", fmt.Errorf("invalid path: basepath is not a prefix of fullpath")
145+
return "", fmt.Errorf("invalid path: filesystem basepath is not a prefix of file fullpath")
107146
}
108147

109148
return fullPath, nil
110149
}
150+
151+
func (filesystem *Filesystem) GetDirEntriesNames(dir string) ([]string, error) {
152+
entries, err := os.ReadDir(dir)
153+
if err != nil {
154+
return nil, fmt.Errorf("failed to read directory %s: %v", dir, err)
155+
}
156+
157+
names := make([]string, 0, len(entries))
158+
159+
for _, entry := range entries {
160+
names = append(names, entry.Name())
161+
}
162+
163+
return names, nil
164+
}

storage/filesystem/interface.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
)
88

99
type IFilesystem interface {
10-
SaveFile(c *gin.Context, prefix string, id string, filename string, file *multipart.FileHeader) error
11-
RemoveFile(prefix string, id string, filename string) error
12-
GetFilePath(prefix string, id string, filename string) (string, error)
10+
SaveFile(c *gin.Context, resourceInfo *ResourceInfo, file *multipart.FileHeader) error
11+
RemoveFile(resourceInfo *ResourceInfo) error
12+
GetFile(resourceInfo *ResourceInfo) (string, error)
1313
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package filesystem
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"path/filepath"
7+
"testing_system/common/connectors/storageconn"
8+
"testing_system/common/constants/resource"
9+
)
10+
11+
type ResourceInfo struct {
12+
Request *storageconn.Request
13+
ID uint64
14+
Filepath string
15+
DataType resource.DataType
16+
EmptyStorageFilename bool
17+
}
18+
19+
func (resourseInfo *ResourceInfo) ParseDataType() error {
20+
request := resourseInfo.Request
21+
22+
switch request.Resource {
23+
case resource.Checker, resource.Interactor:
24+
resourseInfo.DataType = resource.Problem
25+
return nil
26+
case resource.TestInput, resource.TestAnswer:
27+
resourseInfo.DataType = resource.Problem
28+
return nil
29+
case resource.SourceCode, resource.CompiledBinary, resource.CompileOutput:
30+
resourseInfo.DataType = resource.Submission
31+
return nil
32+
case resource.TestOutput, resource.TestStderr, resource.CheckerOutput:
33+
resourseInfo.DataType = resource.Submission
34+
return nil
35+
default:
36+
return errors.New("unknown resource type")
37+
}
38+
}
39+
40+
func (resourseInfo *ResourceInfo) ParseDataID() error {
41+
request := resourseInfo.Request
42+
43+
switch resourseInfo.DataType {
44+
case resource.Problem:
45+
if request.ProblemID == 0 {
46+
return errors.New("ProblemID is not specified for promlem resource")
47+
}
48+
resourseInfo.ID = request.ProblemID
49+
return nil
50+
case resource.Submission:
51+
if request.SubmitID == 0 {
52+
return errors.New("SubmitID is not specified for submission resource")
53+
}
54+
resourseInfo.ID = request.SubmitID
55+
return nil
56+
default:
57+
return errors.New("unavailable to get data id")
58+
}
59+
}
60+
61+
func (resourseInfo *ResourceInfo) ParseFilepath() error {
62+
filepathFolder, err := resourseInfo.parseFilepathFolder()
63+
64+
if err != nil {
65+
return fmt.Errorf("unavailable to parse filepathFolder: %v", err)
66+
}
67+
68+
filepathFilename, err := resourseInfo.parseFilepathFilename()
69+
70+
if err != nil {
71+
return fmt.Errorf("unavailable to parse filepathFilename: %v", err)
72+
}
73+
74+
resourseInfo.Filepath = filepath.Join(filepathFolder, filepathFilename)
75+
76+
return nil
77+
}
78+
79+
func (resourseInfo *ResourceInfo) parseFilepathFolder() (string, error) {
80+
request := resourseInfo.Request
81+
82+
filepathFolder, ok := resource.FilepathFolderMapping[request.Resource]
83+
if !ok {
84+
return "", fmt.Errorf("there is no type %s in FilepathFolderMapping", request.Resource.String())
85+
}
86+
87+
return filepathFolder, nil
88+
}
89+
90+
func (resourseInfo *ResourceInfo) parseFilepathFilename() (string, error) {
91+
request := resourseInfo.Request
92+
93+
if request.StorageFilename != "" {
94+
return request.StorageFilename, nil
95+
}
96+
97+
resourseInfo.EmptyStorageFilename = true
98+
99+
filepathFilename, ok := resource.FilepathFilenameMapping[request.Resource]
100+
if !ok {
101+
return "", fmt.Errorf("there is no type %s in FilepathFilenameMapping", request.Resource.String())
102+
}
103+
104+
switch request.Resource {
105+
case resource.TestInput, resource.TestAnswer, resource.TestOutput, resource.TestStderr, resource.CheckerOutput:
106+
if request.TestID == 0 {
107+
return "", errors.New("TestID is not specified for test resource")
108+
}
109+
return fmt.Sprintf(filepathFilename, request.TestID), nil
110+
default:
111+
return filepathFilename, nil
112+
}
113+
}

0 commit comments

Comments
 (0)