public void DeleteBooks(List <BookFile> books, CalibreSettings settings) { var idString = books.Where(x => x.CalibreId != 0).Select(x => x.CalibreId).ConcatToString(","); var request = GetBuilder($"cdb/delete-books/{idString}/{settings.Library}", settings).Build(); _httpClient.Post(request); }
public void AddFormat(BookFile file, CalibreSettings settings) { var format = Path.GetExtension(file.Path); var bookData = Convert.ToBase64String(File.ReadAllBytes(file.Path)); var payload = new CalibreChangesPayload { LoadedBookIds = new List <int> { file.CalibreId }, Changes = new CalibreChanges { AddedFormats = new List <CalibreAddFormat> { new CalibreAddFormat { Ext = format, Data = bookData } } } }; ExecuteSetFields(file.CalibreId, payload, settings); }
private void PollEmbedStatus(BookFile file, CalibreSettings settings) { var previous = new FileInfo(file.Path); Thread.Sleep(100); FileInfo current = null; var i = 0; while (i++ < 20) { current = new FileInfo(file.Path); if (current.LastWriteTimeUtc == previous.LastWriteTimeUtc && current.LastWriteTimeUtc != file.Modified) { break; } previous = current; Thread.Sleep(1000); } file.Size = current.Length; file.Modified = current.LastWriteTimeUtc; if (file.Id > 0) { _mediaFileService.Update(file); } }
public List <string> GetAllBookFilePaths(CalibreSettings settings) { _bookCache.Clear(); try { var builder = GetBuilder($"ajax/books", settings); var request = builder.Build(); var response = _httpClient.Get <Dictionary <int, CalibreBook> >(request); var result = new List <string>(); foreach (var book in response.Resource.Values) { var remotePath = book?.Formats.Values.OrderBy(f => f.LastModified).FirstOrDefault()?.Path; if (remotePath == null) { continue; } var localPath = _pathMapper.RemapRemoteToLocal(settings.Host, new OsPath(remotePath)).FullPath; result.Add(localPath); _bookCache.Set(localPath, book, TimeSpan.FromMinutes(5)); } return(result); } catch (HttpException ex) { throw new CalibreException("Unable to connect to calibre library: {0}", ex, ex.Message); } }
public List <CalibreBook> GetBooks(List <int> calibreIds, CalibreSettings settings) { var builder = GetBuilder($"ajax/books/{settings.Library}", settings); builder.LogResponseContent = false; builder.AddQueryParam("ids", calibreIds.ConcatToString(",")); var request = builder.Build(); try { var response = _httpClient.Get <Dictionary <int, CalibreBook> >(request); var result = response.Resource.Values.ToList(); foreach (var book in result) { foreach (var format in book.Formats.Values) { format.Path = _pathMapper.RemapRemoteToLocal(settings.Host, new OsPath(format.Path)).FullPath; } } return(result); } catch (HttpException ex) { throw new CalibreException("Unable to connect to Calibre library: {0}", ex, ex.Message); } }
public CalibreImportJob AddBook(BookFile book, CalibreSettings settings) { var jobid = (int)(DateTime.UtcNow.Ticks % 1000000000); var addDuplicates = false; var path = book.Path; var filename = $"$dummy{Path.GetExtension(path)}"; var body = File.ReadAllBytes(path); _logger.Trace($"Read {body.Length} bytes from {path}"); try { var builder = GetBuilder($"cdb/add-book/{jobid}/{addDuplicates}/{filename}/{settings.Library}", settings); var request = builder.Build(); request.SetContent(body); var response = _httpClient.Post <CalibreImportJob>(request).Resource; if (response.Id == 0) { throw new CalibreException("Calibre rejected duplicate book"); } return(response); } catch (HttpException ex) { throw new CalibreException("Unable to add file to Calibre library: {0}", ex, ex.Message); } }
public void SetFields(BookFile file, CalibreSettings settings) { var edition = file.Edition.Value; var book = edition.Book.Value; var serieslink = book.SeriesLinks.Value.FirstOrDefault(); var series = serieslink?.Series.Value; double?seriesIndex = null; if (double.TryParse(serieslink?.Position, out var index)) { _logger.Trace($"Parsed {serieslink?.Position} as {index}"); seriesIndex = index; } _logger.Trace($"Book: {book} Series: {series?.Title}, Position: {seriesIndex}"); var cover = edition.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Cover); string image = null; if (cover != null) { var imageFile = _mediaCoverService.GetCoverPath(edition.BookId, MediaCoverEntity.Book, cover.CoverType, cover.Extension, null); if (File.Exists(imageFile)) { var imageData = File.ReadAllBytes(imageFile); image = Convert.ToBase64String(imageData); } } var payload = new CalibreChangesPayload { LoadedBookIds = new List <int> { file.CalibreId }, Changes = new CalibreChanges { Title = edition.Title, Authors = new List <string> { file.Author.Value.Name }, Cover = image, PubDate = book.ReleaseDate, Comments = edition.Overview, Rating = edition.Ratings.Value * 2, Identifiers = new Dictionary <string, string> { { "isbn", edition.Isbn13 }, { "asin", edition.Asin }, { "goodreads", edition.ForeignEditionId } }, Series = series?.Title, SeriesIndex = seriesIndex } }; ExecuteSetFields(file.CalibreId, payload, settings); }
private CalibreLibraryInfo GetLibraryInfo(CalibreSettings settings) { var builder = GetBuilder($"ajax/library-info", settings); var request = builder.Build(); var response = _httpClient.Get <CalibreLibraryInfo>(request); return(response.Resource); }
private void EmbedMetadata(int id, CalibreSettings settings) { var request = GetBuilder($"cdb/cmd/embed_metadata", settings) .AddQueryParam("library_id", settings.Library) .Post() .SetHeader("Content-Type", "application/json") .Build(); request.SetContent($"[{id}, null]"); _httpClient.Execute(request); }
private void ExecuteSetFields(int id, object payload, CalibreSettings settings) { var builder = GetBuilder($"cdb/set-fields/{id}", settings) .Post() .SetHeader("Content-Type", "application/json"); var request = builder.Build(); request.SetContent(payload.ToJson()); _httpClient.Execute(request); }
public void Test(CalibreSettings settings) { var failures = new List <ValidationFailure> { TestCalibre(settings) }; var validationResult = new ValidationResult(failures); var result = new NzbDroneValidationResult(validationResult.Errors); if (!result.IsValid || result.HasWarnings) { throw new ValidationException(result.Failures); } }
public void DeleteBook(BookFile book, CalibreSettings settings) { try { var request = GetBuilder($"cdb/delete-books/{book.CalibreId}/{settings.Library}", settings).Build(); _httpClient.Post(request); } catch (Exception e) { Console.WriteLine(e); throw; } }
public void GetLibraryInfo(CalibreSettings settings) { try { var builder = GetBuilder($"ajax/library-info", settings); var request = builder.Build(); var response = _httpClient.Execute(request); } catch (HttpException ex) { throw new CalibreException("Unable to connect to calibre library: {0}", ex, ex.Message); } }
private bool HasWriteAccess(CalibreSettings settings) { var request = GetBuilder($"cdb/cmd/saved_searches", settings) .Post() .SetHeader("Content-Type", "application/json") .Build(); request.SuppressHttpError = true; request.SetContent("[\"list\"]"); var response = _httpClient.Get(request); return(response.StatusCode != HttpStatusCode.Forbidden); }
public void RemoveFormats(int calibreId, IEnumerable <string> formats, CalibreSettings settings) { var payload = new { changes = new { removed_formats = formats }, loaded_book_ids = new[] { calibreId } }; ExecuteSetFields(calibreId, payload, settings); }
public BookFile AddAndConvert(BookFile file, CalibreSettings settings) { _logger.Trace($"Importing to calibre: {file.Path} calibre id: {file.CalibreId}"); if (file.CalibreId == 0) { var import = AddBook(file, settings); file.CalibreId = import.Id; } else { AddFormat(file, settings); } SetFields(file, settings, true, _configService.EmbedMetadata); if (settings.OutputFormat.IsNotNullOrWhiteSpace()) { _logger.Trace($"Getting book data for {file.CalibreId}"); var options = GetBookData(file.CalibreId, settings); var inputFormat = file.Quality.Quality.Name.ToUpper(); options.Conversion_options.Input_fmt = inputFormat; var formats = settings.OutputFormat.Split(',').Select(x => x.Trim()); foreach (var format in formats) { if (format.ToLower() == inputFormat || options.Input_formats.Contains(format, StringComparer.OrdinalIgnoreCase)) { continue; } options.Conversion_options.Output_fmt = format; if (settings.OutputProfile != (int)CalibreProfile.@default) { options.Conversion_options.Options.Output_profile = ((CalibreProfile)settings.OutputProfile).ToString(); } _logger.Trace($"Starting conversion to {format}"); _rootFolderWatchingService.ReportFileSystemChangeBeginning(Path.ChangeExtension(file.Path, format)); ConvertBook(file.CalibreId, options.Conversion_options, settings); } } return(file); }
public void RemoveFormats(int calibreId, IEnumerable <string> formats, CalibreSettings settings) { var payload = new CalibreChangesPayload { LoadedBookIds = new List <int> { calibreId }, Changes = new CalibreChanges { RemovedFormats = formats.ToList() } }; ExecuteSetFields(calibreId, payload, settings); }
private CalibreBookData GetBookData(int calibreId, CalibreSettings settings) { try { var request = GetBuilder($"conversion/book-data/{calibreId}", settings) .AddQueryParam("library_id", settings.Library) .Build(); return(_httpClient.Get <CalibreBookData>(request).Resource); } catch (HttpException ex) { throw new CalibreException("Unable to add file to Calibre library: {0}", ex, ex.Message); } }
public CalibreBookData GetBookData(int calibreId, CalibreSettings settings) { try { var builder = GetBuilder($"conversion/book-data/{calibreId}", settings); var request = builder.Build(); return(_httpClient.Get <CalibreBookData>(request).Resource); } catch (HttpException ex) { throw new CalibreException("Unable to add file to calibre library: {0}", ex, ex.Message); } }
private void EmbedMetadata(BookFile file, CalibreSettings settings) { _rootFolderWatchingService.ReportFileSystemChangeBeginning(file.Path); var request = GetBuilder($"cdb/cmd/embed_metadata", settings) .AddQueryParam("library_id", settings.Library) .Post() .SetHeader("Content-Type", "application/json") .Build(); request.SetContent($"[{file.CalibreId}, null]"); _httpClient.Execute(request); PollEmbedStatus(file, settings); }
public List <string> GetAllBookFilePaths(CalibreSettings settings) { var ids = GetAllBookIds(settings); var result = new List <string>(); var offset = 0; while (offset < ids.Count) { var builder = GetBuilder($"ajax/books/{settings.Library}", settings); builder.LogResponseContent = false; builder.AddQueryParam("ids", ids.Skip(offset).Take(PAGE_SIZE).ConcatToString(",")); var request = builder.Build(); try { var response = _httpClient.Get <Dictionary <int, CalibreBook> >(request); foreach (var book in response.Resource.Values) { var remotePath = book?.Formats .Where(x => MediaFileExtensions.TextExtensions.Contains("." + x.Key)) .OrderBy(f => f.Value.LastModified) .FirstOrDefault().Value?.Path; if (remotePath == null) { continue; } var localPath = _pathMapper.RemapRemoteToLocal(settings.Host, new OsPath(remotePath)).FullPath; result.Add(localPath); _bookCache.Set(localPath, book); } } catch (HttpException ex) { throw new CalibreException("Unable to connect to Calibre library: {0}", ex, ex.Message); } offset += PAGE_SIZE; } return(result); }
private HttpRequestBuilder GetBuilder(string relativePath, CalibreSettings settings) { var baseUrl = HttpRequestBuilder.BuildBaseUrl(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase); baseUrl = HttpUri.CombinePath(baseUrl, relativePath); var builder = new HttpRequestBuilder(baseUrl) .Accept(HttpAccept.Json); builder.LogResponseContent = true; if (settings.Username.IsNotNullOrWhiteSpace()) { builder.NetworkCredential = new NetworkCredential(settings.Username, settings.Password); } return(builder); }
public CalibreBook GetBook(int calibreId, CalibreSettings settings) { try { var builder = GetBuilder($"ajax/book/{calibreId}", settings); var request = builder.Build(); var book = _httpClient.Get <CalibreBook>(request).Resource; foreach (var format in book.Formats.Values) { format.Path = _pathMapper.RemapRemoteToLocal(settings.Host, new OsPath(format.Path)).FullPath; } return(book); } catch (HttpException ex) { throw new CalibreException("Unable to connect to calibre library: {0}", ex, ex.Message); } }
public long ConvertBook(int calibreId, CalibreConversionOptions options, CalibreSettings settings) { try { var builder = GetBuilder($"conversion/start/{calibreId}", settings); var request = builder.Build(); request.SetContent(options.ToJson()); var jobId = _httpClient.Post <long>(request).Resource; // Run async task to check if conversion complete _ = PollConvertStatus(jobId, settings); return(jobId); } catch (HttpException ex) { throw new CalibreException("Unable to start calibre conversion: {0}", ex, ex.Message); } }
public void SetFields(BookFile file, CalibreSettings settings) { var edition = file.Edition.Value; var cover = edition.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Cover); string image = null; if (cover != null) { var imageFile = _mediaCoverService.GetCoverPath(edition.BookId, MediaCoverEntity.Book, cover.CoverType, cover.Extension, null); if (File.Exists(imageFile)) { var imageData = File.ReadAllBytes(imageFile); image = Convert.ToBase64String(imageData); } } var payload = new { changes = new { title = edition.Title, authors = new[] { file.Author.Value.Name }, cover = image, pubdate = edition.Book.Value.ReleaseDate, comments = edition.Overview, rating = edition.Ratings.Value * 2, identifiers = new Dictionary <string, string> { { "isbn", edition.Isbn13 }, { "asin", edition.Asin }, { "goodreads", edition.ForeignEditionId } } }, loaded_book_ids = new[] { file.CalibreId } }; ExecuteSetFields(file.CalibreId, payload, settings); }
public List <int> GetAllBookIds(CalibreSettings settings) { // the magic string is 'allbooks' converted to hex var builder = GetBuilder($"/ajax/category/616c6c626f6f6b73/{settings.Library}", settings); var offset = 0; var ids = new List <int>(); while (true) { var result = GetPaged <CalibreCategory>(builder, PAGE_SIZE, offset); if (!result.Resource.BookIds.Any()) { break; } offset += PAGE_SIZE; ids.AddRange(result.Resource.BookIds); } return(ids); }
private async Task PollConvertStatus(long jobId, CalibreSettings settings) { var builder = GetBuilder($"/conversion/status/{jobId}", settings); var request = builder.Build(); while (true) { var status = _httpClient.Get <CalibreConversionStatus>(request).Resource; if (!status.Running) { if (!status.Ok) { _logger.Warn("Calibre conversion failed.\n{0}\n{1}", status.Traceback, status.Log); } return; } await Task.Delay(2000); } }
public void AddFormat(BookFile file, CalibreSettings settings) { var format = Path.GetExtension(file.Path); var bookData = Convert.ToBase64String(File.ReadAllBytes(file.Path)); var payload = new { changes = new { added_formats = new[] { new { ext = format, data_url = bookData } } }, loaded_book_ids = new[] { file.CalibreId } }; ExecuteSetFields(file.CalibreId, payload, settings); }
public void SetFields(BookFile file, CalibreSettings settings, bool updateCover = true, bool embed = false) { var edition = file.Edition.Value; var book = edition.Book.Value; var serieslink = book.SeriesLinks.Value.FirstOrDefault(x => x.Series.Value.Title.IsNotNullOrWhiteSpace()); var series = serieslink?.Series.Value; double?seriesIndex = null; if (double.TryParse(serieslink?.Position, out var index)) { _logger.Trace($"Parsed {serieslink?.Position} as {index}"); seriesIndex = index; } _logger.Trace($"Book: {book} Series: {series?.Title}, Position: {seriesIndex}"); var cover = edition.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Cover); string image = null; if (cover != null) { var imageFile = _mediaCoverService.GetCoverPath(edition.BookId, MediaCoverEntity.Book, cover.CoverType, cover.Extension, null); if (File.Exists(imageFile)) { var imageData = File.ReadAllBytes(imageFile); image = Convert.ToBase64String(imageData); } } var payload = new CalibreChangesPayload { LoadedBookIds = new List <int> { file.CalibreId }, Changes = new CalibreChanges { Title = edition.Title, Authors = new List <string> { file.Author.Value.Name }, Cover = updateCover ? image : null, PubDate = book.ReleaseDate, Publisher = edition.Publisher, Languages = edition.Language.CanonicalizeLanguage(), Comments = edition.Overview, Rating = (int)(edition.Ratings.Value * 2), Identifiers = new Dictionary <string, string> { { "isbn", edition.Isbn13 }, { "asin", edition.Asin }, { "goodreads", edition.ForeignEditionId } }, Series = series?.Title, SeriesIndex = seriesIndex } }; ExecuteSetFields(file.CalibreId, payload, settings); if (embed) { _rootFolderWatchingService.ReportFileSystemChangeBeginning(file.Path); EmbedMetadata(file.CalibreId, settings); } }
private ValidationFailure TestCalibre(CalibreSettings settings) { var builder = GetBuilder("", settings); builder.Accept(HttpAccept.Html); builder.SuppressHttpError = true; builder.AllowAutoRedirect = true; var request = builder.Build(); request.LogResponseContent = false; HttpResponse response; try { response = _httpClient.Execute(request); } catch (WebException ex) { _logger.Error(ex, "Unable to connect to Calibre"); if (ex.Status == WebExceptionStatus.ConnectFailure) { return(new NzbDroneValidationFailure("Host", "Unable to connect") { DetailedDescription = "Please verify the hostname and port." }); } return(new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + ex.Message)); } if (response.StatusCode == HttpStatusCode.NotFound) { return(new ValidationFailure("Host", "Could not connect")); } if (response.Content.Contains(@"guac-login")) { return(new ValidationFailure("Port", "Bad port. This is the container's remote Calibre GUI, not the Calibre content server. Try mapping port 8081.")); } if (response.Content.Contains("Calibre-Web")) { return(new ValidationFailure("Port", "This is a Calibre-Web server, not the required Calibre content server. See https://manual.calibre-ebook.com/server.html")); } if (!response.Content.Contains(@"<title>calibre</title>")) { return(new ValidationFailure("Port", "Not a valid Calibre content server. See https://manual.calibre-ebook.com/server.html")); } CalibreLibraryInfo libraryInfo; try { libraryInfo = GetLibraryInfo(settings); } catch (HttpException e) { if (e.Response.StatusCode == HttpStatusCode.Unauthorized) { return(new NzbDroneValidationFailure("Username", "Authentication failure") { DetailedDescription = "Please verify your username and password." }); } else { return(new NzbDroneValidationFailure(string.Empty, "Unknown exception: " + e.Message)); } } if (settings.Library.IsNullOrWhiteSpace()) { settings.Library = libraryInfo.DefaultLibrary; } if (!libraryInfo.LibraryMap.ContainsKey(settings.Library)) { return(new ValidationFailure("Library", "Not a valid library in calibre")); } return(null); }