· 10 years ago · Sep 17, 2016, 07:20 PM
1/*
2
3# brute
4
5BRUTE stands for: Bibliotik Retail Uploader Torrent Epubs.
6It does not mean anything, but BRUTEs are not known to be particularly
7articulate.
8
9In short, BRUTE allows uploading a retail epub to Bib from the comfort of the
10command line.
11
12It's not the prettiest thing, being hacked together and mashed up in a single
13file for ease of sharing, but BRUTE will try its best to help make quality
14uploads.
15This means it will automate the boring stuff, and let the user focus on making
16sure the torrent metadata is accurate and complete.
17
18BRUTE retrieves metadata from the epub and Goodreads automatically, and forces
19the user to merge and edit the results as needed.
20Everything is saved to a yaml file, for (optional) manual editing before the
21actual upload.
22
23## Building
24
25You need a working Go installation: see https://golang.org/doc/install.
26Put this file in a 'brute' directory in your $GO_PATH, cd to it and just run:
27
28 $ go get .
29 $ go install ...brute
30
31or if you don't want to install it:
32
33 $ go get .
34 $ go run brute.go
35
36To update its dependancies, rebuild from time to time:
37
38 $ go get -u .
39 $ go install ...brute
40
41BRUTE was tested on Linux, but it should probably work wherever you can install
42Go.
43
44## Requirements
45
46- a WhatIMG account
47- a GoodReads account for which you've requested an API Key: see
48 https://www.goodreads.com/api/keys
49
50## Usage
51
52BRUTE only requires one existing epub file as argument.
53
54 $ brute /path/to/book.epub
55
56It will:
57
58 - read epub metadata
59 - retrieve information from Goodreads if ISBN is found
60 - allow (alright, force) the user to edit the merged information to prepare
61 the bibliotik upload form
62 - save that information to /path/to/book.yaml for manual editing if deemed
63 necessary
64 - upload the cover to whatimg and retrieve its url
65
66At this point BRUTE will ask if you want to upload directly or not.
67If you say no, you will be able to manually edit the yaml file as you wish.
68Run BRUTE again with the same argument, and it will load the yaml file with your
69changes, and proceed to:
70
71 - generate the .torrent from the epub with the user's passkey
72 - upload the torrent with the cover and information collected in previous steps
73 or loaded from a previously saved yaml file
74 - copy the .torrent and .epub where you want to begin seeding
75 - archive everything for later reference: epub, cover, torrent, and yaml in a
76 tarball.
77 - (optionally) remove the original files
78
79Conventions:
80- the script assumes /path/to/book.jpg exists and is the cover for this epub.
81- it will create /path/to/book.torrent and /path/to/book.yaml
82
83## Configuration
84
85BRUTE needs a configuration file ("config.yaml") with the following information:
86
87 whatimg_login: xx
88 whatimg_password: xx
89 bibliotik_login: xx
90 bibliotik_password: xx
91 bibliotik_passkey: https://bibliotik.me/announce.php?passkey=xx
92 torrent_watch_directory: /home/user/torrents
93 torrent_seed_directory: /home/user/downloads
94 archive_directory: /home/user/uploads
95 goodreads_api_key: xx
96 tag_aliases:
97 science-fiction:
98 - sf
99 - sci-fi
100
101tag_aliases allows automatic removal of duplicate tags (from duplicate shelves
102on Goodreads).
103This configuration file must be in the working directory.
104Lots of information in a clear text file, I know.
105Keep it close to your heart.
106
107## Things BRUTE isn't good at
108
109BRUTE is not good at:
110- retrieving information when it cannot find an ISBN number or if Goodreads
111does not know about an ISBN
112- retrieving editors, contributors, translators, languages (defaults to english)
113- it will try to extract tags from Goodreads shelves, and clean out the most
114blatantly awful ones. ALWAYS EDIT TAGS MANUALLY.
115
116Fortunately, you can overcome these shortcomings by manually editing
117/path/to/book.yaml!
118No excuses!
119
120## Things BRUTE is useless at
121
122BRUTE can't help you if you're dealing with anything other than epubs.
123
124You could use it to upload non-retail epubs, but then you MUST manually set the
125RetailField to "0" in the yaml file.
126
127## Changelog
128
129v1.0.4: Updated to work with latest version of third party packages.
130v1.0.3: Updated to work with latest version of third party packages.
131v1.0.2: In description, title is in italics and author name in bold.
132v1.0.1: BRUTE should now correctly retrieve edition year instead of original
133publication year from GR.
134v1.0: original release.
135
136*/
137
138package main
139
140import (
141 "bytes"
142 "errors"
143 "fmt"
144 "io"
145 "io/ioutil"
146 "log"
147 "mime/multipart"
148 "net/http"
149 "net/http/cookiejar"
150 "net/url"
151 "os"
152 "path/filepath"
153 "regexp"
154 "strings"
155 "time"
156
157 "github.com/anacrolix/torrent/metainfo"
158 b "github.com/barsanuphe/endive/book"
159 e "github.com/barsanuphe/endive/endive"
160 u "github.com/barsanuphe/endive/ui"
161 "github.com/jhoonb/archivex"
162 "github.com/skratchdot/open-golang/open"
163 "github.com/spf13/viper"
164 "golang.org/x/net/publicsuffix"
165 "gopkg.in/yaml.v2"
166)
167
168func main() {
169 var ui e.UserInterface
170 ui = u.UI{}
171
172 // 1. load configuration
173 cfg := Config{}
174 if err := cfg.load("config.yaml"); err != nil {
175 fmt.Println("Error loading config.")
176 return
177 }
178 if err := cfg.check(); err != nil {
179 fmt.Println("Error checking config: " + err.Error())
180 return
181 }
182 // 2. check argument
183 if len(os.Args) != 2 {
184 fmt.Println("Please provide an epub filename.")
185 return
186 }
187 c := UploadCandidate{
188 epubFile: os.Args[1],
189 torrentFile: strings.TrimSuffix(os.Args[1], ".epub") + ".torrent",
190 imageFile: strings.TrimSuffix(os.Args[1], ".epub") + ".jpg",
191 infoFile: strings.TrimSuffix(os.Args[1], ".epub") + ".yaml",
192 }
193 if err := c.check(); err != nil {
194 fmt.Println(err.Error())
195 return
196 }
197 // 3. get info from file + GR
198 if err := c.getInfo(cfg, ui); err != nil {
199 fmt.Println(err.Error())
200 return
201 }
202 // 4. upload cover if necessary
203 if c.info.ImageField == "" || ui.YesOrNo("Image URL found, upload again") {
204 if err := c.uploadImage(cfg); err != nil {
205 fmt.Println(err.Error())
206 return
207 }
208 }
209 // 5. save info
210 err := c.saveInfo()
211 if err != nil {
212 fmt.Println(err.Error())
213 }
214 ui.Title("You can now either manually edit " + filepath.Base(c.infoFile) + " and run this script again, or directly upload to bibliotik if you are satisfied the quality of the metadata is excellent.")
215 if ui.YesOrNo("Upload to bibliotik") {
216 // 6. generate torrent
217 if err := c.generateTorrent(cfg); err != nil {
218 fmt.Println(err.Error())
219 return
220 }
221 // 7. upload to bibliotik
222 if err := c.uploadTorrent(cfg); err != nil {
223 fmt.Println(err.Error())
224 return
225 }
226 // 8. copy torrent to watch dir && epub to incoming
227 if err := c.seed(cfg); err != nil {
228 fmt.Println(err.Error())
229 return
230 }
231 // 9. backup all relevant files to zip with date
232 if err := c.archive(cfg); err != nil {
233 fmt.Println(err.Error())
234 return
235 }
236 // 10. clean up
237 if ui.YesOrNo("Remove original files") {
238 if err := c.cleanUp(); err != nil {
239 fmt.Println(err.Error())
240 return
241 }
242 }
243 } else if ui.YesOrNo("Launch this book's Goodreads page in your browser for reference?") {
244 err = open.Start("https://www.goodreads.com/search?q=" + c.info.IsbnField)
245 if err != nil {
246 fmt.Println("Error: could not open goodreads page!")
247 }
248 }
249}
250
251//------------------------------------------------------------------------------
252
253type Config struct {
254 bibUser string
255 bibPassword string
256 bibPasskey string
257 whatimgUser string
258 whatimgPassword string
259 torrentWatchDir string
260 torrentSeedDir string
261 archiveDir string
262 grApiKey string
263 TagAliases map[string][]string
264}
265
266func (c *Config) load(path string) (err error) {
267 conf := viper.New()
268 conf.SetConfigType("yaml")
269 conf.SetConfigFile(path)
270
271 err = conf.ReadInConfig()
272 if err != nil {
273 return
274 }
275 c.bibUser = conf.GetString("bibliotik_login")
276 c.bibPassword = conf.GetString("bibliotik_password")
277 c.bibPasskey = conf.GetString("bibliotik_passkey")
278 c.whatimgUser = conf.GetString("whatimg_login")
279 c.whatimgPassword = conf.GetString("whatimg_password")
280 c.torrentSeedDir = conf.GetString("torrent_seed_directory")
281 c.torrentWatchDir = conf.GetString("torrent_watch_directory")
282 c.archiveDir = conf.GetString("archive_directory")
283 c.grApiKey = conf.GetString("goodreads_api_key")
284 c.TagAliases = conf.GetStringMapStringSlice("tag_aliases")
285 return
286}
287
288func (c *Config) check() (err error) {
289 if !e.DirectoryExists(c.torrentWatchDir) {
290 return errors.New("Torrent Watch directory does not exist")
291 }
292 if !e.DirectoryExists(c.torrentSeedDir) {
293 return errors.New("Torrent download directory does not exist")
294 }
295 if !e.DirectoryExists(c.archiveDir) {
296 return errors.New("Archive directory does not exist")
297 }
298 return
299}
300
301//------------------------------------------------------------------------------
302
303// newTrue allows setting the value of a *bool in a struct.
304// it is arguably awful.
305func newTrue() *bool {
306 b := true
307 return &b
308}
309
310func checkErrors(errs ...error) error {
311 for _, err := range errs {
312 if err != nil {
313 return err
314 }
315 }
316 return nil
317}
318
319func login(siteUrl, username, password string) (hc *http.Client, returnData string, err error) {
320 form := url.Values{}
321 form.Add("username", username)
322 form.Add("password", password)
323 req, err := http.NewRequest("POST", siteUrl, strings.NewReader(form.Encode()))
324 if err != nil {
325 fmt.Println(err.Error())
326 }
327 req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
328
329 options := cookiejar.Options{
330 PublicSuffixList: publicsuffix.List,
331 }
332 jar, err := cookiejar.New(&options)
333 if err != nil {
334 log.Fatal(err)
335 }
336 hc = &http.Client{Jar: jar}
337
338 resp, err := hc.Do(req)
339 if err != nil {
340 fmt.Println(err.Error())
341 }
342 defer resp.Body.Close()
343
344 if resp.StatusCode != http.StatusOK {
345 err = errors.New("Returned status: " + resp.Status)
346 }
347
348 data, err := ioutil.ReadAll(resp.Body)
349 if err != nil {
350 log.Fatal(err)
351 }
352 returnData = string(data)
353 return
354}
355
356func uploadImage(client *http.Client, uploadUrl, image string) (responseData string, err error) {
357 // preparing a form
358 b := new(bytes.Buffer)
359 w := multipart.NewWriter(b)
360 // adding image to form
361 f, err := os.Open(image)
362 if err != nil {
363 return "", err
364 }
365 defer f.Close()
366 fw, err := w.CreateFormFile("userfile[]", filepath.Base(image))
367 if err != nil {
368 return "", err
369 }
370 if _, err = io.Copy(fw, f); err != nil {
371 return
372 }
373 if err = w.WriteField("upload_to", "0"); err != nil {
374 return
375 }
376 if err = w.WriteField("private_upload", "1"); err != nil {
377 return
378 }
379 if err = w.WriteField("upload_type", "standard"); err != nil {
380 return
381 }
382 w.Close()
383
384 req, err := http.NewRequest("POST", uploadUrl, b)
385 if err != nil {
386 return "", err
387 }
388 req.Header.Set("Content-Type", w.FormDataContentType())
389
390 resp, err := client.Do(req)
391 if err != nil {
392 return "", err
393 }
394 defer resp.Body.Close()
395
396 if resp.StatusCode != http.StatusOK {
397 err = errors.New("Returned status: " + resp.Status)
398 }
399
400 data, err := ioutil.ReadAll(resp.Body)
401 if err != nil {
402 return "", err
403 }
404 responseData = string(data)
405 return
406}
407
408//------------------------------------------------------------------------------
409
410// BUInfo tracks all of the upload form fields.
411type BUInfo struct {
412 TorrentFileField string
413 authkey string
414 upload string // default: empty
415 TitleField string
416 EditorsField string
417 ContributorsField string
418 TranslatorsField string
419 PublishersField string
420 PagesField string
421 AuthorsField string
422 FormatField string // EPUB == "15"
423 IsbnField string
424 TagsField string
425 DescriptionField string
426 RetailField string // retail == "1"
427 NotifyField string // default "1"
428 LanguageField string // english == "1"
429 YearField string
430 ImageField string
431}
432
433// ShowInfo returns a table with relevant information about a book.
434func (i *BUInfo) ShowInfo() (desc string) {
435 var rows [][]string
436 rows = append(rows, []string{"TorrentFile", i.TorrentFileField})
437 rows = append(rows, []string{"Title", i.TitleField})
438 rows = append(rows, []string{"Authors", i.AuthorsField})
439 rows = append(rows, []string{"Editors", i.EditorsField})
440 rows = append(rows, []string{"Contributors", i.ContributorsField})
441 rows = append(rows, []string{"Translators", i.TranslatorsField})
442 rows = append(rows, []string{"Publishers", i.PublishersField})
443 rows = append(rows, []string{"Isbn", i.IsbnField})
444 rows = append(rows, []string{"Pages", i.PagesField})
445 rows = append(rows, []string{"Year", i.YearField})
446 rows = append(rows, []string{"Format", i.FormatField})
447 rows = append(rows, []string{"Language", i.LanguageField})
448 rows = append(rows, []string{"Retail", i.RetailField})
449 rows = append(rows, []string{"Tags", i.TagsField})
450 rows = append(rows, []string{"Image", i.ImageField})
451 rows = append(rows, []string{"Description", i.DescriptionField})
452 rows = append(rows, []string{"Notify", i.NotifyField})
453 return e.TabulateRows(rows, "Info", "Book")
454}
455
456func (i *BUInfo) load(path string) (err error) {
457 data, err := ioutil.ReadFile(path)
458 if err != nil {
459 return
460 }
461 return yaml.Unmarshal(data, i)
462}
463
464func (i *BUInfo) save(path string) (err error) {
465 d, err := yaml.Marshal(i)
466 if err != nil {
467 return err
468 }
469 return ioutil.WriteFile(path, d, 0777)
470}
471
472func (i *BUInfo) fill(m b.Metadata, torrentPath string) {
473 i.TorrentFileField = torrentPath
474 i.authkey = ""
475 i.upload = ""
476 if m.HasAny() {
477 seriesInfo := ""
478 if m.Series.HasAny() {
479 for i := range m.Series {
480 seriesInfo += fmt.Sprintf(" (%s, Book %s)", m.Series[i].Name, m.Series[i].Position)
481 }
482 }
483 i.TitleField = m.Title() + seriesInfo
484 i.EditorsField = "" // TODO
485 i.ContributorsField = "" // TODO
486 i.TranslatorsField = "" // TODO
487 i.PublishersField = m.Publisher
488 i.PagesField = m.NumPages
489 i.AuthorsField = strings.Join(m.Authors, ", ")
490 i.FormatField = "15" // EPUB
491 i.IsbnField = m.ISBN
492 i.TagsField = m.Category + ", " + m.MainGenre + ", " + m.Tags.String()
493
494 r := strings.NewReplacer(
495 i.AuthorsField, "[b]"+i.AuthorsField+"[/b]",
496 m.Title(), "[i]"+m.Title()+"[/i]",
497 )
498 i.DescriptionField = r.Replace(m.Description)
499 i.RetailField = "1"
500 i.NotifyField = "1"
501 i.LanguageField = "1" // English
502 i.YearField = m.EditionYear
503 }
504}
505
506func (i *BUInfo) generateUploadRequest(uploadURL string) (req *http.Request, err error) {
507 // setting up the form
508 b := new(bytes.Buffer)
509 w := multipart.NewWriter(b)
510 // adding the torrent file
511 f, err := os.Open(i.TorrentFileField)
512 if err != nil {
513 return nil, err
514 }
515 defer f.Close()
516 fw, err := w.CreateFormFile("TorrentFileField", filepath.Base(i.TorrentFileField))
517 if err != nil {
518 return nil, err
519 }
520 if _, err = io.Copy(fw, f); err != nil {
521 return
522 }
523 errors := []error{}
524 errors = append(errors, w.WriteField("authkey", i.authkey))
525 errors = append(errors, w.WriteField("upload", i.upload))
526 errors = append(errors, w.WriteField("TitleField", i.TitleField))
527 errors = append(errors, w.WriteField("EditorsField", i.EditorsField))
528 errors = append(errors, w.WriteField("ContributorsField", i.ContributorsField))
529 errors = append(errors, w.WriteField("TranslatorsField", i.TranslatorsField))
530 errors = append(errors, w.WriteField("PublishersField", i.PublishersField))
531 errors = append(errors, w.WriteField("PagesField", i.PagesField))
532 errors = append(errors, w.WriteField("AuthorsField", i.AuthorsField))
533 errors = append(errors, w.WriteField("FormatField", i.FormatField))
534 errors = append(errors, w.WriteField("IsbnField", i.IsbnField))
535 errors = append(errors, w.WriteField("TagsField", i.TagsField))
536 errors = append(errors, w.WriteField("DescriptionField", i.DescriptionField))
537 errors = append(errors, w.WriteField("RetailField", i.RetailField))
538 errors = append(errors, w.WriteField("NotifyField", i.NotifyField))
539 errors = append(errors, w.WriteField("LanguageField", i.LanguageField))
540 errors = append(errors, w.WriteField("YearField", i.YearField))
541 errors = append(errors, w.WriteField("ImageField", i.ImageField))
542 if err := checkErrors(errors...); err != nil {
543 return nil, err
544 }
545 w.Close()
546
547 req, err = http.NewRequest("POST", uploadURL, b)
548 if err != nil {
549 return nil, err
550 }
551 req.Header.Set("Content-Type", w.FormDataContentType())
552 return
553}
554
555//------------------------------------------------------------------------------
556
557type UploadCandidate struct {
558 epubFile string
559 torrentFile string
560 imageFile string
561 infoFile string
562 info BUInfo
563}
564
565func (t *UploadCandidate) loadInfo() (err error) {
566 return t.info.load(t.infoFile)
567}
568
569func (t *UploadCandidate) saveInfo() (err error) {
570 fmt.Println("Saving all gathered information to " + t.infoFile)
571 return t.info.save(t.infoFile)
572}
573
574func (t *UploadCandidate) check() (err error) {
575 // assert epub exists
576 if _, err := e.FileExists(t.epubFile); err != nil {
577 return errors.New("Epub does not exist!")
578 }
579 // assert it's an epub
580 if !strings.HasSuffix(strings.ToLower(t.epubFile), ".epub") {
581 return errors.New(t.epubFile + " is not an epub")
582 }
583 // assert jpg exists
584 if _, err := e.FileExists(t.imageFile); err != nil {
585 return errors.New("Cover " + t.imageFile + " does not exist!")
586 }
587 // assert t does not exist yet
588 if _, err := e.FileExists(t.torrentFile); err == nil {
589 return errors.New("Torrent " + t.torrentFile + " should not exist!")
590 }
591 return
592}
593
594func (t *UploadCandidate) getInfo(cfg Config, ui e.UserInterface) (err error) {
595 loadedFromFile := false
596 if _, err := e.FileExists(t.infoFile); err == nil {
597 if ui.YesOrNo("Info file already exists! Load") {
598 if err := t.loadInfo(); err != nil {
599 return err
600 }
601 fmt.Println(t.info.ShowInfo())
602 if !ui.YesOrNo("Confirm") {
603 return errors.New("Incorrect book information rejected.")
604 } else {
605 loadedFromFile = true
606 }
607 }
608 }
609 if !loadedFromFile {
610 bk := b.NewBook(ui, 0, t.epubFile, e.Config{GoodReadsAPIKey: cfg.grApiKey, TagAliases: cfg.TagAliases}, true)
611 // read epub metadata
612 bk.Metadata, err = bk.RetailEpub.ReadMetadata()
613 if err != nil {
614 return err
615 }
616 // find GoodReads metadata and merge
617 err = bk.SearchOnline()
618 if err != nil {
619 return err
620 }
621 t.info.fill(bk.Metadata, t.torrentFile)
622 fmt.Println(t.info.ShowInfo())
623 // review/edit field by field, confirm
624 for !ui.YesOrNo("Confirm") {
625 relevantFields := []string{"series", "title", "author", "year", "publisher", "description", "isbn", "category", "maingenre", "tags"}
626 for _, f := range relevantFields {
627 if err := bk.EditField(f); err != nil {
628 fmt.Println("Could not assign new value to field " + f + ", continuing.")
629 }
630 }
631 t.info.fill(bk.Metadata, t.torrentFile)
632 fmt.Println(t.info.ShowInfo())
633 }
634 }
635 return
636}
637
638func (t *UploadCandidate) uploadImage(cfg Config) error {
639 // login
640 hc, data, err := login("https://whatimg.com/users.php?act=login-d", cfg.whatimgUser, cfg.whatimgPassword)
641 if err != nil {
642 return err
643 }
644 if !strings.Contains(data, "You have been successfully logged in.") {
645 return errors.New("Failed to log in")
646 }
647 // upload image and get link
648 fmt.Printf("Uploading " + filepath.Base(t.imageFile) + " to WhatIMG... ")
649 data, err = uploadImage(hc, "https://whatimg.com/upload.php", t.imageFile)
650 r := regexp.MustCompile(".*value=\"(https://whatimg.com/i/[[:alnum:]]+?.jpg)\".*")
651 if r.MatchString(data) {
652 t.info.ImageField = r.FindStringSubmatch(data)[1]
653 fmt.Println("OK: " + t.info.ImageField)
654 } else {
655 fmt.Println("KO")
656 err = errors.New("Could not find image URL.")
657 }
658 return nil
659}
660
661func (t *UploadCandidate) generateTorrent(cfg Config) error {
662 fmt.Println("Building torrent file " + filepath.Base(t.torrentFile))
663 mi := &metainfo.MetaInfo{Announce: cfg.bibPasskey}
664 mi.Comment = "for bibliotik"
665 mi.CreatedBy = "BRUTE"
666 mi.CreationDate = time.Now().Unix()
667 mi.Info.PieceLength = 256 * 1024
668 mi.Info.BuildFromFilePath(t.epubFile)
669 mi.Info.Private = newTrue()
670
671 f, err := os.Create(t.torrentFile)
672 if err != nil {
673 return err
674 }
675 defer f.Close()
676 return mi.Write(f)
677}
678
679func (t *UploadCandidate) uploadTorrent(cfg Config) (err error) {
680 // login
681 client, data, err := login("https://bibliotik.me", cfg.bibUser, cfg.bibPassword)
682 if err != nil {
683 fmt.Println(err.Error())
684 return err
685 }
686 // getting authkey
687 r := regexp.MustCompile(".*authkey=([[:alnum:]]{40}).*")
688 if r.MatchString(data) {
689 t.info.authkey = r.FindStringSubmatch(data)[1]
690 } else {
691 return errors.New("Could not log in.")
692 }
693 // prepare request
694 req, err := t.info.generateUploadRequest("https://bibliotik.me/upload/ebooks")
695 if err != nil {
696 return err
697 }
698 // submit the request
699 resp, err := client.Do(req)
700 if err != nil {
701 return err
702 }
703 defer resp.Body.Close()
704
705 // check what URL the response came from
706 finalURL := resp.Request.URL.String()
707 if finalURL == "https://bibliotik.me/upload/ebooks" {
708 return errors.New("Upload probably failed, response was from upload form.")
709 }
710 return
711}
712
713func (t *UploadCandidate) seed(cfg Config) (err error) {
714 fmt.Println("Copying files to begin seeding.")
715 err = e.CopyFile(t.epubFile, filepath.Join(cfg.torrentSeedDir, filepath.Base(t.epubFile)))
716 if err != nil {
717 return
718 }
719 err = e.CopyFile(t.torrentFile, filepath.Join(cfg.torrentWatchDir, filepath.Base(t.torrentFile)))
720 if err != nil {
721 return
722 }
723 return
724}
725
726func (t *UploadCandidate) archive(cfg Config) (err error) {
727 // generate archive name
728 currentTime := time.Now().Local()
729 currentDay := currentTime.Format("2006-01-02")
730 archiveName := filepath.Join(cfg.archiveDir, fmt.Sprintf("%s - %s - %s (%s) %s.tar.gz", currentDay, t.info.IsbnField, t.info.AuthorsField, t.info.YearField, t.info.TitleField))
731 if _, err := e.FileExists(archiveName); err == nil {
732 return errors.New("Archive " + archiveName + " already exists")
733 }
734 fmt.Println("Backing up files to " + filepath.Base(archiveName))
735
736 // generate archive
737 tar := new(archivex.TarFile)
738 if err := tar.Create(archiveName); err != nil {
739 return errors.New("Error creating tar.")
740 }
741 if err := tar.AddFile(t.epubFile); err != nil {
742 return errors.New("Error adding epub " + t.epubFile)
743 }
744 if err := tar.AddFile(t.imageFile); err != nil {
745 return errors.New("Error adding image " + t.imageFile)
746 }
747 if err := tar.AddFile(t.torrentFile); err != nil {
748 return errors.New("Error adding torrent " + t.torrentFile)
749 }
750 if err := tar.AddFile(t.infoFile); err != nil {
751 return errors.New("Error adding yaml " + t.infoFile)
752 }
753 if err := tar.Close(); err != nil {
754 return errors.New("Error writing tar.")
755 }
756 return
757}
758
759func (t *UploadCandidate) cleanUp() (err error) {
760 if err := os.Remove(t.imageFile); err != nil {
761 return errors.New("Could not remove " + t.imageFile)
762 }
763 if err := os.Remove(t.torrentFile); err != nil {
764 return errors.New("Could not remove " + t.torrentFile)
765 }
766 if err := os.Remove(t.epubFile); err != nil {
767 return errors.New("Could not remove " + t.epubFile)
768 }
769 if err := os.Remove(t.infoFile); err != nil {
770 return errors.New("Could not remove " + t.infoFile)
771 }
772 return
773}