forked from Cloudbox/autoscan
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
346 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package emby | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/cloudbox/autoscan" | ||
) | ||
|
||
func (t target) Available() error { | ||
// create request | ||
req, err := http.NewRequest("GET", autoscan.JoinURL(t.url, "emby", "System", "Info"), nil) | ||
if err != nil { | ||
return fmt.Errorf("%v: %w", err, autoscan.ErrFatal) | ||
} | ||
|
||
// set headers | ||
req.Header.Set("X-Emby-Token", t.token) | ||
req.Header.Set("Accept", "application/json") | ||
|
||
// send request | ||
res, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return fmt.Errorf("could not check Emby availability: %v: %w", | ||
err, autoscan.ErrTargetUnavailable) | ||
} | ||
|
||
defer res.Body.Close() | ||
|
||
// validate response | ||
if res.StatusCode != 200 { | ||
return fmt.Errorf("could not check Emby availability: %v: %w", | ||
res.StatusCode, autoscan.ErrTargetUnavailable) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package emby | ||
|
||
import ( | ||
"database/sql" | ||
"fmt" | ||
|
||
// database driver | ||
_ "github.com/mattn/go-sqlite3" | ||
) | ||
|
||
func NewDatastore(path string) (*datastore, error) { | ||
db, err := sql.Open("sqlite3", path) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not open database: %v", err) | ||
} | ||
|
||
return &datastore{db: db}, nil | ||
} | ||
|
||
type datastore struct { | ||
db *sql.DB | ||
} | ||
|
||
type library struct { | ||
Name string | ||
Path string | ||
} | ||
|
||
func (d *datastore) Libraries() ([]library, error) { | ||
rows, err := d.db.Query(sqlSelectLibraries) | ||
if err != nil { | ||
return nil, fmt.Errorf("select libraries: %v", err) | ||
} | ||
|
||
defer rows.Close() | ||
|
||
libraries := make([]library, 0) | ||
for rows.Next() { | ||
l := library{} | ||
if err := rows.Scan(&l.Name, &l.Path); err != nil { | ||
return nil, fmt.Errorf("scan library row: %v", err) | ||
} | ||
|
||
libraries = append(libraries, l) | ||
} | ||
|
||
return libraries, nil | ||
} | ||
|
||
type mediaPart struct { | ||
ID int | ||
File string | ||
Size uint64 | ||
} | ||
|
||
func (d *datastore) MediaPartByFile(path string) (*mediaPart, error) { | ||
mp := new(mediaPart) | ||
|
||
row := d.db.QueryRow(sqlSelectMediaPart, path) | ||
err := row.Scan(&mp.ID, &mp.File, &mp.Size) | ||
return mp, err | ||
} | ||
|
||
const ( | ||
sqlSelectLibraries = ` | ||
SELECT | ||
mi.Name, | ||
mi.Path | ||
FROM | ||
MediaItems mi | ||
WHERE | ||
mi.type = 3 AND mi.ParentId = 1 | ||
` | ||
sqlSelectMediaPart = ` | ||
SELECT | ||
mi.Id, | ||
mi.Path, | ||
mi.Size | ||
FROM | ||
MediaItems mi | ||
WHERE | ||
mi.Path = $1 | ||
LIMIT | ||
1 | ||
` | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package emby | ||
|
||
import ( | ||
"github.com/cloudbox/autoscan" | ||
"github.com/rs/zerolog" | ||
) | ||
|
||
type Config struct { | ||
Database string `yaml:"database"` | ||
URL string `yaml:"url"` | ||
Token string `yaml:"token"` | ||
Rewrite autoscan.Rewrite `yaml:"rewrite"` | ||
Verbosity string `yaml:"verbosity"` | ||
} | ||
|
||
type target struct { | ||
url string | ||
token string | ||
libraries []library | ||
|
||
log zerolog.Logger | ||
rewrite autoscan.Rewriter | ||
store *datastore | ||
} | ||
|
||
func New(c Config) (*target, error) { | ||
rewriter, err := autoscan.NewRewriter(c.Rewrite) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
store, err := NewDatastore(c.Database) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
libraries, err := store.Libraries() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
l := autoscan.GetLogger(c.Verbosity).With(). | ||
Str("target", "emby"). | ||
Str("url", c.URL). | ||
Logger() | ||
|
||
l.Debug(). | ||
Interface("libraries", libraries). | ||
Msg("Retrieved libraries") | ||
|
||
return &target{ | ||
url: c.URL, | ||
token: c.Token, | ||
libraries: libraries, | ||
|
||
log: l, | ||
rewrite: rewriter, | ||
store: store, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package emby | ||
|
||
import ( | ||
"bytes" | ||
"database/sql" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/cloudbox/autoscan" | ||
) | ||
|
||
type scanRequest struct { | ||
Path string `json:"path"` | ||
UpdateType string `json:"updateType"` | ||
} | ||
|
||
func (t target) Scan(scans []autoscan.Scan) error { | ||
// ensure scan tasks present (should never fail) | ||
if len(scans) == 0 { | ||
return nil | ||
} | ||
|
||
// check for at-least one missing/changed file | ||
process := false | ||
for _, s := range scans { | ||
targetFilePath := t.rewrite(filepath.Join(s.Folder, s.File)) | ||
|
||
targetFile, err := t.store.MediaPartByFile(targetFilePath) | ||
if err != nil { | ||
if errors.Is(err, sql.ErrNoRows) { | ||
// local file not found in target | ||
t.log.Debug(). | ||
Str("path", targetFilePath). | ||
Msg("At least one local file did not exist in target datastore") | ||
|
||
process = true | ||
break | ||
} | ||
|
||
// unexpected error | ||
return fmt.Errorf("could not check emby datastore: %v: %w", err, autoscan.ErrFatal) | ||
} | ||
|
||
// local file was found in target | ||
if targetFile.Size != s.Size { | ||
// local file did not match in target | ||
t.log.Debug(). | ||
Str("path", targetFilePath). | ||
Uint64("target_size", targetFile.Size). | ||
Uint64("local_size", s.Size). | ||
Msg("Local file size does not match in target datastore") | ||
|
||
process = true | ||
break | ||
} | ||
} | ||
|
||
if !process { | ||
// all local files existed in target | ||
t.log.Debug(). | ||
Interface("scans", scans). | ||
Msg("All local files exist in target") | ||
return nil | ||
} | ||
|
||
scanFolder := t.rewrite(scans[0].Folder) | ||
|
||
// determine library for this scan | ||
lib, err := t.getScanLibrary(scanFolder) | ||
if err != nil { | ||
t.log.Warn(). | ||
Err(err). | ||
Msg("No target library found") | ||
return fmt.Errorf("%v: %w", err, autoscan.ErrRetryScan) | ||
} | ||
|
||
l := t.log.With(). | ||
Str("path", scanFolder). | ||
Str("library", lib.Name). | ||
Logger() | ||
|
||
l.Trace().Msg("Sending scan request") | ||
|
||
// create request payload | ||
payload := new(struct { | ||
Updates []scanRequest `json:"Updates"` | ||
}) | ||
|
||
payload.Updates = append(payload.Updates, scanRequest{ | ||
Path: scanFolder, | ||
UpdateType: "Created", | ||
}) | ||
|
||
b, err := json.Marshal(payload) | ||
if err != nil { | ||
return fmt.Errorf("failed encoding scan request payload: %v, %w", err, autoscan.ErrFatal) | ||
} | ||
|
||
// create request | ||
reqURL := autoscan.JoinURL(t.url, "Library", "Media", "Updated") | ||
req, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(b)) | ||
if err != nil { | ||
// May only occur when the user has provided an invalid URL | ||
return fmt.Errorf("failed creating scan request: %v: %w", err, autoscan.ErrFatal) | ||
} | ||
|
||
// set headers | ||
req.Header.Set("Content-Type", "application/json") | ||
req.Header.Set("X-Emby-Token", t.token) | ||
|
||
// send request | ||
res, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return fmt.Errorf("failed sending scan request: %v: %w", err, autoscan.ErrTargetUnavailable) | ||
} | ||
|
||
defer res.Body.Close() | ||
|
||
// validate response | ||
if res.StatusCode != 204 { | ||
// 404 if some kind of proxy is in-front of Emby while it's offline. | ||
return fmt.Errorf("%v: failed validating scan request response: %w", res.Status, autoscan.ErrTargetUnavailable) | ||
} | ||
|
||
l.Info().Msg("Scan queued") | ||
return nil | ||
} | ||
|
||
func (t target) getScanLibrary(folder string) (*library, error) { | ||
for _, l := range t.libraries { | ||
if strings.HasPrefix(folder, l.Path) { | ||
return &l, nil | ||
} | ||
} | ||
|
||
return nil, fmt.Errorf("%v: failed determining library", folder) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,5 +93,6 @@ FROM | |
media_parts mp | ||
WHERE | ||
mp.file = $1 | ||
LIMIT 1 | ||
` | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters