Skip to content

Commit 7132818

Browse files
authored
3.1.0: Gotify, ignorepath, rework arr services, various fixes (#41)
* #27 removes removeunknownfiles * Typos are dumb. Retool *arr FromConfig() functions so that the code is cleaner overall. * fix CSV writes * #18 patch in m2ts/ts file support on the fly Not the best solution, but it works... * Resolve #31 Fixes ignoreext * actually patches in ts/m2ts * Resolve #36 - Adds Gotify based on the example code * Adds gotify to the example config. Removes unknowndeleted * Round ms to save navbar space * #34 Adds Next run time to WebAPI * fix compiler warning * Resolve #34. Adds next run time using moment.js Cleans up last run time so it displays the last run time until current run ends. * set colors once per page load * Resolve #33. Adds a 'Run Now" Button. * fix bug where "last run" would be an empty object * reduce stats output to last 30 runs * fix bug with schedule not updating on data refresh * Resolve #35 Adds ignorepath * Create a minimal config Users are running checkrr the first time with the full example config. This is the minimal config needed. * Fix issues with logfile and logjson plus the debug flag * Use the right function * ignore test logfile * Resolve #26. Multi arr services. Supports multiple of each arr service. * Don't add the service if it's disabled via process: false * Resolve #30 Adds mappings for docker users. Translates paths in arr services to paths on docker container. The key in mappsings should be what arr services has. The value should be the path on disk as checkrr sees it. * add an explaination of the new mappings and service keys * remove unneeded set function * Resolve #39 fix issues with influxdb2 * Resolve #29 hopefully. If someone wants to PR better instructions feel free. * Removes refs to unknownfilesdeleted * decent static color set * remove mention of discord if the config is nil. * clean up code path * add some debug logging to translatePath * Add some more debug logging around #30 * specify which path map we are debug logging * Update deps * Finally fixes #30. strings.Replace() depth changed to -1. Swapped key and value in strings.Replace() * fixed a typo in logs
1 parent 6605509 commit 7132818

20 files changed

+799
-263
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ test/
66
checkrr
77
.vscode
88
badfiles.csv
9+
checkrr.log
910
webserver/build
1011
webserver/node_modules

README.md

+21-18
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,29 @@ I've been running a media library for the past ~ 8 years migrating my library be
99
Checkrr runs various checks (ffprobe, magic number, mimetype, and file hash on subsequent runs to drastically improve speed) on the path you specify as `checkpath` in the config.
1010

1111
* If the file passes inspection, the hash is recorded in a bbolt flatfile DB so future runs are insanely fast on large libraries.
12-
* If the file fails all checks checkrr will check sonarr and/or radarr for the file removing it and requesting a new version via the correct system (assuming they are enabled... you could just run checkrr in a no-op state by setting `sonarr.process: false` and `radarr.process: false` in the config and then egrep the output like so `checkrr check | egrep "Hash Mismatch|not a recongized file type"` for environments that do not run either of these.)
12+
* If the file fails all checks checkrr will check sonarr and/or radarr for the file removing it and requesting a new version via the correct system (assuming they are enabled... you could just run checkrr in a no-op state by setting `sonarr.process: false` and `radarr.process: false` in the config and then egrep the output like so `checkrr check | egrep "Hash Mismatch|not a recognized file type"` for environments that do not run either of these.)
1313

1414
## Screenshots
1515
![Idle screenshot](./screenshots/Idle.png?raw=true)
1616
![Running screenshot](./screenshots/Running.png?raw=true)
1717

18-
## Installation
19-
cli:
20-
Grab a release from the releases page.
18+
## Installation and running checkrr
19+
### cli
20+
* Install prerequisite packages via your package manager or by downloading the installer (for windows): ffmpeg
21+
* Make sure ffprobe is in your $PATH var. If you installed from a Linux/macOS package manager, it is. If you are on windows, you'll need to make sure you can run ffprobe from a basic command prompt/powershell.
22+
* Grab a release from the releases page.
23+
* Copy the example config from the repo: `wget https://raw.githubusercontent.com/aetaric/checkrr/main/checkrr.yaml.example -O checkrr.yaml`
24+
* Edit the config in your favorite editor. Make sure you remove any sections you aren't using. (If you aren't using influxdb 1 and/or 2 for example, you should remove the entire stats block from your config.)If you aren't sure what the minimal config file can look like, check https://raw.githubusercontent.com/aetaric/checkrr/main/checkrr.yaml.minimal.
25+
* To run checkrr as a daemon, use `checkrr -c /path/to/checkrr.yaml`. If you'd like checkrr to run once and then exit (useful for running in your own cron daemon) `checkrr -c /path/to/checkrr.yaml --run-once`.
2126

