github.com/readium/readium-lcp-server@v0.0.0-20240509124024-799e77a0bbd6/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  }