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
(*****************************************************************************)
(*                                                                           *)
(*  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
    )