Skip to content

Commit be6cc28

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

File tree

11 files changed

+233
-149
lines changed

11 files changed

+233
-149
lines changed

common/connectors/storageconn/connector.go

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io"
88
"mime"
9+
"net/http"
910
"os"
1011
"path/filepath"
1112
"testing_system/common/config"
@@ -25,9 +26,6 @@ func NewConnector(connection *config.Connection) *Connector {
2526

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

3230
if err := os.MkdirAll(request.BaseFolder, 0775); err != nil {
3331
response.Error = fmt.Errorf("failed to create base folder: %v", err)
@@ -54,7 +52,11 @@ func (s *Connector) Download(request *Request) *FileResponse {
5452
}
5553

5654
if resp.IsError() {
57-
response.Error = fmt.Errorf("get request failed with status: %v", resp.Status())
55+
if resp.StatusCode() == http.StatusNotFound {
56+
response.Error = ErrStorageFileNotFound
57+
} else {
58+
response.Error = fmt.Errorf("get request failed with status: %v", resp.Status())
59+
}
5860
return response
5961
}
6062

@@ -99,10 +101,6 @@ func (s *Connector) Download(request *Request) *FileResponse {
99101

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

107105
if request.File == nil {
108106
response.Error = fmt.Errorf("file for upload is not specified")
@@ -121,6 +119,8 @@ func (s *Connector) Upload(request *Request) *Response {
121119
r.SetFormData(map[string]string{
122120
"request": string(requestJSON),
123121
})
122+
123+
// request.StorageFilename can be empty
124124
r.SetFileReader("file", request.StorageFilename, request.File)
125125

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

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

147143
path := "/storage/remove"
148144
r := s.connection.R()
@@ -153,7 +149,7 @@ func (s *Connector) Delete(request *Request) *Response {
153149
return response
154150
}
155151

156-
r.SetQueryParams(map[string]string{
152+
r.SetFormData(map[string]string{
157153
"request": string(requestJSON),
158154
})
159155

@@ -170,4 +166,3 @@ func (s *Connector) Delete(request *Request) *Response {
170166

171167
return response
172168
}
173-
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package storageconn
2+
3+
import "errors"
4+
5+
var ErrStorageFileNotFound = errors.New("file not found in storage")

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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,33 @@ 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+
var FilepathFilenameMapping = map[Type]string{
38+
CompiledBinary: "solution",
39+
CompileOutput: "compile.out",
40+
TestInput: "%02d",
41+
TestAnswer: "%02d.a",
42+
TestOutput: "%02d.out",
43+
TestStderr: "%02d.err",
44+
CheckerOutput: "%02d.check",
45+
}
46+
2247
type DataType int
2348

2449
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: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"mime/multipart"
66
"os"
77
"path/filepath"
8-
"strings"
8+
"strconv"
99
"testing_system/common/config"
1010
"testing_system/lib/logger"
1111

@@ -21,25 +21,24 @@ func NewFilesystem(storageConfig *config.StorageConfig) IFilesystem {
2121
return &Filesystem{Basepath: storageConfig.StoragePath, BlockSize: storageConfig.BlockSize}
2222
}
2323

24-
func (filesystem *Filesystem) generatePathFromID(prefix string, id string) string {
25-
var parts []string
26-
parts = append(parts, prefix)
24+
func (filesystem *Filesystem) generatePathFromID(id string) string {
25+
parts := []string{}
2726
for i := 0; i < len(id); i += int(filesystem.BlockSize) {
2827
end := min(i+int(filesystem.BlockSize), len(id))
2928
parts = append(parts, id[i:end])
3029
}
3130
return filepath.Join(parts...)
3231
}
3332

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)
33+
func (filesystem *Filesystem) SaveFile(c *gin.Context, resourceInfo *ResourceInfo, file *multipart.FileHeader) error {
34+
fullPath, err := filesystem.BuildFilePath(resourceInfo)
3635
if err != nil {
3736
return err
3837
}
3938

40-
fullDirPath := filepath.Dir(fullPath)
41-
if err := os.MkdirAll(fullDirPath, 0755); err != nil {
42-
return fmt.Errorf("failed to create directory: %w", err)
39+
dir := filepath.Dir(fullPath)
40+
if err := os.MkdirAll(dir, 0755); err != nil {
41+
return fmt.Errorf("failed to create directory %s: %w", dir, err)
4342
}
4443

4544
if _, err := os.Stat(fullPath); err == nil {
@@ -49,8 +48,8 @@ func (filesystem *Filesystem) SaveFile(c *gin.Context, prefix string, id string,
4948
return c.SaveUploadedFile(file, fullPath)
5049
}
5150

52-
func (filesystem *Filesystem) RemoveFile(prefix string, id string, filename string) error {
53-
fullPath, err := filesystem.GetFilePath(prefix, id, filename)
51+
func (filesystem *Filesystem) RemoveFile(resourceInfo *ResourceInfo) error {
52+
fullPath, err := filesystem.BuildFilePath(resourceInfo)
5453
if err != nil {
5554
return err
5655
}
@@ -60,16 +59,18 @@ func (filesystem *Filesystem) RemoveFile(prefix string, id string, filename stri
6059
}
6160

6261
dir := filepath.Dir(fullPath)
63-
basePath, _ := filepath.Abs(filesystem.Basepath)
6462

6563
for {
66-
absDir, _ := filepath.Abs(dir)
67-
if absDir == basePath || !strings.HasPrefix(absDir, basePath) {
64+
if dir == filesystem.Basepath {
6865
break
6966
}
7067

7168
entries, err := os.ReadDir(dir)
72-
if err != nil || len(entries) > 0 {
69+
70+
if err != nil {
71+
return fmt.Errorf("failed to read directory %s: %w", dir, err)
72+
}
73+
if len(entries) > 0 {
7374
break
7475
}
7576

@@ -82,28 +83,35 @@ func (filesystem *Filesystem) RemoveFile(prefix string, id string, filename stri
8283
return nil
8384
}
8485

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")
86+
func (filesystem *Filesystem) GetFile(resourceInfo *ResourceInfo) error {
87+
fullPath, err := filesystem.BuildFilePath(resourceInfo)
88+
if err != nil {
89+
return err
8890
}
8991

90-
cleanFilename := filepath.Base(filename)
91-
92-
subpath := filesystem.generatePathFromID(prefix, id)
93-
fullPath := filepath.Join(filesystem.Basepath, subpath, cleanFilename)
92+
_, err = os.Stat(fullPath)
9493

95-
absBasepath, err := filepath.Abs(filesystem.Basepath)
9694
if err != nil {
97-
return "", err
95+
return err
9896
}
9997

100-
absFullPath, err := filepath.Abs(fullPath)
101-
if err != nil {
102-
return "", err
103-
}
98+
return nil
99+
}
104100

105-
if !strings.HasPrefix(absFullPath, absBasepath) {
106-
return "", fmt.Errorf("invalid path: basepath is not a prefix of fullpath")
101+
func (filesystem *Filesystem) BuildFilePath(resourceInfo *ResourceInfo) (string, error) {
102+
subpathID := filesystem.generatePathFromID(strconv.FormatUint(resourceInfo.ID, 10))
103+
fullPath := filepath.Join(filesystem.Basepath, resourceInfo.DataType.String(), subpathID, resourceInfo.Filepath)
104+
105+
if resourceInfo.EmptyStorageFilename {
106+
entries, err := os.ReadDir(fullPath)
107+
if err != nil {
108+
return "", fmt.Errorf("failed to read directory %s: %v", fullPath, err)
109+
}
110+
if len(entries) != 1 || entries[0].IsDir() {
111+
return "", fmt.Errorf("StorageFilename is not specified, but directory %s does not contain exactly one file", fullPath)
112+
}
113+
fullPath = filepath.Join(fullPath, entries[0].Name())
114+
resourceInfo.Filepath = fullPath // It lets use resourceInfo.Filepath after successful SaveFile, RemoveFile and GetFile calls
107115
}
108116

109117
return fullPath, nil

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) error
1313
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 problem 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 filepathFilename, ok := resource.FilepathFilenameMapping[request.Resource]; ok {
94+
switch request.Resource {
95+
case resource.TestInput, resource.TestAnswer, resource.TestOutput, resource.TestStderr, resource.CheckerOutput:
96+
if request.TestID == 0 {
97+
return "", errors.New("TestID is not specified for test resource")
98+
}
99+
return fmt.Sprintf(filepathFilename, request.TestID), nil
100+
default:
101+
return filepathFilename, nil
102+
}
103+
}
104+
105+
if request.StorageFilename != "" {
106+
return request.StorageFilename, nil
107+
}
108+
resourseInfo.EmptyStorageFilename = true
109+
110+
return "", nil
111+
}

0 commit comments

Comments
 (0)