Skip to content

Commit

Permalink
Audiobookshelf upload controller created
Browse files Browse the repository at this point in the history
  • Loading branch information
vpoluyaktov committed Nov 21, 2023
1 parent d8c1d1d commit acf37e8
Show file tree
Hide file tree
Showing 21 changed files with 351 additions and 117 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ bin/
mock/
output/
dist/
__debug_bin
__debug_bin*
abb_ia.log
config.yaml
30 changes: 13 additions & 17 deletions internal/audiobookshelf/audiobookshelf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ func TestLogin(t *testing.T) {
password := config.Instance().GetAudiobookshelfPassword()

if url != "" && username != "" && password != "" {
loginResp, err := audiobookshelf.Login(url+"/login", username, password)

absClient := audiobookshelf.NewClient(url)
err := absClient.Login(username, password)
assert.NoError(t, err)
assert.NotNil(t, loginResp.User.ID)
assert.NotNil(t, loginResp.User.Token)
}
}

Expand All @@ -30,13 +28,14 @@ func TestLibraries(t *testing.T) {
password := config.Instance().GetAudiobookshelfPassword()

if url != "" && username != "" && password != "" {
loginResp, err := audiobookshelf.Login(url+"/login", username, password)
absClient := audiobookshelf.NewClient(url)
err := absClient.Login(username, password)
assert.NoError(t, err)
if err == nil {
libraryResponse, err := audiobookshelf.Libraries(url, loginResp.User.Token)
libraries, err := absClient.GetLibraries()
assert.NoError(t, err)
assert.NotNil(t, libraryResponse)
assert.NotEmpty(t, libraryResponse.Libraries)
assert.NotNil(t, libraries)
assert.NotEmpty(t, libraries)
}
}
}
Expand All @@ -49,19 +48,16 @@ func TestScan(t *testing.T) {
libraryName := config.Instance().GetAudiobookshelfLibrary()

if url != "" && username != "" && password != "" && libraryName != "" {
loginResp, err := audiobookshelf.Login(url+"/login", username, password)
absClient := audiobookshelf.NewClient(url)
err := absClient.Login(username, password)
assert.NoError(t, err)
if err == nil {
libraries, err := absClient.GetLibraries()
assert.NoError(t, err)
libraryResponse, err := audiobookshelf.Libraries(url, loginResp.User.Token)
libraryID, err := absClient.GetLibraryId(libraries, libraryName)
if err == nil {
err = absClient.ScanLibrary(libraryID)
assert.NoError(t, err)
libraryID, err := audiobookshelf.GetLibraryByName(libraryResponse.Libraries, libraryName)
if err == nil {
err = audiobookshelf.ScanLibrary(url, loginResp.User.Token, libraryID)
assert.NoError(t, err)
assert.NotNil(t, libraryResponse)
assert.NotEmpty(t, libraryResponse.Libraries)
}
}
}
}
Expand Down
172 changes: 147 additions & 25 deletions internal/audiobookshelf/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,75 +4,99 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strconv"

"github.com/vpoluyaktov/abb_ia/internal/dto"
)

type AudiobookshelfClient struct {
url string
userName string
password string
loginResponse *LoginResponse
}

func NewClient(url string) *AudiobookshelfClient {
c := &AudiobookshelfClient{
url: url,
}
return c
}

