OnlineLibrary

Форк
0
813 строк · 21.9 Кб
1
package manager
2

3
import (
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

34
const (
35
	CRLF = "\r\n"
36
)
37

38
var (
39
	OperationNotSupported = errors.New("operation not supported")
40
)
41

42
type ChoiceItem struct {
43
	dodp.Choice
44
}
45

46
func (c ChoiceItem) Label() string {
47
	return c.Choice.Label.Text
48
}
49

50
type Manager struct {
51
	provider      providers.Provider
52
	mainWnd       *gui.MainWnd
53
	logger        *log.Logger
54
	book          *books.Book
55
	contentList   *content.List
56
	questions     *dodp.Questions
57
	userResponses []dodp.UserResponse
58
	lastInputText string
59
}
60

61
func NewManager(mainWnd *gui.MainWnd, logger *log.Logger) *Manager {
62
	return &Manager{mainWnd: mainWnd, logger: logger}
63
}
64

65
func (m *Manager) Start(conf *config.Config, done chan<- bool) {
66
	m.logger.Debug("Entering to Manager Loop")
67
	defer func() {
68
		if p := recover(); p != nil {
69
			buf := make([]byte, 4096)
70
			n := runtime.Stack(buf, false)
71
			m.logger.Error("Manager panic: %v:\n\n%v", p, string(buf[:n]))
72
			os.Exit(1)
73
		}
74
		m.cleaning(conf)
75
		m.logger.Debug("Exiting from Manager Loop")
76
		done <- true
77
	}()
78

79
	for message := range m.mainWnd.MsgChan() {
80
		switch message.Code {
81
		case msg.ACTIVATE_MENU:
82
			if m.contentList != nil {
83
				book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
84
				if m.book == nil || m.book.ID() != book.ID() {
85
					if err := m.setBook(conf, book); err != nil {
86
						m.messageBoxError(fmt.Errorf("Setting a book: %w", err))
87
						break
88
					}
89
				}
90
				m.book.PlayPause()
91
			} else if m.questions != nil {
92
				questionIndex := len(m.userResponses)
93
				questionID := m.questions.MultipleChoiceQuestion[questionIndex].ID
94
				value := m.mainWnd.MainListBox().CurrentItem().(ChoiceItem).ID
95
				m.userResponses = append(m.userResponses, dodp.UserResponse{QuestionID: questionID, Value: value})
96
				questionIndex++
97
				if questionIndex < len(m.questions.MultipleChoiceQuestion) {
98
					m.setMultipleChoiceQuestion(questionIndex)
99
					break
100
				}
101
				m.setInputQuestion()
102
			}
103

104
		case msg.OPEN_BOOKSHELF:
105
			m.setContentList(dodp.Issued)
106

107
		case msg.OPEN_NEWBOOKS:
108
			m.setContentList(dodp.New)
109

110
		case msg.MAIN_MENU:
111
			m.setQuestions(dodp.UserResponse{QuestionID: dodp.Default})
112

113
		case msg.SEARCH_BOOK:
114
			m.setQuestions(dodp.UserResponse{QuestionID: dodp.Search})
115

116
		case msg.MENU_BACK:
117
			m.setQuestions(dodp.UserResponse{QuestionID: dodp.Back})
118

119
		case msg.SET_PROVIDER:
120
			id, ok := message.Data.(string)
121
			if !ok {
122
				if conf.General.Provider == "" {
123
					break
124
				}
125
				id = conf.General.Provider
126
			}
127

128
			if m.book != nil {
129
				m.book.Pause(true)
130
			}
131

132
			var provider providers.Provider
133
			var err error
134
			if id == config.LocalStorageID {
135
				provider = localstorage.NewLocalStorage(conf)
136
			} else {
137
				var service *config.Service
138
				service, err = conf.ServiceByID(id)
139
				if err != nil {
140
					m.logger.Debug("Get service %v: %v", id, err)
141
					break
142
				}
143
				provider, err = library.NewLibrary(conf, service)
144
			}
145

146
			if err != nil {
147
				m.messageBoxError(fmt.Errorf("Creating provider %v: %w", id, err))
148
				break
149
			}
150
			m.setProvider(provider, conf, id)
151

152
		case msg.LIBRARY_ADD:
153
			service := new(config.Service)
154
			if gui.CredentialsEntryDialog(m.mainWnd, service) != gui.DlgCmdOK || service.Name == "" {
155
				m.logger.Warning("Library adding: pressed Cancel button or len(service.Name) == 0")
156
				break
157
			}
158

159
			if _, err := conf.ServiceByName(service.Name); err == nil {
160
				gui.MessageBox(m.mainWnd, gotext.Get("Error"), gotext.Get("Account \"%v\" already exists", service.Name), gui.MsgBoxOK|gui.MsgBoxIconError)
161
				break
162
			}
163

164
			// Service id creation. Maximum index value of 256 is intended to protect against an infinite loop
165
			for i := 0; i < 256; i++ {
166
				id := fmt.Sprintf("library%d", i)
167
				if _, err := conf.ServiceByID(id); err != nil {
168
					service.ID = id
169
					m.logger.Debug("Created new service id: %v", id)
170
					break
171
				}
172
			}
173

174
			provider, err := library.NewLibrary(conf, service)
175
			if err != nil {
176
				m.messageBoxError(fmt.Errorf("Creating library: %w", err))
177
				break
178
			}
179
			conf.SetService(service)
180
			m.setProvider(provider, conf, service.ID)
181

182
		case msg.LIBRARY_REMOVE:
183
			lib, ok := m.provider.(*library.Library)
184
			if !ok {
185
				break
186
			}
187
			msg := 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)
188
			if gui.MessageBox(m.mainWnd, gotext.Get("Deleting an account"), msg, gui.MsgBoxYesNo|gui.MsgBoxIconQuestion) != gui.DlgCmdYes {
189
				break
190
			}
191
			conf.RemoveService(lib.Service())
192
			m.cleaning(conf)
193
			m.mainWnd.MenuBar().SetProvidersMenu(conf.Services, "")
194

195
		case msg.ISSUE_BOOK:
196
			if m.contentList == nil {
197
				break
198
			}
199
			book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
200
			if err := m.issueBook(book); err != nil {
201
				m.messageBoxError(fmt.Errorf("Adding a book: %w", err))
202
				break
203
			}
204
			title := gotext.Get("Warning")
205
			msg := gotext.Get("Selected book has been added to the bookshelf")
206
			gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconInformation)
207

208
		case msg.REMOVE_BOOK:
209
			if m.contentList == nil {
210
				break
211
			}
212
			book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
213
			if err := m.removeBook(book); err != nil {
214
				m.messageBoxError(fmt.Errorf("Removing a book: %w", err))
215
				break
216
			}
217
			title := gotext.Get("Warning")
218
			msg := gotext.Get("Selected book has been removed from the bookshelf")
219
			gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconInformation)
220
			// If a bookshelf is open, it must be updated to reflect the changes made
221
			if m.contentList.ID == dodp.Issued {
222
				m.setContentList(dodp.Issued)
223
			}
224

225
		case msg.DOWNLOAD_BOOK:
226
			if m.contentList == nil {
227
				break
228
			}
229
			book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
230
			if err := m.downloadBook(book); err != nil {
231
				m.messageBoxError(fmt.Errorf("Downloading a book: %w", err))
232
			}
233

234
		case msg.BOOK_DESCRIPTION:
235
			if m.contentList == nil {
236
				break
237
			}
238
			book := m.mainWnd.MainListBox().CurrentItem().(content.Item)
239
			md, err := book.ContentMetadata()
240
			if err != nil {
241
				m.messageBoxError(err)
242
				break
243
			}
244
			description := strings.Join(md.Metadata.Description, CRLF)
245
			if description == "" {
246
				description = gotext.Get("No description")
247
			}
248
			gui.BookInfoDialog(m.mainWnd, gotext.Get("Book information"), description)
249

250
		case msg.PLAYER_PLAY_PAUSE:
251
			if m.book != nil {
252
				m.book.PlayPause()
253
			}
254

255
		case msg.PLAYER_STOP:
256
			if m.book != nil {
257
				m.book.Stop()
258
			}
259

260
		case msg.PLAYER_OFFSET_FRAGMENT:
261
			offset, ok := message.Data.(int)
262
			if !ok {
263
				m.logger.Error("Invalid fragment offset")
264
				break
265
			}
266
			if m.book != nil {
267
				fragment := m.book.Fragment()
268
				m.book.SetFragment(fragment + offset)
269
			}
270

271
		case msg.PLAYER_SPEED_RESET:
272
			if m.book != nil {
273
				m.book.SetSpeed(player.DEFAULT_SPEED)
274
			}
275

276
		case msg.PLAYER_SPEED_UP:
277
			if m.book != nil {
278
				value := m.book.Speed()
279
				m.book.SetSpeed(value + player.STEP_SPEED)
280
			}
281

282
		case msg.PLAYER_SPEED_DOWN:
283
			if m.book != nil {
284
				value := m.book.Speed()
285
				m.book.SetSpeed(value - player.STEP_SPEED)
286
			}
287

288
		case msg.PLAYER_VOLUME_RESET:
289
			if m.book != nil {
290
				m.book.SetVolume(player.DEFAULT_VOLUME)
291
			}
292

293
		case msg.PLAYER_VOLUME_UP:
294
			if m.book != nil {
295
				volume := m.book.Volume()
296
				m.book.SetVolume(volume + player.STEP_VOLUME)
297
			}
298

299
		case msg.PLAYER_VOLUME_DOWN:
300
			if m.book != nil {
301
				volume := m.book.Volume()
302
				m.book.SetVolume(volume - player.STEP_VOLUME)
303
			}
304

305
		case msg.PLAYER_OFFSET_POSITION:
306
			offset, ok := message.Data.(time.Duration)
307
			if !ok {
308
				m.logger.Error("Invalid position offset")
309
				break
310
			}
311
			if m.book != nil {
312
				pos := m.book.Position()
313
				m.book.SetPosition(pos + offset)
314
			}
315

316
		case msg.PLAYER_GOTO_FRAGMENT:
317
			if m.book == nil {
318
				break
319
			}
320
			fragment, ok := message.Data.(int)
321
			if !ok {
322
				var text string
323
				var err error
324
				fragment = m.book.Fragment()
325
				fragment++ // User needs a fragment number instead of an index
326
				if gui.TextEntryDialog(m.mainWnd, gotext.Get("Go to fragment"), gotext.Get("Enter fragment number:"), strconv.Itoa(fragment), &text) != gui.DlgCmdOK {
327
					break
328
				}
329
				fragment, err = strconv.Atoi(text)
330
				if err != nil {
331
					m.logger.Error("Goto fragment: %v", err)
332
					break
333
				}
334
				fragment-- // Player needs the fragment index instead of its number
335
			}
336
			m.book.SetFragment(fragment)
337

338
		case msg.PLAYER_GOTO_POSITION:
339
			if m.book == nil {
340
				break
341
			}
342
			pos, ok := message.Data.(time.Duration)
343
			if !ok {
344
				var text string
345
				var err error
346
				pos = m.book.Position()
347
				if gui.TextEntryDialog(m.mainWnd, gotext.Get("Go to position"), gotext.Get("Enter fragment position:"), util.FmtDuration(pos), &text) != gui.DlgCmdOK {
348
					break
349
				}
350
				pos, err = util.ParseDuration(text)
351
				if err != nil {
352
					m.logger.Error("Goto position: %v", err)
353
					break
354
				}
355
			}
356
			m.book.SetPosition(pos)
357

358
		case msg.PLAYER_OUTPUT_DEVICE:
359
			device, ok := message.Data.(string)
360
			if !ok {
361
				m.logger.Error("Invalid output device")
362
				break
363
			}
364
			conf.General.OutputDevice = device
365
			if m.book != nil {
366
				m.book.SetOutputDevice(device)
367
			}
368

369
		case msg.PLAYER_SET_TIMER:
370
			var text string
371
			var d int
372
			if m.book != nil {
373
				d = int(m.book.TimerDuration().Minutes())
374
			}
375

376
			if gui.TextEntryDialog(m.mainWnd, gotext.Get("Setting the pause timer"), gotext.Get("Enter the timer value in minutes:"), strconv.Itoa(d), &text) != gui.DlgCmdOK {
377
				break
378
			}
379

380
			n, err := strconv.Atoi(text)
381
			if err != nil {
382
				break
383
			}
384

385
			conf.General.PauseTimer = time.Minute * time.Duration(n)
386
			m.mainWnd.MenuBar().SetPauseTimerLabel(n)
387
			if m.book != nil {
388
				m.book.SetTimerDuration(conf.General.PauseTimer)
389
			}
390

391
		case msg.BOOKMARK_SET:
392
			if m.book == nil {
393
				// To set a bookmark, need a book
394
				break
395
			}
396
			if bookmarkID, ok := message.Data.(string); ok {
397
				m.book.SetBookmarkWithID(bookmarkID)
398
				break
399
			}
400
			var bookmarkName string
401
			if gui.TextEntryDialog(m.mainWnd, gotext.Get("Adding a new bookmark"), gotext.Get("Bookmark name:"), "", &bookmarkName) != gui.DlgCmdOK {
402
				break
403
			}
404
			if err := m.book.SetBookmarkWithName(bookmarkName); err != nil {
405
				m.logger.Warning("Set bookmark with name: %v", err)
406
			}
407
			m.mainWnd.MenuBar().SetBookmarksMenu(m.book.Bookmarks())
408

409
		case msg.BOOKMARK_FETCH:
410
			if m.book == nil {
411
				break
412
			}
413
			if bookmarkID, ok := message.Data.(string); ok {
414
				if err := m.book.ToBookmark(bookmarkID); err != nil {
415
					m.logger.Warning("Bookmark fetching: %v", err)
416
				}
417
			}
418

419
		case msg.BOOKMARK_REMOVE:
420
			if m.book == nil {
421
				break
422
			}
423
			if bookmarkID, ok := message.Data.(string); ok {
424
				bookmark, err := m.book.Bookmark(bookmarkID)
425
				if err != nil {
426
					m.logger.Warning("Bookmark removing: %v", err)
427
					break
428
				}
429
				msg := gotext.Get("Are you sure you want to delete the bookmark \"%v\"?", bookmark.Name)
430
				if gui.MessageBox(m.mainWnd, gotext.Get("Deleting a bookmark"), msg, gui.MsgBoxYesNo|gui.MsgBoxIconQuestion) != gui.DlgCmdYes {
431
					break
432
				}
433
				m.book.RemoveBookmark(bookmarkID)
434
				m.mainWnd.MenuBar().SetBookmarksMenu(m.book.Bookmarks())
435
			}
436

437
		case msg.LOG_SET_LEVEL:
438
			level, ok := message.Data.(log.Level)
439
			if !ok {
440
				m.logger.Error("Invalid log level")
441
				break
442
			}
443
			conf.General.LogLevel = level.String()
444
			m.logger.SetLevel(level)
445
			m.logger.Info("Set log level to %v", level)
446

447
		case msg.LIBRARY_INFO:
448
			if m.provider == nil {
449
				break
450
			}
451
			lib, ok := m.provider.(*library.Library)
452
			if !ok {
453
				break
454
			}
455
			var lines []string
456
			attrs := lib.ServiceAttributes()
457
			lines = append(lines, gotext.Get("Name: %v (%v)", attrs.Service.Label.Text, attrs.Service.ID))
458
			lines = append(lines, gotext.Get("Back command support: %v", attrs.SupportsServerSideBack))
459
			lines = append(lines, gotext.Get("Search command support: %v", attrs.SupportsSearch))
460
			lines = append(lines, gotext.Get("Audio labels support: %v", attrs.SupportsAudioLabels))
461
			lines = append(lines, gotext.Get("Supported Optional Operations: %v", attrs.SupportedOptionalOperations.Operation))
462
			title := gotext.Get("Library information")
463
			msg := strings.Join(lines, CRLF)
464
			gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconInformation)
