Compare commits
606 Commits
Author | SHA1 | Date |
---|---|---|
Óscar García Amor | 90205fe0fb | |
Óscar García Amor | 2165ce75b3 | |
Óscar García Amor | 8b3ee0a8d6 | |
birdbird | 695b2df63f | |
tzugen | 798d795e81 | |
tzugen | ecfce59e0f | |
tzugen | de0cb7713b | |
tzugen | 78bfab3753 | |
tzugen | b955d77152 | |
tzugen | b11694d6a2 | |
tzugen | 31a1fdace1 | |
tzugen | 5b03b632fd | |
tzugen | 152b1d261a | |
tzugen | 53a1a5545a | |
tzugen | ad54db5bcb | |
tzugen | 177329abcf | |
tzugen | 241e51015f | |
tzugen | 60dbe70ca5 | |
tzugen | 8490f7115d | |
tzugen | ee67f4c744 | |
tzugen | 3a3bd10fdb | |
birdbird | 3445576dc9 | |
tzugen | 8c40f662a1 | |
birdbird | 6c6227ce41 | |
tzugen | 240a2fa8f6 | |
tzugen | 7de775dc26 | |
birdbird | d034fc9c71 | |
birdbird | 05ada9297d | |
Maxence G | aa6c037b20 | |
Maxence G | b8c924be27 | |
Maxence G | 0929a6a1bd | |
Maxence G | fefee74a66 | |
Maxence G | 37e3ce09c1 | |
Maxence G | 16b3fcad32 | |
Maxence G | d6aebd9989 | |
Maxence G | 3f408600cb | |
Maxence G | 9014b47b74 | |
tzugen | ac489ae8b9 | |
tzugen | e7f8fa21cb | |
tzugen | b1c3cabfef | |
tzugen | 77865a143d | |
Óscar García Amor | ff9c7b2435 | |
Óscar García Amor | 737563bf6b | |
tzugen | 9a73d72fa4 | |
tzugen | 98ce519014 | |
tzugen | 83fc54d332 | |
Maxence G | a2b9c6b9a3 | |
Maxence G | 5ae56d26c5 | |
Maxence G | 4efb6dcb58 | |
tzugen | 8a90e98989 | |
tzugen | 46a8f4640d | |
tzugen | ab41966943 | |
tzugen | 00d7ce326c | |
Maxence G | bc4b0aa832 | |
Maxence G | 23fd336ffd | |
Maxence G | b57a973510 | |
Maxence G | 8796006ced | |
Maxence G | 545b65921e | |
Maxence G | cf367ead92 | |
Maxence G | 9961213f09 | |
tzugen | 5deb7d4d58 | |
tzugen | 5f31eaaffe | |
tzugen | cad6477cd9 | |
tzugen | b440821ea8 | |
Holger Müller | 8663b9d50e | |
Óscar García Amor | 2bae243be0 | |
Óscar García Amor | 139e810186 | |
tzugen | 66443ba018 | |
tzugen | f8b78a47d2 | |
tzugen | 4cda114f4c | |
tzugen | d8b5b774ee | |
tzugen | b6730f5a93 | |
tzugen | 87c160610f | |
tzugen | 70f8b75019 | |
tzugen | 147d7cd46e | |
tzugen | 59e37e62a6 | |
tzugen | 1e571e165c | |
tzugen | 5e0dd14c4f | |
tzugen | 53ae0cd232 | |
tzugen | 608f86ac5f | |
tzugen | 669b51c0d2 | |
tzugen | 6e1478d896 | |
Nite | d9e4b8b3d3 | |
Nite | f790e29add | |
Nite | faf07f2887 | |
i-do-cpp | 057644f592 | |
tzugen | 926081f84c | |
tzugen | 4a00494647 | |
Nite | 34e0178db3 | |
Nite | cbe3992b01 | |
Nite | 46846bd5c9 | |
tzugen | 707339b88b | |
tzugen | 827654c0c1 | |
tzugen | 1d236aa6e3 | |
tzugen | 9cdba9a27a | |
tzugen | 7ba599f58c | |
tzugen | 2e1e627b7a | |
tzugen | d550eabf88 | |
tzugen | dda86b42c7 | |
tzugen | b6e890b26c | |
tzugen | c2ac1d436f | |
tzugen | 2aaa3c2119 | |
tzugen | 5d4aff1f21 | |
tzugen | 6115ac995f | |
tzugen | 647435fe55 | |
tzugen | 81d24f6cbb | |
tzugen | 69c78f4c37 | |
tzugen | 3691428a68 | |
tzugen | 788538ee6a | |
tzugen | 762aeec5d3 | |
tzugen | a3a0c7f41d | |
tzugen | 1564379bd1 | |
tzugen | 7d33770fd6 | |
tzugen | 728afad00c | |
Óscar García Amor | f121e297df | |
Óscar García Amor | 3f2cfb131a | |
Óscar García Amor | b8b4b81726 | |
Óscar García Amor | 383089a409 | |
Óscar García Amor | 8d8a5f05ea | |
Óscar García Amor | 46a2e5d67b | |
tzugen | 92ef78a36a | |
tzugen | e5021959c3 | |
tzugen | 3ca25ed1c6 | |
tzugen | 6da83db9df | |
tzugen | 9779844620 | |
tzugen | f936ad690c | |
tzugen | 5230ce011d | |
Holger Müller | a98c9e2ffd | |
tzugen | 0128a8b29d | |
tzugen | 41f5520f1f | |
tzugen | fd34199c27 | |
tzugen | bb77216eff | |
tzugen | e1f4ee15d5 | |
tzugen | d0959ffcb5 | |
tzugen | 4c22c8b41b | |
tzugen | ba1a1c5538 | |
tzugen | 7742f67796 | |
tzugen | 1a69507e34 | |
tzugen | 46fb7664c3 | |
tzugen | dd65a12b53 | |
tzugen | 2f7f47783a | |
tzugen | b1c2d020b5 | |
tzugen | 5dc9fda7a4 | |
tzugen | 1313fb6c0c | |
tzugen | 5966dd7299 | |
tzugen | 1703f02aad | |
tzugen | 922022ab03 | |
tzugen | bfc11f9924 | |
tzugen | e77b5abd3e | |
tzugen | 988bf62acf | |
tzugen | 1a46f7e2c6 | |
tzugen | 1d88c585c4 | |
tzugen | 287169649a | |
tzugen | 020f67d5e6 | |
Óscar García Amor | fcc57ae316 | |
tzugen | 0fd17bfe8c | |
tzugen | 0c016bff41 | |
tzugen | 12435ed9ec | |
tzugen | c2226ba202 | |
tzugen | 892b441c0d | |
tzugen | e53da92dac | |
tzugen | 2de59b2206 | |
tzugen | 34c13d7908 | |
tzugen | 88918bd839 | |
tzugen | f49063664b | |
tzugen | 126efd35c6 | |
tzugen | a6a052781d | |
tzugen | 107b01fd91 | |
tzugen | d05ac1489e | |
Holger Müller | fc94d28862 | |
Holger Müller | 8bec74e66a | |
Holger Müller | acf6c5a681 | |
tzugen | 81a21ce8b7 | |
Holger Müller | cf86101de2 | |
Nite | 5a44fcfe29 | |
Nite | 2aa5174fbd | |
Nite | cf7cef9831 | |
Nite | 423957d954 | |
Holger Müller | 0944bd2217 | |
Holger Müller | 7b750c692c | |
Holger Müller | c247e930c4 | |
tzugen | 44d68a71da | |
tzugen | 7a51c271ba | |
Óscar García Amor | 4d91068535 | |
tzugen | fe3b713241 | |
Holger Müller | 2f5704548c | |
tzugen | 17850980e1 | |
tzugen | 34d2b45d71 | |
tzugen | 555ef5b7ff | |
tzugen | c269243a0d | |
tzugen | 2d8b93301f | |
tzugen | e4a41de3ef | |
tzugen | f0447105d2 | |
tzugen | 65c4f2b100 | |
tzugen | f6f9683a9c | |
tzugen | c2d62e8688 | |
tzugen | e153565086 | |
tzugen | f30a582c7b | |
Holger Müller | cc5f29ca98 | |
Holger Müller | 8f18192c36 | |
Holger Müller | ae2055e324 | |
Nite | dee4675715 | |
Nite | e8c31db90f | |
Nite | 34fb63c783 | |
Nite | 9ee03aae2f | |
Nite | ebfc06c423 | |
Nite | 0587f4d837 | |
Nite | 6bfd06c6a0 | |
Óscar García Amor | 6442bae882 | |
Nite | c81c685800 | |
Holger Müller | 5941e5ab87 | |
Holger Müller | cf52d76698 | |
Cem Eren | 273ac8f9b8 | |
Nite | 465c211017 | |
Nite | 4fbedc3d2b | |
Cem Eren | 0961f56a7d | |
Cem Eren | eb0fa67431 | |
Óscar García Amor | 3d65c0a90c | |
Nite | 7dd479c0d2 | |
Nite | b5bfd87fcc | |
Philippe Daouadi | d03b633eeb | |
Nite | 4ee4b70e09 | |
Nite | 8675f25668 | |
Nite | 00dc87d5df | |
Nite | b7d1e4acf6 | |
Óscar García Amor | baa0c92c7a | |
Nite | 7c9d51f758 | |
Nite | 9bf7e99abd | |
Nite | b5e606455e | |
Óscar García Amor | e2ddb35ce3 | |
Óscar García Amor | 9581a17bd9 | |
Óscar García Amor | 3fbf47dedf | |
Óscar García Amor | 87aea4847c | |
Nite | f210a6e363 | |
Nite | a9ee09bc2f | |
Nite | bf96f36cb4 | |
Nite | da69fb9f1f | |
Nite | 5962cc2add | |
Nite | 0d4b400105 | |
Nite | ca9ba68a5e | |
Nite | d00a30940e | |
Nite | 586bc51a9c | |
Óscar García Amor | a53d5378bf | |
Óscar García Amor | 4778d18fb9 | |
Óscar García Amor | 2ff944d77d | |
Nite | 462719ccd1 | |
Nite | e39f232732 | |
dependabot[bot] | ca89d4b55e | |
Óscar García Amor | 3558479278 | |
dependabot[bot] | c66ea1c437 | |
Óscar García Amor | 23d0783164 | |
Óscar García Amor | 22c9db59c3 | |
Óscar García Amor | 121bcde18a | |
Óscar García Amor | 41a462708d | |
Nite | 5e24c3ab40 | |
tzugen | f68b46678a | |
tzugen | fd239e8e72 | |
tzugen | 10892f7527 | |
Óscar García Amor | e2a6a32b76 | |
Óscar García Amor | 0dd666415d | |
Óscar García Amor | 8f43f69a66 | |
Óscar García Amor | 193fe5ac3a | |
Óscar García Amor | 3588bb9380 | |
Óscar García Amor | 95b78a3c11 | |
tzugen | 9f5bc85cf2 | |
tzugen | 5a02467ee8 | |
tzugen | 4a996f8edc | |
tzugen | 8d5675f461 | |
tzugen | a5e8daa912 | |
tzugen | c2b23c4001 | |
tzugen | 10b83805a9 | |
Óscar García Amor | fbf5a63396 | |
Óscar García Amor | bb370bfc44 | |
Óscar García Amor | 2153650d8b | |
tzugen | 987fbbe02a | |
tzugen | b958dcabe4 | |
tzugen | d66de5955b | |
tzugen | 1eca5a756e | |
tzugen | dd92d00be5 | |
tzugen | 7531a4d4aa | |
tzugen | 82f45bd5dd | |
tzugen | c0ef964a3e | |
Nite | 34e232a43a | |
tzugen | 65347a20fa | |
tzugen | 7f42ed6a37 | |
dependabot[bot] | 95f37ba2e2 | |
tzugen | fa434342d9 | |
Nite | 17e49ff49e | |
Nite | adf72d6460 | |
Nite | 505405e87f | |
Nite | a3ad17692b | |
Nite | 5c3e2f6e37 | |
Nite | 941fbd907f | |
Nite | 095cf4ef4a | |
tzugen | 3be480a9a2 | |
tzugen | 5bfa0044ab | |
tzugen | 6b1fc7575a | |
tzugen | 6fcea86097 | |
tzugen | 28d5e5043f | |
tzugen | eb2aeabd5d | |
tzugen | a0da791b28 | |
tzugen | f4554ff29e | |
tzugen | 9acc5121a4 | |
Nite | 107146c8d9 | |
Nite | d51544f927 | |
Nite | 66e7732ec2 | |
Nite | fa4214a0ac | |
Nite | 34c5ced32e | |
Nite | 90638e5fd7 | |
tzugen | 6730713763 | |
dependabot[bot] | 38fa4964f8 | |
tzugen | b85230c056 | |
dependabot[bot] | fb85fb4e82 | |
Nite | 8c716da213 | |
tzugen | 80e587c1aa | |
tzugen | e337177715 | |
tzugen | d8cdc81424 | |
tzugen | 351ad914e7 | |
Nite | 0d24c87eef | |
tzugen | de04f4cbe6 | |
tzugen | 026aa79572 | |
tzugen | 6daa17efd5 | |
tzugen | f2948cd3db | |
tzugen | 2ac1ea3f89 | |
tzugen | f1e789ea9b | |
tzugen | bdac092eff | |
tzugen | aa33d7c882 | |
tzugen | 775f56c6fa | |
tzugen | 2f0ff384d0 | |
tzugen | 82d90a6aee | |
tzugen | b33fe2d451 | |
tzugen | 4e37a2483c | |
tzugen | 5dfb66eec2 | |
tzugen | ad793e27a5 | |
tzugen | 2086a6cac5 | |
tzugen | eeb2d13d96 | |
tzugen | f8a87f7c85 | |
tzugen | 7640f4c4aa | |
tzugen | d243ae1b44 | |
tzugen | 6277ee73c0 | |
tzugen | 7a2dbf65d9 | |
tzugen | 19d014709f | |
tzugen | d0e39efc50 | |
tzugen | e81b1ef8c2 | |
tzugen | 5f716f5008 | |
Nite | 5cf914f555 | |
Nite | 5c7cde2349 | |
Nite | 3f570636dd | |
Nite | 1d0bb944e1 | |
Nite | 5fac1b74a3 | |
Nite | d84a0a3929 | |
Nite | f3ac843a9c | |
tzugen | cddbe72752 | |
tzugen | 744282f10a | |
tzugen | 51d6a23208 | |
tzugen | a327a5b390 | |
tzugen | 36b581e3c1 | |
tzugen | 1ed9360bc7 | |
tzugen | ed152fa52a | |
Nite | 45e9728e0f | |
tzugen | b1cb70764c | |
tzugen | 00cd4fce44 | |
tzugen | 97eb753413 | |
tzugen | 00781ba7de | |
Nite | d6f908b80c | |
Nite | e019ec788d | |
tzugen | f73457298d | |
tzugen | c9e276dc76 | |
Nite | 28ef67a210 | |
tzugen | bb36116d70 | |
tzugen | 8830d76497 | |
tzugen | 61f23fa948 | |
tzugen | 92adcf47bd | |
tzugen | 7e3cb19bac | |
tzugen | dc312d4592 | |
dependabot[bot] | 435376b48b | |
Nite | 69825b28bb | |
Nite | 66df5b1daf | |
Nite | 38979bf9d4 | |
Nite | 68549992f4 | |
Nite | 2e482e02a2 | |
Nite | bf18c43d73 | |
Nite | 7fe4845305 | |
dependabot[bot] | c29b8ebe0e | |
Nite | 3bacabe480 | |
tzugen | aac73cd6d7 | |
tzugen | a66d07ae84 | |
Nite | 210ae35ee0 | |
Nite | 4e3102f131 | |
Nite | eba42b82dc | |
Nite | a34fc809d9 | |
Nite | ffb2d59886 | |
tzugen | a6e76e9d53 | |
tzugen | c4e2c786d1 | |
tzugen | 050161bbb0 | |
tzugen | f085a8ab65 | |
tzugen | dfb3561965 | |
tzugen | 8c99c84a90 | |
tzugen | 4fb4ab18da | |
tzugen | e6624ada9a | |
tzugen | a58e541ccc | |
tzugen | 416bc57eea | |
dependabot[bot] | 34c3936513 | |
dependabot[bot] | aece29559e | |
tzugen | e32b3461c9 | |
Nite | fec2d78d30 | |
Nite | 7ed91db250 | |
Nite | 7c43d01f8e | |
Nite | 5eaf9cccb1 | |
tzugen | bd23f54783 | |
tzugen | 5fe1921ca5 | |
tzugen | e5f7ca6310 | |
tzugen | 4d42c0d9d2 | |
tzugen | e19d43d6b3 | |
tzugen | 2d9a212b5c | |
Nite | f0c02f5551 | |
tzugen | 7c956566a0 | |
Nite | ada780ab25 | |
Nite | 70e42fb443 | |
tzugen | c83a9778fc | |
Nite | 6636d6a558 | |
Nite | 7ccb9d055c | |
Nite | 9782e18b6e | |
tzugen | eb2e6ada0a | |
Nite | cef1153f89 | |
tzugen | fea515a526 | |
tzugen | c44257f569 | |
tzugen | 939cd8583c | |
tzugen | 7d2923230c | |
tzugen | ece53f7687 | |
Nite | 2847a51674 | |
tzugen | 24092ce465 | |
tzugen | 6f676d20b0 | |
tzugen | 1d5b335f97 | |
tzugen | 9bc19ec044 | |
tzugen | 0bcf51a409 | |
Nite | f58c361e4e | |
Nite | 949e7e58ba | |
Nite | 77a2dcf935 | |
dependabot[bot] | 974099630a | |
Nite | 2197959507 | |
tzugen | 62bade916f | |
Nite | ccf39661ab | |
Nite | 7c66bc7ec8 | |
Nite | 477f6f5d7c | |
Nite | 784c65f96d | |
Nite | 16b2a99631 | |
Nite | 427034053c | |
Nite | 6905c68898 | |
Nite | 2b40577d4b | |
Nite | 23cca33d5a | |
Óscar García Amor | 62986ca79d | |
Óscar García Amor | e2cff64e4e | |
Óscar García Amor | 704f78366a | |
Óscar García Amor | e426081d1d | |
tzugen | bc43cc6874 | |
tzugen | 83f4ecb15a | |
tzugen | fbdf6d846b | |
tzugen | d1e636f553 | |
tzugen | d7cd68c39e | |
tzugen | b892b7b8d3 | |
tzugen | 5c9b149bec | |
tzugen | ec4f57b5b6 | |
tzugen | 87c2e44ab8 | |
tzugen | 910a05d49d | |
Nite | b44e0517ce | |
Nite | 5a3eb6482a | |
Nite | 7a6f993602 | |
Nite | 2f98bf9ae0 | |
tzugen | 855f111cb2 | |
tzugen | aea2e6baef | |
Nite | eff1a714e2 | |
Nite | f752307a38 | |
Nite | 86f2aa1656 | |
Nite | 256f785d39 | |
Nite | 62150b77d3 | |
Nite | 652f85b070 | |
Nite | 381e29b2d1 | |
Nite | 4b4853374c | |
Óscar García Amor | 7091967fa5 | |
Óscar García Amor | c3df5ead37 | |
Nite | a3128c5f7f | |
Nite | 221f218856 | |
Nite | ed59136fa6 | |
dependabot[bot] | 0da22ef75b | |
tzugen | 6b24e0ae4b | |
Nite | 90e269525e | |
dependabot[bot] | 1d26d9ba3a | |
Nite | d89ae50ef4 | |
dependabot[bot] | e77a8e67a8 | |
tzugen | a7a895af96 | |
tzugen | 35a0dd761d | |
Nite | fda746905d | |
Nite | 2237b476dc | |
Nite | b640805559 | |
Nite | d4ce10ebfa | |
Nite | be49145aa8 | |
Nite | 01aa1fe887 | |
Nite | 77f857b1c6 | |
tzugen | 02129a8fd0 | |
tzugen | ecc7e870f1 | |
tzugen | 28097bf325 | |
tzugen | 611539be55 | |
tzugen | 5ff4d21abc | |
tzugen | ec49775d7e | |
tzugen | c48c41284e | |
tzugen | ba745a5f9a | |
Nite | 8ab903c7d2 | |
dependabot[bot] | ac3635a8f4 | |
Nite | 546039fd5e | |
dependabot[bot] | 86a772f115 | |
Nite | d3c7fcf7e7 | |
Nite | 1f5de82004 | |
Óscar García Amor | a64fe47353 | |
Óscar García Amor | 8928980bae | |
tzugen | 950691daab | |
Nite | ee1e4548e3 | |
Nite | 07f334a7fd | |
Nite | 5ef3ced111 | |
Óscar García Amor | 4ce5cfcb8e | |
Nite | 25fd2f4772 | |
dependabot[bot] | 008520fbd0 | |
Nite | 225bb78263 | |
Nite | 84d28178bf | |
Nite | f29db522d4 | |
Nite | 9f1315b6dc | |
Nite | 9f02ee95a4 | |
tzugen | d08a38ea1c | |
tzugen | ad81f3bf6d | |
tzugen | 594e94eea7 | |
tzugen | f9aac1ca43 | |
tzugen | b8eddb2d24 | |
tzugen | e0df24182e | |
tzugen | 029f0fa4da | |
tzugen | 662cb1728b | |
tzugen | e8baea6195 | |
tzugen | e00137a635 | |
tzugen | d83cf0917c | |
Maxence G (Hello-Fluffy) | 0a2e7358fa | |
Maxence G (Hello-Fluffy) | 7eb3d01ae8 | |
Maxence G (Hello-Fluffy) | a23e5ff794 | |
tzugen | 6aa3124ee9 | |
dependabot[bot] | 413707e482 | |
tzugen | e753d95ed3 | |
tzugen | 0579bd9754 | |
Nite | bb1451b800 | |
dependabot[bot] | 01be4c7148 | |
Nite | cb51c0b723 | |
tzugen | 0a39891b93 | |
tzugen | 7e82efddad | |
Óscar García Amor | 6202484df7 | |
Óscar García Amor | 4a10b76067 | |
dependabot[bot] | 9ea0246032 | |
Nite | bd34345c12 | |
Nite | 2655a4a606 | |
tzugen | 059f6d8f32 | |
tzugen | 27971cb426 | |
tzugen | a051d4d040 | |
tzugen | 8b15c9a57f | |
tzugen | 54f39be7ca | |
tzugen | 3afb86c22c | |
dependabot[bot] | d01030b61d | |
Nite | fc2e4b7ae4 | |
Nite | 6e6cf3a86a | |
dependabot[bot] | 43a806a699 | |
Nite | c64420c11f | |
dependabot[bot] | adca273d3f | |
Nite | 2703a4f35b | |
Nite | 9546bdeab5 | |
James Wells | 961c726da8 | |
Nite | c9f2050c46 | |
Nite | 982639d2c7 | |
Nite | cf05d3c781 | |
Nite | f50d6f13f4 | |
Nite | 635ea2f55e | |
Nite | 51dbdfb39a | |
Nite | 56af9e4bf2 | |
Nite | 83c6b76d0a | |
dependabot[bot] | ba323fa9ac | |
Nite | ea3b055288 | |
dependabot[bot] | 6558f871ca | |
Nite | ee76c661a1 | |
dependabot[bot] | 808eafc85d | |
Nite | 649a702ebb | |
dependabot[bot] | 090d7f8ab7 | |
Nite | 1938b3bb4b | |
dependabot[bot] | e60ac32c3f | |
Nite | 6791f0631a | |
James Wells | db0669098c | |
James Wells | 04de4544ee | |
dependabot[bot] | 62189dce5c | |
tzugen | 69c9739db0 | |
tzugen | b546f2c2fb | |
tzugen | fe9b2f9700 | |
tzugen | dbdb59bbff | |
tzugen | fa94cd24da | |
tzugen | c99c4478f2 | |
tzugen | 67f2d1f9a8 | |
dependabot[bot] | f15891cbdd | |
James Wells | 4ff167e497 | |
James Wells | 793c4a6ca7 | |
James Wells | 3853fce818 | |
James Wells | 8855d19113 | |
James Wells | e95b2ce09c | |
James Wells | be4ffc2c7e | |
James Wells | e666498f13 |
|
@ -1,23 +1,33 @@
|
||||||
version: 3
|
version: 2.1
|
||||||
|
parameters:
|
||||||
|
memory-config:
|
||||||
|
type: string
|
||||||
|
default: "-Xmx3200m -Xms256m -XX:MaxMetaspaceSize=1g"
|
||||||
|
memory-config-debug:
|
||||||
|
type: string
|
||||||
|
default: "-Xmx3200m -Xms256m -XX:MaxMetaspaceSize=1g -verbose:gc -Xlog:gc*"
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-29
|
- image: cimg/android:2022.06.1
|
||||||
working_directory: ~/ultrasonic
|
working_directory: ~/ultrasonic
|
||||||
environment:
|
environment:
|
||||||
JVM_OPTS: -Xmx3200m
|
JVM_OPTS: << pipeline.parameters.memory-config >>
|
||||||
|
JAVA_TOOL_OPTIONS: << pipeline.parameters.memory-config >>
|
||||||
|
GRADLE_OPTS: << pipeline.parameters.memory-config >>
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- v1-ultrasonic-{{ .Branch }}-{{ checksum "dependencies.gradle" }}
|
- v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }}
|
||||||
- v1-ultrasonic-{{ .Branch }}
|
- v2-ultrasonic-{{ .Branch }}
|
||||||
- v1-ultrasonic
|
- v2-ultrasonic
|
||||||
- run:
|
- run:
|
||||||
name: configure gradle.properties for CI building
|
name: configure gradle.properties for CI building
|
||||||
command: |
|
command: |
|
||||||
sed -i '/^org.gradle.jvmargs/d' gradle.properties
|
sed -i '/^org.gradle.jvmargs/d' gradle.properties
|
||||||
sed -i 's/^org.gradle.daemon=true/org.gradle.daemon=false/g' gradle.properties
|
sed -i 's/^org.gradle.daemon=true/org.gradle.daemon=false/g' gradle.properties
|
||||||
|
cat gradle.properties
|
||||||
- run:
|
- run:
|
||||||
name: checkstyle
|
name: checkstyle
|
||||||
command: ./gradlew -Pqc ktlintCheck
|
command: ./gradlew -Pqc ktlintCheck
|
||||||
|
@ -25,34 +35,34 @@ jobs:
|
||||||
name: static analysis
|
name: static analysis
|
||||||
command: ./gradlew -Pqc detekt
|
command: ./gradlew -Pqc detekt
|
||||||
- run:
|
- run:
|
||||||
name: build
|
name: build debug
|
||||||
command: ./gradlew assembleDebug
|
command: ./gradlew assembleDebug
|
||||||
- run:
|
- run:
|
||||||
name: unit-tests
|
name: unit-tests
|
||||||
command: |
|
command: |
|
||||||
./gradlew ciTest testDebugUnitTest
|
./gradlew ciTest testDebugUnitTest
|
||||||
./gradlew jacocoFullReport
|
|
||||||
- run:
|
- run:
|
||||||
name: lint
|
name: lint
|
||||||
command: ./gradlew :ultrasonic:lintRelease
|
command: ./gradlew :ultrasonic:lintRelease
|
||||||
- run:
|
- run:
|
||||||
name: assemble release build
|
name: build
|
||||||
command: ./gradlew build assembleRelease
|
command: ./gradlew buildRelease
|
||||||
|
- run:
|
||||||
|
name: assemble release
|
||||||
|
command: ./gradlew assembleRelease
|
||||||
- save_cache:
|
- save_cache:
|
||||||
paths:
|
paths:
|
||||||
- ~/.gradle
|
- ~/.gradle
|
||||||
key: v1-ultrasonic-{{ .Branch }}-{{ checksum "dependencies.gradle" }}
|
key: v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }}
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: ultrasonic/build/reports
|
path: ultrasonic/build/reports
|
||||||
destination: reports
|
destination: reports
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: subsonic-api/build/reports
|
path: subsonic-api/build/reports
|
||||||
destination: reports
|
destination: reports
|
||||||
- store_artifacts:
|
|
||||||
path: build/reports/jacoco/jacocoFullReport/
|
|
||||||
push_translations:
|
push_translations:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/python:3.6
|
- image: cimg/python:3.6
|
||||||
working_directory: ~/ultrasonic
|
working_directory: ~/ultrasonic
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
@ -72,15 +82,19 @@ jobs:
|
||||||
tx push -s
|
tx push -s
|
||||||
generate_signed_apk:
|
generate_signed_apk:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/android:api-28
|
- image: cimg/android:2022.06.1
|
||||||
working_directory: ~/ultrasonic
|
working_directory: ~/ultrasonic
|
||||||
|
environment:
|
||||||
|
JVM_OPTS: << pipeline.parameters.memory-config >>
|
||||||
|
JAVA_TOOL_OPTIONS: << pipeline.parameters.memory-config >>
|
||||||
|
GRADLE_OPTS: << pipeline.parameters.memory-config >>
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- v1-ultrasonic-{{ .Branch }}-{{ checksum "dependencies.gradle" }}
|
- v2-ultrasonic-{{ .Branch }}-{{ checksum "gradle/libs.versions.toml" }}
|
||||||
- v1-ultrasonic-{{ .Branch }}
|
- v2-ultrasonic-{{ .Branch }}
|
||||||
- v1-ultrasonic
|
- v2-ultrasonic
|
||||||
- run:
|
- run:
|
||||||
name: decrypt ultrasonic-keystore
|
name: decrypt ultrasonic-keystore
|
||||||
command: openssl aes-256-cbc -K ${ULTRASONIC_KEYSTORE_KEY} -iv ${ULTRASONIC_KEYSTORE_IV} -in ultrasonic-keystore.enc -out ultrasonic-keystore -d
|
command: openssl aes-256-cbc -K ${ULTRASONIC_KEYSTORE_KEY} -iv ${ULTRASONIC_KEYSTORE_IV} -in ultrasonic-keystore.enc -out ultrasonic-keystore -d
|
||||||
|
@ -90,23 +104,24 @@ jobs:
|
||||||
- run:
|
- run:
|
||||||
name: sign release apk
|
name: sign release apk
|
||||||
command: |
|
command: |
|
||||||
|
export PATH="${JAVA_HOME}/bin:${PATH}"
|
||||||
mkdir -p /tmp/ultrasonic-release
|
mkdir -p /tmp/ultrasonic-release
|
||||||
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/ultrasonic/ultrasonic-keystore -storepass ${ULTRASONIC_KEYSTORE_STOREPASS} -keypass ${ULTRASONIC_KEYSTORE_KEYPASS} ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk ultrasonic
|
${ANDROID_HOME}/build-tools/32.0.0/zipalign -v 4 ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||||
jarsigner -verify ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk
|
${ANDROID_HOME}/build-tools/32.0.0/apksigner sign --verbose --ks ~/ultrasonic/ultrasonic-keystore --ks-pass pass:${ULTRASONIC_KEYSTORE_STOREPASS} --key-pass pass:${ULTRASONIC_KEYSTORE_KEYPASS} /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||||
${ANDROID_HOME}/build-tools/27.0.0/zipalign -v 4 ultrasonic/build/outputs/apk/release/ultrasonic-release-unsigned.apk /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
${ANDROID_HOME}/build-tools/32.0.0/apksigner verify --verbose /tmp/ultrasonic-release/ultrasonic-${CIRCLE_TAG}.apk
|
||||||
- persist_to_workspace:
|
- persist_to_workspace:
|
||||||
root: /tmp/ultrasonic-release
|
root: /tmp/ultrasonic-release
|
||||||
paths:
|
paths:
|
||||||
- ultrasonic-*.apk
|
- ultrasonic-*.apk*
|
||||||
publish_github_signed_apk:
|
publish_github_signed_apk:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang
|
- image: cimg/go:1.18
|
||||||
steps:
|
steps:
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: /tmp/ultrasonic-release
|
at: /tmp/ultrasonic-release
|
||||||
- run:
|
- run:
|
||||||
name: install ghr
|
name: install ghr
|
||||||
command: go get -v github.com/tcnksm/ghr
|
command: go install -v github.com/tcnksm/ghr@latest
|
||||||
- run:
|
- run:
|
||||||
name: publish release on github tag
|
name: publish release on github tag
|
||||||
command: ghr -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} ${CIRCLE_TAG} /tmp/ultrasonic-release
|
command: ghr -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} ${CIRCLE_TAG} /tmp/ultrasonic-release
|
||||||
|
@ -122,11 +137,10 @@ workflows:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- develop
|
- develop
|
||||||
- master
|
|
||||||
- generate_signed_apk:
|
- generate_signed_apk:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /^[0-9]+(\.[0-9]+)*/
|
only: /^[0-9]+(\.[0-9]+)*(-beta\.[0-9]+)?/
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
- publish_github_signed_apk:
|
- publish_github_signed_apk:
|
||||||
|
@ -134,7 +148,7 @@ workflows:
|
||||||
- generate_signed_apk
|
- generate_signed_apk
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
only: /^[0-9]+(\.[0-9]+)*/
|
only: /^[0-9]+(\.[0-9]+)*(-beta\.[0-9]+)?/
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,8 @@ updates:
|
||||||
- package-ecosystem: "gradle" # See documentation for possible values
|
- package-ecosystem: "gradle" # See documentation for possible values
|
||||||
directory: "/" # Location of package manifests
|
directory: "/" # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "monthly"
|
||||||
|
ignore:
|
||||||
|
- dependency-name: "*"
|
||||||
|
update-types: ["version-update:semver-patch"]
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ captures/
|
||||||
*.iml
|
*.iml
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
*.jks
|
*.jks
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<JetCodeStyleSettings>
|
||||||
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</JetCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>name</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="kotlin">
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="11" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="CopyrightManager">
|
||||||
|
<copyright>
|
||||||
|
<option name="notice" value="&#36;file.fileName Copyright (C) 2009-&#36;today.year Ultrasonic developers Distributed under terms of the GNU GPLv3 license." />
|
||||||
|
<option name="myName" value="Default" />
|
||||||
|
</copyright>
|
||||||
|
</component>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<component name="CopyrightManager">
|
||||||
|
<settings default="Default">
|
||||||
|
<LanguageOptions name="Kotlin">
|
||||||
|
<option name="fileTypeOverride" value="3" />
|
||||||
|
</LanguageOptions>
|
||||||
|
</settings>
|
||||||
|
</component>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Reformat" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="processChangedFilesOnly" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -18,18 +18,46 @@ By default Pull Request should be opened against **develop** branch, PR against
|
||||||
### Here are a few guidelines you should follow before submitting:
|
### Here are a few guidelines you should follow before submitting:
|
||||||
|
|
||||||
1. **License Acceptance:** All contributions must be licensed as [GNU GPLv3](LICENSE) to be accepted.
|
1. **License Acceptance:** All contributions must be licensed as [GNU GPLv3](LICENSE) to be accepted.
|
||||||
Use `git commit --signoff` to acknowledge this.
|
Use `git commit --signoff` to acknowledge this.
|
||||||
2. **App is migrating to [Kotlin](https://kotlinlang.org/) programming language:** new Pull Requests
|
2. **No Breakage:** New features or changes to existing ones must not degrade the user experience.
|
||||||
should be written in this programming language.
|
3. **Coding standards:** best-practices should be followed, comment generously, and avoid "clever" algorithms.
|
||||||
3. **No Breakage:** New features or changes to existing ones must not degrade the user experience.
|
|
||||||
4. **Coding standards:** best-practices should be followed, comment generously, and avoid "clever" algorithms.
|
|
||||||
Refactoring existing messes is great, but watch out for breakage.
|
Refactoring existing messes is great, but watch out for breakage.
|
||||||
5. **No large PR:** Try to limit the scope of PR only to the related issue, so it will be easier to review
|
4. **No large PR:** Try to limit the scope of PR only to the related issue, so it will be easier to review
|
||||||
and test.
|
and test.
|
||||||
|
|
||||||
### Pull Request Process
|
### Pull Request Process
|
||||||
|
On each Pull Request Github runs a number of checks to make sure there are no problems.
|
||||||
|
|
||||||
|
#### Signed commits
|
||||||
|
Commits must be signed. [See here how to set it up](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits)
|
||||||
|
|
||||||
|
#### KtLint
|
||||||
|
This programm checks if the source code is formatted correctly.
|
||||||
|
You can run it yourself locally with
|
||||||
|
|
||||||
|
`./gradlew -Pqc ktlintFormat`
|
||||||
|
|
||||||
|
Running this command will fix common problems and will notify you of problems it couldn't fix automatically.
|
||||||
|
|
||||||
|
#### Detekt
|
||||||
|
|
||||||
|
Detekt is a static analyser. It helps to find potential bugs in our code.
|
||||||
|
|
||||||
|
You can run it yourself locally with
|
||||||
|
|
||||||
|
`./gradlew -Pqc detekt`
|
||||||
|
|
||||||
|
There is a "baseline" file, in which errors which have been in the code base before are noted.
|
||||||
|
Sometimes it is necessary to regenerate this file by running:
|
||||||
|
|
||||||
|
`./gradlew -Pqc detektBaseline`
|
||||||
|
|
||||||
|
#### Lint
|
||||||
|
Lint looks for general problems in the code or unused resources etc.
|
||||||
|
You can run it with
|
||||||
|
|
||||||
|
`./gradlew -Pqc lintRelease`
|
||||||
|
|
||||||
|
If there is a need to regenerate the baseline, remove `ultrasonic/lint-baseline.xml` and rerun the command.
|
||||||
|
|
||||||
|
|
||||||
1. Ensure [all commits are signed-off](https://docs.github.com/en/free-pro-team@latest/github/authenticating-to-github/about-commit-signature-verification).
|
|
||||||
2. Check tests for the new code are added.
|
|
||||||
3. Check code style is passing.
|
|
||||||
4. Check code static analysis is passing.
|
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
## Problem description
|
## Problem description
|
||||||
|
|
||||||
Describe your problem here. Describe what you want to happen, and what happens
|
Describe your problem here. Describe what you want to happen, and what
|
||||||
if you try to do it. If you have a stack trace or any logs, please format them using
|
happens if you try to do it. If you have a stack trace or any logs, please
|
||||||
github triple backquote notation
|
format them using GitHub triple backquote notation.
|
||||||
|
|
||||||
### Steps to reproduce
|
### Steps to reproduce
|
||||||
|
|
||||||
Describe how somebody else could observe the same behavior you do. Don't share here any logins and
|
Describe how somebody else could observe the same behavior you do. Don't
|
||||||
passwords!
|
share here any logins and passwords!
|
||||||
|
|
||||||
## System information
|
## System information
|
||||||
|
|
||||||
|
### Ultrasonic client
|
||||||
|
|
||||||
* **Ultrasonic version**: *version of the app*
|
* **Ultrasonic version**: *version of the app*
|
||||||
* **Android version**: *Version of Android OS on the device*
|
* **Android version**: *Version of Android OS on the device*
|
||||||
* **Device info**: *Device manufacturer, model*
|
* **Device info**: *Device manufacturer, model*
|
||||||
|
|
||||||
|
### Server
|
||||||
|
|
||||||
|
* **Server name**: *Airsonic, Ampache, Supysonic...*
|
||||||
|
* **Server version**: *version of server software*
|
||||||
|
* **Protocol used**: *http or https (self certificate, letsencrypt...)*
|
||||||
|
|
||||||
## Additional notes
|
## Additional notes
|
||||||
|
|
||||||
Include any extra notes here. Otherwise you may remove this section.
|
Include any extra notes here. Otherwise you may remove this section.
|
||||||
|
|
62
README.md
62
README.md
|
@ -1,14 +1,25 @@
|
||||||
# Ultrasonic
|
# WE HAVE MOVED
|
||||||
[![Build Status](https://circleci.com/gh/ultrasonic/ultrasonic/tree/develop.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/ultrasonic)
|
|
||||||
[![Codecov branch](https://img.shields.io/codecov/c/github/ultrasonic/ultrasonic/develop.svg)]()
|
|
||||||
[![ktlint](https://img.shields.io/badge/code%20style-%E2%9D%A4-FF4081.svg)](https://ktlint.github.io/)
|
|
||||||
|
|
||||||
Ultrasonic is free and open-source music streaming Android client for [Subsonic](http://www.subsonic.org/) [API](http://www.subsonic.org/pages/api.jsp) (version 1.7.0 or higher) compatible servers.
|
Ultrasonic code is now hosted in [GitLab][ultrasonic].
|
||||||
|
|
||||||
|
- New Web: https://ultrasonic.gitlab.io
|
||||||
|
- New Git: https://gitlab.com/ultrasonic/ultrasonic
|
||||||
|
- New bugtracker: https://gitlab.com/ultrasonic/ultrasonic/-/issues
|
||||||
|
- New releases: https://gitlab.com/ultrasonic/ultrasonic/-/packages
|
||||||
|
|
||||||
|
[ultrasonic]: https://gitlab.com/ultrasonic/ultrasonic
|
||||||
|
|
||||||
|
# Ultrasonic
|
||||||
|
|
||||||
|
Ultrasonic is free and open-source music streaming Android client for
|
||||||
|
[Subsonic][subsonic] [API][subapi] (version 1.7.0 or higher) compatible
|
||||||
|
servers.
|
||||||
|
|
||||||
## Help wanted
|
## Help wanted
|
||||||
|
|
||||||
We currently don't have that much time to spend developing Subsonic, so any
|
We currently don't have that much time to spend developing Subsonic, so any
|
||||||
contributions or active developers are always welcomed.
|
contributions or active developers are always welcomed.
|
||||||
|
Have a look at [CONTRIBUTING](CONTRIBUTING.md) to get started.
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
|
@ -16,22 +27,26 @@ App is available to download at following stores:
|
||||||
|
|
||||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="70">](https://play.google.com/store/apps/details?id=org.moire.ultrasonic)
|
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="70">](https://play.google.com/store/apps/details?id=org.moire.ultrasonic)
|
||||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="70">](https://f-droid.org/packages/org.moire.ultrasonic/)
|
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="70">](https://f-droid.org/packages/org.moire.ultrasonic/)
|
||||||
[<img src="https://ultrasonic.github.io/assets/img/get-it-on-github.png" alt="Get it on GitHub" height="70">](https://github.com/ultrasonic/ultrasonic/releases)
|
[<img src="https://ultrasonic.gitlab.io/assets/img/get-it-on-gitlab.png" alt="Get it on GitLab" height="70">](https://gitlab.com/ultrasonic/ultrasonic/-/releases)
|
||||||
|
|
||||||
**Warning**: All three versions (Google Play, F-Droid and the APKs) are not
|
**Warning**: All three versions (Google Play, F-Droid and the APKs) are not
|
||||||
compatible (not signed by the same key)! You must uninstall one to install
|
compatible (not signed by the same key)! You must uninstall one to install
|
||||||
the other, which will delete all your data.
|
the other, which will delete all your data.
|
||||||
|
|
||||||
|
If you want to use the version downloaded from F-Droid or from GitLab with
|
||||||
|
**Android Auto**, you must enable Unknown Sources as it is described in
|
||||||
|
[this wiki page][wikiaa].
|
||||||
|
|
||||||
## Bugs and issues
|
## Bugs and issues
|
||||||
|
|
||||||
First, see if your issue haven’t been yet reported [here](https://github.com/ultrasonic/ultrasonic/issues),
|
First, see if your issue haven’t been yet reported [here][issues], otherwise
|
||||||
otherwise open [a new issue](https://github.com/ultrasonic/ultrasonic/issues/new).
|
open [a new issue][newissue].
|
||||||
|
|
||||||
### Known (not our) bugs
|
### Known (not our) bugs
|
||||||
|
|
||||||
If you are using *Madsonic 5.1.X* several sections of Ultrasonic will not
|
If you are using *Madsonic 5.1.X* several sections of Ultrasonic will not
|
||||||
work. This is caused by bad implementation of Subsonic API by Madsonic. For
|
work. This is caused by bad implementation of Subsonic API by Madsonic. For
|
||||||
more info about this you can read [this bug](https://github.com/ultrasonic/ultrasonic/issues/129).
|
more info about this you can read [this bug][madbug].
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -39,16 +54,29 @@ See [CONTRIBUTING](CONTRIBUTING.md).
|
||||||
|
|
||||||
## Supported (tested) Subsonic API implementations
|
## Supported (tested) Subsonic API implementations
|
||||||
|
|
||||||
- [Subsonic](http://www.subsonic.org/pages/index.jsp)
|
- [Subsonic][subsonic]
|
||||||
- [Airsonic](https://github.com/airsonic/airsonic)
|
- [Airsonic-Advanced][airsonic]
|
||||||
- [Supysonic](https://github.com/spl0k/supysonic)
|
- [Supysonic][supysonic]
|
||||||
- [Ampache](https://ampache.org/)
|
- [Ampache][ampache]
|
||||||
|
|
||||||
Other *Subsonic API* implementations should work as well as long as they follow API
|
Other *Subsonic API* implementations should work as well as long as they
|
||||||
[documentation](http://www.subsonic.org/pages/api.jsp).
|
follow API [documentation][subapi].
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This software is licensed under the terms of the GNU General Public License version 3 (GPLv3).
|
This software is licensed under the terms of the GNU General Public License
|
||||||
|
version 3 (GPLv3).
|
||||||
|
|
||||||
Full text of the license is available in the [LICENSE](LICENSE) file and [online](https://opensource.org/licenses/gpl-3.0.html).
|
Full text of the license is available in the [LICENSE](LICENSE) file and
|
||||||
|
[online][gpl3].
|
||||||
|
|
||||||
|
[wikiaa]: https://gitlab.com/ultrasonic/ultrasonic/-/wikis/Using-Ultrasonic-with-Android-Auto
|
||||||
|
[issues]: https://gitlab.com/ultrasonic/ultrasonic/-/issues
|
||||||
|
[newissue]: https://gitlab.com/ultrasonic/ultrasonic/-/issues/new
|
||||||
|
[madbug]: https://gitlab.com/ultrasonic/ultrasonic/-/issues/129
|
||||||
|
[subsonic]: http://www.subsonic.org/
|
||||||
|
[subapi]: http://www.subsonic.org/pages/api.jsp
|
||||||
|
[airsonic]: https://github.com/airsonic-advanced/airsonic-advanced
|
||||||
|
[supysonic]: https://github.com/spl0k/supysonic
|
||||||
|
[ampache]: https://ampache.org/
|
||||||
|
[gpl3]: https://opensource.org/licenses/gpl-3.0.html
|
||||||
|
|
15
build.gradle
15
build.gradle
|
@ -1,6 +1,6 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
apply from: 'dependencies.gradle'
|
apply from: 'gradle/versions.gradle'
|
||||||
|
|
||||||
ext.bootstrap = [
|
ext.bootstrap = [
|
||||||
kotlinModule : "${project.rootDir}/gradle_scripts/kotlin-module-bootstrap.gradle",
|
kotlinModule : "${project.rootDir}/gradle_scripts/kotlin-module-bootstrap.gradle",
|
||||||
|
@ -13,11 +13,10 @@ buildscript {
|
||||||
maven { url "https://plugins.gradle.org/m2/" }
|
maven { url "https://plugins.gradle.org/m2/" }
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath gradlePlugins.gradle
|
classpath libs.gradle
|
||||||
classpath gradlePlugins.kotlin
|
classpath libs.kotlin
|
||||||
classpath gradlePlugins.ktlintGradle
|
classpath libs.ktlintGradle
|
||||||
classpath gradlePlugins.detekt
|
classpath libs.detekt
|
||||||
classpath gradlePlugins.jacoco
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,9 +43,7 @@ allprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: 'gradle_scripts/jacoco.gradle'
|
|
||||||
|
|
||||||
wrapper {
|
wrapper {
|
||||||
gradleVersion(versions.gradle)
|
gradleVersion(libs.versions.gradle.get())
|
||||||
distributionType("all")
|
distributionType("all")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
apply from: bootstrap.kotlinModule
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api project(':core:domain')
|
|
||||||
api other.twitterSerial
|
|
||||||
|
|
||||||
testImplementation testing.kotlinJunit
|
|
||||||
testImplementation testing.mockito
|
|
||||||
testImplementation testing.mockitoInline
|
|
||||||
testImplementation testing.mockitoKotlin
|
|
||||||
testImplementation testing.kluent
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package org.moire.ultrasonic.cache
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides access to generic directories:
|
|
||||||
* - for temporary caches
|
|
||||||
* - for permanent data storage
|
|
||||||
*/
|
|
||||||
interface Directories {
|
|
||||||
fun getInternalCacheDir(): File
|
|
||||||
fun getInternalDataDir(): File
|
|
||||||
fun getExternalCacheDir(): File?
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
package org.moire.ultrasonic.cache
|
|
||||||
|
|
||||||
import com.twitter.serial.serializer.SerializationContext
|
|
||||||
import com.twitter.serial.serializer.Serializer
|
|
||||||
import com.twitter.serial.stream.Serial
|
|
||||||
import com.twitter.serial.stream.bytebuffer.ByteBufferSerial
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
typealias DomainEntitySerializer<T> = Serializer<T>
|
|
||||||
|
|
||||||
internal const val STORAGE_DIR_NAME = "persistent_storage"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides access to permanent file based storage.
|
|
||||||
*
|
|
||||||
* [serverId] is currently active server. Should be unique per server so stored data will not
|
|
||||||
* interfere with other server data.
|
|
||||||
*
|
|
||||||
* Look at [org.moire.ultrasonic.cache.serializers] package for available [DomainEntitySerializer]s.
|
|
||||||
*/
|
|
||||||
class PermanentFileStorage(
|
|
||||||
private val directories: Directories,
|
|
||||||
private val serverId: String,
|
|
||||||
private val debug: Boolean = false
|
|
||||||
) {
|
|
||||||
private val serializationContext = object : SerializationContext {
|
|
||||||
override fun isDebug(): Boolean = debug
|
|
||||||
override fun isRelease(): Boolean = !debug
|
|
||||||
}
|
|
||||||
|
|
||||||
private val serializer: Serial = ByteBufferSerial(serializationContext)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores given [objectToStore] using [name] as a key and [objectSerializer] as serializer.
|
|
||||||
*/
|
|
||||||
fun <T> store(
|
|
||||||
name: String,
|
|
||||||
objectToStore: T,
|
|
||||||
objectSerializer: DomainEntitySerializer<T>
|
|
||||||
) {
|
|
||||||
val storeFile = getFile(name)
|
|
||||||
if (!storeFile.exists()) storeFile.createNewFile()
|
|
||||||
storeFile.writeBytes(serializer.toByteArray(objectToStore, objectSerializer))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads object with [name] key using [objectDeserializer] deserializer.
|
|
||||||
*/
|
|
||||||
fun <T> load(
|
|
||||||
name: String,
|
|
||||||
objectDeserializer: DomainEntitySerializer<T>
|
|
||||||
): T? {
|
|
||||||
val storeFile = getFile(name)
|
|
||||||
if (!storeFile.exists()) return null
|
|
||||||
|
|
||||||
return serializer.fromByteArray(storeFile.readBytes(), objectDeserializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all files in storage.
|
|
||||||
*/
|
|
||||||
fun clearAll() {
|
|
||||||
val storageDir = getStorageDir()
|
|
||||||
storageDir.listFiles().forEach { it.deleteRecursively() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFile(name: String) = File(getStorageDir(), "$name.ser")
|
|
||||||
|
|
||||||
private fun getStorageDir(): File {
|
|
||||||
val mainDir = File(directories.getInternalDataDir(), STORAGE_DIR_NAME)
|
|
||||||
val serverDir = File(mainDir, serverId)
|
|
||||||
if (!serverDir.exists()) serverDir.mkdirs()
|
|
||||||
return serverDir
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
@file:JvmMultifileClass
|
|
||||||
@file:JvmName("DomainSerializers")
|
|
||||||
package org.moire.ultrasonic.cache.serializers
|
|
||||||
|
|
||||||
import com.twitter.serial.serializer.CollectionSerializers
|
|
||||||
import com.twitter.serial.serializer.ObjectSerializer
|
|
||||||
import com.twitter.serial.serializer.SerializationContext
|
|
||||||
import com.twitter.serial.stream.SerializerDefs
|
|
||||||
import com.twitter.serial.stream.SerializerInput
|
|
||||||
import com.twitter.serial.stream.SerializerOutput
|
|
||||||
import org.moire.ultrasonic.cache.DomainEntitySerializer
|
|
||||||
import org.moire.ultrasonic.domain.Artist
|
|
||||||
|
|
||||||
private const val SERIALIZER_VERSION = 1
|
|
||||||
|
|
||||||
private val artistSerializer get() = object : ObjectSerializer<Artist>(SERIALIZER_VERSION) {
|
|
||||||
override fun serializeObject(
|
|
||||||
context: SerializationContext,
|
|
||||||
output: SerializerOutput<out SerializerOutput<*>>,
|
|
||||||
item: Artist
|
|
||||||
) {
|
|
||||||
output.writeString(item.id)
|
|
||||||
.writeString(item.name)
|
|
||||||
.writeString(item.index)
|
|
||||||
.writeString(item.coverArt)
|
|
||||||
.apply {
|
|
||||||
val albumCount = item.albumCount
|
|
||||||
if (albumCount != null) writeLong(albumCount) else writeNull()
|
|
||||||
}
|
|
||||||
.writeInt(item.closeness)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserializeObject(
|
|
||||||
context: SerializationContext,
|
|
||||||
input: SerializerInput,
|
|
||||||
versionNumber: Int
|
|
||||||
): Artist? {
|
|
||||||
if (versionNumber != SERIALIZER_VERSION) return null
|
|
||||||
|
|
||||||
val id = input.readString()
|
|
||||||
val name = input.readString()
|
|
||||||
val index = input.readString()
|
|
||||||
val coverArt = input.readString()
|
|
||||||
val albumCount = if (input.peekType() == SerializerDefs.TYPE_NULL) {
|
|
||||||
input.readNull()
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
input.readLong()
|
|
||||||
}
|
|
||||||
val closeness = input.readInt()
|
|
||||||
return Artist(id, name, index, coverArt, albumCount, closeness)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializer/deserializer for [Artist] domain entity.
|
|
||||||
*/
|
|
||||||
fun getArtistsSerializer(): DomainEntitySerializer<Artist> = artistSerializer
|
|
||||||
|
|
||||||
private val artistListSerializer = CollectionSerializers.getListSerializer(artistSerializer)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializer/deserializer for list of [Artist] domain entities.
|
|
||||||
*/
|
|
||||||
fun getArtistListSerializer(): DomainEntitySerializer<List<Artist>> = artistListSerializer
|
|
|
@ -1,51 +0,0 @@
|
||||||
@file:JvmMultifileClass
|
|
||||||
@file:JvmName("DomainSerializers")
|
|
||||||
package org.moire.ultrasonic.cache.serializers
|
|
||||||
|
|
||||||
import com.twitter.serial.serializer.ObjectSerializer
|
|
||||||
import com.twitter.serial.serializer.SerializationContext
|
|
||||||
import com.twitter.serial.stream.SerializerInput
|
|
||||||
import com.twitter.serial.stream.SerializerOutput
|
|
||||||
import org.moire.ultrasonic.cache.DomainEntitySerializer
|
|
||||||
import org.moire.ultrasonic.domain.Artist
|
|
||||||
import org.moire.ultrasonic.domain.Indexes
|
|
||||||
|
|
||||||
private const val SERIALIZATION_VERSION = 1
|
|
||||||
|
|
||||||
private val indexesSerializer get() = object : ObjectSerializer<Indexes>(SERIALIZATION_VERSION) {
|
|
||||||
override fun serializeObject(
|
|
||||||
context: SerializationContext,
|
|
||||||
output: SerializerOutput<out SerializerOutput<*>>,
|
|
||||||
item: Indexes
|
|
||||||
) {
|
|
||||||
val artistListSerializer = getArtistListSerializer()
|
|
||||||
output.writeLong(item.lastModified)
|
|
||||||
.writeString(item.ignoredArticles)
|
|
||||||
.writeObject<MutableList<Artist>>(context, item.shortcuts, artistListSerializer)
|
|
||||||
.writeObject<MutableList<Artist>>(context, item.artists, artistListSerializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("ReturnCount")
|
|
||||||
override fun deserializeObject(
|
|
||||||
context: SerializationContext,
|
|
||||||
input: SerializerInput,
|
|
||||||
versionNumber: Int
|
|
||||||
): Indexes? {
|
|
||||||
if (versionNumber != SERIALIZATION_VERSION) return null
|
|
||||||
|
|
||||||
val artistListDeserializer = getArtistListSerializer()
|
|
||||||
val lastModified = input.readLong()
|
|
||||||
val ignoredArticles = input.readString() ?: return null
|
|
||||||
val shortcutsList = input.readObject(context, artistListDeserializer) ?: return null
|
|
||||||
val artistsList = input.readObject(context, artistListDeserializer) ?: return null
|
|
||||||
return Indexes(
|
|
||||||
lastModified, ignoredArticles, shortcutsList.toMutableList(),
|
|
||||||
artistsList.toMutableList()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get serializer/deserializer for [Indexes] entity.
|
|
||||||
*/
|
|
||||||
fun getIndexesSerializer(): DomainEntitySerializer<Indexes> = indexesSerializer
|
|
|
@ -1,51 +0,0 @@
|
||||||
@file:JvmMultifileClass
|
|
||||||
@file:JvmName("DomainSerializers")
|
|
||||||
package org.moire.ultrasonic.cache.serializers
|
|
||||||
|
|
||||||
import com.twitter.serial.serializer.CollectionSerializers
|
|
||||||
import com.twitter.serial.serializer.ObjectSerializer
|
|
||||||
import com.twitter.serial.serializer.SerializationContext
|
|
||||||
import com.twitter.serial.stream.SerializerInput
|
|
||||||
import com.twitter.serial.stream.SerializerOutput
|
|
||||||
import org.moire.ultrasonic.cache.DomainEntitySerializer
|
|
||||||
import org.moire.ultrasonic.domain.MusicFolder
|
|
||||||
|
|
||||||
private const val SERIALIZATION_VERSION = 1
|
|
||||||
|
|
||||||
private val musicFolderSerializer = object : ObjectSerializer<MusicFolder>(SERIALIZATION_VERSION) {
|
|
||||||
|
|
||||||
override fun serializeObject(
|
|
||||||
context: SerializationContext,
|
|
||||||
output: SerializerOutput<out SerializerOutput<*>>,
|
|
||||||
item: MusicFolder
|
|
||||||
) {
|
|
||||||
output.writeString(item.id).writeString(item.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("ReturnCount")
|
|
||||||
override fun deserializeObject(
|
|
||||||
context: SerializationContext,
|
|
||||||
input: SerializerInput,
|
|
||||||
versionNumber: Int
|
|
||||||
): MusicFolder? {
|
|
||||||
if (versionNumber != SERIALIZATION_VERSION) return null
|
|
||||||
|
|
||||||
val id = input.readString() ?: return null
|
|
||||||
val name = input.readString() ?: return null
|
|
||||||
return MusicFolder(id, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializer/deserializer for [MusicFolder] domain entity.
|
|
||||||
*/
|
|
||||||
fun getMusicFolderSerializer(): DomainEntitySerializer<MusicFolder> = musicFolderSerializer
|
|
||||||
|
|
||||||
private val musicFolderListSerializer =
|
|
||||||
CollectionSerializers.getListSerializer(musicFolderSerializer)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializer/deserializer for [List] of [MusicFolder] items.
|
|
||||||
*/
|
|
||||||
fun getMusicFolderListSerializer(): DomainEntitySerializer<List<MusicFolder>> =
|
|
||||||
musicFolderListSerializer
|
|
|
@ -1,42 +0,0 @@
|
||||||
package org.moire.ultrasonic.cache
|
|
||||||
|
|
||||||
import com.twitter.serial.util.SerializationUtils
|
|
||||||
import java.io.File
|
|
||||||
import org.amshove.kluent.`it returns`
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.rules.TemporaryFolder
|
|
||||||
import org.mockito.kotlin.mock
|
|
||||||
|
|
||||||
internal const val INTERNAL_DATA_FOLDER = "data"
|
|
||||||
internal const val INTERNAL_CACHE_FOLDER = "cache"
|
|
||||||
internal const val EXTERNAL_CACHE_FOLDER = "external_cache"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base test class that inits the storage
|
|
||||||
*/
|
|
||||||
abstract class BaseStorageTest {
|
|
||||||
@get:Rule val tempFileRule = TemporaryFolder()
|
|
||||||
|
|
||||||
protected lateinit var mockDirectories: Directories
|
|
||||||
protected lateinit var storage: PermanentFileStorage
|
|
||||||
|
|
||||||
open val serverId: String = ""
|
|
||||||
|
|
||||||
@Before
|
|
||||||
fun setUp() {
|
|
||||||
mockDirectories = mock<Directories> {
|
|
||||||
on { getInternalDataDir() } `it returns` tempFileRule.newFolder(INTERNAL_DATA_FOLDER)
|
|
||||||
on { getInternalCacheDir() } `it returns` tempFileRule.newFolder(INTERNAL_CACHE_FOLDER)
|
|
||||||
on { getExternalCacheDir() } `it returns` tempFileRule.newFolder(EXTERNAL_CACHE_FOLDER)
|
|
||||||
}
|
|
||||||
storage = PermanentFileStorage(mockDirectories, serverId, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected val storageDir get() = File(mockDirectories.getInternalDataDir(), STORAGE_DIR_NAME)
|
|
||||||
|
|
||||||
protected fun validateSerializedData(index: Int = 0) {
|
|
||||||
val serializedFileBytes = storageDir.listFiles()[index].readBytes()
|
|
||||||
SerializationUtils.validateSerializedData(serializedFileBytes)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package org.moire.ultrasonic.cache
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import org.amshove.kluent.`should be equal to`
|
|
||||||
import org.amshove.kluent.`should contain`
|
|
||||||
import org.junit.Test
|
|
||||||
import org.moire.ultrasonic.cache.serializers.getMusicFolderSerializer
|
|
||||||
import org.moire.ultrasonic.domain.MusicFolder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration test for [PermanentFileStorage].
|
|
||||||
*/
|
|
||||||
class PermanentFileStorageTest : BaseStorageTest() {
|
|
||||||
override val serverId: String
|
|
||||||
get() = "some-server-id"
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should create storage dir if it is not exist`() {
|
|
||||||
val item = MusicFolder("1", "2")
|
|
||||||
storage.store("test", item, getMusicFolderSerializer())
|
|
||||||
|
|
||||||
storageDir.exists() `should be equal to` true
|
|
||||||
getServerStorageDir().exists() `should be equal to` true
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should serialize to file`() {
|
|
||||||
val item = MusicFolder("1", "23")
|
|
||||||
val name = "some-name"
|
|
||||||
|
|
||||||
storage.store(name, item, getMusicFolderSerializer())
|
|
||||||
|
|
||||||
val storageFiles = getServerStorageDir().listFiles()
|
|
||||||
storageFiles.size `should be equal to` 1
|
|
||||||
storageFiles[0].name `should contain` name
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should deserialize stored object`() {
|
|
||||||
val item = MusicFolder("some", "nice")
|
|
||||||
val name = "some-name"
|
|
||||||
storage.store(name, item, getMusicFolderSerializer())
|
|
||||||
|
|
||||||
val loadedItem = storage.load(name, getMusicFolderSerializer())
|
|
||||||
|
|
||||||
loadedItem `should be equal to` item
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should overwrite existing stored object`() {
|
|
||||||
val name = "some-nice-name"
|
|
||||||
val item1 = MusicFolder("1", "1")
|
|
||||||
val item2 = MusicFolder("2", "2")
|
|
||||||
storage.store(name, item1, getMusicFolderSerializer())
|
|
||||||
storage.store(name, item2, getMusicFolderSerializer())
|
|
||||||
|
|
||||||
val loadedItem = storage.load(name, getMusicFolderSerializer())
|
|
||||||
|
|
||||||
loadedItem `should be equal to` item2
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should clear all files when clearAll is called`() {
|
|
||||||
storage.store("name1", MusicFolder("1", "1"), getMusicFolderSerializer())
|
|
||||||
storage.store("name2", MusicFolder("2", "2"), getMusicFolderSerializer())
|
|
||||||
|
|
||||||
storage.clearAll()
|
|
||||||
|
|
||||||
getServerStorageDir().listFiles().size `should be equal to` 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should return null if serialized file not available`() {
|
|
||||||
val loadedItem = storage.load("some-name", getMusicFolderSerializer())
|
|
||||||
|
|
||||||
loadedItem `should be equal to` null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getServerStorageDir() = File(storageDir, serverId)
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package org.moire.ultrasonic.cache.serializers
|
|
||||||
|
|
||||||
import org.amshove.kluent.`should be equal to`
|
|
||||||
import org.junit.Test
|
|
||||||
import org.moire.ultrasonic.cache.BaseStorageTest
|
|
||||||
import org.moire.ultrasonic.domain.Artist
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Artist] serializers test.
|
|
||||||
*/
|
|
||||||
class ArtistSerializerTest : BaseStorageTest() {
|
|
||||||
@Test
|
|
||||||
fun `Should correctly serialize Artist object`() {
|
|
||||||
val item = Artist("id", "name", "index", "coverArt", 1, 0)
|
|
||||||
|
|
||||||
storage.store("some-name", item, getArtistsSerializer())
|
|
||||||
|
|
||||||
validateSerializedData()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should correctly deserialize Artist object`() {
|
|
||||||
val itemName = "some-name"
|
|
||||||
val item = Artist("id", "name", "index", "coverArt", null, 0)
|
|
||||||
storage.store(itemName, item, getArtistsSerializer())
|
|
||||||
|
|
||||||
val loadedItem = storage.load(itemName, getArtistsSerializer())
|
|
||||||
|
|
||||||
loadedItem `should be equal to` item
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should correctly serialize list of Artists`() {
|
|
||||||
val itemsList = listOf(
|
|
||||||
Artist(id = "1"),
|
|
||||||
Artist(id = "2", name = "some")
|
|
||||||
)
|
|
||||||
|
|
||||||
storage.store("some-name", itemsList, getArtistListSerializer())
|
|
||||||
|
|
||||||
validateSerializedData()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should correctly deserialize list of Artists`() {
|
|
||||||
val name = "some-name"
|
|
||||||
val itemsList = listOf(
|
|
||||||
Artist(id = "1"),
|
|
||||||
Artist(id = "2", name = "some")
|
|
||||||
)
|
|
||||||
storage.store(name, itemsList, getArtistListSerializer())
|
|
||||||
|
|
||||||
val loadedItems = storage.load(name, getArtistListSerializer())
|
|
||||||
|
|
||||||
loadedItems `should be equal to` itemsList
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package org.moire.ultrasonic.cache.serializers
|
|
||||||
|
|
||||||
import org.amshove.kluent.`should be equal to`
|
|
||||||
import org.junit.Test
|
|
||||||
import org.moire.ultrasonic.cache.BaseStorageTest
|
|
||||||
import org.moire.ultrasonic.domain.Artist
|
|
||||||
import org.moire.ultrasonic.domain.Indexes
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test [Indexes] domain entity serializer.
|
|
||||||
*/
|
|
||||||
class IndexesSerializerTest : BaseStorageTest() {
|
|
||||||
@Test
|
|
||||||
fun `Should correctly serialize Indexes object`() {
|
|
||||||
val item = Indexes(
|
|
||||||
220L, "", mutableListOf(Artist("12")),
|
|
||||||
mutableListOf(Artist("233", "some"))
|
|
||||||
)
|
|
||||||
|
|
||||||
storage.store("some-name", item, getIndexesSerializer())
|
|
||||||
|
|
||||||
validateSerializedData()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should correctly deserialize Indexes object`() {
|
|
||||||
val name = "some-name"
|
|
||||||
val item = Indexes(
|
|
||||||
220L, "", mutableListOf(Artist("12")),
|
|
||||||
mutableListOf(Artist("233", "some"))
|
|
||||||
)
|
|
||||||
storage.store(name, item, getIndexesSerializer())
|
|
||||||
|
|
||||||
val loadedItem = storage.load(name, getIndexesSerializer())
|
|
||||||
|
|
||||||
loadedItem `should be equal to` item
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package org.moire.ultrasonic.cache.serializers
|
|
||||||
|
|
||||||
import org.amshove.kluent.`should be equal to`
|
|
||||||
import org.junit.Test
|
|
||||||
import org.moire.ultrasonic.cache.BaseStorageTest
|
|
||||||
import org.moire.ultrasonic.domain.MusicFolder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [MusicFolder] serializers test.
|
|
||||||
*/
|
|
||||||
class MusicFolderSerializerTest : BaseStorageTest() {
|
|
||||||
@Test
|
|
||||||
fun `Should correctly serialize MusicFolder object`() {
|
|
||||||
val item = MusicFolder("Music", "Folder")
|
|
||||||
|
|
||||||
storage.store("some-name", item, getMusicFolderSerializer())
|
|
||||||
|
|
||||||
validateSerializedData()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should correctly deserialize MusicFolder object`() {
|
|
||||||
val name = "name"
|
|
||||||
val item = MusicFolder("some", "none")
|
|
||||||
storage.store(name, item, getMusicFolderSerializer())
|
|
||||||
|
|
||||||
val loadedItem = storage.load(name, getMusicFolderSerializer())
|
|
||||||
|
|
||||||
loadedItem `should be equal to` item
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should correctly serialize list of MusicFolders objects`() {
|
|
||||||
val itemsList = listOf(
|
|
||||||
MusicFolder("1", "1"),
|
|
||||||
MusicFolder("2", "2")
|
|
||||||
)
|
|
||||||
|
|
||||||
storage.store("some-name", itemsList, getMusicFolderListSerializer())
|
|
||||||
|
|
||||||
validateSerializedData()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Should correctly deserialize list of MusicFolder objects`() {
|
|
||||||
val name = "some-name"
|
|
||||||
val itemsList = listOf(
|
|
||||||
MusicFolder("1", "1"),
|
|
||||||
MusicFolder("2", "2")
|
|
||||||
)
|
|
||||||
storage.store(name, itemsList, getMusicFolderListSerializer())
|
|
||||||
|
|
||||||
val loadedItem = storage.load(name, getMusicFolderListSerializer())
|
|
||||||
|
|
||||||
loadedItem `should be equal to` itemsList
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,8 @@
|
||||||
apply from: bootstrap.kotlinModule
|
apply from: bootstrap.androidModule
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
ext {
|
dependencies {
|
||||||
jacocoExclude = [
|
implementation libs.roomRuntime
|
||||||
'**/domain/**'
|
implementation libs.roomKtx
|
||||||
]
|
kapt libs.room
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="org.moire.ultrasonic.subsonic.domain">
|
||||||
|
</manifest>
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Album.kt
|
||||||
|
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@Entity(tableName = "albums", primaryKeys = ["id", "serverId"])
|
||||||
|
data class Album(
|
||||||
|
override var id: String,
|
||||||
|
@ColumnInfo(defaultValue = "-1")
|
||||||
|
override var serverId: Int = -1,
|
||||||
|
override var parent: String? = null,
|
||||||
|
override var album: String? = null,
|
||||||
|
override var title: String? = null,
|
||||||
|
override val name: String? = null,
|
||||||
|
override var discNumber: Int? = 0,
|
||||||
|
override var coverArt: String? = null,
|
||||||
|
override var songCount: Long? = null,
|
||||||
|
override var created: Date? = null,
|
||||||
|
override var artist: String? = null,
|
||||||
|
override var artistId: String? = null,
|
||||||
|
override var duration: Int? = 0,
|
||||||
|
override var year: Int? = 0,
|
||||||
|
override var genre: String? = null,
|
||||||
|
override var starred: Boolean = false,
|
||||||
|
override var path: String? = null,
|
||||||
|
override var closeness: Int = 0,
|
||||||
|
) : MusicDirectory.Child() {
|
||||||
|
override var isDirectory = true
|
||||||
|
override var isVideo = false
|
||||||
|
}
|
|
@ -1,30 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Artist.kt
|
||||||
|
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.moire.ultrasonic.domain
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
import java.io.Serializable
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
|
||||||
|
@Entity(tableName = "artists", primaryKeys = ["id", "serverId"])
|
||||||
data class Artist(
|
data class Artist(
|
||||||
override var id: String? = null,
|
override var id: String,
|
||||||
|
@ColumnInfo(defaultValue = "-1")
|
||||||
|
override var serverId: Int = -1,
|
||||||
override var name: String? = null,
|
override var name: String? = null,
|
||||||
var index: String? = null,
|
override var index: String? = null,
|
||||||
var coverArt: String? = null,
|
override var coverArt: String? = null,
|
||||||
var albumCount: Long? = null,
|
override var albumCount: Long? = null,
|
||||||
var closeness: Int = 0
|
override var closeness: Int = 0
|
||||||
) : Serializable, GenericEntry(), Comparable<Artist> {
|
) : ArtistOrIndex(id, serverId)
|
||||||
companion object {
|
|
||||||
private const val serialVersionUID = -5790532593784846982L
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun compareTo(other: Artist): Int {
|
|
||||||
when {
|
|
||||||
this.closeness == other.closeness -> {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
this.closeness > other.closeness -> {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* ArtistOrIndex.kt
|
||||||
|
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
|
import androidx.room.Ignore
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
abstract class ArtistOrIndex(
|
||||||
|
@Ignore
|
||||||
|
override var id: String,
|
||||||
|
@Ignore
|
||||||
|
open var serverId: Int,
|
||||||
|
@Ignore
|
||||||
|
override var name: String? = null,
|
||||||
|
@Ignore
|
||||||
|
open var index: String? = null,
|
||||||
|
@Ignore
|
||||||
|
open var coverArt: String? = null,
|
||||||
|
@Ignore
|
||||||
|
open var albumCount: Long? = null,
|
||||||
|
@Ignore
|
||||||
|
open var closeness: Int = 0
|
||||||
|
) : GenericEntry() {
|
||||||
|
|
||||||
|
fun compareTo(other: ArtistOrIndex): Int {
|
||||||
|
return when {
|
||||||
|
this.closeness == other.closeness -> {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
this.closeness > other.closeness -> {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: Identifiable) = compareTo(other as ArtistOrIndex)
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry
|
|
||||||
|
|
||||||
data class Bookmark(
|
data class Bookmark(
|
||||||
val position: Int = 0,
|
val position: Int = 0,
|
||||||
|
@ -10,7 +9,7 @@ data class Bookmark(
|
||||||
val comment: String,
|
val comment: String,
|
||||||
val created: Date? = null,
|
val created: Date? = null,
|
||||||
val changed: Date? = null,
|
val changed: Date? = null,
|
||||||
val entry: Entry
|
val track: Track
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
companion object {
|
companion object {
|
||||||
private const val serialVersionUID = 8988990025189807803L
|
private const val serialVersionUID = 8988990025189807803L
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package org.moire.ultrasonic.domain
|
|
||||||
|
|
||||||
abstract class GenericEntry {
|
|
||||||
// TODO: Should be non-null!
|
|
||||||
abstract val id: String?
|
|
||||||
open val name: String? = null
|
|
||||||
|
|
||||||
// These are just a formality and will never be called,
|
|
||||||
// because Kotlin data classes will have autogenerated equals() and hashCode() functions
|
|
||||||
override operator fun equals(other: Any?): Boolean {
|
|
||||||
return this === other
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = id?.hashCode() ?: 0
|
|
||||||
result = 31 * result + (name?.hashCode() ?: 0)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,13 @@
|
||||||
package org.moire.ultrasonic.domain
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
|
@Entity
|
||||||
data class Genre(
|
data class Genre(
|
||||||
val name: String,
|
@PrimaryKey val index: String,
|
||||||
val index: String
|
val name: String
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
companion object {
|
companion object {
|
||||||
private const val serialVersionUID = -3943025175219134028L
|
private const val serialVersionUID = -3943025175219134028L
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
|
import androidx.room.Ignore
|
||||||
|
|
||||||
|
abstract class GenericEntry : Identifiable {
|
||||||
|
@Ignore
|
||||||
|
open val name: String? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Identifiable : Comparable<Identifiable> {
|
||||||
|
val id: String
|
||||||
|
|
||||||
|
val longId: Long
|
||||||
|
get() = id.hashCode().toLong()
|
||||||
|
|
||||||
|
override fun compareTo(other: Identifiable): Int {
|
||||||
|
return longId.compareTo(other.longId)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Index.kt
|
||||||
|
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
|
||||||
|
@Entity(tableName = "indexes", primaryKeys = ["id", "serverId"])
|
||||||
|
data class Index(
|
||||||
|
override var id: String,
|
||||||
|
@ColumnInfo(defaultValue = "-1")
|
||||||
|
override var serverId: Int = -1,
|
||||||
|
override var name: String? = null,
|
||||||
|
override var index: String? = null,
|
||||||
|
override var coverArt: String? = null,
|
||||||
|
override var albumCount: Long? = null,
|
||||||
|
override var closeness: Int = 0,
|
||||||
|
var musicFolderId: String? = null
|
||||||
|
) : ArtistOrIndex(id, serverId)
|
|
@ -1,14 +0,0 @@
|
||||||
package org.moire.ultrasonic.domain
|
|
||||||
|
|
||||||
import java.io.Serializable
|
|
||||||
|
|
||||||
data class Indexes(
|
|
||||||
val lastModified: Long,
|
|
||||||
val ignoredArticles: String,
|
|
||||||
val shortcuts: MutableList<Artist> = mutableListOf(),
|
|
||||||
val artists: MutableList<Artist> = mutableListOf()
|
|
||||||
) : Serializable {
|
|
||||||
companion object {
|
|
||||||
private const val serialVersionUID = 8156117238598414701L
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +1,61 @@
|
||||||
|
/*
|
||||||
|
* MusicDirectory.kt
|
||||||
|
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.moire.ultrasonic.domain
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
import java.io.Serializable
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class MusicDirectory {
|
class MusicDirectory : ArrayList<MusicDirectory.Child>() {
|
||||||
var name: String? = null
|
var name: String? = null
|
||||||
private val children = mutableListOf<Entry>()
|
|
||||||
|
|
||||||
fun addAll(entries: Collection<Entry>) {
|
|
||||||
children.addAll(entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addFirst(child: Entry) {
|
|
||||||
children.add(0, child)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addChild(child: Entry) {
|
|
||||||
children.add(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun findChild(id: String): Entry? = children.lastOrNull { it.id == id }
|
|
||||||
|
|
||||||
fun getAllChild(): List<Entry> = children.toList()
|
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun getChildren(
|
fun getChildren(
|
||||||
includeDirs: Boolean = true,
|
includeDirs: Boolean = true,
|
||||||
includeFiles: Boolean = true
|
includeFiles: Boolean = true
|
||||||
): List<Entry> {
|
): List<Child> {
|
||||||
if (includeDirs && includeFiles) {
|
if (includeDirs && includeFiles) {
|
||||||
return children
|
return toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
return children.filter { it.isDirectory && includeDirs || !it.isDirectory && includeFiles }
|
return filter { it.isDirectory && includeDirs || !it.isDirectory && includeFiles }
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Entry(
|
fun getTracks(): List<Track> {
|
||||||
override var id: String,
|
return mapNotNull {
|
||||||
var parent: String? = null,
|
it as? Track
|
||||||
var isDirectory: Boolean = false,
|
|
||||||
var title: String? = null,
|
|
||||||
var album: String? = null,
|
|
||||||
var albumId: String? = null,
|
|
||||||
var artist: String? = null,
|
|
||||||
var artistId: String? = null,
|
|
||||||
var track: Int? = 0,
|
|
||||||
var year: Int? = 0,
|
|
||||||
var genre: String? = null,
|
|
||||||
var contentType: String? = null,
|
|
||||||
var suffix: String? = null,
|
|
||||||
var transcodedContentType: String? = null,
|
|
||||||
var transcodedSuffix: String? = null,
|
|
||||||
var coverArt: String? = null,
|
|
||||||
var size: Long? = null,
|
|
||||||
var songCount: Long? = null,
|
|
||||||
var duration: Int? = null,
|
|
||||||
var bitRate: Int? = null,
|
|
||||||
var path: String? = null,
|
|
||||||
var isVideo: Boolean = false,
|
|
||||||
var starred: Boolean = false,
|
|
||||||
var discNumber: Int? = null,
|
|
||||||
var type: String? = null,
|
|
||||||
var created: Date? = null,
|
|
||||||
var closeness: Int = 0,
|
|
||||||
var bookmarkPosition: Int = 0,
|
|
||||||
var userRating: Int? = null,
|
|
||||||
var averageRating: Float? = null
|
|
||||||
) : Serializable, GenericEntry(), Comparable<Entry> {
|
|
||||||
fun setDuration(duration: Long) {
|
|
||||||
this.duration = duration.toInt()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
fun getAlbums(): List<Album> {
|
||||||
private const val serialVersionUID = -3339106650010798108L
|
return mapNotNull {
|
||||||
|
it as? Album
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun compareTo(other: Entry): Int {
|
abstract class Child : GenericEntry() {
|
||||||
when {
|
abstract override var id: String
|
||||||
this.closeness == other.closeness -> {
|
abstract var serverId: Int
|
||||||
return 0
|
abstract var parent: String?
|
||||||
}
|
abstract var isDirectory: Boolean
|
||||||
this.closeness > other.closeness -> {
|
abstract var album: String?
|
||||||
return -1
|
abstract var title: String?
|
||||||
}
|
abstract override val name: String?
|
||||||
else -> {
|
abstract var discNumber: Int?
|
||||||
return 1
|
abstract var coverArt: String?
|
||||||
}
|
abstract var songCount: Long?
|
||||||
}
|
abstract var created: Date?
|
||||||
}
|
abstract var artist: String?
|
||||||
|
abstract var artistId: String?
|
||||||
|
abstract var duration: Int?
|
||||||
|
abstract var year: Int?
|
||||||
|
abstract var genre: String?
|
||||||
|
abstract var starred: Boolean
|
||||||
|
abstract var path: String?
|
||||||
|
abstract var closeness: Int
|
||||||
|
abstract var isVideo: Boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,22 @@
|
||||||
|
/*
|
||||||
|
* MusicFolder.kt
|
||||||
|
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
package org.moire.ultrasonic.domain
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a top level directory in which music or other media is stored.
|
* Represents a top level directory in which music or other media is stored.
|
||||||
*/
|
*/
|
||||||
|
@Entity(tableName = "music_folders", primaryKeys = ["id", "serverId"])
|
||||||
data class MusicFolder(
|
data class MusicFolder(
|
||||||
override val id: String,
|
override val id: String,
|
||||||
override val name: String
|
override val name: String,
|
||||||
|
@ColumnInfo(defaultValue = "-1")
|
||||||
|
var serverId: Int
|
||||||
) : GenericEntry()
|
) : GenericEntry()
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
package org.moire.ultrasonic.domain
|
|
||||||
|
|
||||||
enum class PlayerState {
|
|
||||||
IDLE,
|
|
||||||
DOWNLOADING,
|
|
||||||
PREPARING,
|
|
||||||
PREPARED,
|
|
||||||
STARTED,
|
|
||||||
STOPPED,
|
|
||||||
PAUSED,
|
|
||||||
COMPLETED
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package org.moire.ultrasonic.domain
|
|
||||||
|
|
||||||
enum class RepeatMode {
|
|
||||||
OFF {
|
|
||||||
override operator fun next(): RepeatMode = ALL
|
|
||||||
},
|
|
||||||
ALL {
|
|
||||||
override operator fun next(): RepeatMode = SINGLE
|
|
||||||
},
|
|
||||||
SINGLE {
|
|
||||||
override operator fun next(): RepeatMode = OFF
|
|
||||||
};
|
|
||||||
|
|
||||||
abstract operator fun next(): RepeatMode
|
|
||||||
}
|
|
|
@ -1,12 +1,10 @@
|
||||||
package org.moire.ultrasonic.domain
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result of a search. Contains matching artists, albums and songs.
|
* The result of a search. Contains matching artists, albums and songs.
|
||||||
*/
|
*/
|
||||||
data class SearchResult(
|
data class SearchResult(
|
||||||
val artists: List<Artist>,
|
val artists: List<ArtistOrIndex> = listOf(),
|
||||||
val albums: List<Entry>,
|
val albums: List<Album> = listOf(),
|
||||||
val songs: List<Entry>
|
val songs: List<Track> = listOf()
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package org.moire.ultrasonic.domain
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry
|
|
||||||
|
|
||||||
data class Share(
|
data class Share(
|
||||||
override var id: String? = null,
|
override var id: String,
|
||||||
var url: String? = null,
|
var url: String? = null,
|
||||||
var description: String? = null,
|
var description: String? = null,
|
||||||
var username: String? = null,
|
var username: String? = null,
|
||||||
|
@ -12,17 +11,22 @@ data class Share(
|
||||||
var lastVisited: String? = null,
|
var lastVisited: String? = null,
|
||||||
var expires: String? = null,
|
var expires: String? = null,
|
||||||
var visitCount: Long? = null,
|
var visitCount: Long? = null,
|
||||||
private val entries: MutableList<Entry> = mutableListOf()
|
private val tracks: MutableList<Track> = mutableListOf()
|
||||||
) : Serializable, GenericEntry() {
|
) : Serializable, GenericEntry() {
|
||||||
override val name: String?
|
override val name: String?
|
||||||
get() = url?.let { urlPattern.matcher(url).replaceFirst("$1") }
|
get() {
|
||||||
|
if (url != null) {
|
||||||
|
return urlPattern.matcher(url!!).replaceFirst("$1")
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
fun getEntries(): List<Entry> {
|
fun getEntries(): List<Track> {
|
||||||
return entries.toList()
|
return tracks.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addEntry(entry: Entry) {
|
fun addEntry(track: Track) {
|
||||||
entries.add(entry)
|
tracks.add(track)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Track.kt
|
||||||
|
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.moire.ultrasonic.domain
|
||||||
|
|
||||||
|
import androidx.room.ColumnInfo
|
||||||
|
import androidx.room.Entity
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
@Entity(tableName = "tracks", primaryKeys = ["id", "serverId"])
|
||||||
|
data class Track(
|
||||||
|
override var id: String,
|
||||||
|
@ColumnInfo(defaultValue = "-1")
|
||||||
|
override var serverId: Int = -1,
|
||||||
|
override var parent: String? = null,
|
||||||
|
override var isDirectory: Boolean = false,
|
||||||
|
override var title: String? = null,
|
||||||
|
override var album: String? = null,
|
||||||
|
var albumId: String? = null,
|
||||||
|
override var artist: String? = null,
|
||||||
|
override var artistId: String? = null,
|
||||||
|
var track: Int? = null,
|
||||||
|
override var year: Int? = null,
|
||||||
|
override var genre: String? = null,
|
||||||
|
var contentType: String? = null,
|
||||||
|
var suffix: String? = null,
|
||||||
|
var transcodedContentType: String? = null,
|
||||||
|
var transcodedSuffix: String? = null,
|
||||||
|
override var coverArt: String? = null,
|
||||||
|
var size: Long? = null,
|
||||||
|
override var songCount: Long? = null,
|
||||||
|
override var duration: Int? = null,
|
||||||
|
var bitRate: Int? = null,
|
||||||
|
override var path: String? = null,
|
||||||
|
override var isVideo: Boolean = false,
|
||||||
|
override var starred: Boolean = false,
|
||||||
|
override var discNumber: Int? = null,
|
||||||
|
var type: String? = null,
|
||||||
|
override var created: Date? = null,
|
||||||
|
override var closeness: Int = 0,
|
||||||
|
var bookmarkPosition: Int = 0,
|
||||||
|
var userRating: Int? = null,
|
||||||
|
var averageRating: Float? = null,
|
||||||
|
override var name: String? = null
|
||||||
|
) : Serializable, MusicDirectory.Child() {
|
||||||
|
fun setDuration(duration: Long) {
|
||||||
|
this.duration = duration.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID = -3339106650010798108L
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compareTo(other: Track): Int {
|
||||||
|
when {
|
||||||
|
this.closeness == other.closeness -> {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
this.closeness > other.closeness -> {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: Identifiable) = compareTo(other as Track)
|
||||||
|
}
|
|
@ -1,30 +1,22 @@
|
||||||
apply from: bootstrap.kotlinModule
|
apply from: bootstrap.kotlinModule
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api other.retrofit
|
api libs.retrofit
|
||||||
api other.jacksonConverter
|
api libs.jacksonConverter
|
||||||
api other.koinCore
|
api libs.koinCore
|
||||||
|
|
||||||
implementation(other.jacksonKotlin) {
|
implementation(libs.jacksonKotlin) {
|
||||||
exclude module: 'kotlin-reflect'
|
exclude module: 'kotlin-reflect'
|
||||||
}
|
}
|
||||||
implementation other.kotlinReflect // for jackson kotlin, but to use the same version
|
implementation libs.kotlinReflect // for jackson kotlin, but to use the same version
|
||||||
implementation other.okhttpLogging
|
implementation libs.okhttpLogging
|
||||||
implementation other.timber
|
implementation libs.timber
|
||||||
|
|
||||||
testImplementation testing.kotlinJunit
|
testImplementation libs.kotlinJunit
|
||||||
testImplementation testing.mockito
|
testImplementation libs.mockito
|
||||||
testImplementation testing.mockitoInline
|
testImplementation libs.mockitoInline
|
||||||
testImplementation testing.mockitoKotlin
|
testImplementation libs.mockitoKotlin
|
||||||
testImplementation testing.kluent
|
testImplementation libs.kluent
|
||||||
testImplementation testing.mockWebServer
|
testImplementation libs.mockWebServer
|
||||||
testImplementation testing.apacheCodecs
|
testImplementation libs.apacheCodecs
|
||||||
}
|
|
||||||
|
|
||||||
ext {
|
|
||||||
// Excluding data classes
|
|
||||||
jacocoExclude = [
|
|
||||||
'**/models/**',
|
|
||||||
'**/di/**'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
import okio.Okio
|
import okio.buffer
|
||||||
|
import okio.source
|
||||||
import org.amshove.kluent.`should be`
|
import org.amshove.kluent.`should be`
|
||||||
import org.amshove.kluent.`should contain`
|
import org.amshove.kluent.`should contain`
|
||||||
import org.amshove.kluent.`should not be`
|
import org.amshove.kluent.`should not be`
|
||||||
|
@ -40,12 +41,12 @@ fun MockWebServer.enqueueResponse(resourceName: String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Any.loadJsonResponse(name: String): String {
|
fun Any.loadJsonResponse(name: String): String {
|
||||||
val source = Okio.buffer(Okio.source(javaClass.classLoader.getResourceAsStream(name)))
|
val source = javaClass.classLoader.getResourceAsStream(name)!!.source().buffer()
|
||||||
return source.readString(Charset.forName("UTF-8"))
|
return source.readString(Charset.forName("UTF-8"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Any.loadResourceStream(name: String): InputStream {
|
fun Any.loadResourceStream(name: String): InputStream {
|
||||||
val source = Okio.buffer(Okio.source(javaClass.classLoader.getResourceAsStream(name)))
|
val source = javaClass.classLoader.getResourceAsStream(name)!!.source().buffer()
|
||||||
return source.inputStream()
|
return source.inputStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@ package org.moire.ultrasonic.api.subsonic
|
||||||
|
|
||||||
import org.amshove.kluent.`should be equal to`
|
import org.amshove.kluent.`should be equal to`
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.moire.ultrasonic.api.subsonic.models.Album
|
||||||
import org.moire.ultrasonic.api.subsonic.models.AlbumListType
|
import org.moire.ultrasonic.api.subsonic.models.AlbumListType
|
||||||
import org.moire.ultrasonic.api.subsonic.models.AlbumListType.BY_GENRE
|
import org.moire.ultrasonic.api.subsonic.models.AlbumListType.BY_GENRE
|
||||||
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Integration tests for [SubsonicAPIDefinition] for getAlbumList call.
|
* Integration tests for [SubsonicAPIDefinition] for getAlbumList call.
|
||||||
|
@ -28,8 +28,8 @@ class SubsonicApiGetAlbumListRequestTest : SubsonicAPIClientTest() {
|
||||||
assertResponseSuccessful(response)
|
assertResponseSuccessful(response)
|
||||||
with(response.body()!!.albumList) {
|
with(response.body()!!.albumList) {
|
||||||
size `should be equal to` 2
|
size `should be equal to` 2
|
||||||
this[1] `should be equal to` MusicDirectoryChild(
|
this[1] `should be equal to` Album(
|
||||||
id = "9997", parent = "9996", isDir = true,
|
id = "9997", parent = "9996",
|
||||||
title = "Endless Forms Most Beautiful", album = "Endless Forms Most Beautiful",
|
title = "Endless Forms Most Beautiful", album = "Endless Forms Most Beautiful",
|
||||||
artist = "Nightwish", year = 2015, genre = "Symphonic Metal",
|
artist = "Nightwish", year = 2015, genre = "Symphonic Metal",
|
||||||
coverArt = "9997", playCount = 11,
|
coverArt = "9997", playCount = 11,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package org.moire.ultrasonic.api.subsonic
|
package org.moire.ultrasonic.api.subsonic
|
||||||
|
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
import org.amshove.kluent.`should be equal to`
|
|
||||||
import org.amshove.kluent.`should be`
|
import org.amshove.kluent.`should be`
|
||||||
|
import org.amshove.kluent.`should be equal to`
|
||||||
import org.amshove.kluent.`should not be`
|
import org.amshove.kluent.`should not be`
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package org.moire.ultrasonic.api.subsonic
|
package org.moire.ultrasonic.api.subsonic
|
||||||
|
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
import org.amshove.kluent.`should be equal to`
|
|
||||||
import org.amshove.kluent.`should be`
|
import org.amshove.kluent.`should be`
|
||||||
|
import org.amshove.kluent.`should be equal to`
|
||||||
import org.amshove.kluent.`should not be`
|
import org.amshove.kluent.`should not be`
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package org.moire.ultrasonic.api.subsonic
|
package org.moire.ultrasonic.api.subsonic
|
||||||
|
|
||||||
import org.amshove.kluent.`should be equal to`
|
|
||||||
import org.amshove.kluent.`should be`
|
import org.amshove.kluent.`should be`
|
||||||
|
import org.amshove.kluent.`should be equal to`
|
||||||
import org.amshove.kluent.`should not be`
|
import org.amshove.kluent.`should not be`
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.moire.ultrasonic.api.subsonic.models.MusicDirectory
|
import org.moire.ultrasonic.api.subsonic.models.MusicDirectory
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.moire.ultrasonic.api.subsonic
|
||||||
import org.amshove.kluent.`should be equal to`
|
import org.amshove.kluent.`should be equal to`
|
||||||
import org.amshove.kluent.`should not be`
|
import org.amshove.kluent.`should not be`
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.moire.ultrasonic.api.subsonic.models.Album
|
||||||
import org.moire.ultrasonic.api.subsonic.models.Artist
|
import org.moire.ultrasonic.api.subsonic.models.Artist
|
||||||
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
|
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
|
||||||
import org.moire.ultrasonic.api.subsonic.models.SearchTwoResult
|
import org.moire.ultrasonic.api.subsonic.models.SearchTwoResult
|
||||||
|
@ -32,9 +33,8 @@ class SubsonicApiSearchTwoTest : SubsonicAPIClientTest() {
|
||||||
artistList.size `should be equal to` 1
|
artistList.size `should be equal to` 1
|
||||||
artistList[0] `should be equal to` Artist(id = "522", name = "The Prodigy")
|
artistList[0] `should be equal to` Artist(id = "522", name = "The Prodigy")
|
||||||
albumList.size `should be equal to` 1
|
albumList.size `should be equal to` 1
|
||||||
albumList[0] `should be equal to` MusicDirectoryChild(
|
albumList[0] `should be equal to` Album(
|
||||||
id = "8867", parent = "522",
|
id = "8867", parent = "522", title = "Always Outnumbered, Never Outgunned",
|
||||||
isDir = true, title = "Always Outnumbered, Never Outgunned",
|
|
||||||
album = "Always Outnumbered, Never Outgunned", artist = "The Prodigy",
|
album = "Always Outnumbered, Never Outgunned", artist = "The Prodigy",
|
||||||
year = 2004, genre = "Electronic", coverArt = "8867", playCount = 0,
|
year = 2004, genre = "Electronic", coverArt = "8867", playCount = 0,
|
||||||
created = parseDate("2016-10-23T20:57:27.000Z")
|
created = parseDate("2016-10-23T20:57:27.000Z")
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package org.moire.ultrasonic.api.subsonic
|
package org.moire.ultrasonic.api.subsonic
|
||||||
|
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
import org.amshove.kluent.`should be equal to`
|
|
||||||
import org.amshove.kluent.`should be`
|
import org.amshove.kluent.`should be`
|
||||||
|
import org.amshove.kluent.`should be equal to`
|
||||||
import org.amshove.kluent.`should not be`
|
import org.amshove.kluent.`should not be`
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ fun Response<out ResponseBody>.toStreamResponse(): StreamResponse {
|
||||||
val contentType = responseBody?.contentType()
|
val contentType = responseBody?.contentType()
|
||||||
if (
|
if (
|
||||||
contentType != null &&
|
contentType != null &&
|
||||||
contentType.type().equals("application", true) &&
|
contentType.type.equals("application", true) &&
|
||||||
contentType.subtype().equals("json", true)
|
contentType.subtype.equals("json", true)
|
||||||
) {
|
) {
|
||||||
val error = SubsonicAPIClient.jacksonMapper.readValue<SubsonicResponse>(
|
val error = SubsonicAPIClient.jacksonMapper.readValue<SubsonicResponse>(
|
||||||
responseBody.byteStream()
|
responseBody.byteStream()
|
||||||
|
@ -40,11 +40,11 @@ fun Response<out ResponseBody>.toStreamResponse(): StreamResponse {
|
||||||
* It creates Exceptions from the results returned by the Subsonic API
|
* It creates Exceptions from the results returned by the Subsonic API
|
||||||
*/
|
*/
|
||||||
@Suppress("ThrowsCount")
|
@Suppress("ThrowsCount")
|
||||||
fun <T : SubsonicResponse> Response<out T>.throwOnFailure(): Response<out T> {
|
fun <T : SubsonicResponse> Response<T>.throwOnFailure(): Response<T> {
|
||||||
val response = this
|
val response = this
|
||||||
|
|
||||||
if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) {
|
if (response.isSuccessful && response.body()!!.status === SubsonicResponse.Status.OK) {
|
||||||
return this as Response<T>
|
return this
|
||||||
}
|
}
|
||||||
if (!response.isSuccessful) {
|
if (!response.isSuccessful) {
|
||||||
throw IOException("Server error, code: " + response.code())
|
throw IOException("Server error, code: " + response.code())
|
||||||
|
|
|
@ -8,6 +8,7 @@ import java.security.cert.X509Certificate
|
||||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
import javax.net.ssl.X509TrustManager
|
import javax.net.ssl.X509TrustManager
|
||||||
|
import okhttp3.Credentials
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
@ -68,12 +69,24 @@ class SubsonicAPIClient(
|
||||||
.addInterceptor { chain ->
|
.addInterceptor { chain ->
|
||||||
// Adds default request params
|
// Adds default request params
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
val newUrl = originalRequest.url().newBuilder()
|
val newUrl = originalRequest.url.newBuilder()
|
||||||
.addQueryParameter("u", config.username)
|
.addQueryParameter("u", config.username)
|
||||||
.addQueryParameter("c", config.clientID)
|
.addQueryParameter("c", config.clientID)
|
||||||
.addQueryParameter("f", "json")
|
.addQueryParameter("f", "json")
|
||||||
.build()
|
.build()
|
||||||
chain.proceed(originalRequest.newBuilder().url(newUrl).build())
|
val newRequestBuilder = originalRequest.newBuilder().url(newUrl)
|
||||||
|
if (originalRequest.url.username.isNotEmpty() &&
|
||||||
|
originalRequest.url.password.isNotEmpty()
|
||||||
|
) {
|
||||||
|
newRequestBuilder.addHeader(
|
||||||
|
"Authorization",
|
||||||
|
Credentials.basic(
|
||||||
|
originalRequest.url.username,
|
||||||
|
originalRequest.url.password
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
chain.proceed(newRequestBuilder.build())
|
||||||
}
|
}
|
||||||
.addInterceptor(versionInterceptor)
|
.addInterceptor(versionInterceptor)
|
||||||
.addInterceptor(proxyPasswordInterceptor)
|
.addInterceptor(proxyPasswordInterceptor)
|
||||||
|
@ -83,7 +96,7 @@ class SubsonicAPIClient(
|
||||||
|
|
||||||
// Create the Retrofit instance, and register a special converter factory
|
// Create the Retrofit instance, and register a special converter factory
|
||||||
// It will update our protocol version to the correct version, once we made a successful call
|
// It will update our protocol version to the correct version, once we made a successful call
|
||||||
val retrofit: Retrofit = Retrofit.Builder()
|
private val retrofit: Retrofit = Retrofit.Builder()
|
||||||
.baseUrl("${config.baseUrl}/rest/")
|
.baseUrl("${config.baseUrl}/rest/")
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
.addConverterFactory(
|
.addConverterFactory(
|
||||||
|
@ -109,17 +122,20 @@ class SubsonicAPIClient(
|
||||||
|
|
||||||
private fun OkHttpClient.Builder.addLogging() {
|
private fun OkHttpClient.Builder.addLogging() {
|
||||||
val loggingInterceptor = HttpLoggingInterceptor(okLogger)
|
val loggingInterceptor = HttpLoggingInterceptor(okLogger)
|
||||||
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
|
loggingInterceptor.level = HttpLoggingInterceptor.Level.HEADERS
|
||||||
this.addInterceptor(loggingInterceptor)
|
this.addInterceptor(loggingInterceptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("TrustAllX509TrustManager", "EmptyFunctionBlock")
|
|
||||||
private fun OkHttpClient.Builder.allowSelfSignedCertificates() {
|
private fun OkHttpClient.Builder.allowSelfSignedCertificates() {
|
||||||
val trustManager = object : X509TrustManager {
|
val trustManager =
|
||||||
override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
|
@Suppress("CustomX509TrustManager")
|
||||||
override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
|
object : X509TrustManager {
|
||||||
override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
|
@Suppress("TrustAllX509TrustManager")
|
||||||
}
|
override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
|
||||||
|
@Suppress("TrustAllX509TrustManager")
|
||||||
|
override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) {}
|
||||||
|
override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
val sslContext = SSLContext.getInstance("SSL")
|
val sslContext = SSLContext.getInstance("SSL")
|
||||||
sslContext.init(null, arrayOf(trustManager), SecureRandom())
|
sslContext.init(null, arrayOf(trustManager), SecureRandom())
|
||||||
|
|
|
@ -229,6 +229,19 @@ interface SubsonicAPIDefinition {
|
||||||
@Header("Range") offset: Long? = null
|
@Header("Range") offset: Long? = null
|
||||||
): Call<ResponseBody>
|
): Call<ResponseBody>
|
||||||
|
|
||||||
|
@Streaming
|
||||||
|
@GET("download.view")
|
||||||
|
fun download(
|
||||||
|
@Query("id") id: String,
|
||||||
|
@Query("maxBitRate") maxBitRate: Int? = null,
|
||||||
|
@Query("format") format: String? = null,
|
||||||
|
@Query("timeOffset") timeOffset: Int? = null,
|
||||||
|
@Query("size") videoSize: String? = null,
|
||||||
|
@Query("estimateContentLength") estimateContentLength: Boolean? = null,
|
||||||
|
@Query("converted") converted: Boolean? = null,
|
||||||
|
@Header("Range") offset: Long? = null
|
||||||
|
): Call<ResponseBody>
|
||||||
|
|
||||||
@GET("jukeboxControl.view")
|
@GET("jukeboxControl.view")
|
||||||
fun jukeboxControl(
|
fun jukeboxControl(
|
||||||
@Query("action") action: JukeboxAction,
|
@Query("action") action: JukeboxAction,
|
||||||
|
|
|
@ -18,7 +18,7 @@ class PasswordHexInterceptor(private val password: String) : Interceptor {
|
||||||
|
|
||||||
override fun intercept(chain: Chain): Response {
|
override fun intercept(chain: Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
val updatedUrl = originalRequest.url().newBuilder()
|
val updatedUrl = originalRequest.url.newBuilder()
|
||||||
.addEncodedQueryParameter("p", passwordHex).build()
|
.addEncodedQueryParameter("p", passwordHex).build()
|
||||||
return chain.proceed(originalRequest.newBuilder().url(updatedUrl).build())
|
return chain.proceed(originalRequest.newBuilder().url(updatedUrl).build())
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ class PasswordMD5Interceptor(private val password: String) : Interceptor {
|
||||||
override fun intercept(chain: Chain): Response {
|
override fun intercept(chain: Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
val salt = getSalt()
|
val salt = getSalt()
|
||||||
val updatedUrl = originalRequest.url().newBuilder()
|
val updatedUrl = originalRequest.url.newBuilder()
|
||||||
.addQueryParameter("t", getPasswordMD5Hash(salt))
|
.addQueryParameter("t", getPasswordMD5Hash(salt))
|
||||||
.addQueryParameter("s", salt)
|
.addQueryParameter("s", salt)
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -19,7 +19,7 @@ internal const val TIMEOUT_MILLIS_PER_OFFSET_BYTE = 0.02
|
||||||
internal class RangeHeaderInterceptor : Interceptor {
|
internal class RangeHeaderInterceptor : Interceptor {
|
||||||
override fun intercept(chain: Chain): Response {
|
override fun intercept(chain: Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
val headers = originalRequest.headers()
|
val headers = originalRequest.headers
|
||||||
return if (headers.names().contains("Range")) {
|
return if (headers.names().contains("Range")) {
|
||||||
val offsetValue = headers["Range"] ?: "0"
|
val offsetValue = headers["Range"] ?: "0"
|
||||||
val offset = "bytes=$offsetValue-"
|
val offset = "bytes=$offsetValue-"
|
||||||
|
|
|
@ -18,7 +18,7 @@ internal class VersionInterceptor(
|
||||||
val newRequest = originalRequest.newBuilder()
|
val newRequest = originalRequest.newBuilder()
|
||||||
.url(
|
.url(
|
||||||
originalRequest
|
originalRequest
|
||||||
.url()
|
.url
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addQueryParameter("v", protocolVersion.restApiVersion)
|
.addQueryParameter("v", protocolVersion.restApiVersion)
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -5,14 +5,20 @@ import java.util.Calendar
|
||||||
|
|
||||||
data class Album(
|
data class Album(
|
||||||
val id: String = "",
|
val id: String = "",
|
||||||
val name: String = "",
|
val parent: String = "",
|
||||||
|
val album: String = "",
|
||||||
|
val title: String? = null,
|
||||||
|
val name: String? = null,
|
||||||
|
val discNumber: Int = 0,
|
||||||
val coverArt: String = "",
|
val coverArt: String = "",
|
||||||
|
val songCount: Int = 0,
|
||||||
|
val created: Calendar? = null,
|
||||||
val artist: String = "",
|
val artist: String = "",
|
||||||
val artistId: String = "",
|
val artistId: String = "",
|
||||||
val songCount: Int = 0,
|
|
||||||
val duration: Int = 0,
|
val duration: Int = 0,
|
||||||
val created: Calendar? = null,
|
|
||||||
val year: Int = 0,
|
val year: Int = 0,
|
||||||
val genre: String = "",
|
val genre: String = "",
|
||||||
@JsonProperty("song") val songList: List<MusicDirectoryChild> = emptyList()
|
val playCount: Int = 0,
|
||||||
|
@JsonProperty("song") val songList: List<MusicDirectoryChild> = emptyList(),
|
||||||
|
@JsonProperty("starred") val starredDate: String = ""
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,6 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
|
||||||
data class SearchTwoResult(
|
data class SearchTwoResult(
|
||||||
@JsonProperty("artist") val artistList: List<Artist> = emptyList(),
|
@JsonProperty("artist") val artistList: List<Artist> = emptyList(),
|
||||||
@JsonProperty("album") val albumList: List<MusicDirectoryChild> = emptyList(),
|
@JsonProperty("album") val albumList: List<Album> = emptyList(),
|
||||||
@JsonProperty("song") val songList: List<MusicDirectoryChild> = emptyList()
|
@JsonProperty("song") val songList: List<MusicDirectoryChild> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.moire.ultrasonic.api.subsonic.response
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicError
|
import org.moire.ultrasonic.api.subsonic.SubsonicError
|
||||||
import org.moire.ultrasonic.api.subsonic.models.MusicDirectoryChild
|
import org.moire.ultrasonic.api.subsonic.models.Album
|
||||||
|
|
||||||
class GetAlbumListResponse(
|
class GetAlbumListResponse(
|
||||||
status: Status,
|
status: Status,
|
||||||
|
@ -12,10 +12,10 @@ class GetAlbumListResponse(
|
||||||
) : SubsonicResponse(status, version, error) {
|
) : SubsonicResponse(status, version, error) {
|
||||||
@JsonProperty("albumList") private val albumWrapper = AlbumWrapper()
|
@JsonProperty("albumList") private val albumWrapper = AlbumWrapper()
|
||||||
|
|
||||||
val albumList: List<MusicDirectoryChild>
|
val albumList: List<Album>
|
||||||
get() = albumWrapper.albumList
|
get() = albumWrapper.albumList
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AlbumWrapper(
|
private class AlbumWrapper(
|
||||||
@JsonProperty("album") val albumList: List<MusicDirectoryChild> = emptyList()
|
@JsonProperty("album") val albumList: List<Album> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.moire.ultrasonic.api.subsonic.models
|
package org.moire.ultrasonic.api.subsonic.models
|
||||||
|
|
||||||
|
import java.util.Locale
|
||||||
import org.amshove.kluent.`should be equal to`
|
import org.amshove.kluent.`should be equal to`
|
||||||
import org.amshove.kluent.`should throw`
|
import org.amshove.kluent.`should throw`
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -10,7 +11,7 @@ import org.junit.Test
|
||||||
class AlbumListTypeTest {
|
class AlbumListTypeTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Should create type from string ignoring case`() {
|
fun `Should create type from string ignoring case`() {
|
||||||
val type = AlbumListType.SORTED_BY_NAME.typeName.toLowerCase()
|
val type = AlbumListType.SORTED_BY_NAME.typeName.lowercase(Locale.ROOT)
|
||||||
|
|
||||||
val albumListType = AlbumListType.fromName(type)
|
val albumListType = AlbumListType.fromName(type)
|
||||||
|
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
ext.versions = [
|
|
||||||
minSdk : 14,
|
|
||||||
targetSdk : 29,
|
|
||||||
compileSdk : 29,
|
|
||||||
// You need to run ./gradlew wrapper after updating the version
|
|
||||||
gradle : '7.0',
|
|
||||||
|
|
||||||
navigation : "2.3.5",
|
|
||||||
gradlePlugin : "4.2.0",
|
|
||||||
androidxcore : "1.5.0",
|
|
||||||
ktlint : "0.37.1",
|
|
||||||
ktlintGradle : "9.2.1",
|
|
||||||
detekt : "1.17.1",
|
|
||||||
jacoco : "0.8.7",
|
|
||||||
preferences : "1.1.1",
|
|
||||||
media : "1.3.1",
|
|
||||||
|
|
||||||
androidSupport : "28.0.0",
|
|
||||||
androidLegacySupport : "1.0.0",
|
|
||||||
androidSupportDesign : "1.3.0",
|
|
||||||
constraintLayout : "2.0.4",
|
|
||||||
multidex : "2.0.1",
|
|
||||||
room : "2.3.0",
|
|
||||||
kotlin : "1.5.10",
|
|
||||||
kotlinxCoroutines : "1.5.0-native-mt",
|
|
||||||
viewModelKtx : "2.2.0",
|
|
||||||
|
|
||||||
retrofit : "2.6.4",
|
|
||||||
jackson : "2.9.5",
|
|
||||||
okhttp : "3.12.13",
|
|
||||||
twitterSerial : "0.1.6",
|
|
||||||
koin : "3.0.2",
|
|
||||||
picasso : "2.71828",
|
|
||||||
sortListView : "1.0.1",
|
|
||||||
|
|
||||||
junit4 : "4.13.2",
|
|
||||||
junit5 : "5.7.1",
|
|
||||||
mockito : "3.11.0",
|
|
||||||
mockitoKotlin : "3.2.0",
|
|
||||||
kluent : "1.64",
|
|
||||||
apacheCodecs : "1.15",
|
|
||||||
robolectric : "4.5.1",
|
|
||||||
dexter : "6.2.2",
|
|
||||||
timber : "4.7.1",
|
|
||||||
fastScroll : "2.0.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
ext.gradlePlugins = [
|
|
||||||
gradle : "com.android.tools.build:gradle:$versions.gradlePlugin",
|
|
||||||
kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:$versions.kotlin",
|
|
||||||
ktlintGradle : "org.jlleitschuh.gradle:ktlint-gradle:$versions.ktlintGradle",
|
|
||||||
detekt : "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$versions.detekt",
|
|
||||||
jacoco : "org.jacoco:org.jacoco.core:$versions.jacoco",
|
|
||||||
]
|
|
||||||
|
|
||||||
ext.androidSupport = [
|
|
||||||
core : "androidx.core:core-ktx:$versions.androidxcore",
|
|
||||||
support : "androidx.legacy:legacy-support-v4:$versions.androidLegacySupport",
|
|
||||||
design : "com.google.android.material:material:$versions.androidSupportDesign",
|
|
||||||
annotations : "com.android.support:support-annotations:$versions.androidSupport",
|
|
||||||
multidex : "androidx.multidex:multidex:$versions.multidex",
|
|
||||||
constraintLayout : "androidx.constraintlayout:constraintlayout:$versions.constraintLayout",
|
|
||||||
room : "androidx.room:room-compiler:$versions.room",
|
|
||||||
roomRuntime : "androidx.room:room-runtime:$versions.room",
|
|
||||||
roomKtx : "androidx.room:room-ktx:$versions.room",
|
|
||||||
viewModelKtx : "androidx.lifecycle:lifecycle-viewmodel-ktx:$versions.viewModelKtx",
|
|
||||||
navigationFragment : "androidx.navigation:navigation-fragment:$versions.navigation",
|
|
||||||
navigationUi : "androidx.navigation:navigation-ui:$versions.navigation",
|
|
||||||
navigationFragmentKtx : "androidx.navigation:navigation-fragment-ktx:$versions.navigation",
|
|
||||||
navigationUiKtx : "androidx.navigation:navigation-ui-ktx:$versions.navigation",
|
|
||||||
navigationFeature : "androidx.navigation:navigation-dynamic-features-fragment:$versions.navigation",
|
|
||||||
preferences : "androidx.preference:preference:$versions.preferences",
|
|
||||||
media : "androidx.media:media:$versions.media",
|
|
||||||
]
|
|
||||||
|
|
||||||
ext.other = [
|
|
||||||
kotlinStdlib : "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin",
|
|
||||||
kotlinReflect : "org.jetbrains.kotlin:kotlin-reflect:$versions.kotlin",
|
|
||||||
kotlinxCoroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$versions.kotlinxCoroutines",
|
|
||||||
retrofit : "com.squareup.retrofit2:retrofit:$versions.retrofit",
|
|
||||||
gsonConverter : "com.squareup.retrofit2:converter-gson:$versions.retrofit",
|
|
||||||
jacksonConverter : "com.squareup.retrofit2:converter-jackson:$versions.retrofit",
|
|
||||||
jacksonKotlin : "com.fasterxml.jackson.module:jackson-module-kotlin:$versions.jackson",
|
|
||||||
okhttpLogging : "com.squareup.okhttp3:logging-interceptor:$versions.okhttp",
|
|
||||||
twitterSerial : "com.twitter.serial:serial:$versions.twitterSerial",
|
|
||||||
koinCore : "io.insert-koin:koin-core:$versions.koin",
|
|
||||||
koinAndroid : "io.insert-koin:koin-android:$versions.koin",
|
|
||||||
koinViewModel : "io.insert-koin:koin-android-viewmodel:$versions.koin",
|
|
||||||
picasso : "com.squareup.picasso:picasso:$versions.picasso",
|
|
||||||
dexter : "com.karumi:dexter:$versions.dexter",
|
|
||||||
timber : "com.jakewharton.timber:timber:$versions.timber",
|
|
||||||
fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll",
|
|
||||||
sortListView : "com.github.tzugen:drag-sort-listview:$versions.sortListView",
|
|
||||||
]
|
|
||||||
|
|
||||||
ext.testing = [
|
|
||||||
junit : "junit:junit:$versions.junit4",
|
|
||||||
junitVintage : "org.junit.vintage:junit-vintage-engine:$versions.junit5",
|
|
||||||
kotlinJunit : "org.jetbrains.kotlin:kotlin-test-junit:$versions.kotlin",
|
|
||||||
mockitoKotlin : "org.mockito.kotlin:mockito-kotlin:$versions.mockitoKotlin",
|
|
||||||
mockito : "org.mockito:mockito-core:$versions.mockito",
|
|
||||||
mockitoInline : "org.mockito:mockito-inline:$versions.mockito",
|
|
||||||
kluent : "org.amshove.kluent:kluent:$versions.kluent",
|
|
||||||
kluentAndroid : "org.amshove.kluent:kluent-android:$versions.kluent",
|
|
||||||
mockWebServer : "com.squareup.okhttp3:mockwebserver:$versions.okhttp",
|
|
||||||
apacheCodecs : "commons-codec:commons-codec:$versions.apacheCodecs",
|
|
||||||
robolectric : "org.robolectric:robolectric:$versions.robolectric"
|
|
||||||
]
|
|
|
@ -1,86 +1,26 @@
|
||||||
<?xml version="1.0" ?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<SmellBaseline>
|
<SmellBaseline>
|
||||||
<ManuallySuppressedIssues></ManuallySuppressedIssues>
|
<ManuallySuppressedIssues/>
|
||||||
<CurrentIssues>
|
<CurrentIssues>
|
||||||
<ID>ComplexCondition:DownloadHandler.kt$DownloadHandler.<no name provided>$!append && !playNext && !unpin && !background</ID>
|
<ID>ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.<no name provided>$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) )</ID>
|
||||||
<ID>ComplexCondition:FilePickerAdapter.kt$FilePickerAdapter$currentDirectory.absolutePath == "/" || currentDirectory.absolutePath == "/storage" || currentDirectory.absolutePath == "/storage/emulated" || currentDirectory.absolutePath == "/mnt"</ID>
|
|
||||||
<ID>ComplexCondition:LocalMediaPlayer.kt$LocalMediaPlayer$Util.getGaplessPlaybackPreference() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && ( playerState === PlayerState.STARTED || playerState === PlayerState.PAUSED )</ID>
|
|
||||||
<ID>ComplexMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
|
||||||
<ID>ComplexMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File)</ID>
|
|
||||||
<ID>ComplexMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean)</ID>
|
|
||||||
<ID>ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun enableButtons()</ID>
|
|
||||||
<ID>ComplexMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
|
||||||
<ID>EmptyFunctionBlock:SongView.kt$SongView${}</ID>
|
|
||||||
<ID>FunctionNaming:ThemeChangedEventDistributor.kt$ThemeChangedEventDistributor$fun RaiseThemeChangedEvent()</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:DownloadFile.kt$DownloadFile$String.format("DownloadFile (%s)", song)</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:DownloadFile.kt$DownloadFile.DownloadTask$String.format("Download of '%s' was cancelled", song)</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:DownloadFile.kt$DownloadFile.DownloadTask$String.format("DownloadTask (%s)", song)</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:EditServerFragment.kt$EditServerFragment.<no name provided>$String.format( "%s %s", resources.getString(R.string.settings_connection_failure), getErrorMessage(error) )</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Failed to write log to %s", file)</ID>
|
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Failed to write log to %s", file)</ID>
|
||||||
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Log file rotated, logging into file %s", file?.name)</ID>
|
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Log file rotated, logging into file %s", file?.name)</ID>
|
||||||
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Logging into file %s", file?.name)</ID>
|
<ID>ImplicitDefaultLocale:FileLoggerTree.kt$FileLoggerTree$String.format("Logging into file %s", file?.name)</ID>
|
||||||
<ID>ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$String.format("BufferTask (%s)", downloadFile)</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$String.format("CheckCompletionTask (%s)", downloadFile)</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:ShareHandler.kt$ShareHandler$String.format("%d:%s", timeSpanAmount, timeSpanType)</ID>
|
<ID>ImplicitDefaultLocale:ShareHandler.kt$ShareHandler$String.format("%d:%s", timeSpanAmount, timeSpanType)</ID>
|
||||||
<ID>ImplicitDefaultLocale:ShareHandler.kt$ShareHandler.<no name provided>$String.format("%s\n\n%s", Util.getShareGreeting(), result.url)</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%02d.", trackNumber)</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s ", bitRate)</ID>
|
|
||||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s > %s", suffix, transcodedSuffix)</ID>
|
|
||||||
<ID>LargeClass:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment</ID>
|
|
||||||
<ID>LongMethod:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
|
||||||
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
<ID>LongMethod:EditServerFragment.kt$EditServerFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
||||||
<ID>LongMethod:FilePickerAdapter.kt$FilePickerAdapter$private fun fileLister(currentDirectory: File)</ID>
|
|
||||||
<ID>LongMethod:LocalMediaPlayer.kt$LocalMediaPlayer$@Synchronized private fun doPlay(downloadFile: DownloadFile, position: Int, start: Boolean)</ID>
|
|
||||||
<ID>LongMethod:MediaPlayerService.kt$MediaPlayerService$private fun updateMediaSession(currentPlaying: DownloadFile?, playerState: PlayerState)</ID>
|
|
||||||
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$override fun onCreate(savedInstanceState: Bundle?)</ID>
|
<ID>LongMethod:NavigationActivity.kt$NavigationActivity$override fun onCreate(savedInstanceState: Bundle?)</ID>
|
||||||
<ID>LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken )</ID>
|
<ID>LongMethod:ShareHandler.kt$ShareHandler$private fun showDialog( fragment: Fragment, shareDetails: ShareDetails, swipe: SwipeRefreshLayout?, cancellationToken: CancellationToken )</ID>
|
||||||
<ID>LongMethod:SongView.kt$SongView$fun setSong(song: MusicDirectory.Entry, checkable: Boolean, draggable: Boolean)</ID>
|
<ID>LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, passedData: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) )</ID>
|
||||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
|
||||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
|
|
||||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateDisplay(refresh: Boolean)</ID>
|
|
||||||
<ID>LongMethod:TrackCollectionFragment.kt$TrackCollectionFragment$private fun updateInterfaceWithEntries(musicDirectory: MusicDirectory)</ID>
|
|
||||||
<ID>LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, private var data: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) )</ID>
|
|
||||||
<ID>MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192</ID>
|
<ID>MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192</ID>
|
||||||
<ID>MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$10</ID>
|
<ID>MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$0.05f</ID>
|
||||||
<ID>MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$60</ID>
|
<ID>MagicNumber:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$50</ID>
|
||||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$60000</ID>
|
|
||||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$100000</ID>
|
|
||||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$1024L</ID>
|
|
||||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8</ID>
|
|
||||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$86400L</ID>
|
|
||||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8L</ID>
|
|
||||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$5000L</ID>
|
|
||||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$50L</ID>
|
|
||||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$256</ID>
|
|
||||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$3</ID>
|
|
||||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$4</ID>
|
|
||||||
<ID>MagicNumber:RESTMusicService.kt$RESTMusicService$206</ID>
|
<ID>MagicNumber:RESTMusicService.kt$RESTMusicService$206</ID>
|
||||||
<ID>MagicNumber:SongView.kt$SongView$3</ID>
|
|
||||||
<ID>MagicNumber:SongView.kt$SongView$4</ID>
|
|
||||||
<ID>MagicNumber:SongView.kt$SongView$60</ID>
|
|
||||||
<ID>MagicNumber:TrackCollectionFragment.kt$TrackCollectionFragment$10</ID>
|
|
||||||
<ID>NestedBlockDepth:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute()</ID>
|
|
||||||
<ID>NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean )</ID>
|
<ID>NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean )</ID>
|
||||||
<ID>NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler()</ID>
|
|
||||||
<ID>ReturnCount:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String</ID>
|
|
||||||
<ID>ReturnCount:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean</ID>
|
|
||||||
<ID>ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean</ID>
|
|
||||||
<ID>TooGenericExceptionCaught:DownloadFile.kt$DownloadFile$e: Exception</ID>
|
|
||||||
<ID>TooGenericExceptionCaught:FileLoggerTree.kt$FileLoggerTree$x: Throwable</ID>
|
<ID>TooGenericExceptionCaught:FileLoggerTree.kt$FileLoggerTree$x: Throwable</ID>
|
||||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$ex: Exception</ID>
|
<ID>TooGenericExceptionCaught:JukeboxMediaPlayer.kt$JukeboxMediaPlayer$x: Throwable</ID>
|
||||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$exception: Throwable</ID>
|
<ID>TooGenericExceptionCaught:JukeboxMediaPlayer.kt$JukeboxMediaPlayer.TaskQueue$x: Throwable</ID>
|
||||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer$x: Exception</ID>
|
<ID>TooGenericExceptionThrown:Downloader.kt$Downloader.DownloadTask$throw RuntimeException( String.format( Locale.ROOT, "Download of '%s' was cancelled", downloadFile.track ) )</ID>
|
||||||
<ID>TooGenericExceptionCaught:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$e: Exception</ID>
|
|
||||||
<ID>TooGenericExceptionCaught:MediaPlayerService.kt$MediaPlayerService$e: Exception</ID>
|
|
||||||
<ID>TooGenericExceptionCaught:SongView.kt$SongView$e: Exception</ID>
|
|
||||||
<ID>TooGenericExceptionCaught:SubsonicUncaughtExceptionHandler.kt$SubsonicUncaughtExceptionHandler$x: Throwable</ID>
|
|
||||||
<ID>TooGenericExceptionCaught:VideoPlayer.kt$VideoPlayer$e: Exception</ID>
|
|
||||||
<ID>TooGenericExceptionThrown:DownloadFile.kt$DownloadFile.DownloadTask$throw Exception(String.format("Download of '%s' was cancelled", song))</ID>
|
|
||||||
<ID>TooManyFunctions:LocalMediaPlayer.kt$LocalMediaPlayer</ID>
|
|
||||||
<ID>TooManyFunctions:MediaPlayerService.kt$MediaPlayerService : Service</ID>
|
|
||||||
<ID>TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService</ID>
|
<ID>TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService</ID>
|
||||||
<ID>TooManyFunctions:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment</ID>
|
|
||||||
<ID>UtilityClassWithPublicConstructor:CommunicationErrorHandler.kt$CommunicationErrorHandler</ID>
|
|
||||||
<ID>UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle</ID>
|
<ID>UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle</ID>
|
||||||
</CurrentIssues>
|
</CurrentIssues>
|
||||||
</SmellBaseline>
|
</SmellBaseline>
|
||||||
|
|
|
@ -42,8 +42,8 @@ empty-blocks:
|
||||||
complexity:
|
complexity:
|
||||||
active: true
|
active: true
|
||||||
TooManyFunctions:
|
TooManyFunctions:
|
||||||
thresholdInFiles: 20
|
thresholdInFiles: 25
|
||||||
thresholdInClasses: 20
|
thresholdInClasses: 25
|
||||||
thresholdInInterfaces: 20
|
thresholdInInterfaces: 20
|
||||||
thresholdInObjects: 30
|
thresholdInObjects: 30
|
||||||
LabeledExpression:
|
LabeledExpression:
|
||||||
|
@ -64,19 +64,16 @@ style:
|
||||||
WildcardImport:
|
WildcardImport:
|
||||||
active: true
|
active: true
|
||||||
MaxLineLength:
|
MaxLineLength:
|
||||||
active: true
|
active: false
|
||||||
maxLineLength: 120
|
|
||||||
excludePackageStatements: false
|
|
||||||
excludeImportStatements: false
|
|
||||||
MagicNumber:
|
MagicNumber:
|
||||||
# 100 common in percentage, 1000 in milliseconds
|
# 100 common in percentage, 1000 in milliseconds
|
||||||
ignoreNumbers: ['-1', '0', '1', '2', '100', '1000']
|
ignoreNumbers: ['-1', '0', '1', '2', '5', '10', '100', '256', '512', '1000', '1024', '4096']
|
||||||
ignoreEnums: true
|
ignoreEnums: true
|
||||||
ignorePropertyDeclaration: true
|
ignorePropertyDeclaration: true
|
||||||
UnnecessaryAbstractClass:
|
UnnecessaryAbstractClass:
|
||||||
active: false
|
active: false
|
||||||
ReturnCount:
|
ReturnCount:
|
||||||
max: 3
|
max: 5
|
||||||
|
|
||||||
comments:
|
comments:
|
||||||
active: true
|
active: true
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Others
|
||||||
|
- #671: Bump versions.mockito from 4.1.0 to 4.3.1.
|
||||||
|
- Update translations.
|
|
@ -0,0 +1,3 @@
|
||||||
|
Enhancements
|
||||||
|
- #683: Rewrite the about and remove the webview.
|
||||||
|
- #685: Server coloring feature.
|
|
@ -0,0 +1,2 @@
|
||||||
|
Bug fixes
|
||||||
|
- #688: Connection failure.
|
|
@ -0,0 +1,14 @@
|
||||||
|
Bug fixes
|
||||||
|
- #673: Disabling "Browse Using ID3 Tags" causes artist search result content to mismatch.
|
||||||
|
- #679: Keyboard should be hidden when navigating away from Search.
|
||||||
|
- #686: In landscape mode, the scroll bar is missing in the about text.
|
||||||
|
- #691: TrackCollectionFragment: fix play all button.
|
||||||
|
- #698: Track based context menus do not function correctly in most fragments.
|
||||||
|
|
||||||
|
Features
|
||||||
|
- #669: Option to change language.
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
- #654: Update OkHttp.
|
||||||
|
- #694: Reword alert for better help.
|
||||||
|
- #702: Show Downloads on Play.
|
|
@ -1 +0,0 @@
|
||||||
Refactor and redesign artist list
|
|
|
@ -1 +0,0 @@
|
||||||
Fall backs to path when comparing tracks and fixes #369
|
|
|
@ -1 +0,0 @@
|
||||||
Refactored the application main menu
|
|
|
@ -1 +0,0 @@
|
||||||
Refactored the application main menu
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Enhancements
|
||||||
|
- #445: Implement a room database for better offline support.
|
||||||
|
- #537: Added Android Auto support.
|
|
@ -0,0 +1,2 @@
|
||||||
|
Bug fixes
|
||||||
|
- #571: Fixed media session null checks.
|
|
@ -0,0 +1,13 @@
|
||||||
|
Bug fixes
|
||||||
|
- #594: Added PlaybackComplete intent when a song playback is completed.
|
||||||
|
- #593: Fixed album lists.
|
||||||
|
- #602: Fix NPE.
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
- #558: Video call can be static.
|
||||||
|
- #559: Add better offline Support.
|
||||||
|
- #568: Rework Downloader.
|
||||||
|
- #567: Use semantically correct API endpoint when streaming/downloading.
|
||||||
|
- #572: Moved drag handle to the left in the Now Playing list.
|
||||||
|
- #585: Added setting to disable Now Playing List sending for incompatible bluetooth devices.
|
||||||
|
- #596: Added option whether to create a share on the server when sharing songs.
|
|
@ -0,0 +1,14 @@
|
||||||
|
Bug fixes
|
||||||
|
- #609: Weird scrobbling behaviour (offset).
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
- #599: Moved server selector and settings to the Navigation menu.
|
||||||
|
- #600: Migrate Permission utitlity to Kotlin, increase min SDK to 17.
|
||||||
|
- #604: Implement a Download view.
|
||||||
|
- #613: targetSdkVersion must be 30 or higher.
|
||||||
|
- #622: Refactor events.
|
||||||
|
- #641: Remove feature storage.
|
||||||
|
- #642: Remove MergeAdapter and SackOfViewsAdapter.
|
||||||
|
- #649: Unify error dialog handling.
|
||||||
|
- #652: Updated custom cache location handling to remove isUri.
|
||||||
|
- #662: Improve database migrations.
|
|
@ -0,0 +1,3 @@
|
||||||
|
Otros
|
||||||
|
- #671: Actualizado versions.mockito de 4.1.0 a 4.3.1.
|
||||||
|
- Traducciones actualizadas.
|
|
@ -0,0 +1,3 @@
|
||||||
|
Mejoras
|
||||||
|
- #683: Reescribir el acerca de y eliminar el webview.
|
||||||
|
- #685: Posibilidad de seleccionar el color del servidor.
|
|
@ -0,0 +1,2 @@
|
||||||
|
Corrección de errores
|
||||||
|
- #688: Fallo de conexión.
|
|
@ -0,0 +1,14 @@
|
||||||
|
Corrección de errores
|
||||||
|
- #673: La desactivación de la opción "Examinar mediante etiquetas ID3" hace que el contenido de los resultados de la búsqueda de artistas no coincida.
|
||||||
|
- #679: El teclado debería estar oculto cuando se navega fuera de la Búsqueda.
|
||||||
|
- #686: En modo apaisado, falta la barra de desplazamiento en el texto de acerca de.
|
||||||
|
- #691: TrackCollectionFragment: arreglar el botón de reproducir todo.
|
||||||
|
- #698: Los menús contextuales basados en pistas no funcionan correctamente en la mayoría de los fragmentos.
|
||||||
|
|
||||||
|
Características
|
||||||
|
- #669: Opción de cambio de idioma.
|
||||||
|
|
||||||
|
Mejoras
|
||||||
|
- #654: Actualización de OkHttp.
|
||||||
|
- #694: Reformular la alerta para mejorar la ayuda.
|
||||||
|
- #702: Mostrar descargas al reproducir.
|
|
@ -1 +0,0 @@
|
||||||
Refactorizada y rediseñada la lista de artistas
|
|
|
@ -1 +0,0 @@
|
||||||
Cuando un fichero no tiene etiquetas de número de pista lo ordena por orden alfabético. Además hemos arreglado el bug #369
|
|
|
@ -1 +0,0 @@
|
||||||
Refactorizado el menú principal de la aplicación
|
|
|
@ -1 +0,0 @@
|
||||||
Refactorizado el menú principal de la aplicación
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Mejoras
|
||||||
|
- #445: Se implementa una base de datos para un mejor soporte sin conexión.
|
||||||
|
- #537: Añadido soporte para Android Auto.
|
|
@ -0,0 +1,2 @@
|
||||||
|
Correción de errores
|
||||||
|
- #571: Se comprueban los valores nulos en la sesión de medios.
|
|
@ -0,0 +1,13 @@
|
||||||
|
Correción de errores
|
||||||
|
- #594: Agregado un intent de PlaybackComplete cuando se completa la reproducción de una canción.
|
||||||
|
- #593: Corregidas las listas de álbumes.
|
||||||
|
- #602: NPE corregido.
|
||||||
|
|
||||||
|
Mejoras
|
||||||
|
- #558: La llamada a video puede ser estática.
|
||||||
|
- #559: Agregado un mejor soporte sin conexión.
|
||||||
|
- #568: Se ha reescrito el downloader.
|
||||||
|
- #567: Se utiliza el endpoint semánticamente correcto al realizar streaming o descargar.
|
||||||
|
- #572: Se ha movido el botón de arrastre de canción hacia la izquierda en la lista de reproducción.
|
||||||
|
- #585: Agregada una configuración para deshabilitar el envío de la Lista de reproducción en curso para dispositivos Bluetooth incompatibles.
|
||||||
|
- #596: Se agregó la opción de crear un recurso compartido en el servidor al compartir canciones.
|
|
@ -0,0 +1,14 @@
|
||||||
|
Corrección de errores
|
||||||
|
- #609: Comportamiento extraño de scrobbling (offset).
|
||||||
|
|
||||||
|
Mejoras
|
||||||
|
- #599: Se ha movido el selector de servidor y la configuración al menú de navegación.
|
||||||
|
- #600: Migración de la utilidad de permisos a Kotlin, aumento del SDK mínimo a 17.
|
||||||
|
- #604: Implementar una vista de Descarga.
|
||||||
|
- #613: targetSdkVersion debe ser 30 o superior.
|
||||||
|
- #622: Refactorización de eventos.
|
||||||
|
- #641: Eliminar el almacenamiento de funciones.
|
||||||
|
- #642: Eliminar MergeAdapter y SackOfViewsAdapter.
|
||||||
|
- #649: Unificar el manejo del diálogo de error.
|
||||||
|
- #652: Manejo de ubicación de caché personalizado actualizado para eliminar isUri.
|
||||||
|
- #662: Mejorar las migraciones de bases de datos.
|
|
@ -2,7 +2,8 @@ org.gradle.parallel=true
|
||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
org.gradle.configureondemand=true
|
org.gradle.configureondemand=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.jvmargs=-Xmx2g
|
org.gradle.jvmargs=-Xmx2g -XX:+UseParallelGC
|
||||||
|
|
||||||
|
|
||||||
kotlin.incremental=true
|
kotlin.incremental=true
|
||||||
kotlin.caching.enabled=true
|
kotlin.caching.enabled=true
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
[versions]
|
||||||
|
# You need to run ./gradlew wrapper after updating the version
|
||||||
|
gradle = "7.3.3"
|
||||||
|
|
||||||
|
navigation = "2.3.5"
|
||||||
|
gradlePlugin = "7.2.1"
|
||||||
|
androidxcore = "1.6.0"
|
||||||
|
ktlint = "0.43.2"
|
||||||
|
ktlintGradle = "10.2.0"
|
||||||
|
detekt = "1.19.0"
|
||||||
|
preferences = "1.1.1"
|
||||||
|
media = "1.3.1"
|
||||||
|
media3 = "1.0.0-beta01"
|
||||||
|
|
||||||
|
androidSupport = "1.4.0"
|
||||||
|
androidLegacySupport = "1.0.0"
|
||||||
|
androidSupportDesign = "1.6.1"
|
||||||
|
constraintLayout = "2.1.1"
|
||||||
|
multidex = "2.0.1"
|
||||||
|
room = "2.4.2"
|
||||||
|
kotlin = "1.6.10"
|
||||||
|
kotlinxCoroutines = "1.6.0-native-mt"
|
||||||
|
kotlinxGuava = "1.6.0"
|
||||||
|
viewModelKtx = "2.4.1"
|
||||||
|
|
||||||
|
retrofit = "2.9.0"
|
||||||
|
jackson = "2.10.1"
|
||||||
|
okhttp = "4.9.1"
|
||||||
|
koin = "3.0.2"
|
||||||
|
picasso = "2.71828"
|
||||||
|
|
||||||
|
junit4 = "4.13.2"
|
||||||
|
junit5 = "5.8.1"
|
||||||
|
mockito = "4.3.1"
|
||||||
|
mockitoKotlin = "4.0.0"
|
||||||
|
kluent = "1.68"
|
||||||
|
apacheCodecs = "1.15"
|
||||||
|
robolectric = "4.6.1"
|
||||||
|
timber = "4.7.1"
|
||||||
|
fastScroll = "2.0.1"
|
||||||
|
colorPicker = "2.2.3"
|
||||||
|
rxJava = "3.1.2"
|
||||||
|
rxAndroid = "3.0.0"
|
||||||
|
multiType = "4.3.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradlePlugin" }
|
||||||
|
kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
|
ktlintGradle = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlintGradle" }
|
||||||
|
detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" }
|
||||||
|
|
||||||
|
core = { module = "androidx.core:core-ktx", version.ref = "androidxcore" }
|
||||||
|
support = { module = "androidx.legacy:legacy-support-v4", version.ref = "androidLegacySupport" }
|
||||||
|
design = { module = "com.google.android.material:material", version.ref = "androidSupportDesign" }
|
||||||
|
annotations = { module = "androidx.annotation:annotation", version.ref = "androidSupport" }
|
||||||
|
multidex = { module = "androidx.multidex:multidex", version.ref = "multidex" }
|
||||||
|
constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintLayout" }
|
||||||
|
room = { module = "androidx.room:room-compiler", version.ref = "room" }
|
||||||
|
roomRuntime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||||
|
roomKtx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
||||||
|
viewModelKtx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "viewModelKtx" }
|
||||||
|
navigationFragment = { module = "androidx.navigation:navigation-fragment", version.ref = "navigation" }
|
||||||
|
navigationUi = { module = "androidx.navigation:navigation-ui", version.ref = "navigation" }
|
||||||
|
navigationFragmentKtx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
|
||||||
|
navigationUiKtx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
|
||||||
|
navigationFeature = { module = "androidx.navigation:navigation-dynamic-features-fragment", version.ref = "navigation" }
|
||||||
|
preferences = { module = "androidx.preference:preference", version.ref = "preferences" }
|
||||||
|
media = { module = "androidx.media:media", version.ref = "media" }
|
||||||
|
media3exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
|
||||||
|
media3okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" }
|
||||||
|
media3session = { module = "androidx.media3:media3-session", version.ref = "media3" }
|
||||||
|
|
||||||
|
kotlinStdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
|
||||||
|
kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
|
||||||
|
kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
|
||||||
|
kotlinxGuava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "kotlinxGuava"}
|
||||||
|
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
|
gsonConverter = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
||||||
|
jacksonConverter = { module = "com.squareup.retrofit2:converter-jackson", version.ref = "retrofit" }
|
||||||
|
jacksonKotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
||||||
|
okhttpLogging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||||
|
koinCore = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||||
|
koinAndroid = { module = "io.insert-koin:koin-android", version.ref = "koin" }
|
||||||
|
koinViewModel = { module = "io.insert-koin:koin-android-viewmodel", version.ref = "koin" }
|
||||||
|
picasso = { module = "com.squareup.picasso:picasso", version.ref = "picasso" }
|
||||||
|
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
|
||||||
|
fastScroll = { module = "com.simplecityapps:recyclerview-fastscroll", version.ref = "fastScroll" }
|
||||||
|
colorPickerView = { module = "com.github.skydoves:colorpickerview", version.ref = "colorPicker" }
|
||||||
|
rxJava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxJava" }
|
||||||
|
rxAndroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxAndroid" }
|
||||||
|
multiType = { module = "com.drakeet.multitype:multitype", version.ref = "multiType" }
|
||||||
|
|
||||||
|
junit = { module = "junit:junit", version.ref = "junit4" }
|
||||||
|
junitVintage = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit5" }
|
||||||
|
kotlinJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
|
mockitoKotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockitoKotlin" }
|
||||||
|
mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
|
||||||
|
mockitoInline = { module = "org.mockito:mockito-inline", version.ref = "mockito" }
|
||||||
|
kluent = { module = "org.amshove.kluent:kluent", version.ref = "kluent" }
|
||||||
|
kluentAndroid = { module = "org.amshove.kluent:kluent-android", version.ref = "kluent" }
|
||||||
|
mockWebServer = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" }
|
||||||
|
apacheCodecs = { module = "commons-codec:commons-codec", version.ref = "apacheCodecs" }
|
||||||
|
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
|
|
@ -0,0 +1,5 @@
|
||||||
|
ext.versions = [
|
||||||
|
minSdk : 21,
|
||||||
|
targetSdk : 33,
|
||||||
|
compileSdk : 31,
|
||||||
|
]
|
Binary file not shown.
|
@ -1,5 +1,6 @@
|
||||||
|
#Fri Jun 17 23:13:49 CEST 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
/**
|
||||||
|
* This module provides a base for for submodules which depend on the Android runtime
|
||||||
|
*/
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'jacoco'
|
|
||||||
apply from: "${project.rootDir}/gradle_scripts/code_quality.gradle"
|
apply from: "${project.rootDir}/gradle_scripts/code_quality.gradle"
|
||||||
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion versions.compileSdk
|
compileSdkVersion versions.compileSdk
|
||||||
|
|
||||||
|
@ -38,30 +39,21 @@ android {
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
buildConfig = false
|
buildConfig = false
|
||||||
|
viewBinding true
|
||||||
|
dataBinding true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tasks.withType(Test) {
|
tasks.withType(Test) {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
jacoco {
|
|
||||||
includeNoLocationClasses = true
|
|
||||||
excludes += jacocoExclude
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api other.kotlinStdlib
|
api libs.kotlinStdlib
|
||||||
|
|
||||||
testImplementation testing.junit
|
testImplementation libs.junit
|
||||||
testRuntimeOnly testing.junitVintage
|
testRuntimeOnly libs.junitVintage
|
||||||
}
|
}
|
||||||
|
|
||||||
jacoco {
|
|
||||||
toolVersion(versions.jacoco)
|
|
||||||
}
|
|
||||||
|
|
||||||
ext {
|
|
||||||
jacocoExclude = ['jdk.internal.*']
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ if (isCodeQualityEnabled) {
|
||||||
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
||||||
|
|
||||||
ktlint {
|
ktlint {
|
||||||
version = versions.ktlint
|
version = libs.versions.ktlint.get()
|
||||||
outputToConsole = true
|
outputToConsole = true
|
||||||
android = true
|
android = true
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ if (isCodeQualityEnabled) {
|
||||||
|
|
||||||
detekt {
|
detekt {
|
||||||
buildUponDefaultConfig = true
|
buildUponDefaultConfig = true
|
||||||
toolVersion = versions.detekt
|
toolVersion = libs.versions.detekt.get()
|
||||||
// Builds the AST in parallel. Rules are always executed in parallel.
|
// Builds the AST in parallel. Rules are always executed in parallel.
|
||||||
// Can lead to speedups in larger projects.
|
// Can lead to speedups in larger projects.
|
||||||
parallel = true
|
parallel = true
|
||||||
|
|
|
@ -1,92 +0,0 @@
|
||||||
apply plugin: 'jacoco'
|
|
||||||
|
|
||||||
jacoco {
|
|
||||||
toolVersion(versions.jacoco)
|
|
||||||
}
|
|
||||||
|
|
||||||
def mergedJacocoExec = file("${project.buildDir}/jacoco/jacocoMerged.exec")
|
|
||||||
|
|
||||||
def merge = tasks.register('jacocoMergeReports', JacocoMerge) {
|
|
||||||
group = "Reporting"
|
|
||||||
description = "Merge all jacoco reports from projects into one."
|
|
||||||
|
|
||||||
ListProperty<File> jacocoFiles = project.objects.listProperty(File.class)
|
|
||||||
project.subprojects { subproject ->
|
|
||||||
subproject.plugins.withId("jacoco") {
|
|
||||||
project.logger.info("${subproject.name} has Jacoco plugin applied")
|
|
||||||
subproject.tasks.withType(Test) { task ->
|
|
||||||
File destFile = task.extensions.getByType(JacocoTaskExtension.class).destinationFile
|
|
||||||
if (destFile.exists() && !task.name.contains("Release")) {
|
|
||||||
jacocoFiles.add(destFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executionData(jacocoFiles)
|
|
||||||
destinationFile(mergedJacocoExec)
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.register('jacocoFullReport', JacocoReport) {
|
|
||||||
dependsOn merge
|
|
||||||
group = "Reporting"
|
|
||||||
description = "Generate full Jacoco coverage report including all modules."
|
|
||||||
|
|
||||||
getClassDirectories().setFrom(files())
|
|
||||||
getSourceDirectories().setFrom(files())
|
|
||||||
getExecutionData().setFrom(files())
|
|
||||||
|
|
||||||
reports {
|
|
||||||
xml.enabled = true
|
|
||||||
html.enabled = true
|
|
||||||
csv.enabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always run merging, as all input calculation is done in doFirst {}
|
|
||||||
outputs.upToDateWhen { false }
|
|
||||||
// Task will run anyway even if initial inputs are empty
|
|
||||||
onlyIf = { true }
|
|
||||||
|
|
||||||
project.subprojects { subproject ->
|
|
||||||
subproject.plugins.withId("jacoco") {
|
|
||||||
project.logger.info("${subproject.name} has Jacoco plugin applied")
|
|
||||||
subproject.plugins.withId("kotlin-android") {
|
|
||||||
project.logger.info("${subproject.name} is android project")
|
|
||||||
def mainSources = subproject.extensions.findByName("android").sourceSets['main']
|
|
||||||
project.logger.info("Android sources: ${mainSources.java.srcDirs}")
|
|
||||||
mainSources.java.srcDirs.forEach {
|
|
||||||
additionalSourceDirs(it)
|
|
||||||
}
|
|
||||||
project.logger.info("Subproject exclude: ${subproject.jacocoExclude}")
|
|
||||||
additionalClassDirs(fileTree(
|
|
||||||
dir: "${subproject.buildDir}/tmp/kotlin-classes/debug",
|
|
||||||
excludes: subproject.jacocoExclude
|
|
||||||
))
|
|
||||||
}
|
|
||||||
subproject.plugins.withId("kotlin") { plugin ->
|
|
||||||
project.logger.info("${subproject.name} is common kotlin project")
|
|
||||||
SourceDirectorySet mainSources = subproject.extensions.getByName("kotlin")
|
|
||||||
.sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
|
|
||||||
.kotlin
|
|
||||||
mainSources.srcDirs.forEach {
|
|
||||||
project.logger.debug("Adding sources: $it")
|
|
||||||
additionalSourceDirs(it)
|
|
||||||
}
|
|
||||||
project.logger.info("Subproject exclude: ${subproject.jacocoExclude}")
|
|
||||||
additionalClassDirs(fileTree(
|
|
||||||
dir: "${subproject.buildDir}/classes/kotlin/main",
|
|
||||||
excludes: subproject.jacocoExclude
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
subproject.tasks.withType(Test) { task ->
|
|
||||||
File destFile = task.extensions.getByType(JacocoTaskExtension.class).destinationFile
|
|
||||||
if (destFile.exists() && !task.name.contains("Release")) {
|
|
||||||
project.logger.info("Adding execution data: $destFile")
|
|
||||||
executionData(destFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,6 +1,8 @@
|
||||||
apply plugin: 'java-library'
|
/**
|
||||||
|
* This module provides a base for for pure kotlin modules
|
||||||
|
*/
|
||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
apply plugin: 'jacoco'
|
apply plugin: 'kotlin-kapt'
|
||||||
apply from: "${project.rootDir}/gradle_scripts/code_quality.gradle"
|
apply from: "${project.rootDir}/gradle_scripts/code_quality.gradle"
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
@ -12,42 +14,14 @@ sourceSets {
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api other.kotlinStdlib
|
api libs.kotlinStdlib
|
||||||
|
|
||||||
testImplementation testing.junit
|
testImplementation libs.junit
|
||||||
testRuntimeOnly testing.junitVintage
|
testRuntimeOnly libs.junitVintage
|
||||||
}
|
|
||||||
|
|
||||||
jacoco {
|
|
||||||
toolVersion(versions.jacoco)
|
|
||||||
}
|
|
||||||
|
|
||||||
ext {
|
|
||||||
// override it in the module
|
|
||||||
jacocoExclude = ['jdk.internal.*']
|
|
||||||
}
|
|
||||||
|
|
||||||
jacocoTestReport {
|
|
||||||
reports {
|
|
||||||
html.enabled true
|
|
||||||
csv.enabled false
|
|
||||||
xml.enabled true
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEvaluate {
|
|
||||||
getClassDirectories().setFrom(files(classDirectories.files.collect {
|
|
||||||
fileTree(dir: it, excludes: jacocoExclude)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named("test").configure {
|
tasks.named("test").configure {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
jacoco {
|
|
||||||
excludes += jacocoExclude
|
|
||||||
includeNoLocationClasses = true
|
|
||||||
}
|
|
||||||
finalizedBy jacocoTestReport
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register("ciTest") {
|
tasks.register("ciTest") {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright 2015 the original author or authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
|
@ -17,67 +17,101 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
app_path=$0
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
# Need this for daisy-chained symlinks.
|
||||||
ls=`ls -ld "$PRG"`
|
while
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
[ -h "$app_path" ]
|
||||||
PRG="$link"
|
do
|
||||||
else
|
ls=$( ls -ld "$app_path" )
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
link=${ls#*' -> '}
|
||||||
fi
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
@ -106,80 +140,95 @@ location of your Java installation."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
warn "Could not query maximum file descriptor limit"
|
||||||
fi
|
esac
|
||||||
ulimit -n $MAX_FD
|
case $MAX_FD in #(
|
||||||
if [ $? -ne 0 ] ; then
|
'' | soft) :;; #(
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
*)
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
else
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
|
||||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=`expr $i + 1`
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
0) set -- ;;
|
|
||||||
1) set -- "$args0" ;;
|
|
||||||
2) set -- "$args0" "$args1" ;;
|
|
||||||
3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue