Source file queryMusic.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
(*****************************************************************************)
(*                                                                           *)
(*  Copyright (C) 2026 Yves Ndiaye                                           *)
(*                                                                           *)
(* This Source Code Form is subject to the terms of the Mozilla Public       *)
(* License, v. 2.0. If a copy of the MPL was not distributed with this       *)
(* file, You can obtain one at https://mozilla.org/MPL/2.0/.                 *)
(*                                                                           *)
(*****************************************************************************)

module Metadata = SkhcMetadata.Metadata
module Ids = SkhcDb.Ids
module Roles = SkhcDb.Roles
include QueryUtil

let matches_node substrings (music_file, album, participants, properties) node =
  let metadata = SkhcMetadata.Metadata.init None [||] properties in
  let album_name = SkhcDb.Album.name album in
  let id = SkhcDb.MusicFile.id music_file in
  let path = SkhcDb.MusicFile.path music_file in
  let extension = Filename.extension path in
  let size = SkhcDb.MusicFile.size music_file in
  let play_count = SkhcDb.MusicFile.play_count music_file in
  let flag = SkhcDb.MusicFile.flag music_file in
  let rating = SkhcDb.MusicFile.rating music_file in
  let duration = SkhcDb.MusicFile.duration music_file in
  let has_cover = SkhcDb.MusicFile.has_cover music_file in
  let title = Metadata.title metadata in
  let sort_name = Metadata.title_sort metadata in
  let track_number = Metadata.track metadata in
  let disc_number = Metadata.disc metadata in
  let year = Metadata.year metadata in
  let genre = Metadata.genre metadata in

  match node with
  | Node.Substring s ->
      let fields = match_participants participants in
      let fields =
        Substrings.(
          fields
          |> add_field_if_some Title title
          |> add_field_if_some SortName sort_name
          |> add Album album_name
          |> add_field_if_some Genre genre
          |> add FileExtension extension
          |> add Path path
          |> filter (fun key _ -> List.mem key substrings)
          |> bindings |> List.map snd
        )
      in
      List.exists (matches ~insensible:true false s) fields
  | Field (FString s) -> (
      let regex = Field.String.regex s in
      let insensible = Field.String.insensible s in
      let field = Field.String.key s in
      let value = Field.String.value s in
      match field with
      | Title ->
          omatches ~insensible regex value title
      | SortName ->
          omatches ~insensible regex value sort_name
      | Artist ->
          Ids.Map.exists
            (fun _ (artist_name, roles) ->
              Roles.mem Artist roles
              && matches ~insensible regex value artist_name
            )
            participants
      | Album ->
          matches ~insensible regex value album_name
      | AlbumArtist ->
          Ids.Map.exists
            (fun _ (artist_name, roles) ->
              Roles.mem AlbumArtist roles
              && matches ~insensible regex value artist_name
            )
            participants
      | Composer ->
          Ids.Map.exists
            (fun _ (artist_name, roles) ->
              Roles.mem Composer roles
              && matches ~insensible regex value artist_name
            )
            participants
      | Genre ->
          omatches ~insensible regex value genre
      | Path ->
          matches ~insensible regex value path
      | FileExtension ->
          matches ~insensible regex value extension
      | Arbitrary key ->
          let key = String.uppercase_ascii key in
          let mvalues = Metadata.property_list key metadata in
          List.exists (matches ~insensible regex value) mvalues
    )
  | Field (FBool b) -> (
      let field = Field.Bool.key b in
      let b = Field.Bool.value b in
      match field with Art -> b = has_cover | Flag -> flag = b
    )
  | Field (FInt64 i64) -> (
      let field = Field.Int64.key i64 in
      let frange = Field.Int64.value i64 in
      match field with
      | Id ->
          Range.matches frange id
      | Size ->
          Range.matches frange (Int64.of_int size)
    )
  | Field (FInt i) -> (
      let field = Field.Int.key i in
      let frange = Field.Int.value i in
      match field with
      | Track ->
          track_number
          |> Option.map (Range.matches frange)
          |> Option.value ~default:false
      | Year ->
          year
          |> Option.map (Range.matches frange)
          |> Option.value ~default:false
      | Disc ->
          Option.fold ~none:false ~some:(Range.matches frange) disc_number
      | Plays ->
          Range.matches frange play_count
      | Length ->
          Range.matches frange duration
      | Rating ->
          rating
          |> Option.map (Range.matches frange)
          |> Option.value ~default:false
      | AlbumCount ->
          Range.matches frange 1
    )
  | Field (FFloat f) -> (
      let field = Field.Float.key f in
      let frange = Field.Float.value f in
      match field with
      | AvgDuration ->
          Range.matches frange (Float.of_int duration)
      | AvgRating ->
          rating
          |> Option.map (fun rating ->
              let rating = Float.of_int rating in
              Range.matches frange rating
          )
          |> Option.value ~default:false
      | AvgSize ->
          Range.matches frange (Int.to_float size)
    )

let matches_metadata bmetadata ex_metadata =
  let module M = SkhcMetadata.Metadata in
  let ostre ?(eq = Stdlib.( = )) f lhs rhs =
    match (f lhs, f rhs) with
    | None, None ->
        false
    | Some lhs, Some rhs ->
        eq lhs rhs
    | _ ->
        false
  in
  function
  | Field.FString s -> (
      match Field.String.key s with
      | Title ->
          ostre M.title bmetadata ex_metadata
      | SortName ->
          ostre M.title bmetadata ex_metadata
      | Artist ->
          ostre ~eq:M.Values.subset (M.property M.Keys.artist) bmetadata
            ex_metadata
      | Album ->
          ostre M.album bmetadata ex_metadata
      | AlbumArtist ->
          ostre ~eq:M.Values.subset
            (M.property M.Keys.album_artist)
            bmetadata ex_metadata
      | Composer ->
          ostre ~eq:M.Values.subset
            (M.property M.Keys.composer)
            bmetadata ex_metadata
      | Genre ->
          ostre ~eq:M.Values.subset (M.property M.Keys.artist) bmetadata
            ex_metadata
      | Path ->
          false
      | FileExtension ->
          ostre
            (M.property_min M.Keys.skhc_file_extension)
            bmetadata ex_metadata
      | Arbitrary s ->
          let s = String.uppercase_ascii s in
          ostre ~eq:M.Values.subset (M.property s) bmetadata ex_metadata
    )
  | Field.FBool b -> (
      match Field.Bool.key b with Art -> false | Flag -> false
    )
  | Field.FInt64 i -> (
      match Field.Int64.key i with
      | Id ->
          false
      | Size ->
          ostre (M.property_min M.Keys.skhc_file_size) bmetadata ex_metadata
    )
  | Field.FInt i -> (
      match Field.Int.key i with
      | Track ->
          ostre M.track bmetadata ex_metadata
      | Year ->
          ostre M.year bmetadata ex_metadata
      | Disc ->
          ostre M.disc bmetadata ex_metadata
      | Plays ->
          false
      | Length -> (
          let lhs = M.audioproperties bmetadata in
          let rhs = M.audioproperties ex_metadata in
          let f (a : Otaglibc.audioproperties) = a.length in
          match (lhs, rhs) with
          | Some lhs, Some rhs ->
              f lhs = f rhs
          | _ ->
              false
        )
      | Rating ->
          false
      | AlbumCount ->
          true
    )
  | Field.FFloat f -> (
      match Field.Float.key f with AvgDuration | AvgRating | AvgSize -> false
    )

let compare_key (reversed, key) lhs rhs =
  let module Strings = Set.Make (String) in
  let module M = SkhcMetadata.Metadata in
  let set_roles r map =
    SkhcDb.Ids.Map.fold
      (fun _ (name, roles) names ->
        if SkhcDb.Roles.mem r roles then
          Strings.add name names
        else
          names
      )
      map Strings.empty
  in
  let (lm, lalbum, lroles, lprops), (rm, ralbum, rroles, rprops) =
    if reversed then
      (rhs, lhs)
    else
      (lhs, rhs)
  in
  let lprops =
    SkhcMetadata.Metadata.of_properties
      ~audioproperties:(SkhcDb.MusicFile.audioproperties lm)
      lprops
  in
  let rprops =
    SkhcMetadata.Metadata.of_properties
      ~audioproperties:(SkhcDb.MusicFile.audioproperties rm)
      rprops
  in
  let cmp c f lhs rhs = c (f lhs) (f rhs) in
  let ocmp c f lhs rhs = Option.compare c (f lhs) (f rhs) in
  match key with
  | Field.FString s -> (
      let key = Field.String.key s in
      match key with
      | Title ->
          ocmp String.compare M.title lprops rprops
      | Album ->
          cmp String.compare SkhcDb.Album.name lalbum ralbum
      | SortName ->
          ocmp String.compare M.title_sort lprops rprops
      | Artist ->
          let lhs = set_roles Artist lroles in
          let rhs = set_roles Artist rroles in
          Strings.compare lhs rhs
      | AlbumArtist ->
          let lhs = set_roles AlbumArtist lroles in
          let rhs = set_roles AlbumArtist rroles in
          Strings.compare lhs rhs
      | Composer ->
          let lhs = set_roles Composer lroles in
          let rhs = set_roles Composer rroles in
          Strings.compare lhs rhs
      | Genre ->
          ocmp String.compare M.genre lprops rprops
      | Path ->
          cmp String.compare SkhcDb.MusicFile.path lm rm
      | FileExtension ->
          cmp String.compare
            (fun s -> Filename.extension @@ SkhcDb.MusicFile.path s)
            lm rm
      | Arbitrary key ->
          cmp (List.compare String.compare) (M.property_list key) lprops rprops
    )
  | FBool b -> (
      let key = Field.Bool.key b in
      match key with
      | Art ->
          cmp Bool.compare SkhcDb.MusicFile.has_cover lm rm
      | Flag ->
          cmp Bool.compare SkhcDb.MusicFile.flag lm rm
    )
  | FInt64 i64 -> (
      let key = Field.Int64.key i64 in
      match key with
      | Id ->
          cmp Int64.compare SkhcDb.MusicFile.id lm rm
      | Size ->
          cmp Int.compare SkhcDb.MusicFile.size lm rm
    )
  | FInt i -> (
      let field = Field.Int.key i in
      match field with
      | Year ->
          ocmp Int.compare M.year lprops rprops
      | Plays ->
          (* Even on 32 bit system, 
             the max int is 2^{30} - 1 which is more the 1 billions, 
             so we have some before it overflows
          *)
          cmp Int.compare SkhcDb.MusicFile.play_count lm rm
      | Length ->
          cmp Int.compare SkhcDb.MusicFile.duration lm rm
      | Rating ->
          ocmp Int.compare SkhcDb.MusicFile.rating lm rm
      | AlbumCount ->
          0
      | Track ->
          ocmp Int.compare M.track lprops rprops
      | Disc ->
          ocmp Int.compare M.disc lprops rprops
    )
  | FFloat f -> (
      let key = Field.Float.key f in
      match key with
      | AvgDuration ->
          cmp Int.compare SkhcDb.MusicFile.duration lm rm
      | AvgRating ->
          ocmp Int.compare SkhcDb.MusicFile.rating lm rm
      | AvgSize ->
          cmp Int.compare SkhcDb.MusicFile.size lm rm
    )