465

466
		case msg.SET_LANGUAGE:
467
			lang, ok := message.Data.(string)
468
			if !ok {
469
				break
470
			}
471
			conf.General.Language = lang
472
			title := gotext.Get("Warning")
473
			msg := gotext.Get("Changes will be reflected upon restarting the program")
474
			gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconWarning)
475

476
		default:
477
			m.logger.Warning("Unknown message: %v", message.Code)
478

479
		}
480
	}
481
}
482

483
func (m *Manager) cleaning(conf *config.Config) {
484
	m.setBook(conf, nil)
485
	m.mainWnd.MainListBox().Clear()
486
	m.mainWnd.MenuBar().SetBookMenuEnabled(false)
487
	m.contentList = nil
488
	m.questions = nil
489
	m.userResponses = nil
490

491
	if m.provider != nil {
492
		if term, ok := m.provider.(providers.Terminator); ok {
493
			m.logger.Warning("Terminating current provider")
494
			if err := term.Terminate(); err != nil {
495
				m.logger.Warning("Provider terminating: %v", err)
496
			}
497
		}
498
		m.provider = nil
499
	}
500
}
501

502
func (m *Manager) setProvider(provider providers.Provider, conf *config.Config, id string) {
503
	m.logger.Info("Set provider: %v", id)
504
	m.cleaning(conf)
505
	m.provider = provider
506
	conf.General.Provider = id
507
	if id, err := m.provider.LastContentListID(); err == nil {
508
		m.setContentList(id)
509
	} else if _, ok := m.provider.(providers.Questioner); ok {
510
		m.setQuestions(dodp.UserResponse{QuestionID: dodp.Default})
511
	}
512
	if id, err := m.provider.LastContentItemID(); err == nil {
513
		if contentItem, err := m.provider.ContentItem(id); err == nil {
514
			if err := m.setBook(conf, contentItem); err != nil {
515
				m.logger.Error("Set book: %v", err)
516
			}
517
		}
518
	}
519
	m.mainWnd.MenuBar().SetProvidersMenu(conf.Services, id)
520
}
521

