@@ -2,6 +2,7 @@ package core
2
2
3
3
import (
4
4
"bufio"
5
+ "encoding/json"
5
6
"errors"
6
7
"fmt"
7
8
"io"
@@ -11,7 +12,9 @@ import (
11
12
"strings"
12
13
)
13
14
14
- // List of common file extensions
15
+ // List of file extensions that I've encountered.
16
+ // Some of them aren't eBooks, but they were returned
17
+ // in previous search results.
15
18
var fileTypes = [... ]string {
16
19
"epub" ,
17
20
"mobi" ,
@@ -20,7 +23,13 @@ var fileTypes = [...]string{
20
23
"rtf" ,
21
24
"pdf" ,
22
25
"cdr" ,
23
- "rar" ,
26
+ "lit" ,
27
+ "cbr" ,
28
+ "doc" ,
29
+ "htm" ,
30
+ "jpg" ,
31
+ "txt" ,
32
+ "rar" , // Compressed extensions should always be last 2 items
24
33
"zip" ,
25
34
}
26
35
@@ -35,8 +44,19 @@ type BookDetail struct {
35
44
}
36
45
37
46
type ParseError struct {
38
- Line string
39
- Error error
47
+ Line string `json:"line"`
48
+ Error error `json:"error"`
49
+ }
50
+
51
+ func (p * ParseError ) MarshalJSON () ([]byte , error ) {
52
+ item := struct {
53
+ Line string `json:"line"`
54
+ Error string `json:"error"`
55
+ }{
56
+ Line : p .Line ,
57
+ Error : p .Error .Error (),
58
+ }
59
+ return json .Marshal (item )
40
60
}
41
61
42
62
func (p ParseError ) String () string {
@@ -51,20 +71,20 @@ func ParseSearchFile(filePath string) ([]BookDetail, []ParseError) {
51
71
}
52
72
defer file .Close ()
53
73
54
- return ParseSearch (file )
74
+ return ParseSearchV2 (file )
55
75
}
56
76
57
77
func ParseSearch (reader io.Reader ) ([]BookDetail , []ParseError ) {
58
78
var books []BookDetail
59
- var errors []ParseError
79
+ var parseErrors []ParseError
60
80
61
81
scanner := bufio .NewScanner (reader )
62
82
for scanner .Scan () {
63
83
line := scanner .Text ()
64
84
if strings .HasPrefix (line , "!" ) {
65
85
dat , err := parseLine (line )
66
86
if err != nil {
67
- errors = append (errors , ParseError {Line : line , Error : err })
87
+ parseErrors = append (parseErrors , ParseError {Line : line , Error : err })
68
88
} else {
69
89
books = append (books , dat )
70
90
}
@@ -73,7 +93,7 @@ func ParseSearch(reader io.Reader) ([]BookDetail, []ParseError) {
73
93
74
94
sort .Slice (books , func (i , j int ) bool { return books [i ].Server < books [j ].Server })
75
95
76
- return books , errors
96
+ return books , parseErrors
77
97
}
78
98
79
99
// Parse line extracts data from a single line
@@ -138,3 +158,122 @@ func parseLine(line string) (BookDetail, error) {
138
158
139
159
return book , nil
140
160
}
161
+
162
+ func ParseSearchV2 (reader io.Reader ) ([]BookDetail , []ParseError ) {
163
+ var books []BookDetail
164
+ var parseErrors []ParseError
165
+
166
+ scanner := bufio .NewScanner (reader )
167
+ for scanner .Scan () {
168
+ line := scanner .Text ()
169
+ if strings .HasPrefix (line , "!" ) {
170
+ dat , err := parseLineV2 (line )
171
+ if err != nil {
172
+ parseErrors = append (parseErrors , ParseError {Line : line , Error : err })
173
+ } else {
174
+ books = append (books , dat )
175
+ }
176
+ }
177
+ }
178
+
179
+ sort .Slice (books , func (i , j int ) bool { return books [i ].Server < books [j ].Server })
180
+
181
+ return books , parseErrors
182
+ }
183
+
184
+ func parseLineV2 (line string ) (BookDetail , error ) {
185
+ getServer := func (line string ) (string , error ) {
186
+ if line [0 ] != '!' {
187
+ return "" , errors .New ("result lines must start with '!'" )
188
+ }
189
+
190
+ firstSpace := strings .Index (line , " " )
191
+ if firstSpace == - 1 {
192
+ return "" , errors .New ("unable parse server name" )
193
+ }
194
+
195
+ return line [1 :firstSpace ], nil
196
+ }
197
+
198
+ getAuthor := func (line string ) (string , error ) {
199
+ firstSpace := strings .Index (line , " " )
200
+ dashChar := strings .Index (line , " - " )
201
+ if dashChar == - 1 {
202
+ return "" , errors .New ("unable to parse author" )
203
+ }
204
+ author := line [firstSpace + len (" " ) : dashChar ]
205
+
206
+ // Handles case with weird author characters %\w% ("%F77FE9FF1CCD% Michael Haag")
207
+ if strings .Contains (author , "%" ) {
208
+ split := strings .SplitAfterN (author , " " , 2 )
209
+ return split [1 ], nil
210
+ }
211
+
212
+ return author , nil
213
+ }
214
+
215
+ getTitle := func (line string ) (string , string , int ) {
216
+ title := ""
217
+ fileFormat := ""
218
+ endIndex := - 1
219
+ // Get the Title
220
+ for _ , ext := range fileTypes { //Loop through each possible file extension we've got on record
221
+ endTitle := strings .Index (line , "." + ext ) // check if it contains our extension
222
+ if endTitle == - 1 {
223
+ continue
224
+ }
225
+ fileFormat = ext
226
+ if ext == "rar" || ext == "zip" { // If the extension is .rar or .zip the actual format is contained in ()
227
+ for _ , ext2 := range fileTypes [:len (fileTypes )- 2 ] { // Range over the eBook formats (exclude archives)
228
+ if strings .Contains (strings .ToLower (line [:endTitle ]), ext2 ) {
229
+ fileFormat = ext2
230
+ }
231
+ }
232
+ }
233
+ startIndex := strings .Index (line , " - " )
234
+ title = line [startIndex + len (" - " ) : endTitle ]
235
+ endIndex = endTitle
236
+ }
237
+
238
+ return title , fileFormat , endIndex
239
+ }
240
+
241
+ getSize := func (line string ) (string , int ) {
242
+ const delimiter = " ::INFO:: "
243
+ infoIndex := strings .LastIndex (line , delimiter )
244
+
245
+ if infoIndex != - 1 {
246
+ // Handle cases when there is additional info after the file size (ex ::HASH:: )
247
+ parts := strings .Split (line [infoIndex + len (delimiter ):], " " )
248
+ return parts [0 ], infoIndex
249
+ }
250
+
251
+ return "N/A" , len (line )
252
+ }
253
+
254
+ server , err := getServer (line )
255
+ if err != nil {
256
+ return BookDetail {}, err
257
+ }
258
+
259
+ author , err := getAuthor (line )
260
+ if err != nil {
261
+ return BookDetail {}, err
262
+ }
263
+
264
+ title , format , titleIndex := getTitle (line )
265
+ if titleIndex == - 1 {
266
+ return BookDetail {}, errors .New ("unable to parse title" )
267
+ }
268
+
269
+ size , endIndex := getSize (line )
270
+
271
+ return BookDetail {
272
+ Server : server ,
273
+ Author : author ,
274
+ Title : title ,
275
+ Format : format ,
276
+ Size : size ,
277
+ Full : strings .TrimSpace (line [:endIndex ]),
278
+ }, nil
279
+ }
0 commit comments