package shakuhachi

  1. Overview
  2. Docs
A music collection manager

Install

dune-project
 Dependency

Authors

Maintainers

Sources

shakuhachi-0.4.1.tar.gz
sha256=4a6e1b929cc60f9239f73bfc2200d1b5a884e45dc81ad1b0f268ed4eba8059e9
sha512=f5419b47be10ece8e257d98355aeeaa893a493c00589b8ce622706f9454bb1762be1c63d5d84844b284cac417a3eafb9f9064f902be9c4a862dc9688baaab38d

doc/src/shakuhachi.database/util.ml.html

Source file util.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
(*****************************************************************************)
(*                                                                           *)
(*  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 Parser = struct
  open Angstrom

  let is_digit = function '0' .. '9' -> true | _ -> false
  let number = map ~f:int_of_string (take_while1 is_digit)
  let number64 = map ~f:Int64.of_string (take_while1 is_digit)
  let string_all k = map (take_while (Fun.const true)) ~f:k
end

module Stats = struct
  let init ~init' ~music_count ~duration ~play_count ~size ~avg_rating =
    let avg what =
      if music_count = 0 then
        Float.zero
      else
        Int.to_float what /. Int.to_float music_count
    in
    let avg_duration = avg duration in
    let avg_size = Int64.to_float size /. Int.to_float music_count in
    init' ~music_count ~duration ~play_count ~size ~avg_duration ~avg_rating
      ~avg_size

  let column_query ~id ~play_count ~duration ~size ~rating ~played_at =
    let played_at =
      Option.fold ~none:String.empty
        ~some:(fun played_at -> Printf.sprintf ",max(%s)" played_at)
        played_at
    in
    Printf.sprintf
      {|
    COUNT(DISTINCT %s)
    ,sum(%s)
	,sum(%s)
	,sum(%s)
	,avg(%s)
	,avg(%s)
	,avg(%s)
	%s
  |}
      id play_count duration size duration rating size played_at
end

let bind_ok database rc f =
  match rc with
  | Sqlite3.Rc.OK | Sqlite3.Rc.DONE ->
      f ()
  | rc ->
      let message = Sqlite3.errmsg database in
      Error (rc, message)

let bind_constraint_ok database rc f =
  match rc with
  | Sqlite3.Rc.OK | Sqlite3.Rc.DONE | Sqlite3.Rc.CONSTRAINT ->
      f ()
  | rc ->
      let message = Sqlite3.errmsg database in
      Error (rc, message)

let bind_option fbind statement n value =
  match value with
  | None ->
      Sqlite3.Rc.OK
  | Some value ->
      fbind statement n value

let sql_participants_format ~ss ~id ~role ~name =
  Printf.sprintf "group_concat(%s || ':' || %s || '_' || %s, '%c')" id role name
    ss

(*
    ss like split string
    sv like split value
*)
let sql_participants ~ss s =
  let participant_parse s =
    let open Angstrom in
    let parser =
      map2 ~f:(fun id d -> (id, d)) (Parser.number64 <* char ':')
      @@ choice
           [
             map2 Parser.number
               (char '_' *> Parser.string_all Fun.id)
               ~f:(fun role name ->
                 let role = Role.of_int role in
                 (name, role)
               );
             map (Parser.string_all Fun.id) ~f:(fun s -> (s, Role.Unknown));
           ]
    in
    match parse_string ~consume:All parser s with
    | Ok e ->
        Some e
    | Error _ ->
        None
  in
  s |> String.split_on_char ss
  |> List.fold_left
       (fun ids s ->
         match participant_parse s with
         | None ->
             ids
         | Some (id, (name, role)) ->
             Ids.Map.update id
               (function
                 | None ->
                     Some (name, Roles.singleton role)
                 | Some (_, roles) ->
                     Some (name, Roles.add role roles)
                 )
               ids
       )
       Ids.Map.empty

let sql_properties ~ss ~sv s =
  s |> String.split_on_char ss
  |> List.fold_left
       (fun properties s ->
         let key_vals = String.split_on_char sv s in
         match key_vals with
         | [] | _ :: [] ->
             properties
         | key :: values ->
             let values = String.concat " " values in
             Metadata.Properties.update key
               (function
                 | None ->
                     Some (Metadata.Values.singleton values)
                 | Some v ->
                     Some (Metadata.Values.add values v)
                 )
               properties
       )
       Metadata.Properties.empty