522
func (m *Manager) setQuestions(response ...dodp.UserResponse) {
523
	qst, ok := m.provider.(providers.Questioner)
524
	if !ok {
525
		m.messageBoxError(OperationNotSupported)
526
		return
527
	}
528

529
	if len(response) == 0 {
530
		m.logger.Error("len(response) == 0")
531
		return
532
	}
533

534
	m.questions = nil
535
	m.userResponses = nil
536
	m.mainWnd.MainListBox().Clear()
537
	m.mainWnd.MenuBar().SetBookMenuEnabled(false)
538

539
	ur := dodp.UserResponses{UserResponse: response}
540
	questions, err := qst.GetQuestions(&ur)
541
	if err != nil {
542
		m.messageBoxError(fmt.Errorf("Getting a questions: %w", err))
543
		return
544
	}
545

546
	if questions.Label.Text != "" {
547
		// We have received a notification from the library. Show it to the user
548
		gui.MessageBox(m.mainWnd, gotext.Get("Warning"), questions.Label.Text, gui.MsgBoxOK|gui.MsgBoxIconInformation)
549
		// Return to the main menu of the library
550
		m.setQuestions(dodp.UserResponse{QuestionID: dodp.Default})
551
		return
552
	}
553

554
	if questions.ContentListRef != "" {
555
		// We got a list of content. Show it to the user
556
		m.setContentList(questions.ContentListRef)
557
		return
558
	}
559

560
	m.questions = questions
561
	m.userResponses = make([]dodp.UserResponse, 0)
562

563
	if len(m.questions.MultipleChoiceQuestion) > 0 {
564
		m.setMultipleChoiceQuestion(0)
565
		return
566
	}
567
	m.setInputQuestion()
568
}
569

