diff --git a/cache.go b/cache.go index 1a95afa..9cf5105 100644 --- a/cache.go +++ b/cache.go @@ -11,15 +11,18 @@ import ( var cacheEnabled bool = true +// EnableCache enables request caching for reads func EnableCache() { cacheEnabled = true } +// DisableCache disables cache reads func DisableCache() { cacheEnabled = false } -func getCachePath() string { +// getBaseCachePath returns the base path for the cache storage +func getBaseCachePath() string { userCacheDir, errCache := os.UserCacheDir() if errCache != nil { logrus.Fatalf("Unable to retrieve cache directory: %s", errCache) @@ -27,25 +30,30 @@ func getCachePath() string { return filepath.Join(userCacheDir, "go-mangadex") } -func getCachePathFor(mangadexURL *url.URL) string { +// getCachePath returns the absolute path to the files cache for the provided URL +func getCachePath(mangadexURL *url.URL) string { fileName := getCacheFilename(mangadexURL) - return filepath.Join(getCachePath(), fileName) + return filepath.Join(getBaseCachePath(), fileName) } +// getCacheFilename generates a cache filename based on the URL of the request +// TODO: Use query arguments as well func getCacheFilename(mangadexURL *url.URL) string { return strings.ReplaceAll(mangadexURL.Path, "/", "_") } +// cacheExists checks that the cache for a certain URL exists or not func cacheExists(mangadexURL *url.URL) bool { - stat, err := os.Stat(getCachePathFor(mangadexURL)) + stat, err := os.Stat(getCachePath(mangadexURL)) if os.IsNotExist(err) { return false } return !stat.IsDir() } +// initCache makes sure that the cache directory exists so reads and writes on cache folders won't fail func initCache() { - cachePath := getCachePath() + cachePath := getBaseCachePath() _, err := os.Stat(cachePath) if os.IsNotExist(err) { logrus.Infof("Cache directory does not exist, creating. [%s]", cachePath) diff --git a/http.go b/http.go index ea8e537..d132c59 100644 --- a/http.go +++ b/http.go @@ -11,19 +11,18 @@ import ( "github.com/sirupsen/logrus" ) -const APIBaseURL = "https://api.mangadex.org/v2/" +const apiBaseURL = "https://api.mangadex.org/v2/" -func doRequest(method string, requestURL string) (*MangaDexResponse, error) { - result := MangaDexResponse{} +func doRequest(method string, requestURL string) (*Response, error) { + result := Response{} parsedURL, errParse := url.Parse(requestURL) if errParse != nil { return &result, errParse } if cacheEnabled { - initCache() if cacheExists(parsedURL) { - cacheData, errRead := ioutil.ReadFile(getCachePathFor(parsedURL)) + cacheData, errRead := ioutil.ReadFile(getCachePath(parsedURL)) if errRead != nil { logrus.Fatalf("Error reading cache for URL: %s [%s]: %s", parsedURL.String(), getCacheFilename(parsedURL), errRead) } @@ -33,9 +32,8 @@ func doRequest(method string, requestURL string) (*MangaDexResponse, error) { } logrus.Debugf("Request loaded from cache: %s", parsedURL.String()) return &result, nil - } else { - logrus.Debugf("Cache not found for %s", parsedURL.String()) } + logrus.Debugf("Cache not found for %s", parsedURL.String()) } logrus.Tracef("Making request %s", parsedURL) @@ -76,7 +74,7 @@ func doRequest(method string, requestURL string) (*MangaDexResponse, error) { // Write cache logrus.Infof("Writting cache for %s", parsedURL.String()) logrus.Infof("Writting cache to: %s", getCacheFilename(parsedURL)) - errWriteCache := ioutil.WriteFile(getCachePathFor(parsedURL), body, 0644) + errWriteCache := ioutil.WriteFile(getCachePath(parsedURL), body, 0644) if errWriteCache != nil { logrus.Warnf("Can't write to cache: %s", errWriteCache) } diff --git a/public.go b/public.go index c53392a..0503a1f 100644 --- a/public.go +++ b/public.go @@ -16,13 +16,13 @@ func (manga *Manga) GetChapters(params ChaptersParams) ([]MangaChapterList, []Ma var mangaGroupsResult []MangaGroup params.validate() - response, errRequest := doRequest("GET", APIBaseURL+path.Join("manga", strconv.Itoa(manga.ID), "chapters")+"?"+params.asQueryParams().Encode()) + response, errRequest := doRequest("GET", apiBaseURL+path.Join("manga", strconv.Itoa(manga.ID), "chapters")+"?"+params.asQueryParams().Encode()) if errRequest != nil { logrus.Errorf("Request error: %s", errRequest) return mangaChaptersResult, mangaGroupsResult, errRequest } - var mangaDexChaptersResponse MangaDexChaptersResponse + var mangaDexChaptersResponse ChaptersResponse errJSON := json.Unmarshal(response.Data, &mangaDexChaptersResponse) if errJSON != nil { @@ -39,7 +39,7 @@ func (manga *Manga) GetChapters(params ChaptersParams) ([]MangaChapterList, []Ma func (manga *Manga) GetChapter(chapter string) (MangaChapterDetail, error) { var result MangaChapterDetail - response, errRequest := doRequest("GET", APIBaseURL+path.Join("chapter", chapter)) + response, errRequest := doRequest("GET", apiBaseURL+path.Join("chapter", chapter)) if errRequest != nil { logrus.Errorf("Request error: %s", errRequest) return result, errRequest @@ -57,7 +57,7 @@ func (manga *Manga) GetChapter(chapter string) (MangaChapterDetail, error) { // GetCovers requests the covers for the provided manga func (manga *Manga) GetCovers() ([]MangaCover, error) { var result []MangaCover - response, errRequest := doRequest("GET", APIBaseURL+path.Join("manga", strconv.Itoa(manga.ID), "covers")) + response, errRequest := doRequest("GET", apiBaseURL+path.Join("manga", strconv.Itoa(manga.ID), "covers")) if errRequest != nil { logrus.Errorf("Request error: %s", errRequest) return result, errRequest @@ -73,8 +73,9 @@ func (manga *Manga) GetCovers() ([]MangaCover, error) { // GetManga retrieves the manga information for the provided ID. func GetManga(mangaID int) (Manga, error) { + initCache() result := Manga{} - response, errRequest := doRequest("GET", APIBaseURL+path.Join("manga", strconv.Itoa(mangaID))) + response, errRequest := doRequest("GET", apiBaseURL+path.Join("manga", strconv.Itoa(mangaID))) if errRequest != nil { logrus.Errorf("Request error: %s", errRequest) return result, errRequest diff --git a/types.go b/types.go index 5dbc7eb..52f9b4d 100644 --- a/types.go +++ b/types.go @@ -6,7 +6,8 @@ import ( "strconv" ) -type MangaDexResponse struct { +// Response handles the response from MangaDex +type Response struct { // Same as HTTP status code Code int `json:"code"` // `OK` or `error` @@ -17,11 +18,19 @@ type MangaDexResponse struct { Data json.RawMessage `json:"data"` } -type MangaDexChaptersResponse struct { +// IsOK Checks if the response is correct +func (response Response) IsOK() bool { + return response.Status == "OK" +} + +// ChaptersResponse handles the response of the chapters list which returns +// two kinds of objects in the `json:"data"` key. +type ChaptersResponse struct { Chapters []MangaChapterList `json:"chapters"` Groups []MangaGroup `json:"groups"` } +// MangaRelation relations between mangas type MangaRelation struct { ID int `json:"id"` IsHentai bool `json:"isHentai"` @@ -29,6 +38,8 @@ type MangaRelation struct { Type int `json:"type"` } +// MangaPublication stores certain information for the publication of the manga +// Some values are easily guessed, others are not ... type MangaPublication struct { // ??? Demographic int8 `json:"demographic"` @@ -37,12 +48,19 @@ type MangaPublication struct { Language string `json:"language"` } +// IsComplete returns if the manga has finished publishing +func (publication MangaPublication) IsComplete() bool { + return publication.Status == 2 +} + +// MangaRating the rating for a particular manga type MangaRating struct { Bayesian float32 `json:"bayesian"` Mean float32 `json:"mean"` Users int `json:"users"` } +// MangaLinks contains the relation of the links map with more verbose names type MangaLinks struct { AniList string `json:"al"` AnimePlanet string `json:"ap"` @@ -56,6 +74,7 @@ type MangaLinks struct { EnglishRaw string `json:"engtl"` } +// Manga stores the base manga object and details type Manga struct { ID int `json:"id"` AlternativeTitles []string `json:"altTitles"` @@ -78,11 +97,13 @@ type Manga struct { Views int `json:"views"` } +// MangaCover stores the cover object type MangaCover struct { URL string `json:"url"` Volume string `json:"volume"` } +// MangaChapterBase the base attributes for a chapter for both the list and the detail type MangaChapterBase struct { ID int `json:"id"` Hash string `json:"hash"` @@ -99,11 +120,15 @@ type MangaChapterBase struct { Views int `json:"views"` } +// MangaChapterList stores the chapter object from the listing, which only uses the +// base details and uses the ID for the groups instead of returning the entire object type MangaChapterList struct { MangaChapterBase Groups []int `json:"groups"` } +// MangaChapterDetail stores the bases of a chapter plus the required attributes +// to retrieve the page blobs type MangaChapterDetail struct { MangaChapterBase Groups []MangaGroup `json:"groups"` @@ -113,11 +138,13 @@ type MangaChapterDetail struct { ServerFallback string `json:"serverFallback"` } +// MangaGroupMember stores the member of a group type MangaGroupMember struct { ID int `json:"id"` Name string `json:"name"` } +// MangaGroup stores the group behind releases of a particular manga type MangaGroup struct { ID int `json:"id"` Name string `json:"name"` @@ -145,12 +172,17 @@ type MangaGroup struct { Banner string `json:"banner"` } +// ChaptersParams the request parameters for the chapters listing type ChaptersParams struct { - Limit int `json:"limit"` - Page int `json:"p"` + // How many items per page (max 100) + Limit int `json:"limit"` + // Page to retrieve (default 0) + Page int `json:"p"` + // Hide groups blocked by the user (auth not implemented) BlockGroups bool `json:"blockgroups"` } +// NewChaptersParams returns a ChapterParams object with sensible defaults func NewChaptersParams() ChaptersParams { return ChaptersParams{ Limit: 100,