release.go 10 KB
Newer Older
1
package varroa
2
3
4
5
6
7
8

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"
9
10
	"time"

11
	"github.com/dustin/go-humanize"
12
13
	"gitlab.com/catastrophic/assistance/fs"
	"gitlab.com/catastrophic/assistance/intslice"
14
	"gitlab.com/catastrophic/assistance/logthis"
15
	"gitlab.com/catastrophic/assistance/strslice"
16
	"gitlab.com/passelecasque/obstruction/tracker"
17
18
)

19
20
const (
	ReleaseString = `Release info:
21
22
23
24
25
26
27
	Artist: %s
	Title: %s
	Year: %d
	Release Type: %s
	Format: %s
	Quality: %s
	HasLog: %t
28
	Log Score: %d
29
30
31
32
33
34
	Has Cue: %t
	Scene: %t
	Source: %s
	Tags: %s
	Torrent URL: %s
	Torrent ID: %s`
35
	TorrentPath         = `%s - %s (%d) [%s %s %s %s] - %s.torrent`
user's avatar
user committed
36
	TorrentNotification = `%s - %s (%d) [%s/%s/%s/%s] [%s]`
37
38
39

	logScoreNotInAnnounce = -9999
)
40
41

type Release struct {
antipathique's avatar
antipathique committed
42
43
	ID          uint32 `storm:"id,increment"`
	Tracker     string `storm:"index"`
antipathique's avatar
antipathique committed
44
	Timestamp   time.Time
45
	TorrentID   string `storm:"index"`
antipathique's avatar
antipathique committed
46
	GroupID     string
47
48
49
50
51
52
53
	Artists     []string
	Title       string
	Year        int
	ReleaseType string
	Format      string
	Quality     string
	HasLog      bool
antipathique's avatar
antipathique committed
54
	LogScore    int
55
56
57
58
	HasCue      bool
	IsScene     bool
	Source      string
	Tags        []string
59
	torrentURL  string
60
61
62
	Size        uint64
	Folder      string
	Filter      string
63
64
}

65
func NewRelease(trackerName string, parts []string, alternative bool) (*Release, error) {
66
	if len(parts) != 19 {
67
		return nil, errors.New("incomplete announce information")
68
	}
69
70

	var tags []string
71
	var torrentURL, torrentID string
72
73
	pattern := `http[s]?://[[:alnum:]\./:]*torrents\.php\?action=download&id=([\d]*)`
	rg := regexp.MustCompile(pattern)
74
75
76
77
78
79
80
81
82
83
84

	if alternative {
		tags = strings.Split(parts[16], ",")
		torrentURL = parts[18]
	} else {
		tags = strings.Split(parts[18], ",")
		torrentURL = parts[17]
	}

	// getting torrentID
	hits := rg.FindAllStringSubmatch(torrentURL, -1)
85
86
87
	if len(hits) != 0 {
		torrentID = hits[0][1]
	}
88
89
90
91
92
	// cleaning up tags
	for i, el := range tags {
		tags[i] = strings.TrimSpace(el)
	}

93
94
95
96
	year, err := strconv.Atoi(parts[3])
	if err != nil {
		year = -1
	}
97
	hasLog := parts[8] != ""
98
99
100
101
102
103
	logScore, err := strconv.Atoi(parts[10])
	if err != nil {
		logScore = logScoreNotInAnnounce
	}
	hasCue := parts[12] != ""
	isScene := parts[15] != ""
antipathique's avatar
antipathique committed
104

105
	artist := []string{parts[1]}
106
	// if the raw Artists announce contains & or "performed by", split and add to slice
107
108
109
110
111
112
113
114
	subArtists := regexp.MustCompile("&|performed by").Split(parts[1], -1)
	if len(subArtists) != 1 {
		for i, a := range subArtists {
			subArtists[i] = strings.TrimSpace(a)
		}
		artist = append(artist, subArtists...)
	}

115
116
	// checks
	releaseType := parts[4]
117
	if !strslice.Contains(tracker.KnownReleaseTypes, releaseType) {
118
119
120
		return nil, errors.New("Unknown release type: " + releaseType)
	}
	format := parts[5]
121
	if !strslice.Contains(tracker.KnownFormats, format) {
122
123
124
		return nil, errors.New("Unknown format: " + format)
	}
	source := parts[13]
125
	if !strslice.Contains(tracker.KnownSources, source) {
126
127
128
		return nil, errors.New("Unknown source: " + source)
	}
	quality := parts[6]
129
	if !strslice.Contains(tracker.KnownQualities, quality) {
130
131
132
		return nil, errors.New("Unknown quality: " + quality)
	}

133
	r := &Release{Tracker: trackerName, Timestamp: time.Now(), Artists: artist, Title: parts[2], Year: year, ReleaseType: releaseType, Format: format, Quality: quality, Source: source, HasLog: hasLog, LogScore: logScore, HasCue: hasCue, IsScene: isScene, torrentURL: torrentURL, Tags: tags, TorrentID: torrentID}
134
135
136
	return r, nil
}

