/
file.go
209 lines (175 loc) · 5.17 KB
/
file.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package file
import (
"errors"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"path"
"path/filepath"
"github.com/kencx/dusk"
"github.com/kencx/dusk/file/epub"
"github.com/kencx/dusk/null"
)
const (
coverFilename = "cover"
)
type Service struct {
Directory string
}
func NewService(path string) (*Service, error) {
err := os.MkdirAll(path, 0755)
if err != nil {
return nil, err
}
return &Service{path}, nil
}
// Upload new format for new book
func (s *Service) UploadBook(payload *Payload) (*dusk.Book, error) {
switch payload.Extension {
case ".epub":
return s.UploadNewEpub(payload)
default:
return nil, errors.New("unsupported file format")
}
}
// Upload new format for existing book
func (s *Service) UploadBookFormat(payload *Payload, book *dusk.Book) error {
switch payload.Extension {
case ".epub":
return s.UploadEpub(payload, book)
default:
return s.UploadOtherFormat(payload, book)
}
}
// Upload EPUB format for new book
func (s *Service) UploadNewEpub(payload *Payload) (*dusk.Book, error) {
ep, err := epub.NewFromReader(payload.File, payload.Size)
if err != nil && !errors.Is(err, epub.ErrNoCovers) {
return nil, fmt.Errorf("failed to parse epub file: %w", err)
}
book := ep.ToBook()
errMap := book.Valid()
if len(errMap) > 0 {
return nil, errMap
}
if err := s.UploadOtherFormat(payload, book); err != nil {
return nil, err
}
// find and upload cover image in epub
if ep.CoverFile != "" {
coverFile, err := ep.Open(ep.CoverFile)
if err != nil {
return nil, err
}
defer coverFile.Close()
bookDir := filepath.Join(s.Directory, book.SafeTitle())
filename := fmt.Sprintf("%s%s", coverFilename, filepath.Ext(ep.CoverFile))
fullPath := filepath.Join(bookDir, filename)
if err = s.UploadFile(coverFile, fullPath); err != nil {
return nil, err
}
book.Cover = null.StringFrom(getRelativePath(fullPath))
}
return book, nil
}
// Upload EPUB format for existing book
func (s *Service) UploadEpub(payload *Payload, book *dusk.Book) error {
ep, err := epub.NewFromReader(payload.File, payload.Size)
if err != nil && !errors.Is(err, epub.ErrNoCovers) {
return fmt.Errorf("failed to parse epub file: %w", err)
}
if err := s.UploadOtherFormat(payload, book); err != nil {
return err
}
if ep.CoverFile != "" {
coverFile, err := ep.Open(ep.CoverFile)
if err != nil {
return err
}
defer coverFile.Close()
bookDir := filepath.Join(s.Directory, book.SafeTitle())
filename := fmt.Sprintf("%s%s", coverFilename, filepath.Ext(ep.CoverFile))
fullPath := filepath.Join(bookDir, filename)
if err = s.UploadFile(coverFile, fullPath); err != nil {
return err
}
book.Cover = null.StringFrom(getRelativePath(fullPath))
}
return nil
}
// Upload other format for existing book
func (s *Service) UploadOtherFormat(payload *Payload, book *dusk.Book) error {
bookDir, err := s.createBookDirectory(book)
if err != nil {
return err
}
filename := fmt.Sprintf("%s%s", book.SafeTitle(), payload.Extension)
fullPath := filepath.Join(bookDir, filename)
if err := s.UploadFile(payload.File, fullPath); err != nil {
return err
}
book.Formats = append(book.Formats, getRelativePath(fullPath))
return nil
}
// Upload book cover for existing book
func (s *Service) UploadBookCover(payload *Payload, book *dusk.Book) error {
bookDir, err := s.createBookDirectory(book)
if err != nil {
return err
}
filename := fmt.Sprintf("%s%s", coverFilename, payload.Extension)
fullPath := filepath.Join(bookDir, filename)
if err := s.UploadFile(payload.File, fullPath); err != nil {
return err
}
book.Cover = null.StringFrom(getRelativePath(fullPath))
return nil
}
// Upload book cover from URL for existing book
func (s *Service) UploadBookCoverFromUrl(url string, book *dusk.Book) error {
bookDir, err := s.createBookDirectory(book)
if err != nil {
return err
}
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("file: failed to fetch file from url: %w", err)
}
defer resp.Body.Close()
ext := path.Ext(path.Base(resp.Request.URL.Path))
filename := fmt.Sprintf("%s%s", coverFilename, ext)
fullPath := filepath.Join(bookDir, filename)
if err := s.UploadFile(resp.Body, fullPath); err != nil {
return err
}
book.Cover = null.StringFrom(getRelativePath(fullPath))
return nil
}
// Upload file to path
func (s *Service) UploadFile(file io.Reader, path string) error {
dest, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
return fmt.Errorf("file: failed to create file: %w", err)
}
defer dest.Close()
if _, err = io.Copy(dest, file); err != nil {
return fmt.Errorf("file: failed to copy file to dest: %w", err)
}
slog.Info("[file] New file uploaded", slog.String("path", path))
return nil
}
// create or get book directory
func (s *Service) createBookDirectory(book *dusk.Book) (string, error) {
bookDir := filepath.Join(s.Directory, book.SafeTitle())
if err := os.MkdirAll(bookDir, 0755); err != nil {
return "", fmt.Errorf("failed to create book directory: %w", err)
}
return bookDir, nil
}
// get last 2 elements of file path - parentDir/filename.ext
func getRelativePath(path string) string {
parentDir := filepath.Base(filepath.Dir(path))
return filepath.Join(parentDir, filepath.Base(path))
}