package shakuhachi

  1. Overview
  2. Docs
A music collection manager

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.1.0.tar.gz
sha256=bb7f91cc44c09f922d2a614e9e01fd0d2976840bacf839a8b99de63c6fc5b3b8
sha512=f02827db4228b017bb2ad7d64cb1c0667831ec2341e6f759266ec834e40627c2affeace143c6d5e769b3acce63f889b3cbd1a9ca4768091840e8fce1d4750f86

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

Source file albumParticipant.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
(**********************************************************************************************)
(*                                                                                            *)
(* This file is part of shakuhachi: a music collection manager                                *)
(* Copyright (C) 2025 Yves Ndiaye                                                             *)
(*                                                                                            *)
(* shakuhachi is free software: you can redistribute it and/or modify it under the terms      *)
(* of the GNU General Public License as published by the Free Software Foundation,            *)
(* either version 3 of the License, or (at your option) any later version.                    *)
(*                                                                                            *)
(* shakuhachi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;    *)
(* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR           *)
(* PURPOSE.  See the GNU General Public License for more details.                             *)
(* You should have received a copy of the GNU General Public License along with shakuhachi.   *)
(* If not, see <http://www.gnu.org/licenses/>.                                                *)
(*                                                                                            *)
(**********************************************************************************************)

type t = { album_id : Int64.t; artist_id : Int64.t; role : Role.t }

let table_name = "album_participants"
let foreign_album_id = "album_id"
let foreign_artist_id = "artist_id"
let key_role = "role"
let p_album_id = Printf.sprintf "%s.%s" table_name foreign_album_id
let p_artist_id = Printf.sprintf "%s.%s" table_name foreign_artist_id
let p_role = Printf.sprintf "%s.%s" table_name key_role

let sql_table =
  Printf.sprintf
    {|CREATE TABLE IF NOT EXISTS %s (
      %s INTEGER NOT NULL,
      %s INTEGER NOT NULL,
      %s INTEGER,
      UNIQUE (%s, %s, %s),
      FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE CASCADE ON DELETE CASCADE
      FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE CASCADE ON DELETE CASCADE
  );|}
    table_name foreign_album_id foreign_artist_id key_role foreign_album_id
    foreign_artist_id key_role foreign_album_id Album.table_name
    Album.primary_key foreign_artist_id Artist.table_name Artist.primary_key

let init ~album_id ~artist_id role = { album_id; artist_id; role }

let update_artist database old new' =
  let ( let* ) rc f =
    match rc with
    | Sqlite3.Rc.OK | Sqlite3.Rc.DONE ->
        f ()
    | rc ->
        let message = Sqlite3.errmsg database in
        Error (rc, message)
  in
  let statement =
    Printf.sprintf "UPDATE %s SET %s = ?1 WHERE %s = ?2;" table_name
      foreign_artist_id foreign_artist_id
  in
  let statement = Sqlite3.prepare database statement in
  let* () = Sqlite3.bind_int64 statement 1 new' in
  let* () = Sqlite3.bind_int64 statement 2 old in
  match Sqlite3.step statement with
  | Sqlite3.Rc.OK | Sqlite3.Rc.DONE | Sqlite3.Rc.CONSTRAINT ->
      let* () = Sqlite3.finalize statement in
      Ok ()
  | rc ->
      let message = Sqlite3.errmsg database in
      Error (rc, message)

let insert database t =
  let { album_id; artist_id; role } = t in
  let statement' =
    Printf.sprintf
      "INSERT INTO %s (album_id, artist_id, role) VALUES (?, ?, ?);" table_name
  in
  let ( let* ) rc f =
    match rc with
    | Sqlite3.Rc.OK | Sqlite3.Rc.DONE ->
        f ()
    | rc ->
        let message = Sqlite3.errmsg database in
        Error (rc, message)
  in
  let statement = Sqlite3.prepare database statement' in
  let* () = Sqlite3.bind_int64 statement 1 album_id in
  let* () = Sqlite3.bind_int64 statement 2 artist_id in
  let* () = Sqlite3.bind_int statement 3 (Role.to_int role) in
  match Sqlite3.step statement with
  | Sqlite3.Rc.OK | Sqlite3.Rc.DONE | Sqlite3.Rc.CONSTRAINT ->
      let* () = Sqlite3.finalize statement in
      Ok ()
  | rc ->
      let message = Sqlite3.errmsg database in
      Error (rc, message)

(** [find_or_insert ~compilation database artists album] tries to find an album
    in [database] with the name [album.name] and at least an arist with the name
    is in [artists] unless [compilation], in this case only the [album.name] is
    check to discriminate albums *)
let find_or_insert ~compilation database (artists : (Artist.t * Role.t) list)
    (album : Album.t) =
  let module Albums = Set.Make (struct
    type t = Album.t

    let compare (lhs : Album.t) (rhs : Album.t) = compare lhs.id rhs.id
  end) in
  let ( let* ) = Result.bind in
  let query =
    Printf.sprintf
      "SELECT %s.*, %s FROM %s INNER JOIN %s ON %s = %s INNER JOIN %s ON %s = \
       %s;"
      Album.table_name Artist.p_name table_name Album.table_name p_album_id
      Album.p_id Artist.table_name p_artist_id Artist.p_id
  in
  let statement = Sqlite3.prepare database query in

  let _rc, albums =
    Sqlite3.fold statement ~init:Albums.empty ~f:(fun albums _ ->
        let qalbum, offset = Album.columns statement 0 in
        let artist_name = Sqlite3.column_text statement offset in
        let artists () =
          compilation
          || List.exists
               (fun ((artist : Artist.t), _) -> artist.name = artist_name)
               artists
        in
        let matched = qalbum.name = album.name && artists () in
        match matched with true -> Albums.add qalbum albums | false -> albums
    )
  in
  match Albums.min_elt_opt albums with
  | None ->
      let* qalbum = Album.insert database album in
      let () =
        List.iter
          (fun ((artist : Artist.t), role) ->
            let t = { album_id = qalbum.id; artist_id = artist.id; role } in
            match insert database t with Ok () -> () | Error _e -> ()
          )
          artists
      in
      Ok qalbum
  | Some qalbum ->
      Ok qalbum

(* Test *)
let equal : t -> t -> bool = ( = )
let compare : t -> t -> int = Stdlib.compare