A tool to sync music with your favorite devices
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

*: first commit

Gee Sawra 177ed077

+5497
+3
.gitignore
··· 1 + /target 2 + *.rhai 3 + *.db
+12
.sqlx/query-0c0a2c6966834584d936f6f7afaa1c2e205653ffe0796c0c1b197fa07ba4b319.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n INSERT OR REPLACE INTO tracks (\n track_id,\n title,\n artist,\n album,\n number,\n file_path,\n disc_number,\n disc_total,\n file_state,\n extension\n ) VALUES (\n ?1,\n ?2,\n ?3,\n ?4,\n ?5,\n ?6,\n ?7,\n ?8,\n ?9,\n ?10\n );\n ", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Right": 10 8 + }, 9 + "nullable": [] 10 + }, 11 + "hash": "0c0a2c6966834584d936f6f7afaa1c2e205653ffe0796c0c1b197fa07ba4b319" 12 + }
+20
.sqlx/query-162ad7afaeca68c57672b2d2589e456b79d3534d3cfe8cbd8289fad927a98960.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n select filter from state;\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "filter", 8 + "ordinal": 0, 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Right": 0 14 + }, 15 + "nullable": [ 16 + true 17 + ] 18 + }, 19 + "hash": "162ad7afaeca68c57672b2d2589e456b79d3534d3cfe8cbd8289fad927a98960" 20 + }
+12
.sqlx/query-186c1e745f338479c3b44204587723b32b6f2f9b1d78d831e38f52cc997ff868.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n INSERT INTO state (\n version,\n is_external\n ) VALUES (\n ?1,\n ?2\n );\n ", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Right": 2 8 + }, 9 + "nullable": [] 10 + }, 11 + "hash": "186c1e745f338479c3b44204587723b32b6f2f9b1d78d831e38f52cc997ff868" 12 + }
+20
.sqlx/query-19d2eae743ae7b1d2cde049253f7dfe0314a7edaac73d4ef13fe7ae807faa32d.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n SELECT version FROM state GROUP BY version;\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "version", 8 + "ordinal": 0, 9 + "type_info": "Int64" 10 + } 11 + ], 12 + "parameters": { 13 + "Right": 0 14 + }, 15 + "nullable": [ 16 + false 17 + ] 18 + }, 19 + "hash": "19d2eae743ae7b1d2cde049253f7dfe0314a7edaac73d4ef13fe7ae807faa32d" 20 + }
+20
.sqlx/query-24ce08f39db9c0c7a13bd6773dc0ed3d090177118d6924ae88abc488fbd38704.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n SELECT is_external FROM state GROUP BY version;\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "is_external", 8 + "ordinal": 0, 9 + "type_info": "Bool" 10 + } 11 + ], 12 + "parameters": { 13 + "Right": 0 14 + }, 15 + "nullable": [ 16 + true 17 + ] 18 + }, 19 + "hash": "24ce08f39db9c0c7a13bd6773dc0ed3d090177118d6924ae88abc488fbd38704" 20 + }
+12
.sqlx/query-3b3f431e874c180c01f178993da659b9a9215bbfcb555a47698720b856543681.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n DELETE FROM tracks WHERE id = ?1;\n ", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Right": 1 8 + }, 9 + "nullable": [] 10 + }, 11 + "hash": "3b3f431e874c180c01f178993da659b9a9215bbfcb555a47698720b856543681" 12 + }
+20
.sqlx/query-4269cff6b3f024043865e48148cf4ca56860bb09032850e0cf18e9dcb306c9aa.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "SELECT * FROM directories;", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "directory", 8 + "ordinal": 0, 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Right": 0 14 + }, 15 + "nullable": [ 16 + false 17 + ] 18 + }, 19 + "hash": "4269cff6b3f024043865e48148cf4ca56860bb09032850e0cf18e9dcb306c9aa" 20 + }
+20
.sqlx/query-5f54bdccb58cf9524504f207dd522954d9fc632d6f88a5b68e242a33bd6b48bc.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n SELECT id FROM tracks WHERE file_path = ?1;\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "id", 8 + "ordinal": 0, 9 + "type_info": "Int64" 10 + } 11 + ], 12 + "parameters": { 13 + "Right": 1 14 + }, 15 + "nullable": [ 16 + false 17 + ] 18 + }, 19 + "hash": "5f54bdccb58cf9524504f207dd522954d9fc632d6f88a5b68e242a33bd6b48bc" 20 + }
+12
.sqlx/query-678a980b3a8e4c7fb23225b9f14e5b752bd67c1169618c36bc67785b3fc2be06.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n update state set filter = ?1;", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Right": 1 8 + }, 9 + "nullable": [] 10 + }, 11 + "hash": "678a980b3a8e4c7fb23225b9f14e5b752bd67c1169618c36bc67785b3fc2be06" 12 + }
+20
.sqlx/query-716eb6fc78c3f4d43e1b55fa003753d7e25079e08d046f00e89f3d0a0434f0df.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "SELECT file_path FROM tracks where file_path LIKE ?1", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "file_path", 8 + "ordinal": 0, 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Right": 1 14 + }, 15 + "nullable": [ 16 + false 17 + ] 18 + }, 19 + "hash": "716eb6fc78c3f4d43e1b55fa003753d7e25079e08d046f00e89f3d0a0434f0df" 20 + }
+80
.sqlx/query-86245ea0c3119d94e08fe0404482b4f8de3241bc067136c0181ff4e0aa2bcc67.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n SELECT * FROM tracks WHERE file_state = ?1;\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "id", 8 + "ordinal": 0, 9 + "type_info": "Int64" 10 + }, 11 + { 12 + "name": "track_id", 13 + "ordinal": 1, 14 + "type_info": "Text" 15 + }, 16 + { 17 + "name": "title", 18 + "ordinal": 2, 19 + "type_info": "Text" 20 + }, 21 + { 22 + "name": "artist", 23 + "ordinal": 3, 24 + "type_info": "Text" 25 + }, 26 + { 27 + "name": "album", 28 + "ordinal": 4, 29 + "type_info": "Text" 30 + }, 31 + { 32 + "name": "number", 33 + "ordinal": 5, 34 + "type_info": "Int64" 35 + }, 36 + { 37 + "name": "disc_number", 38 + "ordinal": 6, 39 + "type_info": "Int64" 40 + }, 41 + { 42 + "name": "disc_total", 43 + "ordinal": 7, 44 + "type_info": "Int64" 45 + }, 46 + { 47 + "name": "file_state", 48 + "ordinal": 8, 49 + "type_info": "Int64" 50 + }, 51 + { 52 + "name": "file_path", 53 + "ordinal": 9, 54 + "type_info": "Text" 55 + }, 56 + { 57 + "name": "extension", 58 + "ordinal": 10, 59 + "type_info": "Text" 60 + } 61 + ], 62 + "parameters": { 63 + "Right": 1 64 + }, 65 + "nullable": [ 66 + false, 67 + false, 68 + false, 69 + false, 70 + false, 71 + false, 72 + false, 73 + false, 74 + false, 75 + false, 76 + false 77 + ] 78 + }, 79 + "hash": "86245ea0c3119d94e08fe0404482b4f8de3241bc067136c0181ff4e0aa2bcc67" 80 + }
+80
.sqlx/query-90d659e4ebb83559ac94ba4618a065c5d9966e99d5c318c5b8f9dcaa670ae5b2.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n select * from tracks where artist = ?2 and album = ?1 group by extension;\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "id", 8 + "ordinal": 0, 9 + "type_info": "Int64" 10 + }, 11 + { 12 + "name": "track_id", 13 + "ordinal": 1, 14 + "type_info": "Text" 15 + }, 16 + { 17 + "name": "title", 18 + "ordinal": 2, 19 + "type_info": "Text" 20 + }, 21 + { 22 + "name": "artist", 23 + "ordinal": 3, 24 + "type_info": "Text" 25 + }, 26 + { 27 + "name": "album", 28 + "ordinal": 4, 29 + "type_info": "Text" 30 + }, 31 + { 32 + "name": "number", 33 + "ordinal": 5, 34 + "type_info": "Int64" 35 + }, 36 + { 37 + "name": "disc_number", 38 + "ordinal": 6, 39 + "type_info": "Int64" 40 + }, 41 + { 42 + "name": "disc_total", 43 + "ordinal": 7, 44 + "type_info": "Int64" 45 + }, 46 + { 47 + "name": "file_state", 48 + "ordinal": 8, 49 + "type_info": "Int64" 50 + }, 51 + { 52 + "name": "file_path", 53 + "ordinal": 9, 54 + "type_info": "Text" 55 + }, 56 + { 57 + "name": "extension", 58 + "ordinal": 10, 59 + "type_info": "Text" 60 + } 61 + ], 62 + "parameters": { 63 + "Right": 2 64 + }, 65 + "nullable": [ 66 + false, 67 + false, 68 + false, 69 + false, 70 + false, 71 + false, 72 + false, 73 + false, 74 + false, 75 + false, 76 + false 77 + ] 78 + }, 79 + "hash": "90d659e4ebb83559ac94ba4618a065c5d9966e99d5c318c5b8f9dcaa670ae5b2" 80 + }
+12
.sqlx/query-9bf2fda97d829ad09e463504c964619c055032255c4b8c1919c83cba24cd2739.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "INSERT OR REPLACE INTO directories (directory) VALUES (?1);", 4 + "describe": { 5 + "columns": [], 6 + "parameters": { 7 + "Right": 1 8 + }, 9 + "nullable": [] 10 + }, 11 + "hash": "9bf2fda97d829ad09e463504c964619c055032255c4b8c1919c83cba24cd2739" 12 + }
+32
.sqlx/query-b621def2ebaef4131a5593bbc5d5a38948d6d66c3f3729a44e37061f3c443fbd.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n SELECT artist, title, count(*) as count FROM albums\n GROUP BY artist, title\n HAVING count > 1;\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "artist", 8 + "ordinal": 0, 9 + "type_info": "Text" 10 + }, 11 + { 12 + "name": "title", 13 + "ordinal": 1, 14 + "type_info": "Text" 15 + }, 16 + { 17 + "name": "count", 18 + "ordinal": 2, 19 + "type_info": "Int64" 20 + } 21 + ], 22 + "parameters": { 23 + "Right": 0 24 + }, 25 + "nullable": [ 26 + false, 27 + false, 28 + false 29 + ] 30 + }, 31 + "hash": "b621def2ebaef4131a5593bbc5d5a38948d6d66c3f3729a44e37061f3c443fbd" 32 + }
+32
.sqlx/query-bc56cdf6e63bf49e2d59a1dd65d769f19ef009ce6caa009e761069d5858252a3.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n SELECT * FROM albums;\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "title", 8 + "ordinal": 0, 9 + "type_info": "Text" 10 + }, 11 + { 12 + "name": "artist", 13 + "ordinal": 1, 14 + "type_info": "Text" 15 + }, 16 + { 17 + "name": "format", 18 + "ordinal": 2, 19 + "type_info": "Text" 20 + } 21 + ], 22 + "parameters": { 23 + "Right": 0 24 + }, 25 + "nullable": [ 26 + false, 27 + false, 28 + false 29 + ] 30 + }, 31 + "hash": "bc56cdf6e63bf49e2d59a1dd65d769f19ef009ce6caa009e761069d5858252a3" 32 + }
+20
.sqlx/query-f026bae33f7999c12b01bae1175d9cff43be9124660d427830305999c5c7ee2c.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "\n SELECT track_id FROM tracks WHERE file_state = ?1;\n ", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "track_id", 8 + "ordinal": 0, 9 + "type_info": "Text" 10 + } 11 + ], 12 + "parameters": { 13 + "Right": 1 14 + }, 15 + "nullable": [ 16 + false 17 + ] 18 + }, 19 + "hash": "f026bae33f7999c12b01bae1175d9cff43be9124660d427830305999c5c7ee2c" 20 + }
+80
.sqlx/query-f1349b72ca08058d2503c7348ee66ef638dfc1c3ce09cfd2167c01237ed25840.json
··· 1 + { 2 + "db_name": "SQLite", 3 + "query": "SELECT * from tracks;", 4 + "describe": { 5 + "columns": [ 6 + { 7 + "name": "id", 8 + "ordinal": 0, 9 + "type_info": "Int64" 10 + }, 11 + { 12 + "name": "track_id", 13 + "ordinal": 1, 14 + "type_info": "Text" 15 + }, 16 + { 17 + "name": "title", 18 + "ordinal": 2, 19 + "type_info": "Text" 20 + }, 21 + { 22 + "name": "artist", 23 + "ordinal": 3, 24 + "type_info": "Text" 25 + }, 26 + { 27 + "name": "album", 28 + "ordinal": 4, 29 + "type_info": "Text" 30 + }, 31 + { 32 + "name": "number", 33 + "ordinal": 5, 34 + "type_info": "Int64" 35 + }, 36 + { 37 + "name": "disc_number", 38 + "ordinal": 6, 39 + "type_info": "Int64" 40 + }, 41 + { 42 + "name": "disc_total", 43 + "ordinal": 7, 44 + "type_info": "Int64" 45 + }, 46 + { 47 + "name": "file_state", 48 + "ordinal": 8, 49 + "type_info": "Int64" 50 + }, 51 + { 52 + "name": "file_path", 53 + "ordinal": 9, 54 + "type_info": "Text" 55 + }, 56 + { 57 + "name": "extension", 58 + "ordinal": 10, 59 + "type_info": "Text" 60 + } 61 + ], 62 + "parameters": { 63 + "Right": 0 64 + }, 65 + "nullable": [ 66 + false, 67 + false, 68 + false, 69 + false, 70 + false, 71 + false, 72 + false, 73 + false, 74 + false, 75 + false, 76 + false 77 + ] 78 + }, 79 + "hash": "f1349b72ca08058d2503c7348ee66ef638dfc1c3ce09cfd2167c01237ed25840" 80 + }
+2899
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 3 4 + 5 + [[package]] 6 + name = "addr2line" 7 + version = "0.22.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 + dependencies = [ 11 + "gimli", 12 + ] 13 + 14 + [[package]] 15 + name = "adler" 16 + version = "1.0.2" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 + 20 + [[package]] 21 + name = "ahash" 22 + version = "0.8.11" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 25 + dependencies = [ 26 + "cfg-if", 27 + "const-random", 28 + "getrandom", 29 + "once_cell", 30 + "version_check", 31 + "zerocopy", 32 + ] 33 + 34 + [[package]] 35 + name = "aho-corasick" 36 + version = "1.1.3" 37 + source = "registry+https://github.com/rust-lang/crates.io-index" 38 + checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 39 + dependencies = [ 40 + "memchr", 41 + ] 42 + 43 + [[package]] 44 + name = "allocator-api2" 45 + version = "0.2.18" 46 + source = "registry+https://github.com/rust-lang/crates.io-index" 47 + checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 48 + 49 + [[package]] 50 + name = "anstream" 51 + version = "0.6.14" 52 + source = "registry+https://github.com/rust-lang/crates.io-index" 53 + checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 54 + dependencies = [ 55 + "anstyle", 56 + "anstyle-parse", 57 + "anstyle-query", 58 + "anstyle-wincon", 59 + "colorchoice", 60 + "is_terminal_polyfill", 61 + "utf8parse", 62 + ] 63 + 64 + [[package]] 65 + name = "anstyle" 66 + version = "1.0.7" 67 + source = "registry+https://github.com/rust-lang/crates.io-index" 68 + checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 69 + 70 + [[package]] 71 + name = "anstyle-parse" 72 + version = "0.2.4" 73 + source = "registry+https://github.com/rust-lang/crates.io-index" 74 + checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 75 + dependencies = [ 76 + "utf8parse", 77 + ] 78 + 79 + [[package]] 80 + name = "anstyle-query" 81 + version = "1.0.3" 82 + source = "registry+https://github.com/rust-lang/crates.io-index" 83 + checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" 84 + dependencies = [ 85 + "windows-sys 0.52.0", 86 + ] 87 + 88 + [[package]] 89 + name = "anstyle-wincon" 90 + version = "3.0.3" 91 + source = "registry+https://github.com/rust-lang/crates.io-index" 92 + checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 93 + dependencies = [ 94 + "anstyle", 95 + "windows-sys 0.52.0", 96 + ] 97 + 98 + [[package]] 99 + name = "anyhow" 100 + version = "1.0.86" 101 + source = "registry+https://github.com/rust-lang/crates.io-index" 102 + checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" 103 + 104 + [[package]] 105 + name = "async-attributes" 106 + version = "1.1.2" 107 + source = "registry+https://github.com/rust-lang/crates.io-index" 108 + checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" 109 + dependencies = [ 110 + "quote", 111 + "syn 1.0.109", 112 + ] 113 + 114 + [[package]] 115 + name = "async-channel" 116 + version = "1.9.0" 117 + source = "registry+https://github.com/rust-lang/crates.io-index" 118 + checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" 119 + dependencies = [ 120 + "concurrent-queue", 121 + "event-listener 2.5.3", 122 + "futures-core", 123 + ] 124 + 125 + [[package]] 126 + name = "async-channel" 127 + version = "2.3.1" 128 + source = "registry+https://github.com/rust-lang/crates.io-index" 129 + checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" 130 + dependencies = [ 131 + "concurrent-queue", 132 + "event-listener-strategy 0.5.2", 133 + "futures-core", 134 + "pin-project-lite", 135 + ] 136 + 137 + [[package]] 138 + name = "async-executor" 139 + version = "1.12.0" 140 + source = "registry+https://github.com/rust-lang/crates.io-index" 141 + checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0" 142 + dependencies = [ 143 + "async-task", 144 + "concurrent-queue", 145 + "fastrand 2.1.0", 146 + "futures-lite 2.3.0", 147 + "slab", 148 + ] 149 + 150 + [[package]] 151 + name = "async-fs" 152 + version = "2.1.2" 153 + source = "registry+https://github.com/rust-lang/crates.io-index" 154 + checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" 155 + dependencies = [ 156 + "async-lock 3.3.0", 157 + "blocking", 158 + "futures-lite 2.3.0", 159 + ] 160 + 161 + [[package]] 162 + name = "async-global-executor" 163 + version = "2.4.1" 164 + source = "registry+https://github.com/rust-lang/crates.io-index" 165 + checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" 166 + dependencies = [ 167 + "async-channel 2.3.1", 168 + "async-executor", 169 + "async-io 2.3.2", 170 + "async-lock 3.3.0", 171 + "blocking", 172 + "futures-lite 2.3.0", 173 + "once_cell", 174 + ] 175 + 176 + [[package]] 177 + name = "async-io" 178 + version = "1.13.0" 179 + source = "registry+https://github.com/rust-lang/crates.io-index" 180 + checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" 181 + dependencies = [ 182 + "async-lock 2.8.0", 183 + "autocfg", 184 + "cfg-if", 185 + "concurrent-queue", 186 + "futures-lite 1.13.0", 187 + "log", 188 + "parking", 189 + "polling 2.8.0", 190 + "rustix 0.37.27", 191 + "slab", 192 + "socket2", 193 + "waker-fn", 194 + ] 195 + 196 + [[package]] 197 + name = "async-io" 198 + version = "2.3.2" 199 + source = "registry+https://github.com/rust-lang/crates.io-index" 200 + checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" 201 + dependencies = [ 202 + "async-lock 3.3.0", 203 + "cfg-if", 204 + "concurrent-queue", 205 + "futures-io", 206 + "futures-lite 2.3.0", 207 + "parking", 208 + "polling 3.7.0", 209 + "rustix 0.38.34", 210 + "slab", 211 + "tracing", 212 + "windows-sys 0.52.0", 213 + ] 214 + 215 + [[package]] 216 + name = "async-lock" 217 + version = "2.8.0" 218 + source = "registry+https://github.com/rust-lang/crates.io-index" 219 + checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" 220 + dependencies = [ 221 + "event-listener 2.5.3", 222 + ] 223 + 224 + [[package]] 225 + name = "async-lock" 226 + version = "3.3.0" 227 + source = "registry+https://github.com/rust-lang/crates.io-index" 228 + checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" 229 + dependencies = [ 230 + "event-listener 4.0.3", 231 + "event-listener-strategy 0.4.0", 232 + "pin-project-lite", 233 + ] 234 + 235 + [[package]] 236 + name = "async-net" 237 + version = "2.0.0" 238 + source = "registry+https://github.com/rust-lang/crates.io-index" 239 + checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" 240 + dependencies = [ 241 + "async-io 2.3.2", 242 + "blocking", 243 + "futures-lite 2.3.0", 244 + ] 245 + 246 + [[package]] 247 + name = "async-process" 248 + version = "2.2.3" 249 + source = "registry+https://github.com/rust-lang/crates.io-index" 250 + checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a" 251 + dependencies = [ 252 + "async-channel 2.3.1", 253 + "async-io 2.3.2", 254 + "async-lock 3.3.0", 255 + "async-signal", 256 + "async-task", 257 + "blocking", 258 + "cfg-if", 259 + "event-listener 5.3.1", 260 + "futures-lite 2.3.0", 261 + "rustix 0.38.34", 262 + "tracing", 263 + "windows-sys 0.52.0", 264 + ] 265 + 266 + [[package]] 267 + name = "async-signal" 268 + version = "0.2.8" 269 + source = "registry+https://github.com/rust-lang/crates.io-index" 270 + checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" 271 + dependencies = [ 272 + "async-io 2.3.2", 273 + "async-lock 3.3.0", 274 + "atomic-waker", 275 + "cfg-if", 276 + "futures-core", 277 + "futures-io", 278 + "rustix 0.38.34", 279 + "signal-hook-registry", 280 + "slab", 281 + "windows-sys 0.52.0", 282 + ] 283 + 284 + [[package]] 285 + name = "async-std" 286 + version = "1.12.0" 287 + source = "registry+https://github.com/rust-lang/crates.io-index" 288 + checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" 289 + dependencies = [ 290 + "async-attributes", 291 + "async-channel 1.9.0", 292 + "async-global-executor", 293 + "async-io 1.13.0", 294 + "async-lock 2.8.0", 295 + "crossbeam-utils", 296 + "futures-channel", 297 + "futures-core", 298 + "futures-io", 299 + "futures-lite 1.13.0", 300 + "gloo-timers", 301 + "kv-log-macro", 302 + "log", 303 + "memchr", 304 + "once_cell", 305 + "pin-project-lite", 306 + "pin-utils", 307 + "slab", 308 + "wasm-bindgen-futures", 309 + ] 310 + 311 + [[package]] 312 + name = "async-task" 313 + version = "4.7.1" 314 + source = "registry+https://github.com/rust-lang/crates.io-index" 315 + checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 316 + 317 + [[package]] 318 + name = "async-trait" 319 + version = "0.1.80" 320 + source = "registry+https://github.com/rust-lang/crates.io-index" 321 + checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" 322 + dependencies = [ 323 + "proc-macro2", 324 + "quote", 325 + "syn 2.0.65", 326 + ] 327 + 328 + [[package]] 329 + name = "async-walkdir" 330 + version = "1.0.0" 331 + source = "registry+https://github.com/rust-lang/crates.io-index" 332 + checksum = "73f6338023cbfc0555eccb8e83d3d4dcf1183b51ca9140a03b1dbb8a559193db" 333 + dependencies = [ 334 + "async-fs", 335 + "futures-lite 2.3.0", 336 + ] 337 + 338 + [[package]] 339 + name = "atoi" 340 + version = "2.0.0" 341 + source = "registry+https://github.com/rust-lang/crates.io-index" 342 + checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" 343 + dependencies = [ 344 + "num-traits", 345 + ] 346 + 347 + [[package]] 348 + name = "atomic-waker" 349 + version = "1.1.2" 350 + source = "registry+https://github.com/rust-lang/crates.io-index" 351 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 352 + 353 + [[package]] 354 + name = "audiotags" 355 + version = "0.5.0" 356 + source = "registry+https://github.com/rust-lang/crates.io-index" 357 + checksum = "44e797ce0164cf599c71f2c3849b56301d96a3dc033544588e875686b050ed39" 358 + dependencies = [ 359 + "audiotags-macro", 360 + "id3", 361 + "metaflac", 362 + "mp4ameta", 363 + "readme-rustdocifier", 364 + "thiserror", 365 + ] 366 + 367 + [[package]] 368 + name = "audiotags-macro" 369 + version = "0.2.0" 370 + source = "registry+https://github.com/rust-lang/crates.io-index" 371 + checksum = "8eaa9b2312fc01f7291f3b7b0f52ed08b1c0177c96a2e696ab55695cc4d06889" 372 + 373 + [[package]] 374 + name = "autocfg" 375 + version = "1.3.0" 376 + source = "registry+https://github.com/rust-lang/crates.io-index" 377 + checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 378 + 379 + [[package]] 380 + name = "backtrace" 381 + version = "0.3.72" 382 + source = "registry+https://github.com/rust-lang/crates.io-index" 383 + checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" 384 + dependencies = [ 385 + "addr2line", 386 + "cc", 387 + "cfg-if", 388 + "libc", 389 + "miniz_oxide", 390 + "object", 391 + "rustc-demangle", 392 + ] 393 + 394 + [[package]] 395 + name = "base64" 396 + version = "0.21.7" 397 + source = "registry+https://github.com/rust-lang/crates.io-index" 398 + checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 399 + 400 + [[package]] 401 + name = "base64ct" 402 + version = "1.6.0" 403 + source = "registry+https://github.com/rust-lang/crates.io-index" 404 + checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 405 + 406 + [[package]] 407 + name = "bitflags" 408 + version = "1.3.2" 409 + source = "registry+https://github.com/rust-lang/crates.io-index" 410 + checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 411 + 412 + [[package]] 413 + name = "bitflags" 414 + version = "2.5.0" 415 + source = "registry+https://github.com/rust-lang/crates.io-index" 416 + checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 417 + dependencies = [ 418 + "serde", 419 + ] 420 + 421 + [[package]] 422 + name = "block-buffer" 423 + version = "0.10.4" 424 + source = "registry+https://github.com/rust-lang/crates.io-index" 425 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 426 + dependencies = [ 427 + "generic-array", 428 + ] 429 + 430 + [[package]] 431 + name = "blocking" 432 + version = "1.6.1" 433 + source = "registry+https://github.com/rust-lang/crates.io-index" 434 + checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" 435 + dependencies = [ 436 + "async-channel 2.3.1", 437 + "async-task", 438 + "futures-io", 439 + "futures-lite 2.3.0", 440 + "piper", 441 + ] 442 + 443 + [[package]] 444 + name = "bumpalo" 445 + version = "3.16.0" 446 + source = "registry+https://github.com/rust-lang/crates.io-index" 447 + checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 448 + 449 + [[package]] 450 + name = "byteorder" 451 + version = "1.5.0" 452 + source = "registry+https://github.com/rust-lang/crates.io-index" 453 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 454 + 455 + [[package]] 456 + name = "bytes" 457 + version = "1.6.0" 458 + source = "registry+https://github.com/rust-lang/crates.io-index" 459 + checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 460 + 461 + [[package]] 462 + name = "cc" 463 + version = "1.0.98" 464 + source = "registry+https://github.com/rust-lang/crates.io-index" 465 + checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" 466 + 467 + [[package]] 468 + name = "cfg-if" 469 + version = "1.0.0" 470 + source = "registry+https://github.com/rust-lang/crates.io-index" 471 + checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 472 + 473 + [[package]] 474 + name = "clap" 475 + version = "4.5.4" 476 + source = "registry+https://github.com/rust-lang/crates.io-index" 477 + checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 478 + dependencies = [ 479 + "clap_builder", 480 + "clap_derive", 481 + ] 482 + 483 + [[package]] 484 + name = "clap_builder" 485 + version = "4.5.2" 486 + source = "registry+https://github.com/rust-lang/crates.io-index" 487 + checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 488 + dependencies = [ 489 + "anstream", 490 + "anstyle", 491 + "clap_lex", 492 + "strsim", 493 + ] 494 + 495 + [[package]] 496 + name = "clap_derive" 497 + version = "4.5.4" 498 + source = "registry+https://github.com/rust-lang/crates.io-index" 499 + checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 500 + dependencies = [ 501 + "heck 0.5.0", 502 + "proc-macro2", 503 + "quote", 504 + "syn 2.0.65", 505 + ] 506 + 507 + [[package]] 508 + name = "clap_lex" 509 + version = "0.7.0" 510 + source = "registry+https://github.com/rust-lang/crates.io-index" 511 + checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 512 + 513 + [[package]] 514 + name = "colorchoice" 515 + version = "1.0.1" 516 + source = "registry+https://github.com/rust-lang/crates.io-index" 517 + checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 518 + 519 + [[package]] 520 + name = "concurrent-queue" 521 + version = "2.5.0" 522 + source = "registry+https://github.com/rust-lang/crates.io-index" 523 + checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 524 + dependencies = [ 525 + "crossbeam-utils", 526 + ] 527 + 528 + [[package]] 529 + name = "console" 530 + version = "0.15.8" 531 + source = "registry+https://github.com/rust-lang/crates.io-index" 532 + checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 533 + dependencies = [ 534 + "encode_unicode", 535 + "lazy_static", 536 + "libc", 537 + "unicode-width", 538 + "windows-sys 0.52.0", 539 + ] 540 + 541 + [[package]] 542 + name = "const-oid" 543 + version = "0.9.6" 544 + source = "registry+https://github.com/rust-lang/crates.io-index" 545 + checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 546 + 547 + [[package]] 548 + name = "const-random" 549 + version = "0.1.18" 550 + source = "registry+https://github.com/rust-lang/crates.io-index" 551 + checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" 552 + dependencies = [ 553 + "const-random-macro", 554 + ] 555 + 556 + [[package]] 557 + name = "const-random-macro" 558 + version = "0.1.16" 559 + source = "registry+https://github.com/rust-lang/crates.io-index" 560 + checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" 561 + dependencies = [ 562 + "getrandom", 563 + "once_cell", 564 + "tiny-keccak", 565 + ] 566 + 567 + [[package]] 568 + name = "cpufeatures" 569 + version = "0.2.12" 570 + source = "registry+https://github.com/rust-lang/crates.io-index" 571 + checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 572 + dependencies = [ 573 + "libc", 574 + ] 575 + 576 + [[package]] 577 + name = "crc" 578 + version = "3.2.1" 579 + source = "registry+https://github.com/rust-lang/crates.io-index" 580 + checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" 581 + dependencies = [ 582 + "crc-catalog", 583 + ] 584 + 585 + [[package]] 586 + name = "crc-catalog" 587 + version = "2.4.0" 588 + source = "registry+https://github.com/rust-lang/crates.io-index" 589 + checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 590 + 591 + [[package]] 592 + name = "crc32fast" 593 + version = "1.4.2" 594 + source = "registry+https://github.com/rust-lang/crates.io-index" 595 + checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 596 + dependencies = [ 597 + "cfg-if", 598 + ] 599 + 600 + [[package]] 601 + name = "crossbeam" 602 + version = "0.8.4" 603 + source = "registry+https://github.com/rust-lang/crates.io-index" 604 + checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" 605 + dependencies = [ 606 + "crossbeam-channel", 607 + "crossbeam-deque", 608 + "crossbeam-epoch", 609 + "crossbeam-queue", 610 + "crossbeam-utils", 611 + ] 612 + 613 + [[package]] 614 + name = "crossbeam-channel" 615 + version = "0.5.13" 616 + source = "registry+https://github.com/rust-lang/crates.io-index" 617 + checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" 618 + dependencies = [ 619 + "crossbeam-utils", 620 + ] 621 + 622 + [[package]] 623 + name = "crossbeam-deque" 624 + version = "0.8.5" 625 + source = "registry+https://github.com/rust-lang/crates.io-index" 626 + checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 627 + dependencies = [ 628 + "crossbeam-epoch", 629 + "crossbeam-utils", 630 + ] 631 + 632 + [[package]] 633 + name = "crossbeam-epoch" 634 + version = "0.9.18" 635 + source = "registry+https://github.com/rust-lang/crates.io-index" 636 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 637 + dependencies = [ 638 + "crossbeam-utils", 639 + ] 640 + 641 + [[package]] 642 + name = "crossbeam-queue" 643 + version = "0.3.11" 644 + source = "registry+https://github.com/rust-lang/crates.io-index" 645 + checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" 646 + dependencies = [ 647 + "crossbeam-utils", 648 + ] 649 + 650 + [[package]] 651 + name = "crossbeam-utils" 652 + version = "0.8.20" 653 + source = "registry+https://github.com/rust-lang/crates.io-index" 654 + checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 655 + 656 + [[package]] 657 + name = "crunchy" 658 + version = "0.2.2" 659 + source = "registry+https://github.com/rust-lang/crates.io-index" 660 + checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 661 + 662 + [[package]] 663 + name = "crypto-common" 664 + version = "0.1.6" 665 + source = "registry+https://github.com/rust-lang/crates.io-index" 666 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 667 + dependencies = [ 668 + "generic-array", 669 + "typenum", 670 + ] 671 + 672 + [[package]] 673 + name = "der" 674 + version = "0.7.9" 675 + source = "registry+https://github.com/rust-lang/crates.io-index" 676 + checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" 677 + dependencies = [ 678 + "const-oid", 679 + "pem-rfc7468", 680 + "zeroize", 681 + ] 682 + 683 + [[package]] 684 + name = "digest" 685 + version = "0.10.7" 686 + source = "registry+https://github.com/rust-lang/crates.io-index" 687 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 688 + dependencies = [ 689 + "block-buffer", 690 + "const-oid", 691 + "crypto-common", 692 + "subtle", 693 + ] 694 + 695 + [[package]] 696 + name = "directories" 697 + version = "5.0.1" 698 + source = "registry+https://github.com/rust-lang/crates.io-index" 699 + checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" 700 + dependencies = [ 701 + "dirs-sys", 702 + ] 703 + 704 + [[package]] 705 + name = "dirs-sys" 706 + version = "0.4.1" 707 + source = "registry+https://github.com/rust-lang/crates.io-index" 708 + checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 709 + dependencies = [ 710 + "libc", 711 + "option-ext", 712 + "redox_users", 713 + "windows-sys 0.48.0", 714 + ] 715 + 716 + [[package]] 717 + name = "dotenvy" 718 + version = "0.15.7" 719 + source = "registry+https://github.com/rust-lang/crates.io-index" 720 + checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 721 + 722 + [[package]] 723 + name = "edit" 724 + version = "0.1.5" 725 + source = "registry+https://github.com/rust-lang/crates.io-index" 726 + checksum = "f364860e764787163c8c8f58231003839be31276e821e2ad2092ddf496b1aa09" 727 + dependencies = [ 728 + "tempfile", 729 + "which", 730 + ] 731 + 732 + [[package]] 733 + name = "either" 734 + version = "1.12.0" 735 + source = "registry+https://github.com/rust-lang/crates.io-index" 736 + checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" 737 + dependencies = [ 738 + "serde", 739 + ] 740 + 741 + [[package]] 742 + name = "encode_unicode" 743 + version = "0.3.6" 744 + source = "registry+https://github.com/rust-lang/crates.io-index" 745 + checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 746 + 747 + [[package]] 748 + name = "env_filter" 749 + version = "0.1.0" 750 + source = "registry+https://github.com/rust-lang/crates.io-index" 751 + checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" 752 + dependencies = [ 753 + "log", 754 + "regex", 755 + ] 756 + 757 + [[package]] 758 + name = "env_logger" 759 + version = "0.11.3" 760 + source = "registry+https://github.com/rust-lang/crates.io-index" 761 + checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" 762 + dependencies = [ 763 + "anstream", 764 + "anstyle", 765 + "env_filter", 766 + "humantime", 767 + "log", 768 + ] 769 + 770 + [[package]] 771 + name = "equivalent" 772 + version = "1.0.1" 773 + source = "registry+https://github.com/rust-lang/crates.io-index" 774 + checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 775 + 776 + [[package]] 777 + name = "errno" 778 + version = "0.3.9" 779 + source = "registry+https://github.com/rust-lang/crates.io-index" 780 + checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 781 + dependencies = [ 782 + "libc", 783 + "windows-sys 0.52.0", 784 + ] 785 + 786 + [[package]] 787 + name = "etcetera" 788 + version = "0.8.0" 789 + source = "registry+https://github.com/rust-lang/crates.io-index" 790 + checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" 791 + dependencies = [ 792 + "cfg-if", 793 + "home", 794 + "windows-sys 0.48.0", 795 + ] 796 + 797 + [[package]] 798 + name = "event-listener" 799 + version = "2.5.3" 800 + source = "registry+https://github.com/rust-lang/crates.io-index" 801 + checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 802 + 803 + [[package]] 804 + name = "event-listener" 805 + version = "4.0.3" 806 + source = "registry+https://github.com/rust-lang/crates.io-index" 807 + checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" 808 + dependencies = [ 809 + "concurrent-queue", 810 + "parking", 811 + "pin-project-lite", 812 + ] 813 + 814 + [[package]] 815 + name = "event-listener" 816 + version = "5.3.1" 817 + source = "registry+https://github.com/rust-lang/crates.io-index" 818 + checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" 819 + dependencies = [ 820 + "concurrent-queue", 821 + "parking", 822 + "pin-project-lite", 823 + ] 824 + 825 + [[package]] 826 + name = "event-listener-strategy" 827 + version = "0.4.0" 828 + source = "registry+https://github.com/rust-lang/crates.io-index" 829 + checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" 830 + dependencies = [ 831 + "event-listener 4.0.3", 832 + "pin-project-lite", 833 + ] 834 + 835 + [[package]] 836 + name = "event-listener-strategy" 837 + version = "0.5.2" 838 + source = "registry+https://github.com/rust-lang/crates.io-index" 839 + checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" 840 + dependencies = [ 841 + "event-listener 5.3.1", 842 + "pin-project-lite", 843 + ] 844 + 845 + [[package]] 846 + name = "fastrand" 847 + version = "1.9.0" 848 + source = "registry+https://github.com/rust-lang/crates.io-index" 849 + checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" 850 + dependencies = [ 851 + "instant", 852 + ] 853 + 854 + [[package]] 855 + name = "fastrand" 856 + version = "2.1.0" 857 + source = "registry+https://github.com/rust-lang/crates.io-index" 858 + checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" 859 + 860 + [[package]] 861 + name = "flate2" 862 + version = "1.0.30" 863 + source = "registry+https://github.com/rust-lang/crates.io-index" 864 + checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" 865 + dependencies = [ 866 + "crc32fast", 867 + "miniz_oxide", 868 + ] 869 + 870 + [[package]] 871 + name = "flume" 872 + version = "0.11.0" 873 + source = "registry+https://github.com/rust-lang/crates.io-index" 874 + checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" 875 + dependencies = [ 876 + "futures-core", 877 + "futures-sink", 878 + "spin 0.9.8", 879 + ] 880 + 881 + [[package]] 882 + name = "form_urlencoded" 883 + version = "1.2.1" 884 + source = "registry+https://github.com/rust-lang/crates.io-index" 885 + checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 886 + dependencies = [ 887 + "percent-encoding", 888 + ] 889 + 890 + [[package]] 891 + name = "fs_extra" 892 + version = "1.3.0" 893 + source = "registry+https://github.com/rust-lang/crates.io-index" 894 + checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 895 + 896 + [[package]] 897 + name = "futures" 898 + version = "0.3.30" 899 + source = "registry+https://github.com/rust-lang/crates.io-index" 900 + checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 901 + dependencies = [ 902 + "futures-channel", 903 + "futures-core", 904 + "futures-executor", 905 + "futures-io", 906 + "futures-sink", 907 + "futures-task", 908 + "futures-util", 909 + ] 910 + 911 + [[package]] 912 + name = "futures-channel" 913 + version = "0.3.30" 914 + source = "registry+https://github.com/rust-lang/crates.io-index" 915 + checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 916 + dependencies = [ 917 + "futures-core", 918 + "futures-sink", 919 + ] 920 + 921 + [[package]] 922 + name = "futures-core" 923 + version = "0.3.30" 924 + source = "registry+https://github.com/rust-lang/crates.io-index" 925 + checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 926 + 927 + [[package]] 928 + name = "futures-executor" 929 + version = "0.3.30" 930 + source = "registry+https://github.com/rust-lang/crates.io-index" 931 + checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 932 + dependencies = [ 933 + "futures-core", 934 + "futures-task", 935 + "futures-util", 936 + ] 937 + 938 + [[package]] 939 + name = "futures-intrusive" 940 + version = "0.5.0" 941 + source = "registry+https://github.com/rust-lang/crates.io-index" 942 + checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" 943 + dependencies = [ 944 + "futures-core", 945 + "lock_api", 946 + "parking_lot", 947 + ] 948 + 949 + [[package]] 950 + name = "futures-io" 951 + version = "0.3.30" 952 + source = "registry+https://github.com/rust-lang/crates.io-index" 953 + checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 954 + 955 + [[package]] 956 + name = "futures-lite" 957 + version = "1.13.0" 958 + source = "registry+https://github.com/rust-lang/crates.io-index" 959 + checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" 960 + dependencies = [ 961 + "fastrand 1.9.0", 962 + "futures-core", 963 + "futures-io", 964 + "memchr", 965 + "parking", 966 + "pin-project-lite", 967 + "waker-fn", 968 + ] 969 + 970 + [[package]] 971 + name = "futures-lite" 972 + version = "2.3.0" 973 + source = "registry+https://github.com/rust-lang/crates.io-index" 974 + checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" 975 + dependencies = [ 976 + "fastrand 2.1.0", 977 + "futures-core", 978 + "futures-io", 979 + "parking", 980 + "pin-project-lite", 981 + ] 982 + 983 + [[package]] 984 + name = "futures-macro" 985 + version = "0.3.30" 986 + source = "registry+https://github.com/rust-lang/crates.io-index" 987 + checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 988 + dependencies = [ 989 + "proc-macro2", 990 + "quote", 991 + "syn 2.0.65", 992 + ] 993 + 994 + [[package]] 995 + name = "futures-sink" 996 + version = "0.3.30" 997 + source = "registry+https://github.com/rust-lang/crates.io-index" 998 + checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 999 + 1000 + [[package]] 1001 + name = "futures-task" 1002 + version = "0.3.30" 1003 + source = "registry+https://github.com/rust-lang/crates.io-index" 1004 + checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 1005 + 1006 + [[package]] 1007 + name = "futures-util" 1008 + version = "0.3.30" 1009 + source = "registry+https://github.com/rust-lang/crates.io-index" 1010 + checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 1011 + dependencies = [ 1012 + "futures-channel", 1013 + "futures-core", 1014 + "futures-io", 1015 + "futures-macro", 1016 + "futures-sink", 1017 + "futures-task", 1018 + "memchr", 1019 + "pin-project-lite", 1020 + "pin-utils", 1021 + "slab", 1022 + ] 1023 + 1024 + [[package]] 1025 + name = "generic-array" 1026 + version = "0.14.7" 1027 + source = "registry+https://github.com/rust-lang/crates.io-index" 1028 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 1029 + dependencies = [ 1030 + "typenum", 1031 + "version_check", 1032 + ] 1033 + 1034 + [[package]] 1035 + name = "getrandom" 1036 + version = "0.2.15" 1037 + source = "registry+https://github.com/rust-lang/crates.io-index" 1038 + checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 1039 + dependencies = [ 1040 + "cfg-if", 1041 + "libc", 1042 + "wasi", 1043 + ] 1044 + 1045 + [[package]] 1046 + name = "gimli" 1047 + version = "0.29.0" 1048 + source = "registry+https://github.com/rust-lang/crates.io-index" 1049 + checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 1050 + 1051 + [[package]] 1052 + name = "gloo-timers" 1053 + version = "0.2.6" 1054 + source = "registry+https://github.com/rust-lang/crates.io-index" 1055 + checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" 1056 + dependencies = [ 1057 + "futures-channel", 1058 + "futures-core", 1059 + "js-sys", 1060 + "wasm-bindgen", 1061 + ] 1062 + 1063 + [[package]] 1064 + name = "hashbrown" 1065 + version = "0.14.5" 1066 + source = "registry+https://github.com/rust-lang/crates.io-index" 1067 + checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1068 + dependencies = [ 1069 + "ahash", 1070 + "allocator-api2", 1071 + ] 1072 + 1073 + [[package]] 1074 + name = "hashlink" 1075 + version = "0.8.4" 1076 + source = "registry+https://github.com/rust-lang/crates.io-index" 1077 + checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" 1078 + dependencies = [ 1079 + "hashbrown", 1080 + ] 1081 + 1082 + [[package]] 1083 + name = "heck" 1084 + version = "0.4.1" 1085 + source = "registry+https://github.com/rust-lang/crates.io-index" 1086 + checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 1087 + dependencies = [ 1088 + "unicode-segmentation", 1089 + ] 1090 + 1091 + [[package]] 1092 + name = "heck" 1093 + version = "0.5.0" 1094 + source = "registry+https://github.com/rust-lang/crates.io-index" 1095 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1096 + 1097 + [[package]] 1098 + name = "hermit-abi" 1099 + version = "0.3.9" 1100 + source = "registry+https://github.com/rust-lang/crates.io-index" 1101 + checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 1102 + 1103 + [[package]] 1104 + name = "hex" 1105 + version = "0.4.3" 1106 + source = "registry+https://github.com/rust-lang/crates.io-index" 1107 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1108 + 1109 + [[package]] 1110 + name = "hkdf" 1111 + version = "0.12.4" 1112 + source = "registry+https://github.com/rust-lang/crates.io-index" 1113 + checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 1114 + dependencies = [ 1115 + "hmac", 1116 + ] 1117 + 1118 + [[package]] 1119 + name = "hmac" 1120 + version = "0.12.1" 1121 + source = "registry+https://github.com/rust-lang/crates.io-index" 1122 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1123 + dependencies = [ 1124 + "digest", 1125 + ] 1126 + 1127 + [[package]] 1128 + name = "home" 1129 + version = "0.5.9" 1130 + source = "registry+https://github.com/rust-lang/crates.io-index" 1131 + checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 1132 + dependencies = [ 1133 + "windows-sys 0.52.0", 1134 + ] 1135 + 1136 + [[package]] 1137 + name = "humantime" 1138 + version = "2.1.0" 1139 + source = "registry+https://github.com/rust-lang/crates.io-index" 1140 + checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 1141 + 1142 + [[package]] 1143 + name = "id3" 1144 + version = "1.13.1" 1145 + source = "registry+https://github.com/rust-lang/crates.io-index" 1146 + checksum = "79f41f7e5ad125c63d55b112a98afb753742fa7f97692bfbbc52544b89e1ff1f" 1147 + dependencies = [ 1148 + "bitflags 2.5.0", 1149 + "byteorder", 1150 + "flate2", 1151 + ] 1152 + 1153 + [[package]] 1154 + name = "idna" 1155 + version = "0.5.0" 1156 + source = "registry+https://github.com/rust-lang/crates.io-index" 1157 + checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 1158 + dependencies = [ 1159 + "unicode-bidi", 1160 + "unicode-normalization", 1161 + ] 1162 + 1163 + [[package]] 1164 + name = "indexmap" 1165 + version = "2.2.6" 1166 + source = "registry+https://github.com/rust-lang/crates.io-index" 1167 + checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 1168 + dependencies = [ 1169 + "equivalent", 1170 + "hashbrown", 1171 + ] 1172 + 1173 + [[package]] 1174 + name = "indicatif" 1175 + version = "0.17.8" 1176 + source = "registry+https://github.com/rust-lang/crates.io-index" 1177 + checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" 1178 + dependencies = [ 1179 + "console", 1180 + "instant", 1181 + "number_prefix", 1182 + "portable-atomic", 1183 + "unicode-width", 1184 + ] 1185 + 1186 + [[package]] 1187 + name = "instant" 1188 + version = "0.1.13" 1189 + source = "registry+https://github.com/rust-lang/crates.io-index" 1190 + checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 1191 + dependencies = [ 1192 + "cfg-if", 1193 + ] 1194 + 1195 + [[package]] 1196 + name = "io-lifetimes" 1197 + version = "1.0.11" 1198 + source = "registry+https://github.com/rust-lang/crates.io-index" 1199 + checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" 1200 + dependencies = [ 1201 + "hermit-abi", 1202 + "libc", 1203 + "windows-sys 0.48.0", 1204 + ] 1205 + 1206 + [[package]] 1207 + name = "is_terminal_polyfill" 1208 + version = "1.70.0" 1209 + source = "registry+https://github.com/rust-lang/crates.io-index" 1210 + checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 1211 + 1212 + [[package]] 1213 + name = "itertools" 1214 + version = "0.12.1" 1215 + source = "registry+https://github.com/rust-lang/crates.io-index" 1216 + checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 1217 + dependencies = [ 1218 + "either", 1219 + ] 1220 + 1221 + [[package]] 1222 + name = "itoa" 1223 + version = "1.0.11" 1224 + source = "registry+https://github.com/rust-lang/crates.io-index" 1225 + checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 1226 + 1227 + [[package]] 1228 + name = "js-sys" 1229 + version = "0.3.69" 1230 + source = "registry+https://github.com/rust-lang/crates.io-index" 1231 + checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 1232 + dependencies = [ 1233 + "wasm-bindgen", 1234 + ] 1235 + 1236 + [[package]] 1237 + name = "jwalk" 1238 + version = "0.8.1" 1239 + source = "registry+https://github.com/rust-lang/crates.io-index" 1240 + checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56" 1241 + dependencies = [ 1242 + "crossbeam", 1243 + "rayon", 1244 + ] 1245 + 1246 + [[package]] 1247 + name = "kdam" 1248 + version = "0.5.2" 1249 + source = "registry+https://github.com/rust-lang/crates.io-index" 1250 + checksum = "526586ea01a9a132b5f8d3a60f6d6b41b411550236f5ee057795f20b37316957" 1251 + dependencies = [ 1252 + "terminal_size", 1253 + "windows-sys 0.52.0", 1254 + ] 1255 + 1256 + [[package]] 1257 + name = "kv-log-macro" 1258 + version = "1.0.7" 1259 + source = "registry+https://github.com/rust-lang/crates.io-index" 1260 + checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 1261 + dependencies = [ 1262 + "log", 1263 + ] 1264 + 1265 + [[package]] 1266 + name = "lazy_static" 1267 + version = "1.4.0" 1268 + source = "registry+https://github.com/rust-lang/crates.io-index" 1269 + checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1270 + dependencies = [ 1271 + "spin 0.5.2", 1272 + ] 1273 + 1274 + [[package]] 1275 + name = "libc" 1276 + version = "0.2.155" 1277 + source = "registry+https://github.com/rust-lang/crates.io-index" 1278 + checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 1279 + 1280 + [[package]] 1281 + name = "libm" 1282 + version = "0.2.8" 1283 + source = "registry+https://github.com/rust-lang/crates.io-index" 1284 + checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" 1285 + 1286 + [[package]] 1287 + name = "libredox" 1288 + version = "0.0.2" 1289 + source = "registry+https://github.com/rust-lang/crates.io-index" 1290 + checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" 1291 + dependencies = [ 1292 + "bitflags 2.5.0", 1293 + "libc", 1294 + "redox_syscall 0.4.1", 1295 + ] 1296 + 1297 + [[package]] 1298 + name = "libredox" 1299 + version = "0.1.3" 1300 + source = "registry+https://github.com/rust-lang/crates.io-index" 1301 + checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1302 + dependencies = [ 1303 + "bitflags 2.5.0", 1304 + "libc", 1305 + ] 1306 + 1307 + [[package]] 1308 + name = "libsqlite3-sys" 1309 + version = "0.27.0" 1310 + source = "registry+https://github.com/rust-lang/crates.io-index" 1311 + checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" 1312 + dependencies = [ 1313 + "cc", 1314 + "pkg-config", 1315 + "vcpkg", 1316 + ] 1317 + 1318 + [[package]] 1319 + name = "linux-raw-sys" 1320 + version = "0.3.8" 1321 + source = "registry+https://github.com/rust-lang/crates.io-index" 1322 + checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" 1323 + 1324 + [[package]] 1325 + name = "linux-raw-sys" 1326 + version = "0.4.14" 1327 + source = "registry+https://github.com/rust-lang/crates.io-index" 1328 + checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 1329 + 1330 + [[package]] 1331 + name = "lock_api" 1332 + version = "0.4.12" 1333 + source = "registry+https://github.com/rust-lang/crates.io-index" 1334 + checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1335 + dependencies = [ 1336 + "autocfg", 1337 + "scopeguard", 1338 + ] 1339 + 1340 + [[package]] 1341 + name = "log" 1342 + version = "0.4.21" 1343 + source = "registry+https://github.com/rust-lang/crates.io-index" 1344 + checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 1345 + dependencies = [ 1346 + "value-bag", 1347 + ] 1348 + 1349 + [[package]] 1350 + name = "md-5" 1351 + version = "0.10.6" 1352 + source = "registry+https://github.com/rust-lang/crates.io-index" 1353 + checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 1354 + dependencies = [ 1355 + "cfg-if", 1356 + "digest", 1357 + ] 1358 + 1359 + [[package]] 1360 + name = "memchr" 1361 + version = "2.7.2" 1362 + source = "registry+https://github.com/rust-lang/crates.io-index" 1363 + checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 1364 + 1365 + [[package]] 1366 + name = "metaflac" 1367 + version = "0.2.5" 1368 + source = "registry+https://github.com/rust-lang/crates.io-index" 1369 + checksum = "e1470d3cc1bb0d692af5eb3afb594330b8ba09fd91c32c4e1c6322172a5ba750" 1370 + dependencies = [ 1371 + "byteorder", 1372 + "hex", 1373 + "log", 1374 + ] 1375 + 1376 + [[package]] 1377 + name = "minimal-lexical" 1378 + version = "0.2.1" 1379 + source = "registry+https://github.com/rust-lang/crates.io-index" 1380 + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1381 + 1382 + [[package]] 1383 + name = "miniz_oxide" 1384 + version = "0.7.3" 1385 + source = "registry+https://github.com/rust-lang/crates.io-index" 1386 + checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" 1387 + dependencies = [ 1388 + "adler", 1389 + ] 1390 + 1391 + [[package]] 1392 + name = "mp4ameta" 1393 + version = "0.11.0" 1394 + source = "registry+https://github.com/rust-lang/crates.io-index" 1395 + checksum = "eb23d62e8eb5299a3f79657c70ea9269eac8f6239a76952689bcd06a74057e81" 1396 + dependencies = [ 1397 + "lazy_static", 1398 + "mp4ameta_proc", 1399 + ] 1400 + 1401 + [[package]] 1402 + name = "mp4ameta_proc" 1403 + version = "0.6.0" 1404 + source = "registry+https://github.com/rust-lang/crates.io-index" 1405 + checksum = "07dcca13d1740c0a665f77104803360da0bdb3323ecce2e93fa2c959a6d52806" 1406 + 1407 + [[package]] 1408 + name = "nom" 1409 + version = "7.1.3" 1410 + source = "registry+https://github.com/rust-lang/crates.io-index" 1411 + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1412 + dependencies = [ 1413 + "memchr", 1414 + "minimal-lexical", 1415 + ] 1416 + 1417 + [[package]] 1418 + name = "num-bigint-dig" 1419 + version = "0.8.4" 1420 + source = "registry+https://github.com/rust-lang/crates.io-index" 1421 + checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 1422 + dependencies = [ 1423 + "byteorder", 1424 + "lazy_static", 1425 + "libm", 1426 + "num-integer", 1427 + "num-iter", 1428 + "num-traits", 1429 + "rand", 1430 + "smallvec", 1431 + "zeroize", 1432 + ] 1433 + 1434 + [[package]] 1435 + name = "num-integer" 1436 + version = "0.1.46" 1437 + source = "registry+https://github.com/rust-lang/crates.io-index" 1438 + checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1439 + dependencies = [ 1440 + "num-traits", 1441 + ] 1442 + 1443 + [[package]] 1444 + name = "num-iter" 1445 + version = "0.1.45" 1446 + source = "registry+https://github.com/rust-lang/crates.io-index" 1447 + checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 1448 + dependencies = [ 1449 + "autocfg", 1450 + "num-integer", 1451 + "num-traits", 1452 + ] 1453 + 1454 + [[package]] 1455 + name = "num-traits" 1456 + version = "0.2.19" 1457 + source = "registry+https://github.com/rust-lang/crates.io-index" 1458 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1459 + dependencies = [ 1460 + "autocfg", 1461 + "libm", 1462 + ] 1463 + 1464 + [[package]] 1465 + name = "number_prefix" 1466 + version = "0.4.0" 1467 + source = "registry+https://github.com/rust-lang/crates.io-index" 1468 + checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 1469 + 1470 + [[package]] 1471 + name = "numtoa" 1472 + version = "0.1.0" 1473 + source = "registry+https://github.com/rust-lang/crates.io-index" 1474 + checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" 1475 + 1476 + [[package]] 1477 + name = "object" 1478 + version = "0.35.0" 1479 + source = "registry+https://github.com/rust-lang/crates.io-index" 1480 + checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" 1481 + dependencies = [ 1482 + "memchr", 1483 + ] 1484 + 1485 + [[package]] 1486 + name = "once_cell" 1487 + version = "1.19.0" 1488 + source = "registry+https://github.com/rust-lang/crates.io-index" 1489 + checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1490 + 1491 + [[package]] 1492 + name = "option-ext" 1493 + version = "0.2.0" 1494 + source = "registry+https://github.com/rust-lang/crates.io-index" 1495 + checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 1496 + 1497 + [[package]] 1498 + name = "parking" 1499 + version = "2.2.0" 1500 + source = "registry+https://github.com/rust-lang/crates.io-index" 1501 + checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" 1502 + 1503 + [[package]] 1504 + name = "parking_lot" 1505 + version = "0.12.3" 1506 + source = "registry+https://github.com/rust-lang/crates.io-index" 1507 + checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1508 + dependencies = [ 1509 + "lock_api", 1510 + "parking_lot_core", 1511 + ] 1512 + 1513 + [[package]] 1514 + name = "parking_lot_core" 1515 + version = "0.9.10" 1516 + source = "registry+https://github.com/rust-lang/crates.io-index" 1517 + checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1518 + dependencies = [ 1519 + "cfg-if", 1520 + "libc", 1521 + "redox_syscall 0.5.1", 1522 + "smallvec", 1523 + "windows-targets 0.52.5", 1524 + ] 1525 + 1526 + [[package]] 1527 + name = "paste" 1528 + version = "1.0.15" 1529 + source = "registry+https://github.com/rust-lang/crates.io-index" 1530 + checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1531 + 1532 + [[package]] 1533 + name = "pem-rfc7468" 1534 + version = "0.7.0" 1535 + source = "registry+https://github.com/rust-lang/crates.io-index" 1536 + checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 1537 + dependencies = [ 1538 + "base64ct", 1539 + ] 1540 + 1541 + [[package]] 1542 + name = "percent-encoding" 1543 + version = "2.3.1" 1544 + source = "registry+https://github.com/rust-lang/crates.io-index" 1545 + checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1546 + 1547 + [[package]] 1548 + name = "pgbar" 1549 + version = "0.3.1" 1550 + source = "registry+https://github.com/rust-lang/crates.io-index" 1551 + checksum = "f8c7921e36d17e0f1b57447488717d77cec98aef00db416d0141f9b0adfe5600" 1552 + dependencies = [ 1553 + "termion", 1554 + ] 1555 + 1556 + [[package]] 1557 + name = "pin-project-lite" 1558 + version = "0.2.14" 1559 + source = "registry+https://github.com/rust-lang/crates.io-index" 1560 + checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1561 + 1562 + [[package]] 1563 + name = "pin-utils" 1564 + version = "0.1.0" 1565 + source = "registry+https://github.com/rust-lang/crates.io-index" 1566 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1567 + 1568 + [[package]] 1569 + name = "piper" 1570 + version = "0.2.2" 1571 + source = "registry+https://github.com/rust-lang/crates.io-index" 1572 + checksum = "464db0c665917b13ebb5d453ccdec4add5658ee1adc7affc7677615356a8afaf" 1573 + dependencies = [ 1574 + "atomic-waker", 1575 + "fastrand 2.1.0", 1576 + "futures-io", 1577 + ] 1578 + 1579 + [[package]] 1580 + name = "pkcs1" 1581 + version = "0.7.5" 1582 + source = "registry+https://github.com/rust-lang/crates.io-index" 1583 + checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 1584 + dependencies = [ 1585 + "der", 1586 + "pkcs8", 1587 + "spki", 1588 + ] 1589 + 1590 + [[package]] 1591 + name = "pkcs8" 1592 + version = "0.10.2" 1593 + source = "registry+https://github.com/rust-lang/crates.io-index" 1594 + checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 1595 + dependencies = [ 1596 + "der", 1597 + "spki", 1598 + ] 1599 + 1600 + [[package]] 1601 + name = "pkg-config" 1602 + version = "0.3.30" 1603 + source = "registry+https://github.com/rust-lang/crates.io-index" 1604 + checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 1605 + 1606 + [[package]] 1607 + name = "polling" 1608 + version = "2.8.0" 1609 + source = "registry+https://github.com/rust-lang/crates.io-index" 1610 + checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" 1611 + dependencies = [ 1612 + "autocfg", 1613 + "bitflags 1.3.2", 1614 + "cfg-if", 1615 + "concurrent-queue", 1616 + "libc", 1617 + "log", 1618 + "pin-project-lite", 1619 + "windows-sys 0.48.0", 1620 + ] 1621 + 1622 + [[package]] 1623 + name = "polling" 1624 + version = "3.7.0" 1625 + source = "registry+https://github.com/rust-lang/crates.io-index" 1626 + checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" 1627 + dependencies = [ 1628 + "cfg-if", 1629 + "concurrent-queue", 1630 + "hermit-abi", 1631 + "pin-project-lite", 1632 + "rustix 0.38.34", 1633 + "tracing", 1634 + "windows-sys 0.52.0", 1635 + ] 1636 + 1637 + [[package]] 1638 + name = "portable-atomic" 1639 + version = "1.6.0" 1640 + source = "registry+https://github.com/rust-lang/crates.io-index" 1641 + checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 1642 + 1643 + [[package]] 1644 + name = "ppv-lite86" 1645 + version = "0.2.17" 1646 + source = "registry+https://github.com/rust-lang/crates.io-index" 1647 + checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1648 + 1649 + [[package]] 1650 + name = "proc-macro2" 1651 + version = "1.0.83" 1652 + source = "registry+https://github.com/rust-lang/crates.io-index" 1653 + checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" 1654 + dependencies = [ 1655 + "unicode-ident", 1656 + ] 1657 + 1658 + [[package]] 1659 + name = "progressing" 1660 + version = "3.0.2" 1661 + source = "registry+https://github.com/rust-lang/crates.io-index" 1662 + checksum = "97b7db19a74ba7c34de36558abed080568491d2b8999a34de914b1793b0b4b1b" 1663 + dependencies = [ 1664 + "log", 1665 + ] 1666 + 1667 + [[package]] 1668 + name = "quote" 1669 + version = "1.0.36" 1670 + source = "registry+https://github.com/rust-lang/crates.io-index" 1671 + checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 1672 + dependencies = [ 1673 + "proc-macro2", 1674 + ] 1675 + 1676 + [[package]] 1677 + name = "rand" 1678 + version = "0.8.5" 1679 + source = "registry+https://github.com/rust-lang/crates.io-index" 1680 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1681 + dependencies = [ 1682 + "libc", 1683 + "rand_chacha", 1684 + "rand_core", 1685 + ] 1686 + 1687 + [[package]] 1688 + name = "rand_chacha" 1689 + version = "0.3.1" 1690 + source = "registry+https://github.com/rust-lang/crates.io-index" 1691 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1692 + dependencies = [ 1693 + "ppv-lite86", 1694 + "rand_core", 1695 + ] 1696 + 1697 + [[package]] 1698 + name = "rand_core" 1699 + version = "0.6.4" 1700 + source = "registry+https://github.com/rust-lang/crates.io-index" 1701 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1702 + dependencies = [ 1703 + "getrandom", 1704 + ] 1705 + 1706 + [[package]] 1707 + name = "rayon" 1708 + version = "1.10.0" 1709 + source = "registry+https://github.com/rust-lang/crates.io-index" 1710 + checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 1711 + dependencies = [ 1712 + "either", 1713 + "rayon-core", 1714 + ] 1715 + 1716 + [[package]] 1717 + name = "rayon-core" 1718 + version = "1.12.1" 1719 + source = "registry+https://github.com/rust-lang/crates.io-index" 1720 + checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 1721 + dependencies = [ 1722 + "crossbeam-deque", 1723 + "crossbeam-utils", 1724 + ] 1725 + 1726 + [[package]] 1727 + name = "readme-rustdocifier" 1728 + version = "0.1.1" 1729 + source = "registry+https://github.com/rust-lang/crates.io-index" 1730 + checksum = "08ad765b21a08b1a8e5cdce052719188a23772bcbefb3c439f0baaf62c56ceac" 1731 + 1732 + [[package]] 1733 + name = "redox_syscall" 1734 + version = "0.4.1" 1735 + source = "registry+https://github.com/rust-lang/crates.io-index" 1736 + checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1737 + dependencies = [ 1738 + "bitflags 1.3.2", 1739 + ] 1740 + 1741 + [[package]] 1742 + name = "redox_syscall" 1743 + version = "0.5.1" 1744 + source = "registry+https://github.com/rust-lang/crates.io-index" 1745 + checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 1746 + dependencies = [ 1747 + "bitflags 2.5.0", 1748 + ] 1749 + 1750 + [[package]] 1751 + name = "redox_termios" 1752 + version = "0.1.3" 1753 + source = "registry+https://github.com/rust-lang/crates.io-index" 1754 + checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" 1755 + 1756 + [[package]] 1757 + name = "redox_users" 1758 + version = "0.4.5" 1759 + source = "registry+https://github.com/rust-lang/crates.io-index" 1760 + checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" 1761 + dependencies = [ 1762 + "getrandom", 1763 + "libredox 0.1.3", 1764 + "thiserror", 1765 + ] 1766 + 1767 + [[package]] 1768 + name = "regex" 1769 + version = "1.10.5" 1770 + source = "registry+https://github.com/rust-lang/crates.io-index" 1771 + checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" 1772 + dependencies = [ 1773 + "aho-corasick", 1774 + "memchr", 1775 + "regex-automata", 1776 + "regex-syntax", 1777 + ] 1778 + 1779 + [[package]] 1780 + name = "regex-automata" 1781 + version = "0.4.6" 1782 + source = "registry+https://github.com/rust-lang/crates.io-index" 1783 + checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 1784 + dependencies = [ 1785 + "aho-corasick", 1786 + "memchr", 1787 + "regex-syntax", 1788 + ] 1789 + 1790 + [[package]] 1791 + name = "regex-syntax" 1792 + version = "0.8.3" 1793 + source = "registry+https://github.com/rust-lang/crates.io-index" 1794 + checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 1795 + 1796 + [[package]] 1797 + name = "rhai" 1798 + version = "1.19.0" 1799 + source = "registry+https://github.com/rust-lang/crates.io-index" 1800 + checksum = "61797318be89b1a268a018a92a7657096d83f3ecb31418b9e9c16dcbb043b702" 1801 + dependencies = [ 1802 + "ahash", 1803 + "bitflags 2.5.0", 1804 + "instant", 1805 + "num-traits", 1806 + "once_cell", 1807 + "rhai_codegen", 1808 + "smallvec", 1809 + "smartstring", 1810 + "thin-vec", 1811 + ] 1812 + 1813 + [[package]] 1814 + name = "rhai_codegen" 1815 + version = "2.1.0" 1816 + source = "registry+https://github.com/rust-lang/crates.io-index" 1817 + checksum = "59aecf17969c04b9c0c5d21f6bc9da9fec9dd4980e64d1871443a476589d8c86" 1818 + dependencies = [ 1819 + "proc-macro2", 1820 + "quote", 1821 + "syn 2.0.65", 1822 + ] 1823 + 1824 + [[package]] 1825 + name = "rsa" 1826 + version = "0.9.6" 1827 + source = "registry+https://github.com/rust-lang/crates.io-index" 1828 + checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" 1829 + dependencies = [ 1830 + "const-oid", 1831 + "digest", 1832 + "num-bigint-dig", 1833 + "num-integer", 1834 + "num-traits", 1835 + "pkcs1", 1836 + "pkcs8", 1837 + "rand_core", 1838 + "signature", 1839 + "spki", 1840 + "subtle", 1841 + "zeroize", 1842 + ] 1843 + 1844 + [[package]] 1845 + name = "rustc-demangle" 1846 + version = "0.1.24" 1847 + source = "registry+https://github.com/rust-lang/crates.io-index" 1848 + checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1849 + 1850 + [[package]] 1851 + name = "rustix" 1852 + version = "0.37.27" 1853 + source = "registry+https://github.com/rust-lang/crates.io-index" 1854 + checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" 1855 + dependencies = [ 1856 + "bitflags 1.3.2", 1857 + "errno", 1858 + "io-lifetimes", 1859 + "libc", 1860 + "linux-raw-sys 0.3.8", 1861 + "windows-sys 0.48.0", 1862 + ] 1863 + 1864 + [[package]] 1865 + name = "rustix" 1866 + version = "0.38.34" 1867 + source = "registry+https://github.com/rust-lang/crates.io-index" 1868 + checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" 1869 + dependencies = [ 1870 + "bitflags 2.5.0", 1871 + "errno", 1872 + "libc", 1873 + "linux-raw-sys 0.4.14", 1874 + "windows-sys 0.52.0", 1875 + ] 1876 + 1877 + [[package]] 1878 + name = "ryu" 1879 + version = "1.0.18" 1880 + source = "registry+https://github.com/rust-lang/crates.io-index" 1881 + checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1882 + 1883 + [[package]] 1884 + name = "same-file" 1885 + version = "1.0.6" 1886 + source = "registry+https://github.com/rust-lang/crates.io-index" 1887 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1888 + dependencies = [ 1889 + "winapi-util", 1890 + ] 1891 + 1892 + [[package]] 1893 + name = "scopeguard" 1894 + version = "1.2.0" 1895 + source = "registry+https://github.com/rust-lang/crates.io-index" 1896 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1897 + 1898 + [[package]] 1899 + name = "serde" 1900 + version = "1.0.203" 1901 + source = "registry+https://github.com/rust-lang/crates.io-index" 1902 + checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 1903 + dependencies = [ 1904 + "serde_derive", 1905 + ] 1906 + 1907 + [[package]] 1908 + name = "serde_derive" 1909 + version = "1.0.203" 1910 + source = "registry+https://github.com/rust-lang/crates.io-index" 1911 + checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 1912 + dependencies = [ 1913 + "proc-macro2", 1914 + "quote", 1915 + "syn 2.0.65", 1916 + ] 1917 + 1918 + [[package]] 1919 + name = "serde_json" 1920 + version = "1.0.117" 1921 + source = "registry+https://github.com/rust-lang/crates.io-index" 1922 + checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" 1923 + dependencies = [ 1924 + "itoa", 1925 + "ryu", 1926 + "serde", 1927 + ] 1928 + 1929 + [[package]] 1930 + name = "sha1" 1931 + version = "0.10.6" 1932 + source = "registry+https://github.com/rust-lang/crates.io-index" 1933 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1934 + dependencies = [ 1935 + "cfg-if", 1936 + "cpufeatures", 1937 + "digest", 1938 + ] 1939 + 1940 + [[package]] 1941 + name = "sha2" 1942 + version = "0.10.8" 1943 + source = "registry+https://github.com/rust-lang/crates.io-index" 1944 + checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1945 + dependencies = [ 1946 + "cfg-if", 1947 + "cpufeatures", 1948 + "digest", 1949 + ] 1950 + 1951 + [[package]] 1952 + name = "sha256" 1953 + version = "1.5.0" 1954 + source = "registry+https://github.com/rust-lang/crates.io-index" 1955 + checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" 1956 + dependencies = [ 1957 + "async-trait", 1958 + "bytes", 1959 + "hex", 1960 + "sha2", 1961 + "tokio", 1962 + ] 1963 + 1964 + [[package]] 1965 + name = "signal-hook-registry" 1966 + version = "1.4.2" 1967 + source = "registry+https://github.com/rust-lang/crates.io-index" 1968 + checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1969 + dependencies = [ 1970 + "libc", 1971 + ] 1972 + 1973 + [[package]] 1974 + name = "signature" 1975 + version = "2.2.0" 1976 + source = "registry+https://github.com/rust-lang/crates.io-index" 1977 + checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 1978 + dependencies = [ 1979 + "digest", 1980 + "rand_core", 1981 + ] 1982 + 1983 + [[package]] 1984 + name = "similar-string" 1985 + version = "1.4.3" 1986 + source = "registry+https://github.com/rust-lang/crates.io-index" 1987 + checksum = "d3ac42455f28f7f9fc2ca816746b7143356f51ae195abb35d5bb4ac3808c7fa3" 1988 + 1989 + [[package]] 1990 + name = "slab" 1991 + version = "0.4.9" 1992 + source = "registry+https://github.com/rust-lang/crates.io-index" 1993 + checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1994 + dependencies = [ 1995 + "autocfg", 1996 + ] 1997 + 1998 + [[package]] 1999 + name = "smallvec" 2000 + version = "1.13.2" 2001 + source = "registry+https://github.com/rust-lang/crates.io-index" 2002 + checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 2003 + 2004 + [[package]] 2005 + name = "smartstring" 2006 + version = "1.0.1" 2007 + source = "registry+https://github.com/rust-lang/crates.io-index" 2008 + checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" 2009 + dependencies = [ 2010 + "autocfg", 2011 + "static_assertions", 2012 + "version_check", 2013 + ] 2014 + 2015 + [[package]] 2016 + name = "smol" 2017 + version = "2.0.0" 2018 + source = "registry+https://github.com/rust-lang/crates.io-index" 2019 + checksum = "e635339259e51ef85ac7aa29a1cd991b957047507288697a690e80ab97d07cad" 2020 + dependencies = [ 2021 + "async-channel 2.3.1", 2022 + "async-executor", 2023 + "async-fs", 2024 + "async-io 2.3.2", 2025 + "async-lock 3.3.0", 2026 + "async-net", 2027 + "async-process", 2028 + "blocking", 2029 + "futures-lite 2.3.0", 2030 + ] 2031 + 2032 + [[package]] 2033 + name = "socket2" 2034 + version = "0.4.10" 2035 + source = "registry+https://github.com/rust-lang/crates.io-index" 2036 + checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" 2037 + dependencies = [ 2038 + "libc", 2039 + "winapi", 2040 + ] 2041 + 2042 + [[package]] 2043 + name = "spin" 2044 + version = "0.5.2" 2045 + source = "registry+https://github.com/rust-lang/crates.io-index" 2046 + checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 2047 + 2048 + [[package]] 2049 + name = "spin" 2050 + version = "0.9.8" 2051 + source = "registry+https://github.com/rust-lang/crates.io-index" 2052 + checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 2053 + dependencies = [ 2054 + "lock_api", 2055 + ] 2056 + 2057 + [[package]] 2058 + name = "spki" 2059 + version = "0.7.3" 2060 + source = "registry+https://github.com/rust-lang/crates.io-index" 2061 + checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 2062 + dependencies = [ 2063 + "base64ct", 2064 + "der", 2065 + ] 2066 + 2067 + [[package]] 2068 + name = "sqlformat" 2069 + version = "0.2.3" 2070 + source = "registry+https://github.com/rust-lang/crates.io-index" 2071 + checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" 2072 + dependencies = [ 2073 + "itertools", 2074 + "nom", 2075 + "unicode_categories", 2076 + ] 2077 + 2078 + [[package]] 2079 + name = "sqlx" 2080 + version = "0.7.4" 2081 + source = "registry+https://github.com/rust-lang/crates.io-index" 2082 + checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" 2083 + dependencies = [ 2084 + "sqlx-core", 2085 + "sqlx-macros", 2086 + "sqlx-mysql", 2087 + "sqlx-postgres", 2088 + "sqlx-sqlite", 2089 + ] 2090 + 2091 + [[package]] 2092 + name = "sqlx-core" 2093 + version = "0.7.4" 2094 + source = "registry+https://github.com/rust-lang/crates.io-index" 2095 + checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" 2096 + dependencies = [ 2097 + "ahash", 2098 + "async-io 1.13.0", 2099 + "async-std", 2100 + "atoi", 2101 + "byteorder", 2102 + "bytes", 2103 + "crc", 2104 + "crossbeam-queue", 2105 + "either", 2106 + "event-listener 2.5.3", 2107 + "futures-channel", 2108 + "futures-core", 2109 + "futures-intrusive", 2110 + "futures-io", 2111 + "futures-util", 2112 + "hashlink", 2113 + "hex", 2114 + "indexmap", 2115 + "log", 2116 + "memchr", 2117 + "once_cell", 2118 + "paste", 2119 + "percent-encoding", 2120 + "serde", 2121 + "serde_json", 2122 + "sha2", 2123 + "smallvec", 2124 + "sqlformat", 2125 + "thiserror", 2126 + "tracing", 2127 + "url", 2128 + ] 2129 + 2130 + [[package]] 2131 + name = "sqlx-macros" 2132 + version = "0.7.4" 2133 + source = "registry+https://github.com/rust-lang/crates.io-index" 2134 + checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" 2135 + dependencies = [ 2136 + "proc-macro2", 2137 + "quote", 2138 + "sqlx-core", 2139 + "sqlx-macros-core", 2140 + "syn 1.0.109", 2141 + ] 2142 + 2143 + [[package]] 2144 + name = "sqlx-macros-core" 2145 + version = "0.7.4" 2146 + source = "registry+https://github.com/rust-lang/crates.io-index" 2147 + checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" 2148 + dependencies = [ 2149 + "async-std", 2150 + "dotenvy", 2151 + "either", 2152 + "heck 0.4.1", 2153 + "hex", 2154 + "once_cell", 2155 + "proc-macro2", 2156 + "quote", 2157 + "serde", 2158 + "serde_json", 2159 + "sha2", 2160 + "sqlx-core", 2161 + "sqlx-mysql", 2162 + "sqlx-sqlite", 2163 + "syn 1.0.109", 2164 + "tempfile", 2165 + "url", 2166 + ] 2167 + 2168 + [[package]] 2169 + name = "sqlx-mysql" 2170 + version = "0.7.4" 2171 + source = "registry+https://github.com/rust-lang/crates.io-index" 2172 + checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" 2173 + dependencies = [ 2174 + "atoi", 2175 + "base64", 2176 + "bitflags 2.5.0", 2177 + "byteorder", 2178 + "bytes", 2179 + "crc", 2180 + "digest", 2181 + "dotenvy", 2182 + "either", 2183 + "futures-channel", 2184 + "futures-core", 2185 + "futures-io", 2186 + "futures-util", 2187 + "generic-array", 2188 + "hex", 2189 + "hkdf", 2190 + "hmac", 2191 + "itoa", 2192 + "log", 2193 + "md-5", 2194 + "memchr", 2195 + "once_cell", 2196 + "percent-encoding", 2197 + "rand", 2198 + "rsa", 2199 + "serde", 2200 + "sha1", 2201 + "sha2", 2202 + "smallvec", 2203 + "sqlx-core", 2204 + "stringprep", 2205 + "thiserror", 2206 + "tracing", 2207 + "whoami", 2208 + ] 2209 + 2210 + [[package]] 2211 + name = "sqlx-postgres" 2212 + version = "0.7.4" 2213 + source = "registry+https://github.com/rust-lang/crates.io-index" 2214 + checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" 2215 + dependencies = [ 2216 + "atoi", 2217 + "base64", 2218 + "bitflags 2.5.0", 2219 + "byteorder", 2220 + "crc", 2221 + "dotenvy", 2222 + "etcetera", 2223 + "futures-channel", 2224 + "futures-core", 2225 + "futures-io", 2226 + "futures-util", 2227 + "hex", 2228 + "hkdf", 2229 + "hmac", 2230 + "home", 2231 + "itoa", 2232 + "log", 2233 + "md-5", 2234 + "memchr", 2235 + "once_cell", 2236 + "rand", 2237 + "serde", 2238 + "serde_json", 2239 + "sha2", 2240 + "smallvec", 2241 + "sqlx-core", 2242 + "stringprep", 2243 + "thiserror", 2244 + "tracing", 2245 + "whoami", 2246 + ] 2247 + 2248 + [[package]] 2249 + name = "sqlx-sqlite" 2250 + version = "0.7.4" 2251 + source = "registry+https://github.com/rust-lang/crates.io-index" 2252 + checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" 2253 + dependencies = [ 2254 + "atoi", 2255 + "flume", 2256 + "futures-channel", 2257 + "futures-core", 2258 + "futures-executor", 2259 + "futures-intrusive", 2260 + "futures-util", 2261 + "libsqlite3-sys", 2262 + "log", 2263 + "percent-encoding", 2264 + "serde", 2265 + "sqlx-core", 2266 + "tracing", 2267 + "url", 2268 + "urlencoding", 2269 + ] 2270 + 2271 + [[package]] 2272 + name = "static_assertions" 2273 + version = "1.1.0" 2274 + source = "registry+https://github.com/rust-lang/crates.io-index" 2275 + checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 2276 + 2277 + [[package]] 2278 + name = "string-builder" 2279 + version = "0.2.0" 2280 + source = "registry+https://github.com/rust-lang/crates.io-index" 2281 + checksum = "2bd10a070fb1f2796a288abec42695db4682a82b6f12ffacd60fb8d5ad3a4a12" 2282 + 2283 + [[package]] 2284 + name = "stringprep" 2285 + version = "0.1.5" 2286 + source = "registry+https://github.com/rust-lang/crates.io-index" 2287 + checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" 2288 + dependencies = [ 2289 + "unicode-bidi", 2290 + "unicode-normalization", 2291 + "unicode-properties", 2292 + ] 2293 + 2294 + [[package]] 2295 + name = "strsim" 2296 + version = "0.11.1" 2297 + source = "registry+https://github.com/rust-lang/crates.io-index" 2298 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2299 + 2300 + [[package]] 2301 + name = "subtle" 2302 + version = "2.5.0" 2303 + source = "registry+https://github.com/rust-lang/crates.io-index" 2304 + checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 2305 + 2306 + [[package]] 2307 + name = "syn" 2308 + version = "1.0.109" 2309 + source = "registry+https://github.com/rust-lang/crates.io-index" 2310 + checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 2311 + dependencies = [ 2312 + "proc-macro2", 2313 + "quote", 2314 + "unicode-ident", 2315 + ] 2316 + 2317 + [[package]] 2318 + name = "syn" 2319 + version = "2.0.65" 2320 + source = "registry+https://github.com/rust-lang/crates.io-index" 2321 + checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106" 2322 + dependencies = [ 2323 + "proc-macro2", 2324 + "quote", 2325 + "unicode-ident", 2326 + ] 2327 + 2328 + [[package]] 2329 + name = "tempfile" 2330 + version = "3.10.1" 2331 + source = "registry+https://github.com/rust-lang/crates.io-index" 2332 + checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 2333 + dependencies = [ 2334 + "cfg-if", 2335 + "fastrand 2.1.0", 2336 + "rustix 0.38.34", 2337 + "windows-sys 0.52.0", 2338 + ] 2339 + 2340 + [[package]] 2341 + name = "terminal_size" 2342 + version = "0.3.0" 2343 + source = "registry+https://github.com/rust-lang/crates.io-index" 2344 + checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" 2345 + dependencies = [ 2346 + "rustix 0.38.34", 2347 + "windows-sys 0.48.0", 2348 + ] 2349 + 2350 + [[package]] 2351 + name = "termion" 2352 + version = "2.0.3" 2353 + source = "registry+https://github.com/rust-lang/crates.io-index" 2354 + checksum = "c4648c7def6f2043b2568617b9f9b75eae88ca185dbc1f1fda30e95a85d49d7d" 2355 + dependencies = [ 2356 + "libc", 2357 + "libredox 0.0.2", 2358 + "numtoa", 2359 + "redox_termios", 2360 + ] 2361 + 2362 + [[package]] 2363 + name = "thin-vec" 2364 + version = "0.2.13" 2365 + source = "registry+https://github.com/rust-lang/crates.io-index" 2366 + checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" 2367 + 2368 + [[package]] 2369 + name = "thiserror" 2370 + version = "1.0.61" 2371 + source = "registry+https://github.com/rust-lang/crates.io-index" 2372 + checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 2373 + dependencies = [ 2374 + "thiserror-impl", 2375 + ] 2376 + 2377 + [[package]] 2378 + name = "thiserror-impl" 2379 + version = "1.0.61" 2380 + source = "registry+https://github.com/rust-lang/crates.io-index" 2381 + checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 2382 + dependencies = [ 2383 + "proc-macro2", 2384 + "quote", 2385 + "syn 2.0.65", 2386 + ] 2387 + 2388 + [[package]] 2389 + name = "tiny-keccak" 2390 + version = "2.0.2" 2391 + source = "registry+https://github.com/rust-lang/crates.io-index" 2392 + checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 2393 + dependencies = [ 2394 + "crunchy", 2395 + ] 2396 + 2397 + [[package]] 2398 + name = "tinyvec" 2399 + version = "1.6.0" 2400 + source = "registry+https://github.com/rust-lang/crates.io-index" 2401 + checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 2402 + dependencies = [ 2403 + "tinyvec_macros", 2404 + ] 2405 + 2406 + [[package]] 2407 + name = "tinyvec_macros" 2408 + version = "0.1.1" 2409 + source = "registry+https://github.com/rust-lang/crates.io-index" 2410 + checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2411 + 2412 + [[package]] 2413 + name = "tokio" 2414 + version = "1.38.0" 2415 + source = "registry+https://github.com/rust-lang/crates.io-index" 2416 + checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" 2417 + dependencies = [ 2418 + "backtrace", 2419 + "bytes", 2420 + "pin-project-lite", 2421 + ] 2422 + 2423 + [[package]] 2424 + name = "tracing" 2425 + version = "0.1.40" 2426 + source = "registry+https://github.com/rust-lang/crates.io-index" 2427 + checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 2428 + dependencies = [ 2429 + "log", 2430 + "pin-project-lite", 2431 + "tracing-attributes", 2432 + "tracing-core", 2433 + ] 2434 + 2435 + [[package]] 2436 + name = "tracing-attributes" 2437 + version = "0.1.27" 2438 + source = "registry+https://github.com/rust-lang/crates.io-index" 2439 + checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 2440 + dependencies = [ 2441 + "proc-macro2", 2442 + "quote", 2443 + "syn 2.0.65", 2444 + ] 2445 + 2446 + [[package]] 2447 + name = "tracing-core" 2448 + version = "0.1.32" 2449 + source = "registry+https://github.com/rust-lang/crates.io-index" 2450 + checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 2451 + dependencies = [ 2452 + "once_cell", 2453 + ] 2454 + 2455 + [[package]] 2456 + name = "tunesdirector" 2457 + version = "0.1.0" 2458 + dependencies = [ 2459 + "anyhow", 2460 + "async-std", 2461 + "async-walkdir", 2462 + "audiotags", 2463 + "clap", 2464 + "directories", 2465 + "edit", 2466 + "env_logger", 2467 + "fs_extra", 2468 + "futures", 2469 + "indicatif", 2470 + "jwalk", 2471 + "kdam", 2472 + "lazy_static", 2473 + "log", 2474 + "once_cell", 2475 + "pgbar", 2476 + "progressing", 2477 + "regex", 2478 + "rhai", 2479 + "sha256", 2480 + "similar-string", 2481 + "smol", 2482 + "sqlx", 2483 + "string-builder", 2484 + "walkdir", 2485 + ] 2486 + 2487 + [[package]] 2488 + name = "typenum" 2489 + version = "1.17.0" 2490 + source = "registry+https://github.com/rust-lang/crates.io-index" 2491 + checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 2492 + 2493 + [[package]] 2494 + name = "unicode-bidi" 2495 + version = "0.3.15" 2496 + source = "registry+https://github.com/rust-lang/crates.io-index" 2497 + checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 2498 + 2499 + [[package]] 2500 + name = "unicode-ident" 2501 + version = "1.0.12" 2502 + source = "registry+https://github.com/rust-lang/crates.io-index" 2503 + checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 2504 + 2505 + [[package]] 2506 + name = "unicode-normalization" 2507 + version = "0.1.23" 2508 + source = "registry+https://github.com/rust-lang/crates.io-index" 2509 + checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 2510 + dependencies = [ 2511 + "tinyvec", 2512 + ] 2513 + 2514 + [[package]] 2515 + name = "unicode-properties" 2516 + version = "0.1.1" 2517 + source = "registry+https://github.com/rust-lang/crates.io-index" 2518 + checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" 2519 + 2520 + [[package]] 2521 + name = "unicode-segmentation" 2522 + version = "1.11.0" 2523 + source = "registry+https://github.com/rust-lang/crates.io-index" 2524 + checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" 2525 + 2526 + [[package]] 2527 + name = "unicode-width" 2528 + version = "0.1.13" 2529 + source = "registry+https://github.com/rust-lang/crates.io-index" 2530 + checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" 2531 + 2532 + [[package]] 2533 + name = "unicode_categories" 2534 + version = "0.1.1" 2535 + source = "registry+https://github.com/rust-lang/crates.io-index" 2536 + checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 2537 + 2538 + [[package]] 2539 + name = "url" 2540 + version = "2.5.0" 2541 + source = "registry+https://github.com/rust-lang/crates.io-index" 2542 + checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 2543 + dependencies = [ 2544 + "form_urlencoded", 2545 + "idna", 2546 + "percent-encoding", 2547 + ] 2548 + 2549 + [[package]] 2550 + name = "urlencoding" 2551 + version = "2.1.3" 2552 + source = "registry+https://github.com/rust-lang/crates.io-index" 2553 + checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 2554 + 2555 + [[package]] 2556 + name = "utf8parse" 2557 + version = "0.2.1" 2558 + source = "registry+https://github.com/rust-lang/crates.io-index" 2559 + checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 2560 + 2561 + [[package]] 2562 + name = "value-bag" 2563 + version = "1.9.0" 2564 + source = "registry+https://github.com/rust-lang/crates.io-index" 2565 + checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" 2566 + 2567 + [[package]] 2568 + name = "vcpkg" 2569 + version = "0.2.15" 2570 + source = "registry+https://github.com/rust-lang/crates.io-index" 2571 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2572 + 2573 + [[package]] 2574 + name = "version_check" 2575 + version = "0.9.4" 2576 + source = "registry+https://github.com/rust-lang/crates.io-index" 2577 + checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2578 + 2579 + [[package]] 2580 + name = "waker-fn" 2581 + version = "1.2.0" 2582 + source = "registry+https://github.com/rust-lang/crates.io-index" 2583 + checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" 2584 + 2585 + [[package]] 2586 + name = "walkdir" 2587 + version = "2.5.0" 2588 + source = "registry+https://github.com/rust-lang/crates.io-index" 2589 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2590 + dependencies = [ 2591 + "same-file", 2592 + "winapi-util", 2593 + ] 2594 + 2595 + [[package]] 2596 + name = "wasi" 2597 + version = "0.11.0+wasi-snapshot-preview1" 2598 + source = "registry+https://github.com/rust-lang/crates.io-index" 2599 + checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2600 + 2601 + [[package]] 2602 + name = "wasite" 2603 + version = "0.1.0" 2604 + source = "registry+https://github.com/rust-lang/crates.io-index" 2605 + checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 2606 + 2607 + [[package]] 2608 + name = "wasm-bindgen" 2609 + version = "0.2.92" 2610 + source = "registry+https://github.com/rust-lang/crates.io-index" 2611 + checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 2612 + dependencies = [ 2613 + "cfg-if", 2614 + "wasm-bindgen-macro", 2615 + ] 2616 + 2617 + [[package]] 2618 + name = "wasm-bindgen-backend" 2619 + version = "0.2.92" 2620 + source = "registry+https://github.com/rust-lang/crates.io-index" 2621 + checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 2622 + dependencies = [ 2623 + "bumpalo", 2624 + "log", 2625 + "once_cell", 2626 + "proc-macro2", 2627 + "quote", 2628 + "syn 2.0.65", 2629 + "wasm-bindgen-shared", 2630 + ] 2631 + 2632 + [[package]] 2633 + name = "wasm-bindgen-futures" 2634 + version = "0.4.42" 2635 + source = "registry+https://github.com/rust-lang/crates.io-index" 2636 + checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 2637 + dependencies = [ 2638 + "cfg-if", 2639 + "js-sys", 2640 + "wasm-bindgen", 2641 + "web-sys", 2642 + ] 2643 + 2644 + [[package]] 2645 + name = "wasm-bindgen-macro" 2646 + version = "0.2.92" 2647 + source = "registry+https://github.com/rust-lang/crates.io-index" 2648 + checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 2649 + dependencies = [ 2650 + "quote", 2651 + "wasm-bindgen-macro-support", 2652 + ] 2653 + 2654 + [[package]] 2655 + name = "wasm-bindgen-macro-support" 2656 + version = "0.2.92" 2657 + source = "registry+https://github.com/rust-lang/crates.io-index" 2658 + checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 2659 + dependencies = [ 2660 + "proc-macro2", 2661 + "quote", 2662 + "syn 2.0.65", 2663 + "wasm-bindgen-backend", 2664 + "wasm-bindgen-shared", 2665 + ] 2666 + 2667 + [[package]] 2668 + name = "wasm-bindgen-shared" 2669 + version = "0.2.92" 2670 + source = "registry+https://github.com/rust-lang/crates.io-index" 2671 + checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 2672 + 2673 + [[package]] 2674 + name = "web-sys" 2675 + version = "0.3.67" 2676 + source = "registry+https://github.com/rust-lang/crates.io-index" 2677 + checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" 2678 + dependencies = [ 2679 + "js-sys", 2680 + "wasm-bindgen", 2681 + ] 2682 + 2683 + [[package]] 2684 + name = "which" 2685 + version = "4.4.2" 2686 + source = "registry+https://github.com/rust-lang/crates.io-index" 2687 + checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 2688 + dependencies = [ 2689 + "either", 2690 + "home", 2691 + "once_cell", 2692 + "rustix 0.38.34", 2693 + ] 2694 + 2695 + [[package]] 2696 + name = "whoami" 2697 + version = "1.5.1" 2698 + source = "registry+https://github.com/rust-lang/crates.io-index" 2699 + checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" 2700 + dependencies = [ 2701 + "redox_syscall 0.4.1", 2702 + "wasite", 2703 + ] 2704 + 2705 + [[package]] 2706 + name = "winapi" 2707 + version = "0.3.9" 2708 + source = "registry+https://github.com/rust-lang/crates.io-index" 2709 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2710 + dependencies = [ 2711 + "winapi-i686-pc-windows-gnu", 2712 + "winapi-x86_64-pc-windows-gnu", 2713 + ] 2714 + 2715 + [[package]] 2716 + name = "winapi-i686-pc-windows-gnu" 2717 + version = "0.4.0" 2718 + source = "registry+https://github.com/rust-lang/crates.io-index" 2719 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2720 + 2721 + [[package]] 2722 + name = "winapi-util" 2723 + version = "0.1.8" 2724 + source = "registry+https://github.com/rust-lang/crates.io-index" 2725 + checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 2726 + dependencies = [ 2727 + "windows-sys 0.52.0", 2728 + ] 2729 + 2730 + [[package]] 2731 + name = "winapi-x86_64-pc-windows-gnu" 2732 + version = "0.4.0" 2733 + source = "registry+https://github.com/rust-lang/crates.io-index" 2734 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2735 + 2736 + [[package]] 2737 + name = "windows-sys" 2738 + version = "0.48.0" 2739 + source = "registry+https://github.com/rust-lang/crates.io-index" 2740 + checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2741 + dependencies = [ 2742 + "windows-targets 0.48.5", 2743 + ] 2744 + 2745 + [[package]] 2746 + name = "windows-sys" 2747 + version = "0.52.0" 2748 + source = "registry+https://github.com/rust-lang/crates.io-index" 2749 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2750 + dependencies = [ 2751 + "windows-targets 0.52.5", 2752 + ] 2753 + 2754 + [[package]] 2755 + name = "windows-targets" 2756 + version = "0.48.5" 2757 + source = "registry+https://github.com/rust-lang/crates.io-index" 2758 + checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2759 + dependencies = [ 2760 + "windows_aarch64_gnullvm 0.48.5", 2761 + "windows_aarch64_msvc 0.48.5", 2762 + "windows_i686_gnu 0.48.5", 2763 + "windows_i686_msvc 0.48.5", 2764 + "windows_x86_64_gnu 0.48.5", 2765 + "windows_x86_64_gnullvm 0.48.5", 2766 + "windows_x86_64_msvc 0.48.5", 2767 + ] 2768 + 2769 + [[package]] 2770 + name = "windows-targets" 2771 + version = "0.52.5" 2772 + source = "registry+https://github.com/rust-lang/crates.io-index" 2773 + checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 2774 + dependencies = [ 2775 + "windows_aarch64_gnullvm 0.52.5", 2776 + "windows_aarch64_msvc 0.52.5", 2777 + "windows_i686_gnu 0.52.5", 2778 + "windows_i686_gnullvm", 2779 + "windows_i686_msvc 0.52.5", 2780 + "windows_x86_64_gnu 0.52.5", 2781 + "windows_x86_64_gnullvm 0.52.5", 2782 + "windows_x86_64_msvc 0.52.5", 2783 + ] 2784 + 2785 + [[package]] 2786 + name = "windows_aarch64_gnullvm" 2787 + version = "0.48.5" 2788 + source = "registry+https://github.com/rust-lang/crates.io-index" 2789 + checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2790 + 2791 + [[package]] 2792 + name = "windows_aarch64_gnullvm" 2793 + version = "0.52.5" 2794 + source = "registry+https://github.com/rust-lang/crates.io-index" 2795 + checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 2796 + 2797 + [[package]] 2798 + name = "windows_aarch64_msvc" 2799 + version = "0.48.5" 2800 + source = "registry+https://github.com/rust-lang/crates.io-index" 2801 + checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2802 + 2803 + [[package]] 2804 + name = "windows_aarch64_msvc" 2805 + version = "0.52.5" 2806 + source = "registry+https://github.com/rust-lang/crates.io-index" 2807 + checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 2808 + 2809 + [[package]] 2810 + name = "windows_i686_gnu" 2811 + version = "0.48.5" 2812 + source = "registry+https://github.com/rust-lang/crates.io-index" 2813 + checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2814 + 2815 + [[package]] 2816 + name = "windows_i686_gnu" 2817 + version = "0.52.5" 2818 + source = "registry+https://github.com/rust-lang/crates.io-index" 2819 + checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 2820 + 2821 + [[package]] 2822 + name = "windows_i686_gnullvm" 2823 + version = "0.52.5" 2824 + source = "registry+https://github.com/rust-lang/crates.io-index" 2825 + checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 2826 + 2827 + [[package]] 2828 + name = "windows_i686_msvc" 2829 + version = "0.48.5" 2830 + source = "registry+https://github.com/rust-lang/crates.io-index" 2831 + checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2832 + 2833 + [[package]] 2834 + name = "windows_i686_msvc" 2835 + version = "0.52.5" 2836 + source = "registry+https://github.com/rust-lang/crates.io-index" 2837 + checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 2838 + 2839 + [[package]] 2840 + name = "windows_x86_64_gnu" 2841 + version = "0.48.5" 2842 + source = "registry+https://github.com/rust-lang/crates.io-index" 2843 + checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2844 + 2845 + [[package]] 2846 + name = "windows_x86_64_gnu" 2847 + version = "0.52.5" 2848 + source = "registry+https://github.com/rust-lang/crates.io-index" 2849 + checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 2850 + 2851 + [[package]] 2852 + name = "windows_x86_64_gnullvm" 2853 + version = "0.48.5" 2854 + source = "registry+https://github.com/rust-lang/crates.io-index" 2855 + checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2856 + 2857 + [[package]] 2858 + name = "windows_x86_64_gnullvm" 2859 + version = "0.52.5" 2860 + source = "registry+https://github.com/rust-lang/crates.io-index" 2861 + checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 2862 + 2863 + [[package]] 2864 + name = "windows_x86_64_msvc" 2865 + version = "0.48.5" 2866 + source = "registry+https://github.com/rust-lang/crates.io-index" 2867 + checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2868 + 2869 + [[package]] 2870 + name = "windows_x86_64_msvc" 2871 + version = "0.52.5" 2872 + source = "registry+https://github.com/rust-lang/crates.io-index" 2873 + checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 2874 + 2875 + [[package]] 2876 + name = "zerocopy" 2877 + version = "0.7.34" 2878 + source = "registry+https://github.com/rust-lang/crates.io-index" 2879 + checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" 2880 + dependencies = [ 2881 + "zerocopy-derive", 2882 + ] 2883 + 2884 + [[package]] 2885 + name = "zerocopy-derive" 2886 + version = "0.7.34" 2887 + source = "registry+https://github.com/rust-lang/crates.io-index" 2888 + checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" 2889 + dependencies = [ 2890 + "proc-macro2", 2891 + "quote", 2892 + "syn 2.0.65", 2893 + ] 2894 + 2895 + [[package]] 2896 + name = "zeroize" 2897 + version = "1.8.1" 2898 + source = "registry+https://github.com/rust-lang/crates.io-index" 2899 + checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+48
Cargo.toml
··· 1 + [package] 2 + name = "tunesdirector" 3 + version = "0.1.0" 4 + edition = "2021" 5 + 6 + [profile.release] 7 + strip = true # Automatically strip symbols from the binary. 8 + opt-level = "z" # Optimize for size. 9 + lto = true 10 + 11 + [profile.dev.package.sqlx-macros] 12 + opt-level = 3 13 + 14 + [dependencies] 15 + async-std = { version = "1.12.0", features = [ 16 + "async-attributes", 17 + "attributes", 18 + ] } 19 + sqlx = { version = "0.7.4", features = [ 20 + "runtime-async-std", 21 + "sqlite", 22 + "macros", 23 + "migrate", 24 + ] } 25 + audiotags = "0.5.0" 26 + env_logger = "0.11.3" 27 + log = "0.4.21" 28 + async-walkdir = "1.0.0" 29 + futures = "0.3.30" 30 + jwalk = "0.8.1" 31 + clap = { version = "4.5.4", features = ["derive"] } 32 + directories = "5.0.1" 33 + anyhow = "1.0.86" 34 + fs_extra = "1.3.0" 35 + indicatif = "0.17.8" 36 + kdam = "0.5.2" 37 + progressing = "3.0.2" 38 + pgbar = "0.3.1" 39 + smol = "2.0.0" 40 + walkdir = "2.5.0" 41 + sha256 = "1.5.0" 42 + string-builder = "0.2.0" 43 + once_cell = "1.19.0" 44 + similar-string = "1.4.3" 45 + lazy_static = "1.4.0" 46 + regex = "1.10.5" 47 + rhai = "1.19.0" 48 + edit = "0.1.5"
+4
build.rs
··· 1 + fn main() { 2 + println!("cargo:rerun-if-changed=database/migrations/local"); 3 + println!("cargo:rerun-if-changed=database/migrations/destination"); 4 + }
+25
database/migrations/local/0_base.sql
··· 1 + CREATE TABLE IF NOT EXISTS state ( 2 + id INTEGER PRIMARY KEY NOT NULL, 3 + version INTEGER NOT NULL, 4 + is_external BOOL 5 + ); 6 + 7 + CREATE TABLE IF NOT EXISTS tracks ( 8 + id INTEGER PRIMARY KEY NOT NULL, 9 + track_id TEXT NOT NULL, 10 + title TEXT NOT NULL, 11 + artist TEXT NOT NULL, 12 + album TEXT NOT NULL, 13 + number INTEGER NOT NULL, 14 + disc_number INTEGER NOT NULL, 15 + disc_total INTEGER NOT NULL, 16 + file_state INTEGER NOT NULL, 17 + file_path TEXT NOT NULL, 18 + extension TEXT NOT NULL 19 + ); 20 + 21 + CREATE VIEW IF NOT EXISTS albums ( 22 + title, 23 + artist, 24 + format 25 + ) AS SELECT DISTINCT album, artist, extension FROM tracks;
+3
database/migrations/local/1_added_dirs.sql
··· 1 + CREATE TABLE IF NOT EXISTS directories ( 2 + directory TEXT PRIMARY KEY NOT NULL 3 + );
+5
database/migrations/local/2_albums_view.sql
··· 1 + CREATE VIEW IF NOT EXISTS albums ( 2 + title, 3 + artist, 4 + format 5 + ) AS SELECT DISTINCT album, artist, extension FROM tracks;
+9
database/migrations/local/3_fts.sql
··· 1 + CREATE VIRTUAL TABLE track_fts USING fts5(track_id, title, album, artist, extension, content=tracks, content_rowid=id); 2 + 3 + CREATE TRIGGER track_fts_ai_insert AFTER INSERT ON tracks BEGIN 4 + INSERT INTO track_fts(rowid, track_id, title, album, artist, extension) VALUES (new.id, new.track_id, new.title, new.album, new.artist, new.extension); 5 + END; 6 + 7 + CREATE TRIGGER track_fts_ai_delete AFTER DELETE ON tracks BEGIN 8 + INSERT INTO track_fts(track_fts, rowid, track_id, title, album, artist, extension) VALUES('delete', old.id, old.track_id, old.title, old.album, old.artist, old.extension); 9 + END;
+2
database/migrations/local/4_filter.sql
··· 1 + ALTER TABLE state 2 + ADD COLUMN filter TEXT;
+31
src/cli.rs
··· 1 + use clap::{command, Parser, Subcommand}; 2 + 3 + use crate::cmd; 4 + 5 + #[derive(Parser)] 6 + #[command(version, about, long_about = None)] 7 + #[command(propagate_version = true)] 8 + pub struct Cli { 9 + #[command(subcommand)] 10 + pub command: Commands, 11 + } 12 + 13 + #[derive(Subcommand)] 14 + pub enum Commands { 15 + /// Syncs source and destination, based on the database constructed with 'add`. 16 + Sync(cmd::sync::Args), 17 + 18 + /// Adds a directory's content to the database. 19 + Add(cmd::add::Args), 20 + 21 + /// Finds albums that are stored in more than one audio format. 22 + Dupes(cmd::dupes::Args), 23 + 24 + /// Updates the local database with new tracks from previously added directories. 25 + Update(cmd::add::Args), 26 + 27 + /// Cleans destination of uncleanly-copied files. 28 + Clean(cmd::clean::Args), 29 + 30 + Filter(cmd::filter::Args), 31 + }
+226
src/cmd/add.rs
··· 1 + use std::collections::hash_set; 2 + 3 + use crate::cmd::*; 4 + use crate::*; 5 + use anyhow::{Context, Result}; 6 + use clap::Args as ClapArgs; 7 + use futures::{executor::block_on, future::try_join_all}; 8 + use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; 9 + use model::FileState; 10 + 11 + #[derive(ClapArgs, Debug)] 12 + pub struct Args { 13 + /// Directory in which tunesdirector will store its local database. 14 + #[arg(short, long, default_value_t = db::default_database_dir().to_str().unwrap().to_owned())] 15 + pub database_path: String, 16 + 17 + /// A path in which tunesdirector will look for music files. 18 + /// Specify more than one for multiple sources. 19 + #[arg(short, long = "source", value_name = "SOURCE", action = clap::ArgAction::Append)] 20 + pub sources: Option<Vec<String>>, 21 + 22 + /// Specifies if the database is to be written is a destination one. 23 + #[arg( 24 + long = "destination", 25 + value_name = "TRUE|FALSE", 26 + default_value_t = false 27 + )] 28 + pub is_destination: bool, 29 + } 30 + 31 + impl Args { 32 + pub fn validate(&self) -> Result<(), error::Error> { 33 + if let None = self.sources { 34 + return Err(error::Error::ValidationError( 35 + "missing source(s)".to_owned(), 36 + )); 37 + }; 38 + 39 + Ok(()) 40 + } 41 + } 42 + 43 + pub async fn run(args: Args, update: bool) -> Result<()> { 44 + let val_res = args.validate(); 45 + 46 + match update { 47 + true => {} 48 + false => val_res?, 49 + }; 50 + 51 + log::debug!("CLI args: {:?}", args); 52 + 53 + // Open a database 54 + let db = db::Instance::new(&args.database_path, args.is_destination) 55 + .await 56 + .with_context(|| "Cannot open local database instance")?; 57 + 58 + let sources = match update { 59 + false => args.sources.unwrap(), 60 + true => db 61 + .directories() 62 + .await 63 + .with_context(|| "Cannot fetch track directories from database")?, 64 + }; 65 + 66 + let mp = MultiProgress::new(); 67 + let mut tracks = vec![]; 68 + 69 + for source in &sources { 70 + for i in db 71 + .track_paths_from_dir(source.clone()) 72 + .await 73 + .with_context(|| "Cannot fetch track paths from directory")? 74 + { 75 + tracks.push(i) 76 + } 77 + } 78 + 79 + let tracks_set: hash_set::HashSet<String> = tracks.into_iter().collect(); 80 + 81 + let res = try_join_all( 82 + sources 83 + .into_iter() 84 + .map(|source| { 85 + traverse_and_add_param(&db, &mp, source, { 86 + let tracks_set = tracks_set.clone(); 87 + 88 + move |path, db, pb| match update { 89 + false => add_dupe_checker(path, db, pb), 90 + true => Ok(!tracks_set.contains(path)), 91 + } 92 + }) 93 + }) 94 + .collect::<Vec<_>>(), 95 + ) 96 + .await?; 97 + 98 + let totals = res.iter().fold((0, 0), |acc, r| (acc.0 + r.0, acc.1 + r.1)); 99 + 100 + match totals.1 { 101 + 0 => log::info!("Imported {} tracks", totals.0), 102 + _ => match update { 103 + false => log::info!( 104 + "Imported {} new tracks, but found {} duplicates", 105 + totals.0, 106 + totals.1 107 + ), 108 + true => {} 109 + }, 110 + }; 111 + 112 + if update { 113 + let prog = mp.add( 114 + ProgressBar::new_spinner() 115 + .with_message("Looking for tracks not on disk anymore...") 116 + .with_style(ProgressStyle::default_spinner()), 117 + ); 118 + 119 + prog.enable_steady_tick(std::time::Duration::from_millis(50)); 120 + 121 + // look for files in db that are not on the filesystem anymore 122 + let track_iter = db 123 + .tracks_iter() 124 + .await 125 + .with_context(|| "Cannot create an iterator for existing tracks in database")?; 126 + 127 + while let Ok(track) = track_iter.recv().await { 128 + let track = track?; 129 + 130 + let tp = std::path::Path::new(&track.file_path); 131 + 132 + match tp.exists() { 133 + true => {} 134 + false => { 135 + prog.set_message(format!( 136 + "Found track in database not existing on filesystem, deleting: {}", 137 + track.file_path, 138 + )); 139 + 140 + db.delete(track.id) 141 + .await 142 + .with_context(|| "Cannot delete track from database.")?; 143 + } 144 + } 145 + } 146 + 147 + prog.finish(); 148 + mp.remove(&prog); 149 + } 150 + 151 + Ok(()) 152 + } 153 + 154 + fn add_dupe_checker(path: &String, db: &db::Instance, pb: &indicatif::ProgressBar) -> Result<bool> { 155 + block_on(async { 156 + if db.exists(path.clone()).await? { 157 + pb.set_message(format!("Found duplicate at {}", path.clone())); 158 + return Ok(true); 159 + } 160 + 161 + return Ok(false); 162 + }) 163 + } 164 + 165 + pub(crate) async fn traverse_and_add_param<F>( 166 + db: &db::Instance, 167 + mp: &MultiProgress, 168 + path: String, 169 + dupe_checker: F, 170 + ) -> Result<(u64, u64)> 171 + where 172 + F: FnOnce(&String, &db::Instance, &indicatif::ProgressBar) -> Result<bool> + Clone, 173 + { 174 + let paths = fs::traverse(&path).await; 175 + 176 + let base_msg = format!("Reading {}...", path.clone()); 177 + 178 + let prog = mp.add( 179 + ProgressBar::new_spinner() 180 + .with_message(base_msg.clone()) 181 + .with_style(ProgressStyle::default_spinner()), 182 + ); 183 + 184 + prog.enable_steady_tick(std::time::Duration::from_millis(50)); 185 + 186 + let mut new_tracks = 0; 187 + let mut duplicate = 0; 188 + 189 + while let Ok(p) = paths.recv().await { 190 + let p = p?.clone(); 191 + 192 + let dc = dupe_checker.clone(); 193 + if dc(&p, db, &prog)? { 194 + duplicate += 1; 195 + continue; 196 + } 197 + 198 + let tags = audiotags::Tag::new() 199 + .read_from_path(p.clone()) 200 + .with_context(|| format!("Cannot read tags from {}", p.clone()))?; 201 + 202 + let mut track: model::Track = model::RawTrack { tags, path: p }.into(); 203 + track.file_state = FileState::Copied; 204 + 205 + db.insert_track(&track) 206 + .await 207 + .with_context(|| format!("Cannot write track data to database"))?; 208 + 209 + prog.set_message(format!( 210 + "{}\nFound track: {} - {}, from {}", 211 + base_msg.clone(), 212 + track.title, 213 + track.artist, 214 + track.album 215 + )); 216 + 217 + new_tracks += 1; 218 + } 219 + 220 + prog.finish(); 221 + mp.remove(&prog); 222 + 223 + db.insert_directory(path).await?; 224 + 225 + Ok((new_tracks, duplicate)) 226 + }
+46
src/cmd/clean.rs
··· 1 + use super::error; 2 + use crate::{db, model}; 3 + use anyhow::{Context, Result}; 4 + use clap::Args as ClapArgs; 5 + 6 + #[derive(ClapArgs, Debug)] 7 + pub struct Args { 8 + #[arg(long)] 9 + pub destination: Option<String>, 10 + } 11 + 12 + impl Args { 13 + pub fn validate(&self) -> Result<(), error::Error> { 14 + if let None = self.destination { 15 + return Err(error::Error::ValidationError( 16 + "missing destination".to_owned(), 17 + )); 18 + }; 19 + 20 + Ok(()) 21 + } 22 + } 23 + 24 + pub async fn run(args: Args) -> Result<()> { 25 + args.validate()?; 26 + 27 + let dest_dir = args.destination.unwrap(); 28 + 29 + let dest_db = db::Instance::new(&dest_dir, true).await?; 30 + 31 + for track in dest_db.tracks_by_state(model::FileState::Copying).await? { 32 + log::info!( 33 + "Deleting non-cleanly copied track: {} - {}, from {}", 34 + track.title, 35 + track.artist, 36 + track.album, 37 + ); 38 + 39 + let storage = track.storage_path(&dest_dir); 40 + dest_db.delete(track.id).await?; 41 + std::fs::remove_file(storage.clone()) 42 + .with_context(|| format!("Cannot delete file {}", storage.clone()))?; 43 + } 44 + 45 + Ok(()) 46 + }
+19
src/cmd/default_filter.rhai
··· 1 + /* 2 + Hi! 3 + 4 + This is the filter file, in here you can write Rhai code to filter out tracks 5 + during the sync process. 6 + 7 + This file must contain the `filter(track)` function, which will be evaluated for 8 + every candidate copy track: return `true` to copy the track. 9 + 10 + If you want to match any String to a regex, call `regex_match(pattern, your_string)`. 11 + 12 + The `track` object that's being passed as argument is the same as `model::BaseTrack`. 13 + */ 14 + 15 + fn filter(track) { 16 + // add your filtering code here! 17 + 18 + true 19 + }
+179
src/cmd/dupes.rs
··· 1 + use crate::db; 2 + use anyhow::{Context, Result}; 3 + use clap::Args as ClapArgs; 4 + use std::collections::{hash_map, hash_set}; 5 + 6 + #[derive(ClapArgs, Debug)] 7 + pub struct Args { 8 + /// Directory in which tunesdirector will store its local database. 9 + #[arg(short, long, default_value_t = db::default_database_dir().to_str().unwrap().to_owned())] 10 + pub database_path: String, 11 + } 12 + 13 + pub async fn run(args: Args) -> Result<()> { 14 + // Open a database 15 + let db = db::Instance::new(&args.database_path, false) 16 + .await 17 + .with_context(|| "Cannot open local database instance")?; 18 + 19 + let albums = db.albums().await.with_context(|| "Cannot fetch albums")?; 20 + 21 + // (Artist, album keywords) 22 + let keywords: Vec<(String, Vec<String>)> = albums 23 + .into_iter() 24 + .map(|a| { 25 + let album = split_after_parenthesis(a.title.clone()); 26 + 27 + ( 28 + clean(a.artist.clone()), 29 + album 30 + .split_whitespace() 31 + .into_iter() 32 + .filter_map(|word| { 33 + if word.len() > 3 { 34 + return Some(clean(word.to_owned())); 35 + } 36 + 37 + return None; 38 + }) 39 + .collect::<Vec<_>>(), 40 + ) 41 + }) 42 + .collect(); 43 + 44 + let mut dedup: hash_set::HashSet<(String, String)> = hash_set::HashSet::new(); 45 + 46 + for keyword in keywords { 47 + if keyword.1.len() == 0 { 48 + continue; 49 + } 50 + 51 + log::debug!("looking for: {:?}", keyword.1); 52 + // Album name : (track id, format) 53 + let albums = db 54 + .fuzzy_find_album(&keyword.1) 55 + .await 56 + .with_context(|| "Could not fuzzy find album")? 57 + .into_iter() 58 + .map(|e| { 59 + ( 60 + split_after_parenthesis(e.1), 61 + (split_after_parenthesis(e.2), e.0), 62 + ) 63 + }) 64 + .collect::<hash_map::HashMap<_, _>>(); 65 + 66 + log::debug!("found {} entries", albums.len()); 67 + if albums.is_empty() { 68 + log::debug!("did not find any album for fuzzy query"); 69 + continue; 70 + } 71 + 72 + if albums.len() <= 1 { 73 + log::debug!("found just one album for query, likely no dupe"); 74 + continue; 75 + } 76 + 77 + let album_names: Vec<String> = albums.keys().into_iter().cloned().collect(); 78 + 79 + for (album_name, metadata) in &albums { 80 + let mut options = album_names.clone(); 81 + if let Some(pos) = options.iter().position(|x| **x == *album_name) { 82 + options.remove(pos); 83 + } 84 + 85 + let (format, _) = metadata; 86 + 87 + match similar_string::find_best_similarity(album_name.clone(), &options) { 88 + Some(res) => { 89 + let (dupe_name, score) = res; 90 + let an_trim = album_name.trim().to_string(); 91 + 92 + match dedup.get(&(an_trim.clone(), dupe_name.clone())) { 93 + Some(_) => continue, 94 + None => { 95 + dedup.insert((an_trim.clone(), dupe_name.clone())); 96 + } 97 + }; 98 + 99 + if res.1 < 0.6 { 100 + continue; 101 + } 102 + 103 + let dupe_meta = albums.get(&dupe_name).unwrap(); 104 + 105 + let dupe_path = db 106 + .tracks_by_id(vec![dupe_meta.1.clone()]) 107 + .await 108 + .with_context(|| "Cannot fetch dupe track")? 109 + .first() 110 + .unwrap() 111 + .file_path 112 + .clone(); 113 + 114 + // parse dupe path and get the directory containing it 115 + let dupe_path = std::path::Path::new(&dupe_path).parent().unwrap(); 116 + let dupe_path = dupe_path.to_str().unwrap(); 117 + 118 + println!( 119 + "Maybe duplicate:\n\t\"{}\": \"{}\" (confidence: {:.1}%) \n\tat path {}, format {}", 120 + album_name.trim(), 121 + dupe_name.trim(), 122 + score * (100 as f64), 123 + dupe_path, 124 + format, 125 + ); 126 + } 127 + None => {} 128 + } 129 + } 130 + } 131 + 132 + let std_duplicates = db 133 + .duplicate_albums() 134 + .await 135 + .with_context(|| "Cannot fetch duplicate albums")?; 136 + 137 + for sd in std_duplicates { 138 + let (album, amt) = sd; 139 + let paths = db 140 + .album_paths(&album.title, &album.artist) 141 + .await 142 + .with_context(|| "Cannot fetch duplicate album")?; 143 + 144 + println!(r#"Found "{}" in {} formats:"#, album.title, amt); 145 + 146 + for ele in paths { 147 + let (path, ext) = ele; 148 + println!("\t {}: {}", path, ext); 149 + } 150 + } 151 + 152 + Ok(()) 153 + } 154 + 155 + fn clean(s: String) -> String { 156 + s.replace("(", " ") 157 + .replace(")", " ") 158 + .replace(":", " ") 159 + .replace(r#"'"#, r#" "#) 160 + .replace(".", " ") 161 + .replace("<", " ") 162 + .replace(">", " ") 163 + .replace(",", " ") 164 + .replace("-", " ") 165 + .replace("[", " ") 166 + .replace("]", " ") 167 + .replace("?", " ") 168 + .replace("/", " ") 169 + .replace("!", " ") 170 + } 171 + 172 + fn split_after_parenthesis(s: String) -> String { 173 + let split: Vec<(usize, char)> = s.char_indices().filter(|e| e.1 == '(').collect(); 174 + 175 + match split.len() { 176 + 0 => s, 177 + _ => s.clone().split_at(split.first().unwrap().0).0.to_string(), 178 + } 179 + }
+59
src/cmd/error.rs
··· 1 + use crate::*; 2 + 3 + #[derive(Debug)] 4 + pub enum Error { 5 + ValidationError(String), 6 + DatabaseError(sqlx::Error), 7 + IOError(std::io::Error), 8 + CopyError(fs_extra::error::Error), 9 + MediaFileError(audiotags::Error), 10 + FilterError(filter::Error), 11 + } 12 + 13 + impl std::fmt::Display for Error { 14 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 + match self { 16 + Error::ValidationError(ve) => write!(f, "validation error: {:?}", ve), 17 + Error::DatabaseError(de) => match de { 18 + sqlx::Error::Database(de) => write!(f, "database error: {}", de.message()), 19 + sqlx::Error::Protocol(pe) => write!(f, "database error: {pe}"), 20 + other => write!(f, "database error: {:?}", other), 21 + }, 22 + Error::IOError(io) => write!(f, "IO error: {:?}", io), 23 + Error::CopyError(ce) => write!(f, "file copy error kind: {:?}", ce.kind), 24 + Error::MediaFileError(mfe) => write!(f, "media file error error: {:?}", mfe), 25 + Error::FilterError(fe) => write!(f, "Filtering error: {:?}", fe), 26 + } 27 + } 28 + } 29 + 30 + impl From<sqlx::Error> for Error { 31 + fn from(value: sqlx::Error) -> Self { 32 + Self::DatabaseError(value) 33 + } 34 + } 35 + 36 + impl From<std::io::Error> for Error { 37 + fn from(value: std::io::Error) -> Self { 38 + Self::IOError(value) 39 + } 40 + } 41 + 42 + impl From<fs_extra::error::Error> for Error { 43 + fn from(value: fs_extra::error::Error) -> Self { 44 + Self::CopyError(value) 45 + } 46 + } 47 + 48 + impl From<audiotags::Error> for Error { 49 + fn from(value: audiotags::Error) -> Self { 50 + Self::MediaFileError(value) 51 + } 52 + } 53 + 54 + impl From<filter::Error> for Error { 55 + fn from(value: filter::Error) -> Self { 56 + Self::FilterError(value) 57 + } 58 + } 59 + impl std::error::Error for Error {}
+70
src/cmd/filter.rs
··· 1 + use crate::{cmd::error, db, filter}; 2 + use anyhow::{anyhow, Context, Result}; 3 + use clap::Args as ClapArgs; 4 + 5 + const DEFAULT_FILTER: &'static str = include_str!("default_filter.rhai"); 6 + 7 + #[derive(ClapArgs)] 8 + pub struct Args { 9 + /// Path where to store tunesdirector's database, as well as music files. 10 + #[arg(long)] 11 + pub destination: Option<String>, 12 + 13 + /// Read existing filters off the database. 14 + #[arg(long)] 15 + pub read: bool, 16 + 17 + /// Read filtering code from the specified file path, and store it in the database overwriting previously 18 + /// stored filters. 19 + /// This option will not open $EDITOR. 20 + #[arg(long)] 21 + pub file: Option<String>, 22 + } 23 + 24 + impl Args { 25 + pub fn validate(&self) -> Result<()> { 26 + if let None = self.destination { 27 + return Err(anyhow!(error::Error::ValidationError( 28 + "missing destination".to_owned(), 29 + ))); 30 + }; 31 + 32 + Ok(()) 33 + } 34 + } 35 + 36 + pub async fn run(args: Args) -> Result<()> { 37 + args.validate()?; 38 + 39 + let destination = args.destination.unwrap(); 40 + 41 + let dest_db = db::Instance::new(&destination, true).await?; 42 + 43 + let existing_filter = dest_db 44 + .filter() 45 + .await 46 + .with_context(|| "Cannot fetch stored filter")? 47 + .unwrap_or(DEFAULT_FILTER.to_string()); 48 + 49 + if args.read { 50 + println!("{}", existing_filter); 51 + return Ok(()); 52 + } 53 + 54 + let res = match args.file { 55 + None => edit::edit(existing_filter) 56 + .with_context(|| "Cannot open $EDITOR to edit filtering code.")?, 57 + Some(path) => { 58 + let raw = std::fs::read(path).with_context(|| "Cannot read filter code path")?; 59 + 60 + String::from_utf8(raw).unwrap() 61 + } 62 + }; 63 + 64 + filter::check(vec![res.clone()]).with_context(|| "Filtering script check failed")?; 65 + 66 + Ok(dest_db 67 + .set_filter(res) 68 + .await 69 + .with_context(|| "Cannot store filter")?) 70 + }
+6
src/cmd/mod.rs
··· 1 + pub mod add; 2 + pub mod clean; 3 + pub mod dupes; 4 + pub mod error; 5 + pub mod filter; 6 + pub mod sync;
+437
src/cmd/sync.rs
··· 1 + use crate::cmd::*; 2 + use crate::db; 3 + use crate::model; 4 + use anyhow::anyhow; 5 + use anyhow::Ok; 6 + use anyhow::{Context, Result}; 7 + use clap::Args as ClapArgs; 8 + use fs_extra::file::{copy_with_progress, CopyOptions}; 9 + use indicatif::MultiProgress; 10 + use std::collections::hash_set; 11 + use std::os::unix::fs::MetadataExt; 12 + 13 + #[derive(ClapArgs)] 14 + pub struct Args { 15 + /// Path where to look for tunesdirector source data. 16 + #[arg(short, long, default_value_t = db::default_database_dir().to_str().unwrap().to_owned())] 17 + pub database_path: String, 18 + 19 + /// Path where to store tunesdirector's database, as well as music files. 20 + #[arg(long)] 21 + pub destination: Option<String>, 22 + 23 + /// Do not delete from destination tracks that are not contained in the local database instance. 24 + #[arg(long, default_value_t = false)] 25 + pub no_delete: bool, 26 + 27 + /// Do not attempt to copy any file or write any change on destination database, just 28 + /// print what tracks would be copied over. 29 + #[arg(long, default_value_t = false)] 30 + pub dry_run: bool, 31 + } 32 + 33 + impl Args { 34 + pub fn validate(&self) -> Result<()> { 35 + if let None = self.destination { 36 + return Err(anyhow!(error::Error::ValidationError( 37 + "missing destination".to_owned(), 38 + ))); 39 + }; 40 + 41 + Ok(()) 42 + } 43 + } 44 + 45 + pub async fn run(args: Args) -> Result<()> { 46 + args.validate()?; 47 + 48 + let dest_dir = args.destination.unwrap(); 49 + 50 + let local_db = db::Instance::new(&args.database_path, false) 51 + .await 52 + .with_context(|| "Cannot open local database instance")?; 53 + 54 + let dest_db = db::Instance::new(&dest_dir, true) 55 + .await 56 + .with_context(|| "Cannot open destination database instance")?; 57 + 58 + let diff = db::diff(&local_db, &dest_db) 59 + .await 60 + .with_context(|| "Cannot calculate difference between local and destination databases")?; 61 + 62 + let mut reverse_diff = db::diff(&dest_db, &local_db) 63 + .await 64 + .with_context(|| "Cannot calculate difference between destination and local databases")?; 65 + 66 + let raw_filter = local_db 67 + .filter() 68 + .await 69 + .with_context(|| "Could not fetch filters.")?; 70 + 71 + let filters = match raw_filter { 72 + Some(raw_filter) => Some( 73 + crate::filter::evaluate(vec![raw_filter]) 74 + .with_context(|| "Could not evaluate filters")?, 75 + ), 76 + None => None, 77 + }; 78 + 79 + // find any filtered tracks that were already copied 80 + reverse_diff.append(&mut diff_databases(&local_db, &dest_db, filters.as_ref(), false).await?); 81 + 82 + // now filter out all tracks to copy by using the filters 83 + let diff = filter_tracks_by_id(filters.as_ref(), &local_db, diff).await?; 84 + 85 + if args.dry_run { 86 + dry_run_copy(&local_db, &dest_dir, diff, filters.as_ref()).await?; 87 + dry_run_delete(&dest_db, &dest_dir, reverse_diff, filters.as_ref()).await?; 88 + return Ok(()); 89 + } 90 + 91 + if !args.no_delete { 92 + run_delete(&dest_db, &dest_dir, reverse_diff, filters.as_ref()).await? 93 + } 94 + 95 + run_copy(&local_db, &dest_db, &dest_dir, diff, filters.as_ref()).await?; 96 + 97 + Ok(()) 98 + } 99 + 100 + fn progress_bar(size: u64, style: indicatif::ProgressStyle) -> indicatif::ProgressBar { 101 + let bar = indicatif::ProgressBar::new(size); 102 + bar.set_style(style); 103 + 104 + bar 105 + } 106 + 107 + fn total_style() -> indicatif::ProgressStyle { 108 + indicatif::ProgressStyle::with_template( 109 + "Total progress:\n[{percent}% {wide_bar:.green}] {human_pos}/{human_len} {elapsed}\n\n", 110 + ) 111 + .unwrap() 112 + .progress_chars("##-") 113 + } 114 + 115 + fn delete_style() -> indicatif::ProgressStyle { 116 + indicatif::ProgressStyle::with_template( 117 + "Deleting old tracks:\n[{percent}% {wide_bar:.green}] {human_pos}/{human_len} {elapsed}\n\n", 118 + ) 119 + .unwrap() 120 + .progress_chars("##-") 121 + } 122 + 123 + fn track_style() -> indicatif::ProgressStyle { 124 + indicatif::ProgressStyle::with_template( 125 + "{msg}\n[{percent}% {wide_bar:.green}] {decimal_bytes:>7}/{decimal_total_bytes:7} {elapsed}\n\n", 126 + ) 127 + .unwrap() 128 + .progress_chars("##-") 129 + } 130 + 131 + async fn diff_databases( 132 + source: &db::Instance, 133 + destination: &db::Instance, 134 + filters: Option<&Vec<crate::filter::ScriptRuntime>>, 135 + delete: bool, 136 + ) -> Result<Vec<String>> { 137 + let local_tracks = source.tracks_by_state(model::FileState::Copied).await?; 138 + 139 + let local_tracks = filter_tracks(local_tracks, filters, delete)?; 140 + 141 + let dest_tracks = destination 142 + .tracks_by_state(model::FileState::Copied) 143 + .await?; 144 + 145 + let src_ids: hash_set::HashSet<String> = local_tracks 146 + .clone() 147 + .into_iter() 148 + .map(|t| t.track_id) 149 + .collect(); 150 + 151 + let dst_ids: hash_set::HashSet<String> = dest_tracks 152 + .clone() 153 + .into_iter() 154 + .map(|t| t.track_id) 155 + .collect(); 156 + 157 + log::debug!("local {} dest {}", src_ids.len(), dst_ids.len()); 158 + 159 + let d: hash_set::HashSet<&String> = dst_ids.difference(&src_ids).collect(); 160 + 161 + Ok(d.into_iter().map(|e| e.clone()).collect()) 162 + } 163 + 164 + async fn filter_tracks_by_id( 165 + filters: Option<&Vec<crate::filter::ScriptRuntime>>, 166 + db: &db::Instance, 167 + ids: Vec<String>, 168 + ) -> Result<Vec<String>> { 169 + let tracks = db.tracks_by_id(ids).await?; 170 + 171 + let tracks = filter_tracks(tracks, filters, false)?; 172 + 173 + Ok(tracks.into_iter().map(|t| t.track_id).collect()) 174 + } 175 + 176 + fn filter_tracks( 177 + raw_tracks: Vec<model::Track>, 178 + filters: Option<&Vec<crate::filter::ScriptRuntime>>, 179 + delete: bool, 180 + ) -> Result<Vec<model::Track>> { 181 + if let Some(filters) = filters { 182 + let mut filter_res = vec![]; 183 + 184 + for f in filters { 185 + let base_tracks = raw_tracks 186 + .clone() 187 + .into_iter() 188 + .map(|t| Into::<model::BaseTrack>::into(t)) 189 + .collect(); 190 + 191 + filter_res = f.run(base_tracks)?; 192 + } 193 + 194 + return Ok(raw_tracks 195 + .into_iter() 196 + .enumerate() 197 + .filter_map(|elem| { 198 + let (idx, track) = elem; 199 + 200 + if filter_res[idx] && !delete { 201 + return None; 202 + } 203 + 204 + return Some(track); 205 + }) 206 + .collect()); 207 + } 208 + 209 + Ok(raw_tracks) 210 + } 211 + 212 + async fn run_copy( 213 + local_db: &db::Instance, 214 + dest_db: &db::Instance, 215 + dest_dir: &String, 216 + diff: Vec<String>, 217 + filters: Option<&Vec<crate::filter::ScriptRuntime>>, 218 + ) -> Result<()> { 219 + let diff_len = diff.len(); 220 + 221 + let tracks = filter_tracks( 222 + local_db 223 + .tracks_by_id(diff) 224 + .await 225 + .with_context(|| "Cannot get tracks from local database")?, 226 + filters, 227 + false, 228 + )?; 229 + 230 + // Copy tracks 231 + let mp = MultiProgress::new(); 232 + 233 + let total_bar = mp.add(progress_bar(diff_len as u64, total_style())); 234 + 235 + total_bar.tick(); 236 + 237 + for track in tracks { 238 + copy(track, &dest_db, &dest_dir, &mp).await?; 239 + total_bar.inc(1); 240 + } 241 + 242 + total_bar.finish(); 243 + 244 + Ok(()) 245 + } 246 + 247 + async fn dry_run_copy( 248 + local_db: &db::Instance, 249 + dest_dir: &String, 250 + diff: Vec<String>, 251 + filters: Option<&Vec<crate::filter::ScriptRuntime>>, 252 + ) -> Result<()> { 253 + let tracks = filter_tracks( 254 + local_db 255 + .tracks_by_id(diff) 256 + .await 257 + .with_context(|| "Cannot get tracks from local database")?, 258 + filters, 259 + false, 260 + )?; 261 + 262 + for track in tracks { 263 + let track_storage_path = track.storage_path(&dest_dir); 264 + 265 + log::info!("Will copy {} to {}", track.file_path, track_storage_path); 266 + } 267 + 268 + Ok(()) 269 + } 270 + 271 + async fn dry_run_delete( 272 + dest_db: &db::Instance, 273 + dest_dir: &String, 274 + diff: Vec<String>, 275 + filters: Option<&Vec<crate::filter::ScriptRuntime>>, 276 + ) -> Result<()> { 277 + let tracks = filter_tracks( 278 + dest_db 279 + .tracks_by_id(diff) 280 + .await 281 + .with_context(|| "Cannot get tracks from destination database")?, 282 + filters, 283 + true, 284 + )?; 285 + 286 + for track in tracks { 287 + let track_storage_path = track.storage_path(&dest_dir); 288 + 289 + log::info!("Will delete {}", track_storage_path) 290 + } 291 + 292 + Ok(()) 293 + } 294 + 295 + async fn run_delete( 296 + dest_db: &db::Instance, 297 + dest_dir: &String, 298 + diff: Vec<String>, 299 + filters: Option<&Vec<crate::filter::ScriptRuntime>>, 300 + ) -> Result<()> { 301 + let diff_len = diff.len(); 302 + 303 + if diff_len == 0 { 304 + return Ok(()); 305 + } 306 + 307 + let tracks = filter_tracks( 308 + dest_db 309 + .tracks_by_id(diff) 310 + .await 311 + .with_context(|| "Cannot get tracks from destination database")?, 312 + filters, 313 + true, 314 + )?; 315 + 316 + let mp = MultiProgress::new(); 317 + 318 + let total_bar = mp.add(progress_bar(diff_len as u64, delete_style())); 319 + 320 + total_bar.tick(); 321 + 322 + for track in tracks { 323 + delete(track, &dest_db, &dest_dir, &mp).await?; 324 + total_bar.inc(1); 325 + } 326 + 327 + total_bar.finish(); 328 + 329 + Ok(()) 330 + } 331 + 332 + async fn delete( 333 + track: model::Track, 334 + dest_db: &db::Instance, 335 + dest_dir: &String, 336 + mp: &indicatif::MultiProgress, 337 + ) -> Result<()> { 338 + let track_storage_path = track.storage_path(&dest_dir); 339 + 340 + let bar = mp.add( 341 + progress_bar(1, track_style()).with_message(format!("Deleting: {}", track_storage_path)), 342 + ); 343 + 344 + dest_db.delete(track.id).await?; 345 + std::fs::remove_file(track_storage_path.clone()) 346 + .with_context(|| format!("Cannot delete file {}", track_storage_path.clone()))?; 347 + 348 + bar.inc(1); 349 + 350 + mp.remove(&bar); 351 + 352 + Ok(()) 353 + } 354 + 355 + async fn copy( 356 + track: model::Track, 357 + dest_db: &db::Instance, 358 + dest_dir: &String, 359 + mp: &indicatif::MultiProgress, 360 + ) -> Result<()> { 361 + let track_storage_path = track.storage_path(&dest_dir); 362 + let sp = std::path::Path::new(&track_storage_path); 363 + 364 + let parent = sp 365 + .parent() 366 + .with_context(|| "Cannot obtain base destination directory")?; 367 + 368 + // step 1: add an in-flight copy to the destination database 369 + let mut dest_track = track.clone(); 370 + dest_track.file_state = crate::model::FileState::Copying; 371 + dest_db 372 + .insert_track(&dest_track) 373 + .await 374 + .with_context(|| "Cannot insert in-progress copying track in destination database")?; 375 + 376 + // step 2: actually copy the track 377 + std::fs::create_dir_all(parent).with_context(|| { 378 + format!( 379 + "Cannot create destination directory tree {}", 380 + parent.to_str().unwrap() 381 + ) 382 + })?; 383 + 384 + let orig_file_path = std::path::Path::new(&track.file_path); 385 + let orig_file = std::fs::File::open(orig_file_path).with_context(|| { 386 + format!( 387 + "Cannot open origin file {}", 388 + orig_file_path.to_str().unwrap() 389 + ) 390 + })?; 391 + 392 + let orig_file_meta = orig_file.metadata().with_context(|| { 393 + format!( 394 + "Cannot obtain metadata information of {}", 395 + orig_file_path.to_str().unwrap() 396 + ) 397 + })?; 398 + 399 + let bar = mp.add( 400 + progress_bar(orig_file_meta.size(), track_style()).with_message(format!( 401 + "Copying: {}\nTo: {}", 402 + track.file_path, track_storage_path 403 + )), 404 + ); 405 + 406 + let opts = CopyOptions::new().overwrite(true); 407 + 408 + match copy_with_progress( 409 + track.file_path.clone(), 410 + track_storage_path.clone(), 411 + &opts, 412 + |ph| { 413 + bar.set_position(ph.copied_bytes); 414 + }, 415 + ) { 416 + std::result::Result::Ok(_) => {} 417 + Err(err) => { 418 + return Err(error::Error::CopyError(err)).with_context(|| { 419 + format!("Cannot copy {} to {}", track.file_path, track_storage_path) 420 + }); 421 + } 422 + }; 423 + // .with_context(|| format!("Cannot copy {} to {}", track.file_path, track_storage_path))?; 424 + 425 + // step 3: update the destination track with the new state 426 + dest_track.file_state = crate::model::FileState::Copied; 427 + dest_db 428 + .insert_track(&dest_track) 429 + .await 430 + .with_context(|| "Cannot insert copy finished track in destination database")?; 431 + 432 + bar.finish(); 433 + 434 + mp.remove(&bar); 435 + 436 + Ok(()) 437 + }
+454
src/db/instance.rs
··· 1 + use async_std::channel::{Receiver, Sender}; 2 + use futures::StreamExt; 3 + use sqlx::{migrate::Migrator, sqlite::SqliteConnectOptions, Error, Row, SqlitePool}; 4 + 5 + use crate::model; 6 + 7 + static MIGRATOR: Migrator = sqlx::migrate!("database/migrations/local"); 8 + 9 + static DATABASE_DEFAULT_NAME: &str = "tunesdirector.db"; 10 + 11 + pub struct Instance { 12 + pool: SqlitePool, 13 + } 14 + 15 + impl Instance { 16 + pub async fn new(database_path: &str, is_external: bool) -> Result<Instance, sqlx::Error> { 17 + let db_path = std::path::Path::new(database_path); 18 + 19 + if !db_path.is_dir() { 20 + return Err(Error::Protocol(format!( 21 + "{database_path} is not a directory" 22 + ))); 23 + } 24 + 25 + let mut db_path = db_path.to_path_buf(); 26 + db_path.push(DATABASE_DEFAULT_NAME); 27 + 28 + let db_path = db_path.to_str().unwrap(); 29 + 30 + let sqlite_conn_str = ("sqlite:".to_owned()) + db_path; 31 + log::debug!("database path: {sqlite_conn_str}"); 32 + 33 + let pool = SqlitePool::connect_with( 34 + SqliteConnectOptions::new() 35 + .create_if_missing(true) 36 + .filename(db_path), 37 + ) 38 + .await?; 39 + 40 + MIGRATOR.run(&pool).await?; 41 + 42 + let i = Instance { pool }; 43 + 44 + match crate::db::lib::is_initialized(&i.pool).await { 45 + Ok(_) => (), 46 + Err(err) => { 47 + match err { 48 + Error::RowNotFound => i.initialize_state(is_external).await?, 49 + err => return Err(err), 50 + }; 51 + () 52 + } 53 + }; 54 + 55 + if crate::db::lib::is_external(&i.pool).await? != is_external { 56 + return Err(Error::Protocol(format!( 57 + "database is marked as non-external, but it is", 58 + ))); 59 + } 60 + 61 + Ok(i) 62 + } 63 + 64 + pub async fn initialize_state(&self, is_external: bool) -> Result<(), Error> { 65 + let mut conn = self.pool.acquire().await?; 66 + 67 + sqlx::query!( 68 + r#" 69 + INSERT INTO state ( 70 + version, 71 + is_external 72 + ) VALUES ( 73 + ?1, 74 + ?2 75 + ); 76 + "#, 77 + "1.0", 78 + is_external, 79 + ) 80 + .execute(&mut *conn) 81 + .await?; 82 + 83 + Ok(()) 84 + } 85 + 86 + pub async fn exists(&self, path: String) -> Result<bool, Error> { 87 + let mut conn = self.pool.acquire().await?; 88 + 89 + match sqlx::query!( 90 + r#" 91 + SELECT id FROM tracks WHERE file_path = ?1; 92 + "#, 93 + path, 94 + ) 95 + .fetch_one(&mut *conn) 96 + .await 97 + { 98 + Ok(_) => Ok(true), 99 + Err(err) => match err { 100 + Error::RowNotFound => Ok(false), 101 + rest => Err(rest), 102 + }, 103 + } 104 + } 105 + 106 + pub async fn insert_track(&self, track: &model::Track) -> Result<(), Error> { 107 + let mut conn = self.pool.acquire().await?; 108 + 109 + sqlx::query!( 110 + r#" 111 + INSERT OR REPLACE INTO tracks ( 112 + track_id, 113 + title, 114 + artist, 115 + album, 116 + number, 117 + file_path, 118 + disc_number, 119 + disc_total, 120 + file_state, 121 + extension 122 + ) VALUES ( 123 + ?1, 124 + ?2, 125 + ?3, 126 + ?4, 127 + ?5, 128 + ?6, 129 + ?7, 130 + ?8, 131 + ?9, 132 + ?10 133 + ); 134 + "#, 135 + track.track_id, 136 + track.title, 137 + track.artist, 138 + track.album, 139 + track.number, 140 + track.file_path, 141 + track.disc_number, 142 + track.disc_total, 143 + track.file_state, 144 + track.extension, 145 + ) 146 + .execute(&mut *conn) 147 + .await?; 148 + 149 + Ok(()) 150 + } 151 + 152 + pub async fn track_ids_by_state(&self, state: model::FileState) -> Result<Vec<String>, Error> { 153 + let mut conn = self.pool.acquire().await?; 154 + 155 + Ok(sqlx::query!( 156 + r#" 157 + SELECT track_id FROM tracks WHERE file_state = ?1; 158 + "#, 159 + state, 160 + ) 161 + .fetch_all(&mut *conn) 162 + .await? 163 + .into_iter() 164 + .map(|t| t.track_id) 165 + .collect::<Vec<String>>()) 166 + } 167 + 168 + pub async fn tracks_by_id(&self, ids: Vec<String>) -> Result<Vec<model::Track>, Error> { 169 + let mut conn = self.pool.acquire().await?; 170 + 171 + let ids_joined = ids 172 + .into_iter() 173 + .map(|mut id| { 174 + id.insert_str(0, "'"); 175 + id.push_str("'"); 176 + 177 + id 178 + }) 179 + .collect::<Vec<_>>() 180 + .join(","); 181 + 182 + let query = format!("select * from tracks where track_id in ({});", ids_joined); 183 + let rows = sqlx::query(&query).fetch_all(&mut *conn).await?; 184 + 185 + Ok(rows 186 + .into_iter() 187 + .map(|r| model::Track { 188 + id: r.get("id"), 189 + track_id: r.get("track_id"), 190 + title: r.get("title"), 191 + artist: r.get("artist"), 192 + album: r.get("album"), 193 + number: r.get("number"), 194 + file_path: r.get("file_path"), 195 + disc_number: r.get("disc_number"), 196 + disc_total: r.get("disc_total"), 197 + file_state: r.get("file_state"), 198 + extension: r.get("extension"), 199 + }) 200 + .collect()) 201 + } 202 + 203 + pub async fn tracks_by_state( 204 + &self, 205 + state: model::FileState, 206 + ) -> Result<Vec<model::Track>, Error> { 207 + let mut conn = self.pool.acquire().await?; 208 + 209 + Ok(sqlx::query!( 210 + r#" 211 + SELECT * FROM tracks WHERE file_state = ?1; 212 + "#, 213 + state, 214 + ) 215 + .fetch_all(&mut *conn) 216 + .await? 217 + .into_iter() 218 + .map(|r| model::Track { 219 + id: r.id, 220 + track_id: r.track_id, 221 + title: r.title, 222 + artist: r.artist, 223 + album: r.album, 224 + number: r.number, 225 + file_path: r.file_path, 226 + disc_number: r.disc_number, 227 + disc_total: r.disc_total, 228 + file_state: r.file_state.into(), 229 + extension: r.extension, 230 + }) 231 + .collect::<Vec<model::Track>>()) 232 + } 233 + 234 + pub async fn delete(&self, id: i64) -> Result<(), Error> { 235 + let mut conn = self.pool.acquire().await?; 236 + 237 + sqlx::query!( 238 + r#" 239 + DELETE FROM tracks WHERE id = ?1; 240 + "#, 241 + id, 242 + ) 243 + .execute(&mut *conn) 244 + .await?; 245 + 246 + Ok(()) 247 + } 248 + 249 + pub async fn directories(&self) -> Result<Vec<String>, Error> { 250 + let mut conn = self.pool.acquire().await?; 251 + 252 + Ok(sqlx::query!(r#"SELECT * FROM directories;"#) 253 + .fetch_all(&mut *conn) 254 + .await? 255 + .into_iter() 256 + .map(|e| e.directory) 257 + .collect()) 258 + } 259 + 260 + pub async fn insert_directory(&self, directory: String) -> Result<(), Error> { 261 + let mut conn = self.pool.acquire().await?; 262 + 263 + sqlx::query!( 264 + r#"INSERT OR REPLACE INTO directories (directory) VALUES (?1);"#, 265 + directory, 266 + ) 267 + .execute(&mut *conn) 268 + .await?; 269 + 270 + Ok(()) 271 + } 272 + 273 + pub async fn track_paths_from_dir(&self, directory: String) -> Result<Vec<String>, Error> { 274 + let mut conn = self.pool.acquire().await?; 275 + 276 + let directory = format!("{}?", directory); 277 + Ok(sqlx::query!( 278 + r#"SELECT file_path FROM tracks where file_path LIKE ?1"#, 279 + directory, 280 + ) 281 + .fetch_all(&mut *conn) 282 + .await? 283 + .into_iter() 284 + .map(|e| e.file_path) 285 + .collect()) 286 + } 287 + 288 + pub async fn albums(&self) -> Result<Vec<model::Album>, Error> { 289 + let mut conn = self.pool.acquire().await?; 290 + 291 + Ok(sqlx::query!( 292 + r#" 293 + SELECT * FROM albums; 294 + "# 295 + ) 296 + .fetch_all(&mut *conn) 297 + .await? 298 + .into_iter() 299 + .map(|e| model::Album { 300 + title: e.title, 301 + artist: e.artist, 302 + format: e.format, 303 + }) 304 + .collect()) 305 + } 306 + 307 + pub async fn fuzzy_find_album( 308 + &self, 309 + query: &Vec<String>, 310 + ) -> Result<Vec<(String, String, String)>, Error> { 311 + let mut conn = self.pool.acquire().await?; 312 + 313 + let query_str = format!( 314 + r#"select track_id, album, extension from track_fts where album match '{}' group by album;"#, 315 + query.join(" "), 316 + ); 317 + 318 + Ok(sqlx::query(&query_str) 319 + .fetch_all(&mut *conn) 320 + .await? 321 + .into_iter() 322 + .map(|r| { 323 + let album: String = r.get("album"); 324 + let format: String = r.get("extension"); 325 + let track_id: String = r.get("track_id"); 326 + 327 + (track_id, album, format) 328 + }) 329 + .collect()) 330 + } 331 + 332 + pub async fn duplicate_albums(&self) -> Result<Vec<(model::Album, i64)>, Error> { 333 + let mut conn = self.pool.acquire().await?; 334 + 335 + Ok(sqlx::query!( 336 + r#" 337 + SELECT artist, title, count(*) as count FROM albums 338 + GROUP BY artist, title 339 + HAVING count > 1; 340 + "#, 341 + ) 342 + .fetch_all(&mut *conn) 343 + .await? 344 + .into_iter() 345 + .map(|e| { 346 + ( 347 + model::Album { 348 + title: e.title, 349 + artist: e.artist, 350 + format: String::new(), 351 + }, 352 + e.count, 353 + ) 354 + }) 355 + .collect()) 356 + } 357 + 358 + pub async fn album_paths( 359 + &self, 360 + title: &String, 361 + artist: &String, 362 + ) -> Result<Vec<(String, String)>, Error> { 363 + let mut conn = self.pool.acquire().await?; 364 + 365 + Ok(sqlx::query!( 366 + r#" 367 + select * from tracks where artist = ?2 and album = ?1 group by extension; 368 + "#, 369 + title, 370 + artist, 371 + ) 372 + .fetch_all(&mut *conn) 373 + .await? 374 + .into_iter() 375 + .map(|e| { 376 + // parse dupe path and get the directory containing it 377 + let dupe_path = std::path::Path::new(&e.file_path).parent().unwrap(); 378 + let dupe_path = dupe_path.to_str().unwrap().to_string(); 379 + 380 + (dupe_path.clone(), e.extension.clone()) 381 + }) 382 + .collect()) 383 + } 384 + 385 + pub async fn tracks_iter(&self) -> Result<Receiver<Result<model::Track, Error>>, Error> { 386 + let mut conn = self.pool.acquire().await?; 387 + 388 + let (tx, rx): ( 389 + Sender<Result<model::Track, Error>>, 390 + Receiver<Result<model::Track, Error>>, 391 + ) = async_std::channel::unbounded(); 392 + 393 + async_std::task::spawn(async move { 394 + let mut tracks_stream = sqlx::query!("SELECT * from tracks;").fetch(&mut *conn); 395 + 396 + while let Some(track) = tracks_stream.next().await { 397 + match track { 398 + Ok(track) => tx 399 + .send(Ok(model::Track { 400 + id: track.id, 401 + track_id: track.track_id, 402 + title: track.title, 403 + artist: track.artist, 404 + album: track.album, 405 + number: track.number, 406 + file_path: track.file_path, 407 + disc_number: track.disc_number, 408 + disc_total: track.disc_total, 409 + file_state: track.file_state.into(), 410 + extension: track.extension, 411 + })) 412 + .await 413 + .unwrap(), 414 + Err(e) => { 415 + tx.send(Err(e)).await.unwrap(); 416 + tx.close(); 417 + break; 418 + } 419 + } 420 + } 421 + 422 + tx.close(); 423 + }); 424 + 425 + Ok(rx) 426 + } 427 + 428 + pub async fn filter(&self) -> Result<Option<String>, Error> { 429 + let mut conn = self.pool.acquire().await?; 430 + 431 + Ok(sqlx::query!( 432 + r#" 433 + select filter from state; 434 + "#, 435 + ) 436 + .fetch_one(&mut *conn) 437 + .await? 438 + .filter) 439 + } 440 + 441 + pub async fn set_filter(&self, filter: String) -> Result<(), Error> { 442 + let mut conn = self.pool.acquire().await?; 443 + 444 + sqlx::query!( 445 + r#" 446 + update state set filter = ?1;"#, 447 + filter, 448 + ) 449 + .execute(&mut *conn) 450 + .await?; 451 + 452 + Ok(()) 453 + } 454 + }
+64
src/db/lib.rs
··· 1 + use std::{collections::hash_set, path::PathBuf}; 2 + 3 + use sqlx::{Error, SqlitePool}; 4 + 5 + use super::instance::Instance; 6 + 7 + pub(crate) async fn is_initialized(pool: &SqlitePool) -> Result<bool, Error> { 8 + let mut conn = pool.acquire().await?; 9 + 10 + let res = sqlx::query!( 11 + r#" 12 + SELECT version FROM state GROUP BY version; 13 + "# 14 + ) 15 + .fetch_one(&mut *conn) 16 + .await?; 17 + 18 + Ok(res.version == 0) 19 + } 20 + 21 + pub(crate) async fn is_external(pool: &SqlitePool) -> Result<bool, Error> { 22 + let mut conn = pool.acquire().await?; 23 + 24 + let res = sqlx::query!( 25 + r#" 26 + SELECT is_external FROM state GROUP BY version; 27 + "# 28 + ) 29 + .fetch_one(&mut *conn) 30 + .await?; 31 + 32 + Ok(res.is_external.unwrap_or_default()) 33 + } 34 + 35 + pub async fn diff(source: &Instance, destination: &Instance) -> Result<Vec<String>, Error> { 36 + let source_ids = source 37 + .track_ids_by_state(crate::model::FileState::Copied) 38 + .await?; 39 + log::debug!("source ids: {}", source_ids.len(),); 40 + 41 + let source_ids: hash_set::HashSet<String> = source_ids.into_iter().collect(); 42 + let dest_ids: hash_set::HashSet<String> = destination 43 + .track_ids_by_state(crate::model::FileState::Copied) 44 + .await? 45 + .into_iter() 46 + .collect(); 47 + 48 + log::debug!( 49 + "source ids: {} dest ids: {}", 50 + source_ids.len(), 51 + dest_ids.len() 52 + ); 53 + 54 + let d: hash_set::HashSet<&String> = source_ids.difference(&dest_ids).collect(); 55 + 56 + Ok(d.into_iter().map(|e| e.clone()).collect()) 57 + } 58 + 59 + pub fn default_database_dir() -> PathBuf { 60 + let bd = directories::BaseDirs::new().unwrap(); 61 + let conf_dir = bd.config_dir(); 62 + 63 + conf_dir.to_path_buf() 64 + }
+6
src/db/mod.rs
··· 1 + mod instance; 2 + mod lib; 3 + 4 + pub use instance::Instance; 5 + pub use lib::default_database_dir; 6 + pub use lib::diff;
+102
src/filter/filter.rs
··· 1 + use crate::model; 2 + use rhai::{Engine, Scope, AST}; 3 + 4 + #[derive(Debug)] 5 + pub enum Error { 6 + ParseError(String), 7 + RunError(String), 8 + RegexError(regex::Error), 9 + } 10 + 11 + impl std::error::Error for Error {} 12 + 13 + impl std::fmt::Display for Error { 14 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 + match self { 16 + Error::ParseError(e) => write!(f, "{}", e), 17 + Error::RunError(e) => write!(f, "{}", e), 18 + Error::RegexError(e) => write!(f, "{}", e), 19 + } 20 + } 21 + } 22 + 23 + impl From<rhai::ParseError> for Error { 24 + fn from(value: rhai::ParseError) -> Self { 25 + Error::ParseError(format!("{}", value)) 26 + } 27 + } 28 + 29 + impl From<rhai::EvalAltResult> for Error { 30 + fn from(value: rhai::EvalAltResult) -> Self { 31 + Error::RunError(format!("{}", value)) 32 + } 33 + } 34 + 35 + impl From<regex::Error> for Error { 36 + fn from(value: regex::Error) -> Self { 37 + Error::RegexError(value) 38 + } 39 + } 40 + 41 + // fn filter(track: model::BaseTrack) 42 + const FILTER_FN_NAME: &'static str = "filter"; 43 + 44 + pub struct ScriptRuntime { 45 + ast: AST, 46 + engine: Engine, 47 + } 48 + 49 + impl ScriptRuntime { 50 + pub fn run(&self, model: Vec<model::BaseTrack>) -> Result<Vec<bool>, Error> { 51 + let mut ret = vec![]; 52 + for track in model { 53 + let mut scope = Scope::new(); 54 + let res = self 55 + .engine 56 + .call_fn::<bool>(&mut scope, &self.ast, FILTER_FN_NAME, (track,)); 57 + 58 + match res { 59 + Ok(res) => ret.push(res), 60 + Err(result) => return Err((*result).into()), 61 + }; 62 + } 63 + 64 + Ok(ret) 65 + } 66 + } 67 + 68 + pub fn check(scripts: Vec<String>) -> Result<(), Error> { 69 + match evaluate(scripts) { 70 + Err(e) => Err(e), 71 + Ok(_) => Ok(()), 72 + } 73 + } 74 + 75 + pub fn evaluate(scripts: Vec<String>) -> Result<Vec<ScriptRuntime>, Error> { 76 + let mut sc = vec![]; 77 + 78 + for s in scripts { 79 + sc.push(compile(s)?) 80 + } 81 + 82 + Ok(sc) 83 + } 84 + 85 + fn compile(script: String) -> Result<ScriptRuntime, Error> { 86 + let mut engine = Engine::new(); 87 + 88 + engine.register_fn("regex_match", regex_match); 89 + engine.build_type::<model::BaseTrack>(); 90 + engine 91 + .register_type_with_name::<Vec<model::BaseTrack>>("VecTrack") 92 + .register_iterator::<Vec<model::BaseTrack>>(); 93 + let ast = engine.compile(&script)?; 94 + 95 + Ok(ScriptRuntime { ast, engine }) 96 + } 97 + 98 + fn regex_match(expr: String, data: String) -> bool { 99 + let r = regex::Regex::new(&expr).unwrap(); 100 + 101 + r.is_match(&data) 102 + }
+5
src/filter/mod.rs
··· 1 + mod filter; 2 + pub use filter::check; 3 + pub use filter::evaluate; 4 + pub use filter::Error; 5 + pub use filter::ScriptRuntime;
+63
src/fs.rs
··· 1 + use async_std::channel::{Receiver, Sender}; 2 + 3 + /// Traverses the file system from the given path. 4 + pub async fn traverse(path: &str) -> Receiver<Result<String, std::io::Error>> { 5 + let (tx, rx): ( 6 + Sender<Result<String, std::io::Error>>, 7 + Receiver<Result<String, std::io::Error>>, 8 + ) = async_std::channel::unbounded(); 9 + 10 + let path = path.to_owned(); 11 + 12 + async_std::task::spawn(async move { traverse_inner(path, tx).await }); 13 + 14 + rx 15 + } 16 + 17 + async fn traverse_inner(path: String, tx: Sender<Result<String, std::io::Error>>) { 18 + for maybe_path in walkdir::WalkDir::new(path) { 19 + let path = match maybe_path { 20 + Ok(p) => p, 21 + Err(e) => { 22 + tx.send(Err(e.into())).await.unwrap(); 23 + tx.close(); 24 + return; 25 + } 26 + }; 27 + 28 + let meta = match path.metadata() { 29 + Ok(m) => m, 30 + Err(e) => { 31 + tx.send(Err(e.into())).await.unwrap(); 32 + tx.close(); 33 + return; 34 + } 35 + }; 36 + 37 + let path_str = path.path().to_str().unwrap().to_string(); 38 + 39 + match meta.is_dir() { 40 + true => {} 41 + false => { 42 + if is_music(&path_str) { 43 + tx.send(Ok(path_str)).await.unwrap(); 44 + } 45 + } 46 + } 47 + } 48 + 49 + tx.close(); 50 + } 51 + 52 + /// Returns true if name has one of the supported music file extension. 53 + fn is_music(name: &String) -> bool { 54 + let formats = ["flac", "mp3", "ogg", "mp4", "m4a"]; 55 + 56 + formats 57 + .into_iter() 58 + .filter_map(|format| name.ends_with(&format!(".{format}")).then_some(true)) 59 + .collect::<Vec<bool>>() 60 + .into_iter() 61 + .find(|x| *x == true) 62 + .is_some() 63 + }
+31
src/main.rs
··· 1 + use clap::Parser; 2 + mod cli; 3 + mod cmd; 4 + mod db; 5 + mod filter; 6 + mod fs; 7 + mod model; 8 + 9 + #[async_std::main] 10 + async fn main() -> anyhow::Result<()> { 11 + log_setup(); 12 + 13 + let c = cli::Cli::parse(); 14 + 15 + match c.command { 16 + cli::Commands::Sync(sync_args) => Ok(cmd::sync::run(sync_args).await?), 17 + cli::Commands::Add(add_args) => Ok(cmd::add::run(add_args, false).await?), 18 + cli::Commands::Dupes(dupes_args) => Ok(cmd::dupes::run(dupes_args).await?), 19 + cli::Commands::Clean(clean_args) => Ok(cmd::clean::run(clean_args).await?), 20 + cli::Commands::Update(update_args) => Ok(cmd::add::run(update_args, true).await?), 21 + cli::Commands::Filter(filter_args) => Ok(cmd::filter::run(filter_args).await?), 22 + } 23 + } 24 + 25 + fn log_setup() { 26 + if std::env::var("RUST_LOG").is_err() { 27 + std::env::set_var("RUST_LOG", "info") 28 + } 29 + 30 + env_logger::init(); 31 + }
+197
src/model.rs
··· 1 + use audiotags::AudioTag; 2 + use once_cell::sync::Lazy; 3 + use rhai::{CustomType, TypeBuilder}; 4 + 5 + static NULL_CHAR: once_cell::sync::Lazy<String> = Lazy::new(|| String::from_utf8(vec![0]).unwrap()); 6 + 7 + #[derive(Debug, Clone, sqlx::Type, Default)] 8 + #[repr(i64)] 9 + pub enum FileState { 10 + #[default] 11 + Copied, 12 + Copying, 13 + Unknown, 14 + } 15 + 16 + impl From<i64> for FileState { 17 + fn from(value: i64) -> Self { 18 + match value { 19 + 0 => Self::Copied, 20 + 1 => Self::Copying, 21 + _ => Self::Unknown, 22 + } 23 + } 24 + } 25 + 26 + pub struct RawTrack { 27 + pub tags: Box<dyn AudioTag + Send + Sync>, 28 + pub path: String, 29 + } 30 + 31 + #[derive(Debug, Clone, Default, rhai::CustomType)] 32 + pub struct BaseTrack { 33 + pub title: String, 34 + pub artist: String, 35 + pub album: String, 36 + pub number: i64, 37 + pub file_path: String, 38 + pub disc_number: i64, 39 + pub disc_total: i64, 40 + pub extension: String, 41 + } 42 + 43 + impl From<Track> for BaseTrack { 44 + fn from(value: Track) -> Self { 45 + Self { 46 + title: value.title, 47 + artist: value.artist, 48 + album: value.album, 49 + number: value.number, 50 + file_path: value.file_path, 51 + disc_number: value.disc_number, 52 + disc_total: value.disc_total, 53 + extension: value.extension, 54 + } 55 + } 56 + } 57 + 58 + #[derive(Debug, Clone, sqlx::Type, Default)] 59 + pub struct Track { 60 + pub id: i64, 61 + pub track_id: String, 62 + pub title: String, 63 + pub artist: String, 64 + pub album: String, 65 + pub number: i64, 66 + pub file_path: String, 67 + pub disc_number: i64, 68 + pub disc_total: i64, 69 + pub file_state: FileState, 70 + pub extension: String, 71 + } 72 + 73 + impl std::fmt::Display for Track { 74 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 75 + write!(f, "{} - {}, {}", self.title, self.album, self.artist) 76 + } 77 + } 78 + 79 + impl Track { 80 + pub fn storage_path(&self, base: &str) -> String { 81 + let mut p = std::path::PathBuf::new(); 82 + 83 + let extension = std::path::Path::new(&self.file_path) 84 + .extension() 85 + .unwrap_or_default() 86 + .to_str() 87 + .unwrap(); 88 + 89 + let filename = format!("{}.{}", self.title, extension); 90 + 91 + p.push(base); 92 + p.push(clean(self.artist.clone(), false)); 93 + p.push(clean(self.album.clone(), false)); 94 + p.push(clean(self.disc_number.to_string(), false)); 95 + p.push(clean(filename, true)); 96 + 97 + p.to_str().unwrap().to_string() 98 + } 99 + } 100 + 101 + impl From<RawTrack> for Track { 102 + fn from(track: RawTrack) -> Self { 103 + let disc = track.tags.disc(); 104 + 105 + // If no album artist has been found, use the artist tag. 106 + // If that's missing too, we have an Unknown album. 107 + let artist = match track.tags.album_artist() { 108 + Some(aa) => aa, 109 + None => track.tags.artist().unwrap_or("Unknown Album"), 110 + }; 111 + 112 + let mut t = Self { 113 + id: 0, 114 + track_id: Default::default(), 115 + title: track.tags.title().unwrap_or("Unknown Title").to_owned(), 116 + artist: artist.to_owned(), 117 + album: track 118 + .tags 119 + .album_title() 120 + .unwrap_or("Unknown Album") 121 + .to_owned(), 122 + number: track.tags.track_number().unwrap_or_default() as i64, 123 + file_path: track.path, 124 + disc_number: disc.0.unwrap_or_default() as i64, 125 + disc_total: disc.1.unwrap_or_default() as i64, 126 + file_state: FileState::Unknown, 127 + extension: String::new(), 128 + }; 129 + 130 + t.track_id = track_hash(&t); 131 + 132 + let extension = std::path::Path::new(&t.file_path) 133 + .extension() 134 + .unwrap_or(std::ffi::OsStr::new("NONE")) 135 + .to_str() 136 + .unwrap() 137 + .to_string(); 138 + 139 + t.extension = extension; 140 + 141 + t 142 + } 143 + } 144 + 145 + fn track_hash(track: &Track) -> String { 146 + let mut sb = string_builder::Builder::default(); 147 + 148 + sb.append(track.artist.clone()); 149 + sb.append(track.album.clone()); 150 + sb.append(track.title.clone()); 151 + sb.append(track.extension.clone()); 152 + 153 + sha256::digest(sb.string().unwrap()) 154 + } 155 + 156 + fn clean(s: String, is_file: bool) -> String { 157 + let mut s = s.clone(); 158 + 159 + for c in [ 160 + r#"""#, 161 + std::path::MAIN_SEPARATOR_STR, 162 + "*", 163 + "/", 164 + ":", 165 + "<", 166 + ">", 167 + "?", 168 + r#"\"#, 169 + "|", 170 + "+", 171 + ",", 172 + { 173 + if !is_file { 174 + "." 175 + } else { 176 + "" 177 + } 178 + }, 179 + ";", 180 + "=", 181 + "[", 182 + "]", 183 + &NULL_CHAR, 184 + ] { 185 + if c != "" { 186 + s = s.replace(c, "_") 187 + } 188 + } 189 + 190 + s 191 + } 192 + 193 + pub struct Album { 194 + pub title: String, 195 + pub artist: String, 196 + pub format: String, 197 + }