137
138
139
140
141
// IsMusicRelease returns false if the release has no artists
func (r *Release) IsMusicRelease() bool {
	return len(r.Artists) != 0
}

142
func (r *Release) String() string {
143
	return fmt.Sprintf(ReleaseString, strings.Join(r.Artists, ","), r.Title, r.Year, r.ReleaseType, r.Format, r.Quality, r.HasLog, r.LogScore, r.HasCue, r.IsScene, r.Source, r.Tags, r.torrentURL, r.TorrentID)
144
145
}

146
func (r *Release) ShortString() string {
user's avatar
user committed
147
	short := fmt.Sprintf(TorrentNotification, r.Artists[0], r.Title, r.Year, r.ReleaseType, r.Format, r.Quality, r.Source, strings.Join(r.Tags, ", "))
148
	if r.Size != 0 {
antipathique's avatar
antipathique committed
149
		return short + fmt.Sprintf(" [%s]", humanize.IBytes(r.Size))
150
151
	}
	return short
152
153
}

antipathique's avatar
antipathique committed
154
155
func (r *Release) TorrentFile() string {
	torrentFile := fmt.Sprintf(TorrentPath, r.Artists[0], r.Title, r.Year, r.ReleaseType, r.Format, r.Quality, r.Source, r.TorrentID)
156
	return fs.SanitizePath(torrentFile)
antipathique's avatar
antipathique committed
157
158
}

159
func (r *Release) Satisfies(filter *ConfigFilter) bool {
160
161
162
	// no longer filtering on artists. If a filter has artists defined,
	// varroa will now wait until it gets the TorrentInfo and all of the artists
	// to make a call.
163
	if len(filter.Year) != 0 && !intslice.Contains(filter.Year, r.Year) {
164
		logthis.Info(filter.Name+": Wrong year", logthis.VERBOSE)
165
166
		return false
	}
167
	if len(filter.Format) != 0 && !strslice.Contains(filter.Format, r.Format) {
168
		logthis.Info(filter.Name+": Wrong format", logthis.VERBOSE)
169
170
		return false
	}
171
	if len(filter.Source) != 0 && !strslice.Contains(filter.Source, r.Source) {
172
		logthis.Info(filter.Name+": Wrong source", logthis.VERBOSE)
173
174
		return false
	}
175
	if len(filter.Quality) != 0 && !strslice.Contains(filter.Quality, r.Quality) {
176
		logthis.Info(filter.Name+": Wrong quality", logthis.VERBOSE)
177
178
		return false
	}
179
	if r.Source == tracker.SourceCD && r.Format == tracker.FormatFLAC && filter.HasLog && !r.HasLog {
180
		logthis.Info(filter.Name+": Release has no log", logthis.VERBOSE)
181
182
		return false
	}
183
	// only compare logscores if the announce contained that information
184
	if r.Source == tracker.SourceCD && r.Format == tracker.FormatFLAC && filter.LogScore != 0 && (!r.HasLog || (r.LogScore != logScoreNotInAnnounce && filter.LogScore > r.LogScore)) {
185
		logthis.Info(filter.Name+": Incorrect log score", logthis.VERBOSE)
186
187
		return false
	}
188
	if r.Source == tracker.SourceCD && r.Format == tracker.FormatFLAC && filter.HasCue && !r.HasCue {
189
		logthis.Info(filter.Name+": Release has no cue", logthis.VERBOSE)
190
191
		return false
	}
192
	if !filter.AllowScene && r.IsScene {
193
		logthis.Info(filter.Name+": Scene release not allowed", logthis.VERBOSE)
194
195
		return false
	}
196
	if len(filter.ExcludedReleaseType) != 0 && strslice.Contains(filter.ExcludedReleaseType, r.ReleaseType) {
197
		logthis.Info(filter.Name+": Excluded release type", logthis.VERBOSE)
198
199
		return false
	}
200
	if len(filter.ReleaseType) != 0 && !strslice.Contains(filter.ReleaseType, r.ReleaseType) {
201
		logthis.Info(filter.Name+": Wrong release type", logthis.VERBOSE)
202
203
		return false
	}
user's avatar
user committed
204
205
	// checking tags
	if len(filter.TagsRequired) != 0 && !MatchAllInSlice(filter.TagsRequired, r.Tags) {
206
		logthis.Info(filter.Name+": Does not have all required tags", logthis.VERBOSE)
user's avatar
user committed
207
208
		return false
	}
209
	for _, excluded := range filter.TagsExcluded {
210
		if MatchInSlice(excluded, r.Tags) {
211
			logthis.Info(filter.Name+": Has excluded tag", logthis.VERBOSE)
antipathique's avatar
antipathique committed
212
213
214
			return false
		}
	}
215
	if len(filter.TagsIncluded) != 0 {
antipathique's avatar
antipathique committed
216
217
		// if none of r.tags in conf.includedTags, return false
		atLeastOneIncludedTag := false
218
		for _, t := range r.Tags {
219
			if MatchInSlice(t, filter.TagsIncluded) {
antipathique's avatar
antipathique committed
220
221
222
223
224
				atLeastOneIncludedTag = true
				break
			}
		}
		if !atLeastOneIncludedTag {
225
			logthis.Info(filter.Name+": Does not have any wanted tag", logthis.VERBOSE)
antipathique's avatar
antipathique committed
226
227
228
			return false
		}
	}
229
	// taking the opportunity to retrieve and save some info
230
	r.Filter = filter.Name
231
232
	return true
}
233