570
func (m *Manager) setMultipleChoiceQuestion(index int) {
571
	choiceQuestion := m.questions.MultipleChoiceQuestion[index]
572
	items := make([]gui.ListItem, len(choiceQuestion.Choices.Choice))
573
	for i, c := range choiceQuestion.Choices.Choice {
574
		items[i] = ChoiceItem{Choice: c}
575
	}
576
	m.contentList = nil
577
	m.mainWnd.MainListBox().SetItems(items, choiceQuestion.Label.Text, nil)
578
	m.mainWnd.MenuBar().SetBookMenuEnabled(false)
579
}
580

581
func (m *Manager) setInputQuestion() {
582
	for _, inputQuestion := range m.questions.InputQuestion {
583
		var text string
584
		if 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
586
			m.setQuestions(dodp.UserResponse{QuestionID: dodp.Default})
587
			return
588
		}
589
		m.lastInputText = text
590
		m.userResponses = append(m.userResponses, dodp.UserResponse{QuestionID: inputQuestion.ID, Value: text})
591
	}
592
	m.setQuestions(m.userResponses...)
593
}
594

595
func (m *Manager) setContentList(contentID string) {
596
	m.questions = nil
597
	m.mainWnd.MainListBox().Clear()
598
	m.mainWnd.MenuBar().SetBookMenuEnabled(false)
599
	m.logger.Debug("Set content list: %v", contentID)
600

601
	contentList, err := m.provider.ContentList(contentID)
602
	if err != nil {
603
		m.messageBoxError(fmt.Errorf("Getting a content list: %w", err))
604
		return
605
	}
606

607
	if len(contentList.Items) == 0 {
608
		title := gotext.Get("Warning")
609
		msg := gotext.Get("List of books is empty")
610
		gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconWarning)
611
		return
612
	}