22-
docker:
23-
`docker pull ghcr.io/aetaric/checkrr:latest`
27+
### docker
28+
YOU MUST CREATE THE CONFIG AND DB FILES BEFORE STARTING. checkrr will complain if these are directories. Docker doesn't know you want to mount a file unless it already exists.
29+
30+
* creating empty db file: `touch checkrr.db`
31+
* creating a config file from the example: `wget https://raw.githubusercontent.com/aetaric/checkrr/main/checkrr.yaml.example -O checkrr.yaml`
32+
_make sure you edit the example config from the defaults. Remove any unused sections._
33+
While editing the example you might want to add path mappings if the path to your media is differs from arr services and checkrr.
2434

25-
## Usage
2635

2736
### Running Checkrr
2837
cli as a daemon:
@@ -52,20 +61,14 @@ services:
5261
restart: on-failure
5362
```
5463
55-
## Upgrading to 2.x
56-
Checkrr 2.x has a more organized config file and quite a reduction in CLI flags. Checkout `checkrr --help` for the flag changes. You will have to manually conform your config file to the example file in the repo; checkrr no longer outputs a default config.
57-
58-
## Unknown file deletion
59-
If you are feeling especially spicy, there is `RemoveUnknownFiles` flag in the config. This flag is destructive. It will remove any file that isn't detected as a valid Video, Audio, Document, or plain text file.
60-
61-
**Seriously** I don't recommend you run this on the first pass if at all. You are very likely to lose something you didn't expect to lose.
62-
63-
Before using this flag, run checkrr and read the full output to ensure you don't nuke a file that you don't want to lose. Run it again with sonarr and/or radarr enabled.
64+
### unRAID using mrslaw's community applications repo
65+
Please note the Additional Requirements on the details screen prior to pressing install. mrslaw has all the commands you need to run there.
6466
65-
*I am not responsible for your use of this flag. I will not help you sort out any damage you cause to your library. Issues opened around this flag's usage will be summarily closed as PEBCAK.*
67+
## Upgrading to 3.1 or newer
68+
checkrr > 3.1 has changed the way arr services are handled. Please review the example config and bring your config into compliance prior to running checkrr. With the 3.1 release checkrr supports having multiple of each arr service. So you could have 3 sonarr instances connected. Each arr config under `arr:` has a `service` key to tell checkrr what service type it is. This can be set to `sonarr`, `radarr`, or `lidarr`. Please note that if you are running on docker, you will likely want to setup path mappings for each service. checkrr will attempt to translate the paths that the arr services see when working with their APIs.
6669

6770
## Building
68-
Should you want to build checkrr, you can do so with the following:
71+
Should you want to build checkrr from source, you can do so with the following:
6972
`cd webserver && yarn build && cd .. && go build`
7073
Please note, if you build checkrr yourself, you will be told to download the official release if you open an issue for a bug.
7174

check/checkrr.go

+103-64
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import (
88
"net/http"
99
"os"
1010
"path/filepath"
11+
"strings"
1112

1213
"github.com/aetaric/checkrr/connections"
1314
"github.com/aetaric/checkrr/features"
1415
"github.com/aetaric/checkrr/hidden"
1516
"github.com/aetaric/checkrr/notifications"
1617
"github.com/h2non/filetype"
18+
"github.com/h2non/filetype/matchers"
1719
"github.com/kalafut/imohash"
1820
log "github.com/sirupsen/logrus"
1921
"github.com/spf13/viper"
@@ -27,10 +29,11 @@ type Checkrr struct {
2729
Running bool
2830
csv features.CSV
2931
notifications notifications.Notifications
30-
sonarr connections.Sonarr
31-
radarr connections.Radarr
32-
lidarr connections.Lidarr
32+
sonarr []connections.Sonarr
33+
radarr []connections.Radarr
34+
lidarr []connections.Lidarr
3335
ignoreExts []string
36+
ignorePaths []string
3437
ignoreHidden bool
3538
config *viper.Viper
3639
FullConfig *viper.Viper
@@ -57,11 +60,6 @@ func (c *Checkrr) Run() {
5760
// Connect to Sonarr, Radarr, and Lidarr
5861
c.connectServices()
5962

60-
// Unknown File deletion
61-
if c.config.GetBool("removeunknownfiles") {
62-
log.WithFields(log.Fields{"startup": true, "unknownFiles": "enabled"}).Warn(`unknown file deletion is on. You may lose files that are not tracked by services you've enabled in the config. This will still delete files even if those integrations are disabled.`)
63-
}
64-
6563
// Connect to notifications
6664
c.connectNotifications()
6765

