Skip to content

Commit 3a9f160

Browse files
[PSL-1185] split file using 7z (#879)
* [PSL-1185] split file using 7z * [PSL-1185] updates
1 parent c0c9710 commit 3a9f160

File tree

9 files changed

+195
-18
lines changed

9 files changed

+195
-18
lines changed

common/storage/ticketstore/store.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package ticketstore
33
import (
44
"context"
55
"fmt"
6+
"path/filepath"
7+
68
"github.com/jmoiron/sqlx"
79
"github.com/pastelnetwork/gonode/common/configurer"
810
"github.com/pastelnetwork/gonode/common/log"
9-
"path/filepath"
1011
)
1112

1213
const createFilesTable string = `

mixins/go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ require (
2525
github.com/fogleman/gg v1.3.0 // indirect
2626
github.com/go-errors/errors v1.4.2 // indirect
2727
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
28+
github.com/jmoiron/sqlx v1.3.5 // indirect
2829
github.com/jpillora/longestcommon v0.0.0-20161227235612-adb9d91ee629 // indirect
2930
github.com/klauspost/compress v1.16.7 // indirect
3031
github.com/kolesa-team/go-webp v1.0.4 // indirect
3132
github.com/makiuchi-d/gozxing v0.1.1 // indirect
3233
github.com/mattn/go-colorable v0.1.13 // indirect
3334
github.com/mattn/go-isatty v0.0.16 // indirect
35+
github.com/mattn/go-sqlite3 v1.14.17 // indirect
3436
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
3537
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
3638
github.com/modern-go/reflect2 v1.0.2 // indirect

walletnode/api/config.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ const (
77

88
// Config contains settings of the Pastel client.
99
type Config struct {
10-
Hostname string `mapstructure:"hostname" json:"hostname,omitempty"`
11-
Port int `mapstructure:"port" json:"port,omitempty"`
12-
Swagger bool `mapstructure:"swagger" json:"swagger,omitempty"`
13-
StaticFilesDir string `mapstructure:"static_files_dir" json:"static_files_dir,omitempty"`
10+
Hostname string `mapstructure:"hostname" json:"hostname,omitempty"`
11+
Port int `mapstructure:"port" json:"port,omitempty"`
12+
Swagger bool `mapstructure:"swagger" json:"swagger,omitempty"`
13+
StaticFilesDir string `mapstructure:"static_files_dir" json:"static_files_dir,omitempty"`
14+
CascadeFilesDir string `mapstructure:"cascade_files_dir" json:"cascade_files_dir,omitempty"`
1415
}
1516

1617
// NewConfig returns a new Config instance.

walletnode/api/services/cascade.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,6 @@ func (service *CascadeAPIHandler) UploadAsset(ctx context.Context, p *cascade.Up
6767
return nil, cascade.MakeBadRequest(errors.New("file not specified"))
6868
}
6969

70-
fileSize := utils.GetFileSizeInMB(p.Bytes)
71-
if fileSize > maxFileSize {
72-
log.WithError(err).Error("file size exceeds than 350Mb, please use V2 endpoint for uploading the file")
73-
return nil, cascade.MakeInternalServerError(err)
74-
}
75-
7670
id, expiry, err := service.register.StoreFile(ctx, p.Filename)
7771
if err != nil {
7872
log.WithError(err).Error("error storing File")

walletnode/api/services/common.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ var randIDGen = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
1111

1212
// Config represents a config for the common service.
1313
type Config struct {
14-
StaticFilesDir string `mapstructure:"static_files_dir" json:"static_files_dir,omitempty"`
14+
StaticFilesDir string `mapstructure:"static_files_dir" json:"static_files_dir,omitempty"`
15+
CascadeFilesDir string `mapstructure:"cascade_files_dir" json:"cascade_files_dir,omitempty"`
1516
}
1617

1718
// Common represents common service.

walletnode/api/services/multipart.go

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ package services
33
import (
44
"bytes"
55
"context"
6+
"fmt"
67
"io"
78
"io/ioutil"
89
"mime/multipart"
10+
"os"
11+
"path/filepath"
912
"strings"
1013

1114
"github.com/gabriel-vasile/mimetype"
1215
"github.com/pastelnetwork/gonode/walletnode/api/gen/cascade"
16+
"github.com/pastelnetwork/gonode/walletnode/services/common"
1317

18+
"github.com/pastelnetwork/gonode/common/random"
1419
"github.com/pastelnetwork/gonode/common/storage/files"
1520
"github.com/pastelnetwork/gonode/walletnode/api/gen/sense"
1621

@@ -74,10 +79,10 @@ func CascadeUploadAssetDecoderFunc(ctx context.Context, service *CascadeAPIHandl
7479
return func(reader *multipart.Reader, p **cascade.UploadAssetPayload) error {
7580
var res cascade.UploadAssetPayload
7681

77-
filename, errType, err := handleUploadImage(ctx, reader, service.register.ImageHandler.FileStorage, false)
82+
filename, err := handleUploadFile(ctx, reader, service.config.CascadeFilesDir, true)
7883
if err != nil {
7984
return &goa.ServiceError{
80-
Name: errType,
85+
Name: "",
8186
ID: goa.NewErrorID(),
8287
Message: err.Error(),
8388
}
@@ -147,3 +152,75 @@ func handleUploadImage(ctx context.Context, reader *multipart.Reader, storage *f
147152

148153
return filename, "", nil
149154
}
155+
156+
const (
157+
chunkSize = 300 * 1024 * 1024 // 300 MB
158+
)
159+
160+
func handleUploadFile(ctx context.Context, reader *multipart.Reader, baseDir string, putMaxCap bool) (string, error) {
161+
id, err := random.String(8, random.Base62Chars)
162+
if err != nil {
163+
return "", err
164+
}
165+
166+
dirPath := filepath.Join(baseDir, id)
167+
168+
// Create the directory
169+
if err := os.MkdirAll(dirPath, os.ModePerm); err != nil {
170+
return "", errors.New("could not create directory: " + err.Error())
171+
}
172+
173+
var filename, outputFilePath string
174+
var fileSize int64
175+
176+
for {
177+
part, err := reader.NextPart()
178+
if err == io.EOF {
179+
break
180+
}
181+
if err != nil {
182+
return "", errors.New("could not read next part: " + err.Error())
183+
}
184+
185+
if part.FormName() != imagePartName {
186+
continue
187+
}
188+
189+
filename = part.FileName()
190+
fmt.Printf("Upload image %q\n", filename)
191+
outputFilePath = filepath.Join(dirPath, filename)
192+
outputFile, err := os.Create(outputFilePath)
193+
if err != nil {
194+
return "", errors.New("could not create file: " + err.Error())
195+
}
196+
197+
n, err := io.Copy(outputFile, part)
198+
fileSize += n
199+
outputFile.Close()
200+
201+
if err != nil {
202+
return "", errors.New("error writing file: " + err.Error())
203+
}
204+
break // Assuming single file upload for simplicity
205+
}
206+
207+
fs := common.FileSplitter{PartSizeMB: 300}
208+
if fileSize > chunkSize {
209+
if putMaxCap {
210+
return "", errors.New("file size exceeds the maximum allowed size - Please use API V2 to upload large files")
211+
}
212+
213+
// Use 7z to split the file
214+
err := fs.SplitFile(outputFilePath)
215+
if err != nil {
216+
return "", err
217+
}
218+
// Remove the original file after splitting
219+
if err := os.Remove(outputFilePath); err != nil {
220+
return "", errors.New("could not remove original file: " + err.Error())
221+
}
222+
}
223+
224+
fmt.Printf("Uploaded image to %q\n", dirPath)
225+
return id, nil
226+
}

walletnode/cmd/app.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package cmd
33
import (
44
"context"
55
"fmt"
6-
"github.com/pastelnetwork/gonode/common/storage/ticketstore"
76
"io/ioutil"
87
"os"
98
"path/filepath"
109
"sync"
1110

11+
"github.com/pastelnetwork/gonode/common/storage/ticketstore"
12+
1213
"github.com/pastelnetwork/gonode/common/cli"
1314
"github.com/pastelnetwork/gonode/common/configurer"
1415
"github.com/pastelnetwork/gonode/common/errors"
@@ -51,6 +52,7 @@ var (
5152
defaultPastelConfigFile = filepath.Join(defaultPath, "pastel.conf")
5253
defaultRqFilesDir = filepath.Join(defaultPath, rqFilesDir)
5354
defaultStaticFilesDir = filepath.Join(defaultPath, staticFilesDir)
55+
defaultCascadeFilesDir = filepath.Join(defaultPath, "files")
5456
)
5557

5658
// NewApp configures our app by parsing command line flags, config files, and setting up logging and temporary directories
@@ -166,6 +168,14 @@ func runApp(ctx context.Context, config *configs.Config) error {
166168
}
167169
}
168170

171+
if _, err := os.Stat(defaultCascadeFilesDir); os.IsNotExist(err) {
172+
// directory does not exist, create it
173+
errDir := os.MkdirAll(defaultCascadeFilesDir, 0755)
174+
if errDir != nil {
175+
log.Fatal(err)
176+
}
177+
}
178+
169179
// pastelClient reads in the json-formatted hostname, port, username, and password and
170180
// connects over gRPC to cNode for access to Blockchain, Masternodes, Tickets, and PastelID databases
171181
pastelClient := pastel.NewClient(config.Pastel, config.Pastel.BurnAddress())
@@ -221,10 +231,13 @@ func runApp(ctx context.Context, config *configs.Config) error {
221231
// Since the API Server has access to the services, this is what finally exposes useful methods like
222232
// "NftGet" and "Download".
223233
apiSrcvConf := &services.Config{
224-
StaticFilesDir: defaultStaticFilesDir,
234+
StaticFilesDir: defaultStaticFilesDir,
235+
CascadeFilesDir: defaultCascadeFilesDir,
225236
}
226237
config.API.StaticFilesDir = defaultStaticFilesDir
238+
config.API.CascadeFilesDir = defaultCascadeFilesDir
227239
config.NftDownload.StaticDir = defaultStaticFilesDir
240+
config.NftDownload.CascadeFilesDir = defaultCascadeFilesDir
228241

229242
// These services connect the different clients and configs together to provide tasking and handling for
230243
// the required functionality. These services aren't started with these declarations, they will be run

walletnode/services/common/7zip.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package common
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"runtime"
9+
"strconv"
10+
"strings"
11+
12+
"github.com/pastelnetwork/gonode/common/errors"
13+
)
14+
15+
type FileSplitter struct {
16+
PartSizeMB int
17+
}
18+
19+
func get7zPath() string {
20+
basePath := "/usr/bin/" // Adjust the base path as needed. For example: ./bin/ for local dir
21+
switch runtime.GOOS {
22+
case "windows":
23+
return basePath + "7za.exe"
24+
default:
25+
return basePath + "7za"
26+
}
27+
}
28+
29+
func (fs *FileSplitter) SplitFile(filePath string) error {
30+
sevenZPath := get7zPath()
31+
cmd := exec.Command(sevenZPath, "a", "-v"+strconv.Itoa(fs.PartSizeMB)+"m", filePath+".7z", filePath)
32+
cmd.Stdout = os.Stdout
33+
cmd.Stderr = os.Stderr
34+
if err := cmd.Run(); err != nil {
35+
return errors.New("7zip split error: " + err.Error())
36+
}
37+
38+
return nil
39+
}
40+
41+
func (fs *FileSplitter) JoinFiles(dirPath string) error {
42+
// Find the first part of the split files
43+
var firstPartPath string
44+
files, err := os.ReadDir(dirPath)
45+
if err != nil {
46+
return fmt.Errorf("failed to read directory: %v", err)
47+
}
48+
49+
for _, file := range files {
50+
if strings.HasSuffix(file.Name(), ".001") {
51+
firstPartPath = filepath.Join(dirPath, file.Name())
52+
break
53+
}
54+
}
55+
56+
if firstPartPath == "" {
57+
return errors.New("no split parts (.001 file) found in the directory")
58+
}
59+
60+
// Join the split parts
61+
sevenZPath := get7zPath()
62+
fmt.Println("Found .001 file at:", firstPartPath)
63+
cmd := exec.Command(sevenZPath, "x", "-aoa", "-o"+dirPath, firstPartPath)
64+
cmd.Stdout = os.Stdout
65+
cmd.Stderr = os.Stderr
66+
fmt.Println("Executing join command:", cmd)
67+
if err := cmd.Run(); err != nil {
68+
return fmt.Errorf("7zip join error: %v", err)
69+
}
70+
71+
// The assumed path of the joined archive
72+
reassembledPath := strings.TrimSuffix(firstPartPath, ".7z.001")
73+
fmt.Println("Assumed path of the joined archive:", reassembledPath)
74+
75+
// Verify the existence of the joined archive
76+
if _, err := os.Stat(reassembledPath); os.IsNotExist(err) {
77+
fmt.Println("Checking for potential naming error...")
78+
if _, err := os.Stat(reassembledPath + ".7z"); err == nil {
79+
reassembledPath += ".7z"
80+
fmt.Println("Corrected path to joined archive:", reassembledPath)
81+
} else {
82+
return fmt.Errorf("joined archive does not exist at expected path: %s", reassembledPath)
83+
}
84+
}
85+
86+
return nil
87+
}

walletnode/services/download/config.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import (
66

77
// Config contains settings of the registering nft.
88
type Config struct {
9-
common.Config `mapstructure:",squash" json:"-"`
10-
StaticDir string `mapstructure:"static_dir" json:"static_dir"`
9+
common.Config `mapstructure:",squash" json:"-"`
10+
StaticDir string `mapstructure:"static_dir" json:"static_dir"`
11+
CascadeFilesDir string `mapstructure:"cascade_files_dir" json:"cascade_files_dir"`
1112
}
1213

1314
// NewConfig returns a new Config instance.

0 commit comments

Comments
 (0)