613

614
	if contentList.ID == dodp.Issued {
615
		ids := make([]string, len(contentList.Items))
616
		for i := range ids {
617
			book := contentList.Items[i]
618
			ids[i] = book.ID()
619
		}
620
		if m.book != nil && !util.StringInSlice(m.book.ID(), ids) {
621
			ids = append(ids, m.book.ID())
622
		}
623
		m.provider.Tidy(ids)
624
	}
625

626
	m.updateContentList(contentList)
627
}
628

629
func (m *Manager) updateContentList(contentList *content.List) {
630
	m.contentList = contentList
631
	items := make([]gui.ListItem, len(m.contentList.Items))
632
	for i, v := range m.contentList.Items {
633
		items[i] = v
634
	}
635
	m.mainWnd.MainListBox().SetItems(items, contentList.Name, m.mainWnd.MenuBar().BookMenu())
636
	m.mainWnd.MenuBar().SetBookMenuEnabled(true)
637
}
638

639
func (m *Manager) setBook(conf *config.Config, contentItem content.Item) error {
640
	if m.book != nil {
641
		m.book.Pause(true)
642
	}
643

644
	if contentItem != nil {
645
		book, err := books.NewBook(conf.General.OutputDevice, contentItem, m.logger, m.mainWnd.StatusBar())
646
		if err != nil {
647
			return err
648
		}
649
		defer func() {
650
			book.SetTimerDuration(conf.General.PauseTimer)
651
			book.SetVolume(conf.General.Volume)
652
			m.mainWnd.SetTitle(book.Title)
653
			m.mainWnd.MenuBar().SetBookmarksMenu(book.Bookmarks())
654
			m.book = book
655
			m.logger.Debug("Set book: %v", book.ID())
656
		}()
657
	}
658

659
	if m.book != nil {
660
		conf.General.Volume = m.book.Volume()
661
		m.book.Save()
662
		m.book.Stop()
663
		m.mainWnd.SetTitle("")
664
		m.mainWnd.MenuBar().SetBookmarksMenu(nil)
665
		m.book = nil
666
	}
667
	return nil
668
}
669