@@ -72,8 +70,16 @@ func (c *Checkrr) Run() {
7270
}
7371

7472
c.ignoreExts = c.config.GetStringSlice("ignoreexts")
73+
c.ignorePaths = c.config.GetStringSlice("ignorepaths")
7574
c.ignoreHidden = c.config.GetBool("ignorehidden")
7675

76+
// I'm tired of waiting for filetype to support this. We'll force it by adding to the matchers on the fly.
77+
// TODO: if h2non/filetype#120 ever gets completed, remove this logic
78+
ts := filetype.AddType("ts", "MPEG-TS")
79+
m2ts := filetype.AddType("m2ts", "MPEG-TS")
80+
matchers.Video[ts] = mpegts_matcher
81+
matchers.Video[m2ts] = mpegts_matcher
82+
7783
c.Stats.Start()
7884

7985
log.Debug(c.config.GetStringSlice("checkpath"))
@@ -98,7 +104,17 @@ func (c *Checkrr) Run() {
98104

99105
if c.ignoreHidden {
100106
i, _ := hidden.IsHidden(path)
101-
ignore = i
107+
if !ignore {
108+
ignore = i
109+
}
110+
}
111+
112+
for _, v := range c.ignorePaths {
113+
if strings.Contains(path, v) {
114+
if !ignore {
115+
ignore = true
116+
}
117+
}
102118
}
103119

104120
if !ignore {
@@ -161,31 +177,45 @@ func (c *Checkrr) FromConfig(conf *viper.Viper) {
161177
}
162178

163179
func (c *Checkrr) connectServices() {
164-
if viper.GetViper().Sub("sonarr") != nil {
165-
c.sonarr = connections.Sonarr{}
166-
c.sonarr.FromConfig(*viper.GetViper().Sub("sonarr"))
167-
sonarrConnected, sonarrMessage := c.sonarr.Connect()
168-
log.WithFields(log.Fields{"Startup": true, "Sonarr Connected": sonarrConnected}).Info(sonarrMessage)
169-
} else {
170-
log.WithFields(log.Fields{"Startup": true, "Sonarr Connected": false}).Info("Sonarr integration not enabled. Files will not be fixed. (if you expected a no-op, this is fine)")
171-
}
180+
if viper.GetViper().GetStringMap("arr") != nil {
181+
arrConfig := viper.GetViper().Sub("arr")
182+
arrKeys := viper.GetViper().Sub("arr").AllKeys()
183+
for _, key := range arrKeys {
184+
if strings.Contains(key, "service") {
185+
k := strings.Split(key, ".")[0]
186+
config := arrConfig.Sub(k)
187+
188+
if config.GetString("service") == "sonarr" {
189+
sonarr := connections.Sonarr{}
190+
sonarr.FromConfig(config)
191+
sonarrConnected, sonarrMessage := sonarr.Connect()
192+
log.WithFields(log.Fields{"Startup": true, fmt.Sprintf("Sonarr \"%s\" Connected", k): sonarrConnected}).Info(sonarrMessage)
193+
if sonarrConnected {
194+
c.sonarr = append(c.sonarr, sonarr)
195+
}
196+
}
172197

173-
if viper.GetViper().Sub("radarr") != nil {
174-
c.radarr = connections.Radarr{}
175-
c.radarr.FromConfig(*viper.GetViper().Sub("radarr"))
176-
radarrConnected, radarrMessage := c.radarr.Connect()
177-
log.WithFields(log.Fields{"Startup": true, "Radarr Connected": radarrConnected}).Info(radarrMessage)
178-
} else {
179-
log.WithFields(log.Fields{"Startup": true, "Radarr Connected": false}).Info("Radarr integration not enabled. Files will not be fixed. (if you expected a no-op, this is fine)")
180-
}
198+
if config.GetString("service") == "radarr" {
199+
radarr := connections.Radarr{}
200+
radarr.FromConfig(config)
201+
radarrConnected, radarrMessage := radarr.Connect()
202+
log.WithFields(log.Fields{"Startup": true, fmt.Sprintf("Radarr \"%s\" Connected", k): radarrConnected}).Info(radarrMessage)
203+
if radarrConnected {
204+
c.radarr = append(c.radarr, radarr)
205+
}
206+
}
181207

182-
if viper.GetViper().Sub("lidarr") != nil {
183-
c.lidarr = connections.Lidarr{}
184-
c.lidarr.FromConfig(*viper.GetViper().Sub("lidarr"))
185-
lidarrConnected, lidarrMessage := c.lidarr.Connect()
186-
log.WithFields(log.Fields{"Startup": true, "Lidarr Connected": lidarrConnected}).Info(lidarrMessage)
187-
} else {
188-
log.WithFields(log.Fields{"Startup": true, "Lidarr Connected": false}).Info("Lidarr integration not enabled. Files will not be fixed. (if you expected a no-op, this is fine)")
208+
if config.GetString("service") == "lidarr" {
209+
lidarr := connections.Lidarr{}
210+
lidarr.FromConfig(config)
211+
lidarrConnected, lidarrMessage := lidarr.Connect()
212+
log.WithFields(log.Fields{"Startup": true, fmt.Sprintf("Lidarr \"%s\" Connected", k): lidarrConnected}).Info(lidarrMessage)
213+
if lidarrConnected {
214+
c.lidarr = append(c.lidarr, lidarr)
215+
}
216+
}
217+
}
218+
}
189219
}
190220
}
191221

@@ -257,7 +287,7 @@ func (c *Checkrr) checkFile(path string) {
257287
content := http.DetectContentType(buf)
258288
log.WithFields(log.Fields{"FFProbe": false, "Type": "Unknown"}).Debugf("File \"%v\" is of type \"%v\"", path, content)
259289
buf = nil
260-
log.WithFields(log.Fields{"FFProbe": false, "Type": "Unknown"}).Infof("File \"%v\" is not a recongized file type", path)
290+
log.WithFields(log.Fields{"FFProbe": false, "Type": "Unknown"}).Infof("File \"%v\" is not a recognized file type", path)
261291
c.notifications.Notify("Unknown file detected", fmt.Sprintf("\"%v\" is not a Video, Audio, Image, Subtitle, or Plaintext file.", path), "unknowndetected", path)
262292
c.Stats.UnknownFileCount++
263293
c.Stats.Write("UnknownFiles", c.Stats.UnknownFileCount)
@@ -267,40 +297,38 @@ func (c *Checkrr) checkFile(path string) {
267297
}
268298

269299
func (c *Checkrr) deleteFile(path string) {
270-
if c.sonarr.Process && c.sonarr.MatchPath(path) {
271-
c.sonarr.RemoveFile(path)
272-
c.notifications.Notify("File Reacquire", fmt.Sprintf("\"%v\" was sent to sonarr to be reacquired", path), "reacquire", path)
273-
c.Stats.SonarrSubmissions++
274-
c.Stats.Write("Sonarr", c.Stats.SonarrSubmissions)
275-
c.recordBadFile(path, "sonarr")
276-
} else if c.radarr.Process && c.radarr.MatchPath(path) {
277-
c.radarr.RemoveFile(path)
278-
c.notifications.Notify("File Reacquire", fmt.Sprintf("\"%v\" was sent to radarr to be reacquired", path), "reacquire", path)
279-
c.Stats.RadarrSubmissions++
280-
c.Stats.Write("Radarr", c.Stats.RadarrSubmissions)
281-
c.recordBadFile(path, "radarr")
282-
} else if c.lidarr.Process && c.lidarr.MatchPath(path) {
283-
c.lidarr.RemoveFile(path)
284-
c.notifications.Notify("File Reacquire", fmt.Sprintf("\"%v\" was sent to lidarr to be reacquired", path), "reacquire", path)
285-
c.Stats.LidarrSubmissions++
286-
c.Stats.Write("Lidarr", c.Stats.LidarrSubmissions)
287-
c.recordBadFile(path, "lidarr")
288-
} else {
289-
log.WithFields(log.Fields{"Unknown File": true}).Infof("Couldn't find a target for file \"%v\". File is unknown.", path)
290-
c.recordBadFile(path, "unknown")
291-
if c.config.GetBool("removeunknownfiles") {
292-
e := os.Remove(path)
293-
if e != nil {
294-
log.WithFields(log.Fields{"FFProbe": false, "Type": "Unknown", "Deleted": false}).Warnf("Could not delete File: \"%v\"", path)
295-
return
296-
}
297-
log.WithFields(log.Fields{"FFProbe": false, "Type": "Unknown", "Deleted": true}).Warnf("Removed File: \"%v\"", path)
298-
c.notifications.Notify("Unknown file deleted", fmt.Sprintf("\"%v\" was removed.", path), "unknowndeleted", path)
299-
c.Stats.UnknownFilesDeleted++
300-
c.Stats.Write("UnknownDelete", c.Stats.UnknownFilesDeleted)
300+
for _, sonarr := range c.sonarr {
301+
if sonarr.Process && sonarr.MatchPath(path) {
302+
sonarr.RemoveFile(path)
303+
c.notifications.Notify("File Reacquire", fmt.Sprintf("\"%v\" was sent to sonarr to be reacquired", path), "reacquire", path)
304+
c.Stats.SonarrSubmissions++
305+
c.Stats.Write("Sonarr", c.Stats.SonarrSubmissions)
306+
c.recordBadFile(path, "sonarr")
301307
return
302308
}
303309
}
310+
for _, radarr := range c.radarr {
311+
if radarr.Process && radarr.MatchPath(path) {
312+
radarr.RemoveFile(path)
313+
c.notifications.Notify("File Reacquire", fmt.Sprintf("\"%v\" was sent to radarr to be reacquired", path), "reacquire", path)
314+
c.Stats.RadarrSubmissions++
315+
c.Stats.Write("Radarr", c.Stats.RadarrSubmissions)
316+
c.recordBadFile(path, "radarr")
317+
return
318+
}
319+
}
320+
for _, lidarr := range c.lidarr {
321+
if lidarr.Process && lidarr.MatchPath(path) {
322+
lidarr.RemoveFile(path)
323+
c.notifications.Notify("File Reacquire", fmt.Sprintf("\"%v\" was sent to lidarr to be reacquired", path), "reacquire", path)
324+
c.Stats.LidarrSubmissions++
325+
c.Stats.Write("Lidarr", c.Stats.LidarrSubmissions)
326+
c.recordBadFile(path, "lidarr")
327+
return
328+
}
329+
}
330+
log.WithFields(log.Fields{"Unknown File": true}).Infof("Couldn't find a target for file \"%v\". File is unknown.", path)
331+
c.recordBadFile(path, "unknown")
304332
}
305333

306334
func (c *Checkrr) recordBadFile(path string, fileType string) {
@@ -325,13 +353,24 @@ func (c *Checkrr) recordBadFile(path string, fileType string) {
325353
return nil
326354
}
327355
})
356+
328357
if err != nil {
329358
log.WithFields(log.Fields{"DB Update": "Failure"}).Warnf("Error: %v", err.Error())
330359
}
360+
361+
if c.config.GetString("csvfile") != "" {
362+
c.csv.Write(path, fileType)
363+
}
331364
}
332365

333366
type BadFile struct {
334367
FileExt string `json:"fileExt"`
335368
Reacquire bool `json:"reacquire"`
336369
Service string `json:"service"`
337370
}
371+
372+
// TODO: if h2non/filetype#120 ever gets completed, remove this logic
373+
func mpegts_matcher(buf []byte) bool {
374+
return len(buf) > 1 &&
375+
buf[0] == 0x47
376+
}

0 commit comments

Comments
 (0)