package shakuhachi
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
A music collection manager
Install
dune-project
Dependency
Authors
Maintainers
Sources
0.3.2.tar.gz
sha256=c72807d303de137441b643fec1c7630c930ebc792b576783c7961b237c2114db
sha512=770f878bbdac8033d90e904ce8e9c84fc75331c7ff9b35e2204fafe3746f432f2664296dac91942a788b82a2da07478d91b76ed85cd880758d2def8a55144840
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 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(*****************************************************************************) (* *) (* 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/. *) (* *) (*****************************************************************************) type t = { album_id : Int64.t; artist_id : Int64.t; role : Role.t } module Column = struct let album_id = "album_id" let artist_id = "artist_id" let role = "role" end let table_name = "album_participants" let foreign_album_id = Column.album_id let foreign_artist_id = Column.artist_id let key_role = Column.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 columns statement offset = let album_id = Sqlite3.column_int64 statement offset in let artist_id = Sqlite3.column_int64 statement (offset + 1) in let role = Role.of_int (Sqlite3.column_int statement (offset + 2)) in ({ album_id; artist_id; role }, offset + 3) let remove database t = let ( let* ) = Util.bind_ok database in let { album_id; artist_id; role } = t in let statement = Printf.sprintf "DELETE FROM %s WHERE (%s = ?) AND (%s = ?) AND (%s = ?);" table_name Column.album_id Column.artist_id Column.role 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 -> 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 (%s, %s, %s) VALUES (?, ?, ?);" table_name Column.album_id Column.artist_id Column.role 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) let filter_map database f = let statement = Printf.sprintf "SELECT %s, %s, %s FROM %s;" Column.album_id Column.artist_id Column.role table_name in let statement = Sqlite3.prepare database statement in let queue = Queue.create () in let rc = Sqlite3.iter statement ~f:(fun _ -> let participant, _ = columns statement 0 in match f participant with None -> () | Some e -> Queue.add e queue ) in let values = queue |> Queue.to_seq |> List.of_seq in Util.bind_ok database rc @@ fun () -> Ok values let filter_map_with_album_id database album_id f = let ( let* ) = Util.bind_ok database in let statement = Printf.sprintf "SELECT %s, %s, %s FROM %s WHERE %s = ?;" Column.album_id Column.artist_id Column.role table_name Column.album_id in let statement = Sqlite3.prepare database statement in let* () = Sqlite3.bind_int64 statement 1 album_id in let queue = Queue.create () in let rc = Sqlite3.iter statement ~f:(fun _ -> let participant, _ = columns statement 0 in match f participant with None -> () | Some e -> Queue.add e queue ) in let values = queue |> Queue.to_seq |> List.of_seq in Util.bind_ok database rc @@ fun () -> Ok values (** [find ~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 ~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 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 _, 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 Albums.elements albums let find_from_metadata ~compilation database metadata = 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 album_name = SkhcMetadata.Metadata.album metadata in let artists = SkhcMetadata.Metadata.artists metadata @ SkhcMetadata.Metadata.album_artists metadata 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 _, 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 (( = ) artist_name) artists in let matched = Some qalbum.name = album_name && artists () in match matched with true -> Albums.add qalbum albums | false -> albums ) in Albums.elements albums let insert_multitple database (album : Album.t) (artists : (Artist.t * Role.t) list) = List.iter (fun ((artist : Artist.t), role) -> let t = { album_id = album.id; artist_id = artist.id; role } in match insert database t with Ok () -> () | Error _e -> () ) artists (** [find_or_insert ~compilation database artists album] tries to find an album in [database] with the name [album.name] and at least an artist with the name is in [artists] unless [compilation], in this case only the [album.name] is check to discriminate albums. If no album is found, [find_or_insert] will try to insert [album] to [database]. If [insert_on_found], [find_or_insert] will try to add [artists] to the already existing album. @before 0.3.2, no [insert_on_found] optional argument and the behavior of [find_or_insert] was equilatent to [insert_on_found = false]. *) let find_or_insert ?(insert_on_found = false) ~compilation database artists (album : Album.t) = let ( let* ) = Result.bind in let albums = find ~compilation database artists album in match albums with | [] -> let* album = Album.insert database album in let () = insert_multitple database album artists in Ok album | album :: _ -> let () = if insert_on_found then insert_multitple database album artists in Ok album let album_id t = t.album_id let artist_id t = t.artist_id let role t = t.role (* Test *) let equal : t -> t -> bool = ( = ) let compare : t -> t -> int = Stdlib.compare
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>