// Call the Audiobookshelf API login method.
func Login(absUrl string, username, password string) (LoginResponse, error) {
func (c *AudiobookshelfClient) Login(userName string, password string) error {

c.userName = userName
c.password = password

requestBody := LoginRequest{
Username: username,
Password: password,
Username: c.userName,
Password: c.password,
}

requestBodyBytes, err := json.Marshal(requestBody)
if err != nil {
return LoginResponse{}, fmt.Errorf("failed to marshal login response body: %v", err)
return fmt.Errorf("failed to marshal login response body: %v", err)
}

resp, err := http.Post(absUrl, "application/json", bytes.NewBuffer(requestBodyBytes))
resp, err := http.Post(c.url+"/login", "application/json", bytes.NewBuffer(requestBodyBytes))
if err != nil {
return LoginResponse{}, fmt.Errorf("failed to make login API call: %v", err)
return fmt.Errorf("failed to make login API call: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return LoginResponse{}, fmt.Errorf("login API call returned status code: %d", resp.StatusCode)
return fmt.Errorf("login API call returned status code: %d", resp.StatusCode)
}

var loginResp LoginResponse
err = json.NewDecoder(resp.Body).Decode(&loginResp)
if err != nil {
return LoginResponse{}, fmt.Errorf("failed to decode login response: %v", err)
return fmt.Errorf("failed to decode login response: %v", err)
}
c.loginResponse = &loginResp

return loginResp, nil
return nil
}

// Call the Audiobookshelf API libraries method
func Libraries(url string, token string) (LibrariesResponse, error) {
func (c *AudiobookshelfClient) GetLibraries() ([]Library, error) {

client := &http.Client{}
url = url + "/api/libraries"
req, err := http.NewRequest("GET", url, nil)
req, err := http.NewRequest("GET", c.url+"/api/libraries", nil)
if err != nil {
return LibrariesResponse{}, fmt.Errorf("error creating request: %v", err)
return nil, fmt.Errorf("error creating request: %v", err)
}

req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("Authorization", "Bearer "+c.loginResponse.User.Token)

resp, err := client.Do(req)
if err != nil {
return LibrariesResponse{}, fmt.Errorf("error sending request: %v", err)
return nil, fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return LibrariesResponse{}, fmt.Errorf("error reading response body: %v", err)
return nil, fmt.Errorf("error reading response body: %v", err)
}

var response LibrariesResponse
err = json.Unmarshal(body, &response)
if err != nil {
return LibrariesResponse{}, fmt.Errorf("error parsing response body: %v", err)
return nil, fmt.Errorf("error parsing response body: %v", err)
}

return response, nil
return response.Libraries, nil
}

func GetLibraryByName(libraries []Library, libraryName string) (libraryID string, err error) {

func (c *AudiobookshelfClient) GetLibraryId(libraries []Library, libraryName string) (libraryID string, err error) {
for _, library := range libraries {
if library.Name == libraryName {
return library.ID, nil
Expand All @@ -81,14 +105,24 @@ func GetLibraryByName(libraries []Library, libraryName string) (libraryID string
return "", fmt.Errorf("no library with name '%s' found", libraryName)
}

// Call the Audiobookshelf API
func ScanLibrary(url string, authToken string, libraryID string) error {
url = url + "/api/libraries/" + libraryID + "/scan"
func (c *AudiobookshelfClient) GetFolders(libraries []Library, libraryName string) (folders []Folder, err error) {
for _, library := range libraries {
if library.Name == libraryName {
return library.Folders, nil
}
}
return nil, fmt.Errorf("no library with name '%s' found", libraryName)
}


// Call the Audiobookshelf API for a library Scan
func (c *AudiobookshelfClient) ScanLibrary(libraryID string) error {
url := c.url + "/api/libraries/" + libraryID + "/scan"
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+authToken)
req.Header.Set("Authorization", "Bearer "+c.loginResponse.User.Token)

client := &http.Client{}
resp, err := client.Do(req)
Expand All @@ -104,4 +138,92 @@ func ScanLibrary(url string, authToken string, libraryID string) error {
} else {
return nil
}
}
}

// Upload audiobook to The Audibookshelf server
func (c *AudiobookshelfClient) UploadBook(ab *dto.Audiobook, libraryID string, folderID string, callback func(int, int) ) error {
// Open each file for upload
var filesList []*os.File
for _, part := range ab.Parts {
f, err := os.Open(part.M4BFile)
if err != nil {
return err
}
defer f.Close()
filesList = append(filesList, f)
}

body := &bytes.Buffer{}
writer := multipart.NewWriter(body)

// Add metadata fields
_ = writer.WriteField("title", ab.Title)
_ = writer.WriteField("author", ab.Author)
_ = writer.WriteField("series", ab.Series)
_ = writer.WriteField("library", libraryID)
_ = writer.WriteField("folder", folderID)

// Add files to the request with progress reporting
for i, file := range filesList {
part, err := writer.CreateFormFile(strconv.Itoa(i), filepath.Base(file.Name()))
if err != nil {
return err
}

// Create a progress reader to track callback
fileStat, err := file.Stat()
if err != nil {
return err
}
pr := &progressReader{
Reader: file,
Size: fileStat.Size(),
Callback: callback,
}

_, err = io.Copy(part, pr)
if err != nil {
return err
}
}

err := writer.Close()
if err != nil {
return err
}

req, err := http.NewRequest("POST", c.url+"/api/upload", body)
if err != nil {
return err
}
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("Authorization", "Bearer "+c.loginResponse.User.Token)

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

// Check response status code
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to upload audiobook: %s", resp.Status)
}

return nil
}

type progressReader struct {
Reader io.Reader
Size int64
Callback func(int, int)
}

func (pr *progressReader) Read(p []byte) (int, error) {
n, err := pr.Reader.Read(p)
if err == nil {
pr.Callback(n, int(pr.Size))
}
return n, err
}
Loading

0 comments on commit acf37e8

Please sign in to comment.