670
func (m *Manager) downloadBook(book content.Item) error {
671
	if _, ok := m.provider.(*library.Library); !ok {
672
		return OperationNotSupported
673
	}
674

675
	name, err := book.Name()
676
	if err != nil {
677
		return err
678
	}
679

680
	dir, err := config.BookDir(name)
681
	if err != nil {
682
		return err
683
	}
684

685
	rsrc, err := book.Resources()
686
	if err != nil {
687
		return err
688
	}
689

690
	if md, err := book.ContentMetadata(); err == nil {
691
		path := filepath.Join(dir, config.MetadataFileName)
692
		if err := util.SaveXMLFile(path, &md); err != nil {
693
			m.logger.Error("Saving metadata file: %v", err)
694
		}
695
	}
696

697
	dlFunc := func(rsrc []dodp.Resource, dir string, id string) {
698
		var err error
699
		var totalSize, downloadedSize int64
700
		ctx, cancelFunc := context.WithCancel(context.TODO())
701
		dlg := gui.NewProgressDialog(m.mainWnd, gotext.Get("Book downloading"), gotext.Get("Downloading \"%v\"\nSpeed: %d KB/s", name, 0), 100, cancelFunc)
702
		dlg.Run()
703

704
		for _, r := range rsrc {
705
			totalSize += r.Size
706
		}
707

708
		m.logger.Debug("Downloading book %v started", id)
709
		for _, r := range rsrc {
710
			err = func() error {
711
				path := filepath.Join(dir, r.LocalURI)
712
				if util.FileIsExist(path, r.Size) {
713
					// This fragment already exists on disk
714
					downloadedSize += r.Size
715
					dlg.SetValue(int(downloadedSize / (totalSize / 100)))
716
					return nil
717
				}
718

719
				conn, err := connection.NewConnectionWithContext(ctx, r.URI, m.logger)
720
				if err != nil {
721
					return err
722
				}
723
				defer conn.Close()
724

725
				dst, err := util.CreateSecureFile(path)
726
				if err != nil {
727
					return err
728
				}
729
				defer dst.Close()
730

731
				var fragmentSize int64
732
				var src io.Reader = buffer.NewReader(conn)
733
				for err == nil {
734
					var n int64
735
					t := time.Now()
736
					n, err = io.CopyN(dst, src, 512*1024)
737
					sec := time.Since(t).Seconds()
738
					speed := int(float64(n) / 1024 / sec)
739
					dlg.SetLabel(gotext.Get("Downloading \"%v\"\nSpeed: %d KB/s", name, speed))
740
					downloadedSize += n
741
					fragmentSize += n
742
					dlg.SetValue(int(downloadedSize / (totalSize / 100)))
743
				}
744

745
				if err == io.EOF {
746
					err = nil
747
				}
748

749
				if err == nil && r.Size != fragmentSize {
750
					err = errors.New("resource size mismatch")
751
				}
752

753
				if err != nil {
754
					dst.Corrupted()
755
				}
756
				return err
757
			}()
758

759
			if err != nil {
760
				break
761
			}
762
		}
763

764
		dlg.Cancel()
765

766
		switch {
767
		case errors.Is(err, context.Canceled):
768
			title := gotext.Get("Warning")
769
			msg := gotext.Get("Download canceled by user")
770
			gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconWarning)
771
		case err != nil:
772
			gui.MessageBox(m.mainWnd, gotext.Get("Error"), err.Error(), gui.MsgBoxOK|gui.MsgBoxIconError)
773
		default:
774
			title := gotext.Get("Warning")
775
			msg := gotext.Get("Book successfully downloaded")
776
			gui.MessageBox(m.mainWnd, title, msg, gui.MsgBoxOK|gui.MsgBoxIconWarning)
777
			m.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
782
	go dlFunc(rsrc, dir, book.ID())
783
	return nil
784
}
785

786
func (m *Manager) removeBook(book content.Item) error {
787
	returner, ok := book.(content.Returner)
788
	if !ok {
789
		return OperationNotSupported
790
	}
791
	return returner.Return()
792
}
793

794
func (m *Manager) issueBook(book content.Item) error {
795
	issuer, ok := book.(content.Issuer)
796
	if !ok {
797
		return OperationNotSupported
798
	}
799
	return issuer.Issue()
800
}
801

802
func (m *Manager) messageBoxError(err error) {
803
	msg := err.Error()
804
	m.logger.Error(msg)
805
	var netErr *net.OpError
806
	switch {
807
	case errors.As(err, &netErr):
808
		msg = gotext.Get("Network error. Check your Internet connection or try the operation later")
809
	case errors.Is(err, OperationNotSupported):
810
		msg = gotext.Get("Operation not supported")
811
	}
812
	gui.MessageBox(m.mainWnd, gotext.Get("Error"), msg, gui.MsgBoxOK|gui.MsgBoxIconError)
813
}
814

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.