234
func (r *Release) HasCompatibleTrackerInfo(filter *ConfigFilter, blacklistedUploaders []string, info *TrackerMetadata) bool {
235
	// checks
236
	if len(filter.EditionYear) != 0 && !intslice.Contains(filter.EditionYear, info.EditionYear) {
237
		logthis.Info(filter.Name+": Wrong edition year", logthis.VERBOSE)
238
239
		return false
	}
240
	if filter.MaxSizeMB != 0 && uint64(filter.MaxSizeMB) < (info.Size/(1024*1024)) {
241
		logthis.Info(filter.Name+": Release too big.", logthis.VERBOSE)
242
243
		return false
	}
244
	if filter.MinSizeMB > 0 && uint64(filter.MinSizeMB) > (info.Size/(1024*1024)) {
245
		logthis.Info(filter.Name+": Release too small.", logthis.VERBOSE)
246
247
		return false
	}
248
	if r.Source == tracker.SourceCD && r.Format == tracker.FormatFLAC && r.HasLog && filter.LogScore != 0 && filter.LogScore > info.LogScore {
249
		logthis.Info(filter.Name+": Incorrect log score", logthis.VERBOSE)
250
251
		return false
	}
252
	if len(filter.RecordLabel) != 0 && !MatchInSlice(info.RecordLabel, filter.RecordLabel) {
253
		logthis.Info(filter.Name+": No match for record label", logthis.VERBOSE)
254
255
		return false
	}
antipathique's avatar
antipathique committed
256
	if len(filter.Artist) != 0 || len(filter.ExcludedArtist) != 0 {
257
		var foundAtLeastOneArtist bool
258
259
		for _, iArtist := range info.Artists {
			if MatchInSlice(iArtist.Name, filter.Artist) {
260
261
				foundAtLeastOneArtist = true
			}
262
			if MatchInSlice(iArtist.Name, filter.ExcludedArtist) {
263
				logthis.Info(filter.Name+": Found excluded artist "+iArtist.Name, logthis.VERBOSE)
264
265
				return false
			}
266
		}
267
		if !foundAtLeastOneArtist && len(filter.Artist) != 0 {
268
			logthis.Info(filter.Name+": No match for artists", logthis.VERBOSE)
269
270
271
			return false
		}
	}
user's avatar
user committed
272
	if strslice.Contains(blacklistedUploaders, info.Uploader) || strslice.Contains(filter.BlacklistedUploaders, info.Uploader) {
273
		logthis.Info(filter.Name+": Uploader "+info.Uploader+" is blacklisted.", logthis.VERBOSE)
274
275
		return false
	}
276
	if len(filter.Uploader) != 0 && !strslice.Contains(filter.Uploader, info.Uploader) {
277
		logthis.Info(filter.Name+": No match for uploader", logthis.VERBOSE)
278
279
		return false
	}
280
281
	if len(filter.Edition) != 0 {
		found := false
282
		if MatchInSlice(info.EditionName, filter.Edition) {
283
			found = true
284
285
		}
		if !found {
286
			logthis.Info(filter.Name+": Edition name does not match any criteria.", logthis.VERBOSE)
287
288
289
			return false
		}
	}
290
291
292
293
294
295
296
297
298
299
	if len(filter.Title) != 0 {
		found := false
		if MatchInSlice(info.Title, filter.Title) {
			found = true
		}
		if !found {
			logthis.Info(filter.Name+": Title does not match any criteria.", logthis.VERBOSE)
			return false
		}
	}
300
	if filter.RejectUnknown && info.CatalogNumber == "" && info.RecordLabel == "" {
301
		logthis.Info(filter.Name+": Release has neither a record label or catalog number, rejected.", logthis.VERBOSE)
302
303
		return false
	}
304
305
306
307
	if filter.RejectTrumpable && info.Trumpable {
		logthis.Info(filter.Name+": Release is marked as trumpable, rejected.", logthis.VERBOSE)
		return false
	}
308
	// taking the opportunity to retrieve and save some info
309
310
311
312
	r.Size = info.Size
	r.LogScore = info.LogScore
	r.Folder = info.FolderName
	r.GroupID = strconv.Itoa(info.GroupID)
313
314
	return true
}