github.com/readium/readium-lcp-server@v0.0.0-20240101192032-6e95190e99f1/pack/pipeline.go (about) 1 // Copyright 2020 Readium Foundation. All rights reserved. 2 // Use of this source code is governed by a BSD-style license 3 // that can be found in the LICENSE file exposed on Github (readium) in the project repository. 4 5 package pack 6 7 import ( 8 "archive/zip" 9 "crypto/sha256" 10 "encoding/hex" 11 "io" 12 "io/ioutil" 13 "log" 14 "os" 15 "time" 16 17 uuid "github.com/satori/go.uuid" 18 19 "github.com/readium/readium-lcp-server/crypto" 20 "github.com/readium/readium-lcp-server/epub" 21 "github.com/readium/readium-lcp-server/index" 22 "github.com/readium/readium-lcp-server/storage" 23 ) 24 25 // Source in an interface 26 type Source interface { 27 Feed(chan<- *Task) 28 } 29 30 // Task is an interface 31 type Task struct { 32 Name string 33 Body io.ReaderAt 34 Size int64 35 done chan Result 36 } 37 38 // EncryptedFileInfo contains a file, its size and sha256 39 type EncryptedFileInfo struct { 40 File *os.File 41 Size int64 42 Sha256 string 43 } 44 45 // NewTask generates a new task 46 func NewTask(name string, body io.ReaderAt, size int64) *Task { 47 return &Task{Name: name, Body: body, Size: size, done: make(chan Result, 1)} 48 } 49 50 // Result is a result 51 type Result struct { 52 Error error 53 ID string 54 Elapsed time.Duration 55 } 56 57 // Wait returns a result 58 func (t *Task) Wait() Result { 59 r := <-t.done 60 return r 61 } 62 63 // Done returns a result 64 func (t *Task) Done(r Result) { 65 t.done <- r 66 } 67 68 // ManualSource is a struc 69 type ManualSource struct { 70 ch chan<- *Task 71 } 72 73 // Feed is a struc 74 func (s *ManualSource) Feed(ch chan<- *Task) { 75 s.ch = ch 76 } 77 78 // Post is a struc 79 func (s *ManualSource) Post(t *Task) Result { 80 s.ch <- t 81 return t.Wait() 82 } 83 84 // Packager is a struct 85 type Packager struct { 86 Incoming chan *Task 87 done chan struct{} 88 store storage.Store 89 idx index.Index 90 } 91 92 func (p Packager) work() { 93 for t := range p.Incoming { 94 log.Println("Packager working on an incoming EPUB, encryption task") 95 r := Result{} 96 p.genKey(&r) 97 zr := p.readZip(&r, t.Body, t.Size) 98 ep := p.readEpub(&r, zr) 99 encrypted, key := p.encrypt(&r, ep) 100 p.addToStore(&r, encrypted) 101 p.addToIndex(&r, key, t.Name, encrypted, epub.ContentType_EPUB) 102 103 t.Done(r) 104 } 105 } 106 107 func (p Packager) genKey(r *Result) { 108 if r.Error != nil { 109 return 110 } 111 112 uid, err := uuid.NewV4() 113 if err != nil { 114 return 115 } 116 r.ID = uid.String() 117 } 118 119 func (p Packager) readZip(r *Result, in io.ReaderAt, size int64) *zip.Reader { 120 if r.Error != nil { 121 return nil 122 } 123 124 zr, err := zip.NewReader(in, size) 125 r.Error = err 126 return zr 127 } 128 129 func (p Packager) readEpub(r *Result, zr *zip.Reader) epub.Epub { 130 if r.Error != nil { 131 return epub.Epub{} 132 } 133 134 ep, err := epub.Read(zr) 135 r.Error = err 136 137 return ep 138 } 139 140 func (p Packager) encrypt(r *Result, ep epub.Epub) (*EncryptedFileInfo, []byte) { 141 if r.Error != nil { 142 return nil, nil 143 } 144 tmpFile, err := ioutil.TempFile(os.TempDir(), "out-readium-lcp") 145 if err != nil { 146 r.Error = err 147 return nil, nil 148 } 149 encrypter := crypto.NewAESEncrypter_PUBLICATION_RESOURCES() 150 _, key, err := Do(encrypter, "", ep, tmpFile) 151 r.Error = err 152 var encryptedFileInfo EncryptedFileInfo 153 encryptedFileInfo.File = tmpFile 154 //get file length & hash (sha256) 155 hasher := sha256.New() 156 encryptedFileInfo.File.Seek(0, 0) 157 written, err := io.Copy(hasher, encryptedFileInfo.File) 158 //hasher.Write(s) 159 if err != nil { 160 r.Error = err 161 return nil, nil 162 } 163 encryptedFileInfo.Size = written 164 encryptedFileInfo.Sha256 = hex.EncodeToString(hasher.Sum(nil)) 165 166 encryptedFileInfo.File.Seek(0, 0) 167 return &encryptedFileInfo, key 168 } 169 170 func (p Packager) addToStore(r *Result, info *EncryptedFileInfo) { 171 if r.Error != nil { 172 return 173 } 174 175 _, r.Error = p.store.Add(r.ID, info.File) 176 177 info.File.Close() 178 os.Remove(info.File.Name()) 179 } 180 181 func (p Packager) addToIndex(r *Result, key []byte, name string, info *EncryptedFileInfo, contentType string) { 182 if r.Error != nil { 183 return 184 } 185 r.Error = p.idx.Add(index.Content{ID: r.ID, EncryptionKey: key, Location: name, Length: info.Size, Sha256: info.Sha256, Type: contentType}) 186 } 187 188 // NewPackager waits for incoming EPUB files, encrypts them and adds them to the store 189 func NewPackager(store storage.Store, idx index.Index, concurrency int) *Packager { 190 packager := Packager{ 191 Incoming: make(chan *Task), 192 done: make(chan struct{}), 193 store: store, 194 idx: idx, 195 } 196 197 for i := 0; i < concurrency; i++ { 198 go packager.work() 199 } 200 201 return &packager 202 }