OnlineLibrary
813 строк · 21.9 Кб
1package manager
2
3import (
4"context"
5"errors"
6"fmt"
7"io"
8"net"
9"os"
10"path/filepath"
11"runtime"
12"strconv"
13"strings"
14"time"
15
16"OnlineLibrary/internal/books"
17"OnlineLibrary/internal/config"
18"OnlineLibrary/internal/connection"
19"OnlineLibrary/internal/content"
20"OnlineLibrary/internal/gui"
21"OnlineLibrary/internal/gui/msg"
22"OnlineLibrary/internal/log"
23"OnlineLibrary/internal/player"
24"OnlineLibrary/internal/providers"
25"OnlineLibrary/internal/providers/library"
26"OnlineLibrary/internal/providers/localstorage"
27"OnlineLibrary/internal/util"
28"OnlineLibrary/internal/util/buffer"
29
30"github.com/leonelquinteros/gotext"
31"gitverse.ru/kvark128/dodp"
32)
33
34const (
35CRLF = "\r\n"
36)
37
38var (
39OperationNotSupported = errors.New("operation not supported")
40)
41
42type ChoiceItem struct {
43dodp.Choice
44}
45
46func (c ChoiceItem) Label() string {
47return c.Choice.Label.Text
48}
49
50type Manager struct {
51provider providers.Provider
52mainWnd *gui.MainWnd
53logger *log.Logger
54book *books.Book
55contentList *content.List
56questions *dodp.Questions
57userResponses []dodp.UserResponse
58lastInputText string
59}
60
61func NewManager(mainWnd *gui.MainWnd, logger *log.Logger) *Manager {
62return &Manager{mainWnd: mainWnd, logger: logger}
63}
64
65func (m *Manager) Start(conf *config.Config, done chan<- bool) {
66m.logger.Debug("Entering to Manager Loop")
67defer func() {
68if p := recover(); p != nil {
69buf := make([]byte, 4096)
70n := runtime.Stack(buf, false)
71m.logger.Error("Manager panic: %v:\n\n%v", p, string(buf[:n]))
72os.Exit(1)
73}
74m.cleaning(conf)
75m.logger.Debug("Exiting from Manager Loop")
76done <- true
77}()
78
79for message := range m.mainWnd.MsgChan() {
80switch message.Code {
81case msg.ACTIVATE_MENU:
82if m.contentList != nil {
83book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
84if m.book == nil || m.book.ID() != book.ID() {
85if err := m.setBook(conf, book); err != nil {
86m.messageBoxError(fmt.Errorf("Setting a book: %w", err))
87break
88}
89}
90m.book.PlayPause()
91} else if m.questions != nil {
92questionIndex := len(m.userResponses)
93questionID := m.questions.MultipleChoiceQuestion[questionIndex].ID
94value := m.mainWnd.MainListBox().CurrentItem().(ChoiceItem).ID
95m.userResponses = append(m.userResponses, dodp.UserResponse{QuestionID: questionID, Value: value})
96questionIndex++
97if questionIndex < len(m.questions.MultipleChoiceQuestion) {
98m.setMultipleChoiceQuestion(questionIndex)
99break
100}
101m.setInputQuestion()
102}
103
104case msg.OPEN_BOOKSHELF:
105m.setContentList(dodp.Issued)
106
107case msg.OPEN_NEWBOOKS:
108m.setContentList(dodp.New)
109
110case msg.MAIN_MENU:
111m.setQuestions(dodp.UserResponse{QuestionID: dodp.Default})
112
113case msg.SEARCH_BOOK:
114m.setQuestions(dodp.UserResponse{QuestionID: dodp.Search})
115
116case msg.MENU_BACK:
117m.setQuestions(dodp.UserResponse{QuestionID: dodp.Back})
118
119case msg.SET_PROVIDER:
120id, ok := message.Data.(string)
121if !ok {
122if conf.General.Provider == "" {
123break
124}
125id = conf.General.Provider
126}
127
128if m.book != nil {
129m.book.Pause(true)
130}
131
132var provider providers.Provider
133var err error
134if id == config.LocalStorageID {
135provider = localstorage.NewLocalStorage(conf)
136} else {
137var service *config.Service
138service, err = conf.ServiceByID(id)
139if err != nil {
140m.logger.Debug("Get service %v: %v", id, err)
141break
142}
143provider, err = library.NewLibrary(conf, service)
144}
145
146if err != nil {
147m.messageBoxError(fmt.Errorf("Creating provider %v: %w", id, err))
148break
149}
150m.setProvider(provider, conf, id)
151
152case msg.LIBRARY_ADD:
153service := new(config.Service)
154if gui.CredentialsEntryDialog(m.mainWnd, service) != gui.DlgCmdOK || service.Name == "" {
155m.logger.Warning("Library adding: pressed Cancel button or len(service.Name) == 0")
156break
157}
158
159if _, err := conf.ServiceByName(service.Name); err == nil {
160gui.MessageBox(m.mainWnd, gotext.Get("Error"), gotext.Get("Account \"%v\" already exists", service.Name), gui.MsgBoxOK|gui.MsgBoxIconError)
161break
162}
163
164// Service id creation. Maximum index value of 256 is intended to protect against an infinite loop
165for i := 0; i < 256; i++ {
166id := fmt.Sprintf("library%d", i)
167if _, err := conf.ServiceByID(id); err != nil {
168service.ID = id
169m.logger.Debug("Created new service id: %v", id)
170break
171}
172}
173
174provider, err := library.NewLibrary(conf, service)
175if err != nil {
176m.messageBoxError(fmt.Errorf("Creating library: %w", err))
177break
178}
179conf.SetService(service)
180m.setProvider(provider, conf, service.ID)
181
182case msg.LIBRARY_REMOVE:
183lib, ok := m.provider.(*library.Library)
184if !ok {
185break
186}
187msg := gotext.Get("Are you sure you want to delete the account \"%v\"?\nAll saved bookmarks of all books in this library will also be deleted.\nThis action cannot be undone.", lib.Service().Name)
188if gui.MessageBox(m.mainWnd, gotext.Get("Deleting an account"), msg, gui.MsgBoxYesNo|gui.MsgBoxIconQuestion) != gui.DlgCmdYes {
189break
190}
191conf.RemoveService(lib.Service())
192m.cleaning(conf)
193m.mainWnd.MenuBar().SetProvidersMenu(conf.Services, "")
194
195case msg.ISSUE_BOOK:
196if m.contentList == nil {
197break
198}
199book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
200if err := m.issueBook(book); err != nil {
201m.messageBoxError(fmt.Errorf("Adding a book: %w", err))
202break
203}
204title := gotext.Get("Warning")
205msg := gotext.Get("Selected book has been added to the bookshelf")
206gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconInformation)
207
208case msg.REMOVE_BOOK:
209if m.contentList == nil {
210break
211}
212book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
213if err := m.removeBook(book); err != nil {
214m.messageBoxError(fmt.Errorf("Removing a book: %w", err))
215break
216}
217title := gotext.Get("Warning")
218msg := gotext.Get("Selected book has been removed from the bookshelf")
219gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconInformation)
220// If a bookshelf is open, it must be updated to reflect the changes made
221if m.contentList.ID == dodp.Issued {
222m.setContentList(dodp.Issued)
223}
224
225case msg.DOWNLOAD_BOOK:
226if m.contentList == nil {
227break
228}
229book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
230if err := m.downloadBook(book); err != nil {
231m.messageBoxError(fmt.Errorf("Downloading a book: %w", err))
232}
233
234case msg.BOOK_DESCRIPTION:
235if m.contentList == nil {
236break
237}
238book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
239md, err := book.ContentMetadata()
240if err != nil {
241m.messageBoxError(err)
242break
243}
244description := strings.Join(md.Metadata.Description, CRLF)
245if description == "" {
246description = gotext.Get("No description")
247}
248gui.BookInfoDialog(m.mainWnd, gotext.Get("Book information"), description)
249
250case msg.PLAYER_PLAY_PAUSE:
251if m.book != nil {
252m.book.PlayPause()
253}
254
255case msg.PLAYER_STOP:
256if m.book != nil {
257m.book.Stop()
258}
259
260case msg.PLAYER_OFFSET_FRAGMENT:
261offset, ok := message.Data.(int)
262if !ok {
263m.logger.Error("Invalid fragment offset")
264break
265}
266if m.book != nil {
267fragment := m.book.Fragment()
268m.book.SetFragment(fragment + offset)
269}
270
271case msg.PLAYER_SPEED_RESET:
272if m.book != nil {
273m.book.SetSpeed(player.DEFAULT_SPEED)
274}
275
276case msg.PLAYER_SPEED_UP:
277if m.book != nil {
278value := m.book.Speed()
279m.book.SetSpeed(value + player.STEP_SPEED)
280}
281
282case msg.PLAYER_SPEED_DOWN:
283if m.book != nil {
284value := m.book.Speed()
285m.book.SetSpeed(value - player.STEP_SPEED)
286}
287
288case msg.PLAYER_VOLUME_RESET:
289if m.book != nil {
290m.book.SetVolume(player.DEFAULT_VOLUME)
291}
292
293case msg.PLAYER_VOLUME_UP:
294if m.book != nil {
295volume := m.book.Volume()
296m.book.SetVolume(volume + player.STEP_VOLUME)
297}
298
299case msg.PLAYER_VOLUME_DOWN:
300if m.book != nil {
301volume := m.book.Volume()
302m.book.SetVolume(volume - player.STEP_VOLUME)
303}
304
305case msg.PLAYER_OFFSET_POSITION:
306offset, ok := message.Data.(time.Duration)
307if !ok {
308m.logger.Error("Invalid position offset")
309break
310}
311if m.book != nil {
312pos := m.book.Position()
313m.book.SetPosition(pos + offset)
314}
315
316case msg.PLAYER_GOTO_FRAGMENT:
317if m.book == nil {
318break
319}
320fragment, ok := message.Data.(int)
321if !ok {
322var text string
323var err error
324fragment = m.book.Fragment()
325fragment++ // User needs a fragment number instead of an index
326if gui.TextEntryDialog(m.mainWnd, gotext.Get("Go to fragment"), gotext.Get("Enter fragment number:"), strconv.Itoa(fragment), &text) != gui.DlgCmdOK {
327break
328}
329fragment, err = strconv.Atoi(text)
330if err != nil {
331m.logger.Error("Goto fragment: %v", err)
332break
333}
334fragment-- // Player needs the fragment index instead of its number
335}
336m.book.SetFragment(fragment)
337
338case msg.PLAYER_GOTO_POSITION:
339if m.book == nil {
340break
341}
342pos, ok := message.Data.(time.Duration)
343if !ok {
344var text string
345var err error
346pos = m.book.Position()
347if gui.TextEntryDialog(m.mainWnd, gotext.Get("Go to position"), gotext.Get("Enter fragment position:"), util.FmtDuration(pos), &text) != gui.DlgCmdOK {
348break
349}
350pos, err = util.ParseDuration(text)
351if err != nil {
352m.logger.Error("Goto position: %v", err)
353break
354}
355}
356m.book.SetPosition(pos)
357
358case msg.PLAYER_OUTPUT_DEVICE:
359device, ok := message.Data.(string)
360if !ok {
361m.logger.Error("Invalid output device")
362break
363}
364conf.General.OutputDevice = device
365if m.book != nil {
366m.book.SetOutputDevice(device)
367}
368
369case msg.PLAYER_SET_TIMER:
370var text string
371var d int
372if m.book != nil {
373d = int(m.book.TimerDuration().Minutes())
374}
375
376if gui.TextEntryDialog(m.mainWnd, gotext.Get("Setting the pause timer"), gotext.Get("Enter the timer value in minutes:"), strconv.Itoa(d), &text) != gui.DlgCmdOK {
377break
378}
379
380n, err := strconv.Atoi(text)
381if err != nil {
382break
383}
384
385conf.General.PauseTimer = time.Minute * time.Duration(n)
386m.mainWnd.MenuBar().SetPauseTimerLabel(n)
387if m.book != nil {
388m.book.SetTimerDuration(conf.General.PauseTimer)
389}
390
391case msg.BOOKMARK_SET:
392if m.book == nil {
393// To set a bookmark, need a book
394break
395}
396if bookmarkID, ok := message.Data.(string); ok {
397m.book.SetBookmarkWithID(bookmarkID)
398break
399}
400var bookmarkName string
401if gui.TextEntryDialog(m.mainWnd, gotext.Get("Adding a new bookmark"), gotext.Get("Bookmark name:"), "", &bookmarkName) != gui.DlgCmdOK {
402break
403}
404if err := m.book.SetBookmarkWithName(bookmarkName); err != nil {
405m.logger.Warning("Set bookmark with name: %v", err)
406}
407m.mainWnd.MenuBar().SetBookmarksMenu(m.book.Bookmarks())
408
409case msg.BOOKMARK_FETCH:
410if m.book == nil {
411break
412}
413if bookmarkID, ok := message.Data.(string); ok {
414if err := m.book.ToBookmark(bookmarkID); err != nil {
415m.logger.Warning("Bookmark fetching: %v", err)
416}
417}
418
419case msg.BOOKMARK_REMOVE:
420if m.book == nil {
421break
422}
423if bookmarkID, ok := message.Data.(string); ok {
424bookmark, err := m.book.Bookmark(bookmarkID)
425if err != nil {
426m.logger.Warning("Bookmark removing: %v", err)
427break
428}
429msg := gotext.Get("Are you sure you want to delete the bookmark \"%v\"?", bookmark.Name)
430if gui.MessageBox(m.mainWnd, gotext.Get("Deleting a bookmark"), msg, gui.MsgBoxYesNo|gui.MsgBoxIconQuestion) != gui.DlgCmdYes {
431break
432}
433m.book.RemoveBookmark(bookmarkID)
434m.mainWnd.MenuBar().SetBookmarksMenu(m.book.Bookmarks())
435}
436
437case msg.LOG_SET_LEVEL:
438level, ok := message.Data.(log.Level)
439if !ok {
440m.logger.Error("Invalid log level")
441break
442}
443conf.General.LogLevel = level.String()
444m.logger.SetLevel(level)
445m.logger.Info("Set log level to %v", level)
446
447case msg.LIBRARY_INFO:
448if m.provider == nil {
449break
450}
451lib, ok := m.provider.(*library.Library)
452if !ok {
453break
454}
455var lines []string
456attrs := lib.ServiceAttributes()
457lines = append(lines, gotext.Get("Name: %v (%v)", attrs.Service.Label.Text, attrs.Service.ID))
458lines = append(lines, gotext.Get("Back command support: %v", attrs.SupportsServerSideBack))
459lines = append(lines, gotext.Get("Search command support: %v", attrs.SupportsSearch))
460lines = append(lines, gotext.Get("Audio labels support: %v", attrs.SupportsAudioLabels))
461lines = append(lines, gotext.Get("Supported Optional Operations: %v", attrs.SupportedOptionalOperations.Operation))
462title := gotext.Get("Library information")
463msg := strings.Join(lines, CRLF)
464gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconInformation)
465
466case msg.SET_LANGUAGE:
467lang, ok := message.Data.(string)
468if !ok {
469break
470}
471conf.General.Language = lang
472title := gotext.Get("Warning")
473msg := gotext.Get("Changes will be reflected upon restarting the program")
474gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconWarning)
475
476default:
477m.logger.Warning("Unknown message: %v", message.Code)
478
479}
480}
481}
482
483func (m *Manager) cleaning(conf *config.Config) {
484m.setBook(conf, nil)
485m.mainWnd.MainListBox().Clear()
486m.mainWnd.MenuBar().SetBookMenuEnabled(false)
487m.contentList = nil
488m.questions = nil
489m.userResponses = nil
490
491if m.provider != nil {
492if term, ok := m.provider.(providers.Terminator); ok {
493m.logger.Warning("Terminating current provider")
494if err := term.Terminate(); err != nil {
495m.logger.Warning("Provider terminating: %v", err)
496}
497}
498m.provider = nil
499}
500}
501
502func (m *Manager) setProvider(provider providers.Provider, conf *config.Config, id string) {
503m.logger.Info("Set provider: %v", id)
504m.cleaning(conf)
505m.provider = provider
506conf.General.Provider = id
507if id, err := m.provider.LastContentListID(); err == nil {
508m.setContentList(id)
509} else if _, ok := m.provider.(providers.Questioner); ok {
510m.setQuestions(dodp.UserResponse{QuestionID: dodp.Default})
511}
512if id, err := m.provider.LastContentItemID(); err == nil {
513if contentItem, err := m.provider.ContentItem(id); err == nil {
514if err := m.setBook(conf, contentItem); err != nil {
515m.logger.Error("Set book: %v", err)
516}
517}
518}
519m.mainWnd.MenuBar().SetProvidersMenu(conf.Services, id)
520}
521
522func (m *Manager) setQuestions(response ...dodp.UserResponse) {
523qst, ok := m.provider.(providers.Questioner)
524if !ok {
525m.messageBoxError(OperationNotSupported)
526return
527}
528
529if len(response) == 0 {
530m.logger.Error("len(response) == 0")
531return
532}
533
534m.questions = nil
535m.userResponses = nil
536m.mainWnd.MainListBox().Clear()
537m.mainWnd.MenuBar().SetBookMenuEnabled(false)
538
539ur := dodp.UserResponses{UserResponse: response}
540questions, err := qst.GetQuestions(&ur)
541if err != nil {
542m.messageBoxError(fmt.Errorf("Getting a questions: %w", err))
543return
544}
545
546if questions.Label.Text != "" {
547// We have received a notification from the library. Show it to the user
548gui.MessageBox(m.mainWnd, gotext.Get("Warning"), questions.Label.Text, gui.MsgBoxOK|gui.MsgBoxIconInformation)
549// Return to the main menu of the library
550m.setQuestions(dodp.UserResponse{QuestionID: dodp.Default})
551return
552}
553
554if questions.ContentListRef != "" {
555// We got a list of content. Show it to the user
556m.setContentList(questions.ContentListRef)
557return
558}
559
560m.questions = questions
561m.userResponses = make([]dodp.UserResponse, 0)
562
563if len(m.questions.MultipleChoiceQuestion) > 0 {
564m.setMultipleChoiceQuestion(0)
565return
566}
567m.setInputQuestion()
568}
569
570func (m *Manager) setMultipleChoiceQuestion(index int) {
571choiceQuestion := m.questions.MultipleChoiceQuestion[index]
572items := make([]gui.ListItem, len(choiceQuestion.Choices.Choice))
573for i, c := range choiceQuestion.Choices.Choice {
574items[i] = ChoiceItem{Choice: c}
575}
576m.contentList = nil
577m.mainWnd.MainListBox().SetItems(items, choiceQuestion.Label.Text, nil)
578m.mainWnd.MenuBar().SetBookMenuEnabled(false)
579}
580
581func (m *Manager) setInputQuestion() {
582for _, inputQuestion := range m.questions.InputQuestion {
583var text string
584if gui.TextEntryDialog(m.mainWnd, gotext.Get("Entering text"), inputQuestion.Label.Text, m.lastInputText, &text) != gui.DlgCmdOK {
585// Return to the main menu of the library
586m.setQuestions(dodp.UserResponse{QuestionID: dodp.Default})
587return
588}
589m.lastInputText = text
590m.userResponses = append(m.userResponses, dodp.UserResponse{QuestionID: inputQuestion.ID, Value: text})
591}
592m.setQuestions(m.userResponses...)
593}
594
595func (m *Manager) setContentList(contentID string) {
596m.questions = nil
597m.mainWnd.MainListBox().Clear()
598m.mainWnd.MenuBar().SetBookMenuEnabled(false)
599m.logger.Debug("Set content list: %v", contentID)
600
601contentList, err := m.provider.ContentList(contentID)
602if err != nil {
603m.messageBoxError(fmt.Errorf("Getting a content list: %w", err))
604return
605}
606
607if len(contentList.Items) == 0 {
608title := gotext.Get("Warning")
609msg := gotext.Get("List of books is empty")
610gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconWarning)
611return
612}
613
614if contentList.ID == dodp.Issued {
615ids := make([]string, len(contentList.Items))
616for i := range ids {
617book := contentList.Items[i]
618ids[i] = book.ID()
619}
620if m.book != nil && !util.StringInSlice(m.book.ID(), ids) {
621ids = append(ids, m.book.ID())
622}
623m.provider.Tidy(ids)
624}
625
626m.updateContentList(contentList)
627}
628
629func (m *Manager) updateContentList(contentList *content.List) {
630m.contentList = contentList
631items := make([]gui.ListItem, len(m.contentList.Items))
632for i, v := range m.contentList.Items {
633items[i] = v
634}
635m.mainWnd.MainListBox().SetItems(items, contentList.Name, m.mainWnd.MenuBar().BookMenu())
636m.mainWnd.MenuBar().SetBookMenuEnabled(true)
637}
638
639func (m *Manager) setBook(conf *config.Config, contentItem content.Item) error {
640if m.book != nil {
641m.book.Pause(true)
642}
643
644if contentItem != nil {
645book, err := books.NewBook(conf.General.OutputDevice, contentItem, m.logger, m.mainWnd.StatusBar())
646if err != nil {
647return err
648}
649defer func() {
650book.SetTimerDuration(conf.General.PauseTimer)
651book.SetVolume(conf.General.Volume)
652m.mainWnd.SetTitle(book.Title)
653m.mainWnd.MenuBar().SetBookmarksMenu(book.Bookmarks())
654m.book = book
655m.logger.Debug("Set book: %v", book.ID())
656}()
657}
658
659if m.book != nil {
660conf.General.Volume = m.book.Volume()
661m.book.Save()
662m.book.Stop()
663m.mainWnd.SetTitle("")
664m.mainWnd.MenuBar().SetBookmarksMenu(nil)
665m.book = nil
666}
667return nil
668}
669
670func (m *Manager) downloadBook(book content.Item) error {
671if _, ok := m.provider.(*library.Library); !ok {
672return OperationNotSupported
673}
674
675name, err := book.Name()
676if err != nil {
677return err
678}
679
680dir, err := config.BookDir(name)
681if err != nil {
682return err
683}
684
685rsrc, err := book.Resources()
686if err != nil {
687return err
688}
689
690if md, err := book.ContentMetadata(); err == nil {
691path := filepath.Join(dir, config.MetadataFileName)
692if err := util.SaveXMLFile(path, &md); err != nil {
693m.logger.Error("Saving metadata file: %v", err)
694}
695}
696
697dlFunc := func(rsrc []dodp.Resource, dir string, id string) {
698var err error
699var totalSize, downloadedSize int64
700ctx, cancelFunc := context.WithCancel(context.TODO())
701dlg := gui.NewProgressDialog(m.mainWnd, gotext.Get("Book downloading"), gotext.Get("Downloading \"%v\"\nSpeed: %d KB/s", name, 0), 100, cancelFunc)
702dlg.Run()
703
704for _, r := range rsrc {
705totalSize += r.Size
706}
707
708m.logger.Debug("Downloading book %v started", id)
709for _, r := range rsrc {
710err = func() error {
711path := filepath.Join(dir, r.LocalURI)
712if util.FileIsExist(path, r.Size) {
713// This fragment already exists on disk
714downloadedSize += r.Size
715dlg.SetValue(int(downloadedSize / (totalSize / 100)))
716return nil
717}
718
719conn, err := connection.NewConnectionWithContext(ctx, r.URI, m.logger)
720if err != nil {
721return err
722}
723defer conn.Close()
724
725dst, err := util.CreateSecureFile(path)
726if err != nil {
727return err
728}
729defer dst.Close()
730
731var fragmentSize int64
732var src io.Reader = buffer.NewReader(conn)
733for err == nil {
734var n int64
735t := time.Now()
736n, err = io.CopyN(dst, src, 512*1024)
737sec := time.Since(t).Seconds()
738speed := int(float64(n) / 1024 / sec)
739dlg.SetLabel(gotext.Get("Downloading \"%v\"\nSpeed: %d KB/s", name, speed))
740downloadedSize += n
741fragmentSize += n
742dlg.SetValue(int(downloadedSize / (totalSize / 100)))
743}
744
745if err == io.EOF {
746err = nil
747}
748
749if err == nil && r.Size != fragmentSize {
750err = errors.New("resource size mismatch")
751}
752
753if err != nil {
754dst.Corrupted()
755}
756return err
757}()
758
759if err != nil {
760break
761}
762}
763
764dlg.Cancel()
765
766switch {
767case errors.Is(err, context.Canceled):
768title := gotext.Get("Warning")
769msg := gotext.Get("Download canceled by user")
770gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconWarning)
771case err != nil:
772gui.MessageBox(m.mainWnd, gotext.Get("Error"), err.Error(), gui.MsgBoxOK|gui.MsgBoxIconError)
773default:
774title := gotext.Get("Warning")
775msg := gotext.Get("Book successfully downloaded")
776gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconWarning)
777m.logger.Debug("Book %v has been successfully downloaded. Total size: %v", id, totalSize)
778}
779}
780
781// Book downloading should not block handling of other messages
782go dlFunc(rsrc, dir, book.ID())
783return nil
784}
785
786func (m *Manager) removeBook(book content.Item) error {
787returner, ok := book.(content.Returner)
788if !ok {
789return OperationNotSupported
790}
791return returner.Return()
792}
793
794func (m *Manager) issueBook(book content.Item) error {
795issuer, ok := book.(content.Issuer)
796if !ok {
797return OperationNotSupported
798}
799return issuer.Issue()
800}
801
802func (m *Manager) messageBoxError(err error) {
803msg := err.Error()
804m.logger.Error(msg)
805var netErr *net.OpError
806switch {
807case errors.As(err, &netErr):
808msg = gotext.Get("Network error. Check your Internet connection or try the operation later")
809case errors.Is(err, OperationNotSupported):
810msg = gotext.Get("Operation not supported")
811}
812gui.MessageBox(m.mainWnd, gotext.Get("Error"), msg, gui.MsgBoxOK|gui.MsgBoxIconError)
813}
814