Пример #1
0
        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);
        }
Пример #2
0
        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);
        }
Пример #3
0
        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);
            }
        }
Пример #4
0
        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);
            }
        }
Пример #5
0
        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);
            }
        }
Пример #6
0
        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);
            }
        }
Пример #7
0
        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);
        }
Пример #8
0
        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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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);
        }
Пример #11
0
        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);
            }
        }
Пример #12
0
 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;
     }
 }
Пример #13
0
 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);
     }
 }
Пример #14
0
        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);
        }
Пример #15
0
        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);
        }
Пример #16
0
        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);
        }
Пример #17
0
        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);
        }
Пример #18
0
        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);
            }
        }
Пример #19
0
        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);
            }
        }
Пример #20
0
        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);
        }
Пример #21
0
        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);
        }
Пример #22
0
        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);
        }
Пример #23
0
        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);
            }
        }
Пример #24
0
        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);
            }
        }
Пример #25
0
        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);
        }
Пример #26
0
        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);
        }
Пример #27
0
        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);
            }
        }
Пример #28
0
        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);
        }
Пример #29
0
        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);
            }
        }
Пример #30
0
        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);
        }