diff --git a/src/Rust/Cargo.lock b/src/Rust/Cargo.lock index e8b74fe4..7def2f25 100644 --- a/src/Rust/Cargo.lock +++ b/src/Rust/Cargo.lock @@ -10,18 +10,18 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "aligned" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80a21b9440a626c7fc8573a9e3d3a06b75c7c97754c2949bc7857b90353ca655" +checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923" dependencies = [ "as-slice", ] @@ -43,47 +43,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.12" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -91,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arbitrary" @@ -121,15 +122,15 @@ checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -139,9 +140,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block" @@ -157,9 +158,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -169,12 +170,13 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.0.83" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "18e2d530f35b40a84124146478cd16f34225306a8441998836466a2e2961c950" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -191,23 +193,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "clap" -version = "4.5.1" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", @@ -215,9 +217,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", @@ -227,21 +229,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "cocoa" @@ -281,9 +283,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "core-foundation" @@ -303,9 +305,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -327,18 +329,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "cvt" @@ -377,7 +379,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] @@ -440,20 +442,20 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "enum-flags" @@ -474,9 +476,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -484,9 +486,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdeflate" @@ -499,9 +501,9 @@ dependencies = [ [[package]] name = "file-rotate" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf221ceec4517f3cb764dae3541b2bd87666fc8832e51322fbb97250b468c71" +checksum = "7a3ed82142801f5b1363f7d463963d114db80f467e860b1cd82228eaebc627a0" dependencies = [ "chrono", "flate2", @@ -509,9 +511,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -544,7 +546,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] @@ -590,9 +592,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if 1.0.0", "libc", @@ -617,15 +619,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" @@ -687,14 +689,20 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.11.0" @@ -706,24 +714,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -740,42 +748,41 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if 1.0.0", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "libc", - "redox_syscall", ] [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lockfree-object-pool" @@ -785,9 +792,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "malloc_buf" @@ -800,9 +807,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -821,9 +828,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", "simd-adler32", @@ -854,11 +861,10 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -902,9 +908,9 @@ dependencies = [ [[package]] name = "ntest" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da8ec6d2b73d45307e926f5af46809768581044384637af6b3f3fe7c3c88f512" +checksum = "fb183f0a1da7a937f672e5ee7b7edb727bf52b8a52d531374ba8ebb9345c0330" dependencies = [ "ntest_test_cases", "ntest_timeout", @@ -912,9 +918,9 @@ dependencies = [ [[package]] name = "ntest_test_cases" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be7d33be719c6f4d09e64e27c1ef4e73485dc4cc1f4d22201f89860a7fe22e22" +checksum = "16d0d3f2a488592e5368ebbe996e7f1d44aa13156efad201f5b4d84e150eaa93" dependencies = [ "proc-macro2", "quote", @@ -923,9 +929,9 @@ dependencies = [ [[package]] name = "ntest_timeout" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "066b468120587a402f0b47d8f80035c921f6a46f8209efd0632a89a16f5188a4" +checksum = "fcc7c92f190c97f79b4a332f5e81dcf68c8420af2045c936c9be0bc9de6f63b5" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -941,9 +947,9 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -994,11 +1000,11 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.63" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if 1.0.0", "foreign-types 0.3.2", "libc", @@ -1015,7 +1021,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] @@ -1026,9 +1032,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.99" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -1061,9 +1067,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "png" -version = "0.17.12" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c2378060fb13acff3ba0325b83442c1d2c44fbb76df481160ddc1687cce160" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -1105,28 +1111,27 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "once_cell", "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1167,20 +1172,11 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", @@ -1189,9 +1185,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -1201,9 +1197,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -1212,9 +1208,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "remove_dir_all" @@ -1244,11 +1240,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1257,15 +1253,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1287,11 +1283,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -1300,9 +1296,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -1310,35 +1306,35 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -1368,13 +1364,13 @@ dependencies = [ [[package]] name = "simplelog" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acee08041c5de3d5048c8b3f6f13fafb3026b24ba43c6a695a0c76179b844369" +checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" dependencies = [ "log", "termcolor", - "time 0.3.34", + "time 0.3.36", ] [[package]] @@ -1385,30 +1381,30 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] @@ -1424,9 +1420,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.49" +version = "2.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", @@ -1435,9 +1431,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if 1.0.0", "fastrand", @@ -1447,31 +1443,31 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] @@ -1487,9 +1483,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -1510,9 +1506,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -1520,9 +1516,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -1544,15 +1540,15 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", @@ -1573,18 +1569,18 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "ureq" -version = "2.9.6" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" +checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea" dependencies = [ "base64", "flate2", @@ -1596,9 +1592,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -1607,9 +1603,9 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" @@ -1622,6 +1618,7 @@ name = "velopack" version = "0.0.0-local" dependencies = [ "anyhow", + "bitflags 2.6.0", "chrono", "clap", "derivative", @@ -1646,22 +1643,23 @@ dependencies = [ "rand", "regex", "remove_dir_all", + "same-file", "semver", "serde", "serde_json", "sha1_smol", "simple-stopwatch", "simplelog", + "strsim", "strum", "tempfile", - "time 0.3.34", + "time 0.3.36", "ureq", "url", "wait-timeout", "waitpid-any", "webview2-com", "windows", - "windows-sys 0.52.0", "winres", "winsafe", "xml", @@ -1712,9 +1710,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -1722,24 +1720,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1747,33 +1745,33 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "webview2-com" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c914dd492a52f0377bef56fd1b6e74a79090f9ee631d625d5b505a00e4538b6" +checksum = "6516cfa64c6b3212686080eeec378e662c2af54bb2a5b2a22749673f5cb2226f" dependencies = [ "webview2-com-macros", "webview2-com-sys", "windows", - "windows-core 0.56.0", + "windows-core 0.57.0", "windows-implement", "windows-interface", ] @@ -1786,18 +1784,18 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] name = "webview2-com-sys" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a46bcf03482ec28eeb764ca788f67998cde4213adfbbfa90462622058530f5e" +checksum = "c76d5b77320ff155660be1df3e6588bc85c75f1a9feef938cc4dc4dd60d1d7cf" dependencies = [ "thiserror", "windows", - "windows-core 0.56.0", + "windows-core 0.57.0", ] [[package]] @@ -1858,11 +1856,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi 0.3.9", + "windows-sys 0.52.0", ] [[package]] @@ -1873,12 +1871,12 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" dependencies = [ - "windows-core 0.56.0", - "windows-targets 0.52.5", + "windows-core 0.57.0", + "windows-targets 0.52.6", ] [[package]] @@ -1887,50 +1885,50 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ "windows-implement", "windows-interface", "windows-result", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] name = "windows-implement" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] name = "windows-interface" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.49", + "syn 2.0.71", ] [[package]] name = "windows-result" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1948,7 +1946,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1968,18 +1966,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1990,9 +1988,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2002,9 +2000,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2014,15 +2012,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2032,9 +2030,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2044,9 +2042,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2056,9 +2054,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2068,9 +2066,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -2098,18 +2096,18 @@ checksum = "a40369220be405a294b88b13ccc3d916fac12423250b30092c8c4ea19001f7f1" [[package]] name = "xml" -version = "0.8.10" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cb5b0add8d5aaa69357818de5f3b60104a62ef345dca81d0947e2148347a7f7" +checksum = "ede1c99c55b4b3ad0349018ef0eccbe954ce9c342334410707ee87177fcf2ab4" dependencies = [ "xml-rs", ] [[package]] name = "xml-rs" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "yansi" @@ -2119,9 +2117,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zip" -version = "2.1.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd56a4d5921bc2f99947ac5b3abe5f510b1be7376fdc5e9fce4a23c6a93e87c" +checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" dependencies = [ "arbitrary", "crc32fast", @@ -2150,27 +2148,27 @@ dependencies = [ [[package]] name = "zstd" -version = "0.13.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.0.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.12+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" dependencies = [ "cc", "pkg-config", diff --git a/src/Rust/Cargo.toml b/src/Rust/Cargo.toml index d03d400d..7112aa9e 100644 --- a/src/Rust/Cargo.toml +++ b/src/Rust/Cargo.toml @@ -52,14 +52,14 @@ regex = "1.10" rand = "0.8" log = "0.4" simplelog = "0.12" -clap = "4.4" +clap = "4.5" xml = "0.8" semver = "1.0" chrono = "0.4" wait-timeout = "0.2" -lazy_static = "1.4" +lazy_static = "1.5" strum = { version = "0.26", features = ["derive"] } -ureq = { version = "2.9", default-features = false, features = [ +ureq = { version = "2.10", default-features = false, features = [ "native-tls", "gzip", ] } @@ -79,6 +79,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" time = "0.3" os_info = "3.8" +bitflags = "2.6" [target.'cfg(unix)'.dependencies] native-dialog = "0.7" @@ -87,15 +88,15 @@ dialog = "0.3" libc = "0.2" [target.'cfg(windows)'.dependencies] -fs_extra = "1.2" +fs_extra = "1.3" memmap2 = "0.9" -winsafe = { version = "0.0.20", features = ["version", "user", "gui"] } +winsafe = { version = "0.0.20", features = ["gui"] } image = { version = "0.25", default-features = false, features = [ "gif", "jpeg", "png", ] } -windows = { version = "0.56", default-features = false, features = [ +windows = { version = "0.57", default-features = false, features = [ "Win32_Foundation", "Win32_Security", "Win32_System_Com", @@ -104,6 +105,7 @@ windows = { version = "0.56", default-features = false, features = [ "Win32_System_Threading", "Win32_System_SystemInformation", "Win32_System_Variant", + "Win32_System_Environment", "Win32_Storage_EnhancedStorage", "Win32_Storage_FileSystem", "Win32_System_Com_StructuredStorage", @@ -111,24 +113,20 @@ windows = { version = "0.56", default-features = false, features = [ "Win32_System_Threading", "Win32_System_ProcessStatus", "Win32_System_WindowsProgramming", + "Win32_System_LibraryLoader", "Win32_UI_Shell_Common", "Win32_UI_Shell_PropertiesSystem", -] } -windows-sys = { version = "0.52", default-features = false, features = [ - "Win32_Foundation", - "Win32_Security", - "Win32_Storage", - "Win32_Storage_FileSystem", + "Win32_UI_WindowsAndMessaging", "Win32_System_Kernel", - "Win32_System_Threading", - "Win32_System_WindowsProgramming", "Wdk", "Wdk_System", "Wdk_System_Threading", ] } -normpath = "1.0.1" -webview2-com = "0.30" +normpath = "1.2" +webview2-com = "0.31" libloading = "0.8" +strsim = "0.11" +same-file = "1.0" [dev-dependencies] tempfile = "3.9" diff --git a/src/Rust/rustfmt.toml b/src/Rust/rustfmt.toml index 17d5276c..3d5f7ad6 100644 --- a/src/Rust/rustfmt.toml +++ b/src/Rust/rustfmt.toml @@ -1,4 +1,4 @@ -max_width = 160 +max_width = 140 use_small_heuristics = "Max" indent_style = "Visual" unstable_features = true diff --git a/src/Rust/src/commands/apply_windows_impl.rs b/src/Rust/src/commands/apply_windows_impl.rs index 3f0f4cfd..08a946aa 100644 --- a/src/Rust/src/commands/apply_windows_impl.rs +++ b/src/Rust/src/commands/apply_windows_impl.rs @@ -16,7 +16,15 @@ fn ropycopy, P2: AsRef>(source: &P1, dest: &P2) -> Result< let dest = dest.as_ref(); // robocopy C:\source\something.new C:\destination\something /MIR /ZB /W:5 /R:5 /MT:8 /LOG:C:\logs\copy_log.txt - let cmd = std::process::Command::new("robocopy").arg(source).arg(dest).arg("/MIR").arg("/IS").arg("/W:1").arg("/R:5").arg("/MT:2").output()?; + let cmd = std::process::Command::new("robocopy") + .arg(source) + .arg(dest) + .arg("/MIR") + .arg("/IS") + .arg("/W:1") + .arg("/R:5") + .arg("/MT:2") + .output()?; let stdout = String::from_utf8_lossy(&cmd.stdout); let stderr = String::from_utf8_lossy(&cmd.stderr); @@ -33,28 +41,28 @@ fn ropycopy, P2: AsRef>(source: &P1, dest: &P2) -> Result< Ok(()) } -pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &PathBuf, runhooks: bool) -> Result { +pub fn apply_package_impl(root_path: &PathBuf, old_app: &Manifest, package: &PathBuf, run_hooks: bool) -> Result { let bundle = bundle::load_bundle_from_file(package)?; - let manifest = bundle.read_manifest()?; + let new_app = bundle.read_manifest()?; - let found_version = (manifest.version).to_owned(); - info!("Applying package to current: {}", found_version); + let found_version = (new_app.version).to_owned(); + info!("Applying package to current: {} (old version {})", found_version, old_app.version); - if !crate::windows::prerequisite::prompt_and_install_all_missing(&manifest, Some(&app.version))? { + if !crate::windows::prerequisite::prompt_and_install_all_missing(&new_app, Some(&old_app.version))? { bail!("Stopping apply. Pre-requisites are missing and user cancelled."); } - let packages_dir = app.get_packages_path(root_path); + let packages_dir = old_app.get_packages_path(root_path); let packages_dir = Path::new(&packages_dir); - let current_dir = app.get_current_path(root_path); + let current_dir = old_app.get_current_path(root_path); let temp_path_new = packages_dir.join(format!("tmp_{}", shared::random_string(16))); let temp_path_old = packages_dir.join(format!("tmp_{}", shared::random_string(16))); // open a dialog showing progress... let (mut tx, _) = mpsc::channel::(); if !dialogs::get_silent() { - let title = format!("{} Update", &manifest.title); - let message = format!("Installing update {}...", &manifest.version); + let title = format!("{} Update", &new_app.title); + let message = format!("Installing update {}...", &new_app.version); tx = splash::show_progress_dialog(title, message); } @@ -68,15 +76,15 @@ pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &Pat let _ = tx.send(splash::MSG_INDEFINITE); // second, run application hooks (but don't care if it fails) - if runhooks { - crate::windows::run_hook(app, root_path, "--veloapp-obsolete", 15); + if run_hooks { + crate::windows::run_hook(old_app, root_path, "--veloapp-obsolete", 15); } else { info!("Skipping --veloapp-obsolete hook."); } // third, we try _REALLY HARD_ to stop the package let _ = shared::force_stop_package(root_path); - if winsafe::IsWindows10OrGreater() == Ok(true) && !locksmith::close_processes_locking_dir(&app.title, ¤t_dir) { + if winsafe::IsWindows10OrGreater() == Ok(true) && !locksmith::close_processes_locking_dir(&old_app.title, ¤t_dir) { bail!("Failed to close processes locking directory / user cancelled."); } @@ -108,9 +116,10 @@ pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &Pat let _ = tx.send(splash::MSG_CLOSE); info!("Showing error dialog..."); - let title = format!("{} Update", &manifest.title); + let title = format!("{} Update", &new_app.title); let header = "Failed to update"; - let body = format!("Failed to update {} to version {}. Please check the logs for more details.", &manifest.title, &manifest.version); + let body = + format!("Failed to update {} to version {}. Please check the logs for more details.", &new_app.title, &new_app.version); dialogs::show_error(&title, Some(header), &body); bail!("Fatal error performing update."); @@ -119,17 +128,25 @@ pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &Pat // from this point on, we're past the point of no return and should not bail // sixth, we write the uninstall entry - if let Err(e) = manifest.write_uninstall_entry(root_path) { + if let Err(e) = new_app.write_uninstall_entry(root_path) { warn!("Failed to write uninstall entry ({}).", e); } // seventh, we run the post-install hooks - if runhooks { - crate::windows::run_hook(&manifest, &root_path, "--veloapp-updated", 15); + if run_hooks { + crate::windows::run_hook(&new_app, &root_path, "--veloapp-updated", 15); } else { info!("Skipping --veloapp-updated hook."); } + // update application shortcuts + // should try and remove the temp dirs before recalculating the shortcuts, + // because windows may try to use the "Distributed Link Tracking and Object Identifiers (DLT) service" + // to update the shortcut to point at the temp/renamed location + let _ = remove_dir_all::remove_dir_all(&temp_path_new); + let _ = remove_dir_all::remove_dir_all(&temp_path_old); + crate::windows::create_or_update_manifest_lnks(root_path, &new_app, Some(old_app)); + // done! info!("Package applied successfully."); Ok(()) @@ -139,5 +156,5 @@ pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &Pat let _ = remove_dir_all::remove_dir_all(&temp_path_new); let _ = remove_dir_all::remove_dir_all(&temp_path_old); action?; - Ok(manifest) + Ok(new_app) } diff --git a/src/Rust/src/commands/install.rs b/src/Rust/src/commands/install.rs index 8abc1434..db3b4bbc 100644 --- a/src/Rust/src/commands/install.rs +++ b/src/Rust/src/commands/install.rs @@ -3,6 +3,8 @@ use crate::{ shared::{self, bundle, runtime_arch::RuntimeArch}, windows, }; +use ::windows::core::PCWSTR; +use ::windows::Win32::Storage::FileSystem::GetDiskFreeSpaceExW; use anyhow::{anyhow, bail, Result}; use memmap2::Mmap; use pretty_bytes_rust::pretty_bytes; @@ -11,15 +13,14 @@ use std::{ fs::{self, File}, path::{Path, PathBuf}, }; -use winsafe::{self as w, co}; pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Result<()> { let osinfo = os_info::get(); let osarch = RuntimeArch::from_current_system(); info!("OS: {osinfo}, Arch={osarch:#?}"); - if !w::IsWindows7OrGreater()? { - bail!("This installer requires Windows 7 or later and cannot run."); + if !windows::is_windows_7_sp1_or_greater() { + bail!("This installer requires Windows 7 SPA1 or later and cannot run."); } let file = File::open(env::current_exe()?)?; @@ -51,7 +52,7 @@ pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Res let (root_path, root_is_default) = if install_to.is_some() { (install_to.unwrap().clone(), false) } else { - let appdata = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::LocalAppData, co::KF::DONT_UNEXPAND, None)?; + let appdata = windows::known_path::get_local_app_data()?; (Path::new(&appdata).join(&app.id), true) }; @@ -66,18 +67,26 @@ pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Res // do we have enough disk space? let (compressed_size, extracted_size) = pkg.calculate_size(); let required_space = compressed_size + extracted_size + (50 * 1000 * 1000); // archive + velopack overhead + let mut free_space: u64 = 0; - w::GetDiskFreeSpaceEx(Some(&root_path_str), None, None, Some(&mut free_space))?; - if free_space < required_space { - bail!( - "{} requires at least {} disk space to be installed. There is only {} available.", - &app.title, - pretty_bytes(required_space, None), - pretty_bytes(free_space, None) - ); + let root_pcwstr = windows::strings::string_to_u16(root_path_str); + let root_pcwstr: PCWSTR = PCWSTR(root_pcwstr.as_ptr()); + if let Ok(()) = unsafe { GetDiskFreeSpaceExW(root_pcwstr, None, None, Some(&mut free_space)) } { + if free_space < required_space { + bail!( + "{} requires at least {} disk space to be installed. There is only {} available.", + &app.title, + pretty_bytes(required_space, None), + pretty_bytes(free_space, None) + ); + } } - info!("There is {} free space available at destination, this package requires {}.", pretty_bytes(free_space, None), pretty_bytes(required_space, None)); + info!( + "There is {} free space available at destination, this package requires {}.", + pretty_bytes(free_space, None), + pretty_bytes(required_space, None) + ); // does this app support this OS / architecture? if !app.os_min_version.is_empty() && !windows::is_os_version_or_greater(&app.os_min_version)? { @@ -99,8 +108,9 @@ pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Res } info!("User chose to overwrite existing installation."); - shared::force_stop_package(&root_path) - .map_err(|z| anyhow!("Failed to stop application ({}), please close the application and try running the installer again.", z))?; + shared::force_stop_package(&root_path).map_err(|z| { + anyhow!("Failed to stop application ({}), please close the application and try running the installer again.", z) + })?; root_path_renamed = format!("{}_{}", root_path_str, shared::random_string(8)); info!("Renaming existing directory to '{}' to allow rollback...", root_path_renamed); @@ -180,13 +190,17 @@ fn install_impl(pkg: &bundle::BundleInfo, root_path: &PathBuf, tx: &std::sync::m bail!("The main executable could not be found in the package. Please contact the application author."); } - info!("Creating new default shortcuts..."); - let _ = windows::create_default_lnks(&root_path, &app); + info!("Creating shortcuts..."); + windows::create_or_update_manifest_lnks(&root_path, &app, None); info!("Starting process install hook"); - if windows::run_hook(&app, &root_path, "--veloapp-install", 30) == false { + if !windows::run_hook(&app, &root_path, "--veloapp-install", 30) { let setup_name = format!("{} Setup {}", app.title, app.version); - dialogs::show_warn(&setup_name, None, "Installation has completed, but the application install hook failed. It may not have installed correctly."); + dialogs::show_warn( + &setup_name, + None, + "Installation has completed, but the application install hook failed. It may not have installed correctly.", + ); } let _ = tx.send(100); diff --git a/src/Rust/src/commands/start_windows_impl.rs b/src/Rust/src/commands/start_windows_impl.rs index 89213b86..e9d6c092 100644 --- a/src/Rust/src/commands/start_windows_impl.rs +++ b/src/Rust/src/commands/start_windows_impl.rs @@ -13,7 +13,13 @@ use std::{ }; use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow; -pub fn start_impl(root_dir: &PathBuf, app: &Manifest, exe_name: Option<&String>, exe_args: Option>, legacy_args: Option<&String>) -> Result<()> { +pub fn start_impl( + root_dir: &PathBuf, + app: &Manifest, + exe_name: Option<&String>, + exe_args: Option>, + legacy_args: Option<&String>, +) -> Result<()> { match shared::has_app_prefixed_folder(root_dir) { Ok(has_prefix) => { if has_prefix { @@ -86,21 +92,19 @@ fn try_legacy_migration(root_dir: &PathBuf, app: &bundle::Manifest) -> Result<() } } + info!("Removing old shortcuts..."); + win::remove_all_shortcuts_for_root_dir(root_dir); + + let mut modified_app = app.clone(); + modified_app.shortcut_locations = "".to_string(); // reset, so we install new shortcuts + info!("Applying latest full package..."); let buf = Path::new(&package.file_path).to_path_buf(); - super::apply(root_dir, app, false, OperationWait::NoWait, Some(&buf), None, false)?; + super::apply(root_dir, &modified_app, false, OperationWait::NoWait, Some(&buf), None, false)?; info!("Removing old app-* folders..."); shared::delete_app_prefixed_folders(root_dir)?; let _ = remove_dir_all::remove_dir_all(root_dir.join("staging")); - info!("Removing old shortcuts..."); - if let Err(e) = win::remove_all_shortcuts_for_root_dir(root_dir) { - warn!("Failed to remove shortcuts ({}).", e); - } - - info!("Creating new default shortcuts..."); - let _ = win::create_default_lnks(root_dir, app); - Ok(()) } diff --git a/src/Rust/src/commands/uninstall.rs b/src/Rust/src/commands/uninstall.rs index 1f48f283..7ef9b24d 100644 --- a/src/Rust/src/commands/uninstall.rs +++ b/src/Rust/src/commands/uninstall.rs @@ -16,10 +16,8 @@ pub fn uninstall(root_path: &PathBuf, app: &Manifest, delete_self: bool) -> Resu // run uninstall hook windows::run_hook(&app, root_path, "--veloapp-uninstall", 60); - if let Err(e) = windows::remove_all_shortcuts_for_root_dir(&root_path) { - error!("Unable to remove shortcuts ({}).", e); - // finished_with_errors = true; - } + // remove all shortcuts pointing to the app + windows::remove_all_shortcuts_for_root_dir(&root_path); info!("Removing directory '{}'", root_path.to_string_lossy()); if let Err(e) = shared::retry_io(|| remove_dir_all::remove_dir_but_not_self(&root_path)) { diff --git a/src/Rust/src/shared/bundle.rs b/src/Rust/src/shared/bundle.rs index 865fe8d5..327ebaba 100644 --- a/src/Rust/src/shared/bundle.rs +++ b/src/Rust/src/shared/bundle.rs @@ -373,7 +373,10 @@ impl BundleInfo<'_> { for i in 0..archive.len() { let file = archive.by_index(i)?; let key = file.enclosed_name().ok_or_else(|| { - anyhow!("Could not extract file safely ({}). Ensure no paths in archive are absolute or point to a path outside the archive.", file.name()) + anyhow!( + "Could not extract file safely ({}). Ensure no paths in archive are absolute or point to a path outside the archive.", + file.name() + ) })?; files.push(key.to_string_lossy().to_string()); } @@ -396,6 +399,8 @@ pub struct Manifest { pub os: String, pub os_min_version: String, pub channel: String, + pub shortcut_locations: String, + pub shortcut_amuid: String, } #[cfg(target_os = "windows")] @@ -436,7 +441,8 @@ impl Manifest { let uninstall_cmd = format!("\"{}\" --uninstall", updater_path); let uninstall_quiet = format!("\"{}\" --uninstall --silent", updater_path); - let reg_uninstall = w::HKEY::CURRENT_USER.RegCreateKeyEx(Self::UNINST_STR, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0; + let reg_uninstall = + w::HKEY::CURRENT_USER.RegCreateKeyEx(Self::UNINST_STR, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0; let reg_app = reg_uninstall.RegCreateKeyEx(&self.id, None, co::REG_OPTION::NoValue, co::KEY::ALL_ACCESS, None)?.0; reg_app.RegSetKeyValue(None, Some("DisplayIcon"), w::RegistryValue::Sz(main_exe_path))?; reg_app.RegSetKeyValue(None, Some("DisplayName"), w::RegistryValue::Sz(self.title.to_owned()))?; @@ -454,7 +460,8 @@ impl Manifest { } pub fn remove_uninstall_entry(&self) -> Result<()> { info!("Removing uninstall registry keys..."); - let reg_uninstall = w::HKEY::CURRENT_USER.RegCreateKeyEx(Self::UNINST_STR, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0; + let reg_uninstall = + w::HKEY::CURRENT_USER.RegCreateKeyEx(Self::UNINST_STR, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0; reg_uninstall.RegDeleteKey(&self.id)?; Ok(()) } @@ -497,6 +504,10 @@ pub fn read_manifest_from_string(xml: &str) -> Result { obj.os_min_version = text; } else if el_name == "channel" { obj.channel = text; + } else if el_name == "shortcutLocations" { + obj.shortcut_locations = text; + } else if el_name == "shortcutAmuid" { + obj.shortcut_amuid = text; } } Ok(XmlEvent::EndElement { .. }) => { diff --git a/src/Rust/src/shared/download.rs b/src/Rust/src/shared/download.rs index a0080e5b..bb045cb8 100644 --- a/src/Rust/src/shared/download.rs +++ b/src/Rust/src/shared/download.rs @@ -51,7 +51,7 @@ fn get_download_agent() -> Result { let mut tls_builder = native_tls::TlsConnector::builder(); #[cfg(target_os = "windows")] - if !winsafe::IsWindows10OrGreater()? { + if !crate::windows::is_windows_10_or_greater() { warn!("DANGER: Discontinued OS version. TLS certificate verification will be disabled."); warn!("DANGER: Discontinued OS version. TLS certificate verification will be disabled."); warn!("DANGER: Discontinued OS version. TLS certificate verification will be disabled."); @@ -65,7 +65,10 @@ fn get_download_agent() -> Result { #[test] fn test_download_uses_tls_and_encoding_correctly() { - assert_eq!(download_url_as_string("https://dotnetcli.blob.core.windows.net/dotnet/WindowsDesktop/5.0/latest.version").unwrap(), "5.0.17"); + assert_eq!( + download_url_as_string("https://dotnetcli.blob.core.windows.net/dotnet/WindowsDesktop/5.0/latest.version").unwrap(), + "5.0.17" + ); } #[test] diff --git a/src/Rust/src/shared/util_windows.rs b/src/Rust/src/shared/util_windows.rs index 79f09ae4..38c1f120 100644 --- a/src/Rust/src/shared/util_windows.rs +++ b/src/Rust/src/shared/util_windows.rs @@ -1,3 +1,5 @@ +use ::windows::Win32::System::ProcessStatus::EnumProcesses; +use ::windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow; use anyhow::{anyhow, bail, Result}; use regex::Regex; use semver::Version; @@ -7,10 +9,8 @@ use std::{ path::{Path, PathBuf}, process::Command as Process, }; -use windows::Win32::System::ProcessStatus::EnumProcesses; -use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow; -use windows_sys::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation}; -use windows_sys::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION}; +use windows::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation}; +use windows::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION}; use winsafe::{self as w, co, prelude::*}; use super::bundle::{self, EntryNameInfo, Manifest}; @@ -35,7 +35,7 @@ pub fn wait_for_parent_to_exit(ms_to_wait: u32) -> Result<()> { let mut info = PROCESS_BASIC_INFORMATION { AffinityMask: 0, BasePriority: 0, - ExitStatus: 0, + ExitStatus: Default::default(), InheritedFromUniqueProcessId: 0, PebBaseAddress: std::ptr::null_mut(), UniqueProcessId: 0, @@ -43,10 +43,9 @@ pub fn wait_for_parent_to_exit(ms_to_wait: u32) -> Result<()> { let info_ptr: *mut ::core::ffi::c_void = &mut info as *mut _ as *mut ::core::ffi::c_void; let info_size = std::mem::size_of::() as u32; - let hr = unsafe { NtQueryInformationProcess(handle, basic_info, info_ptr, info_size, return_length_ptr) }; - - if hr != 0 { - return Err(anyhow!("Failed to query process information: {}", hr)); + let hres = unsafe { NtQueryInformationProcess(handle, basic_info, info_ptr, info_size, return_length_ptr) }; + if hres.is_err() { + return Err(anyhow!("Failed to query process information: {:?}", hres)); } if info.InheritedFromUniqueProcessId <= 1 { @@ -346,7 +345,7 @@ fn get_all_packages(root_path: &PathBuf) -> Vec { #[test] fn test_get_running_processes_finds_cargo() { - let profile = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Profile, co::KF::DONT_UNEXPAND, None).unwrap(); + let profile = crate::windows::known_path::get_user_profile().unwrap(); let path = Path::new(&profile); let rustup = path.join(".rustup"); diff --git a/src/Rust/src/windows/known_path.rs b/src/Rust/src/windows/known_path.rs new file mode 100644 index 00000000..db3b266b --- /dev/null +++ b/src/Rust/src/windows/known_path.rs @@ -0,0 +1,61 @@ +use anyhow::Result; +use std::path::Path; +use windows::{ + core::GUID, + Win32::UI::Shell::{ + FOLDERID_Desktop, FOLDERID_Downloads, FOLDERID_LocalAppData, FOLDERID_Profile, FOLDERID_ProgramFilesX64, FOLDERID_ProgramFilesX86, + FOLDERID_RoamingAppData, FOLDERID_StartMenu, FOLDERID_Startup, SHGetKnownFolderPath, + }, +}; + +fn get_known_folder(rfid: *const GUID) -> Result { + unsafe { + let flag = windows::Win32::UI::Shell::KNOWN_FOLDER_FLAG(0); + let result = SHGetKnownFolderPath(rfid, flag, None)?; + super::strings::pwstr_to_string(result) + } +} + +pub fn get_local_app_data() -> Result { + get_known_folder(&FOLDERID_LocalAppData) +} + +pub fn get_roaming_app_data() -> Result { + get_known_folder(&FOLDERID_RoamingAppData) +} + +pub fn get_user_desktop() -> Result { + get_known_folder(&FOLDERID_Desktop) +} + +pub fn get_user_profile() -> Result { + get_known_folder(&FOLDERID_Profile) +} + +pub fn get_start_menu() -> Result { + let start_menu = get_known_folder(&FOLDERID_StartMenu)?; + let programs_path = Path::new(&start_menu).join("Programs"); + Ok(programs_path.to_string_lossy().to_string()) +} + +pub fn get_startup() -> Result { + get_known_folder(&FOLDERID_Startup) +} + +pub fn get_downloads() -> Result { + get_known_folder(&FOLDERID_Downloads) +} + +pub fn get_program_files_x64() -> Result { + get_known_folder(&FOLDERID_ProgramFilesX64) +} + +pub fn get_program_files_x86() -> Result { + get_known_folder(&FOLDERID_ProgramFilesX86) +} + +pub fn get_user_pinned() -> Result { + let pinned_str = get_roaming_app_data()?; + let pinned_path = Path::new(&pinned_str).join("Microsoft\\Internet Explorer\\Quick Launch\\User Pinned"); + Ok(pinned_path.to_string_lossy().to_string()) +} diff --git a/src/Rust/src/windows/mitigate.rs b/src/Rust/src/windows/mitigate.rs index 14005376..3250beee 100644 --- a/src/Rust/src/windows/mitigate.rs +++ b/src/Rust/src/windows/mitigate.rs @@ -1,4 +1,4 @@ -use windows_sys::Win32::System::LibraryLoader::{SetDefaultDllDirectories, LOAD_LIBRARY_SEARCH_SYSTEM32}; +use windows::Win32::System::LibraryLoader::{SetDefaultDllDirectories, LOAD_LIBRARY_SEARCH_SYSTEM32}; /// This attempts to defend against malicious DLLs that may sit alongside /// our binary in the user's download folder. #[cfg(windows)] @@ -7,6 +7,6 @@ pub fn pre_main_sideload_mitigation() { // For DLLs loaded at load time, this relies on the `delayload` linker flag. // This is only necessary prior to Windows 10 RS1. See build.rs for details. unsafe { - SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32); + let _ = SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32); } } diff --git a/src/Rust/src/windows/mod.rs b/src/Rust/src/windows/mod.rs index 8b9b5974..badeafaf 100644 --- a/src/Rust/src/windows/mod.rs +++ b/src/Rust/src/windows/mod.rs @@ -3,6 +3,8 @@ pub mod mitigate; pub mod prerequisite; pub mod runtimes; pub mod splash; +pub mod known_path; +pub mod strings; mod self_delete; mod shortcuts; diff --git a/src/Rust/src/windows/prerequisite.rs b/src/Rust/src/windows/prerequisite.rs index aa57183e..88c26568 100644 --- a/src/Rust/src/windows/prerequisite.rs +++ b/src/Rust/src/windows/prerequisite.rs @@ -3,7 +3,6 @@ use crate::shared::{bundle, dialogs, download}; use anyhow::Result; use std::path::Path; -use winsafe::{self as w, co}; pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Option<&semver::Version>) -> Result { info!("Checking application pre-requisites..."); @@ -38,7 +37,7 @@ pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Opt } } - let downloads = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Downloads, co::KF::DONT_UNEXPAND, None)?; + let downloads = super::known_path::get_downloads()?; let downloads = Path::new(downloads.as_str()); info!("Downloading {} missing pre-requisites...", missing.len()); diff --git a/src/Rust/src/windows/runtimes.rs b/src/Rust/src/windows/runtimes.rs index e516613b..90f2e387 100644 --- a/src/Rust/src/windows/runtimes.rs +++ b/src/Rust/src/windows/runtimes.rs @@ -5,8 +5,8 @@ use anyhow::{anyhow, bail, Result}; use regex::Regex; use std::process::Command as Process; use std::{collections::HashMap, fs, path::Path}; +#[cfg(target_os = "windows")] use winsafe::{self as w, co, prelude::*}; - const REDIST_2015_2022_X86: &str = "https://aka.ms/vs/17/release/vc_redist.x86.exe"; const REDIST_2015_2022_X64: &str = "https://aka.ms/vs/17/release/vc_redist.x64.exe"; const REDIST_2015_2022_ARM64: &str = "https://aka.ms/vs/17/release/vc_redist.arm64.exe"; @@ -307,7 +307,7 @@ fn get_dotnet_base_path(runtime_arch: RuntimeArch, runtime_type: DotnetRuntimeTy // it's easy to check if we're looking for x86 dotnet because it's always in the same place. if runtime_arch == RuntimeArch::X86 { - let pf32 = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::ProgramFilesX86, co::KF::DONT_UNEXPAND, None)?; + let pf32 = super::known_path::get_program_files_x86()?; let join = Path::new(&pf32).join("dotnet").join(dotnet_path); let result = join.to_str().ok_or_else(|| anyhow!("Unable to convert path to string."))?; return Ok(result.to_string()); @@ -315,7 +315,7 @@ fn get_dotnet_base_path(runtime_arch: RuntimeArch, runtime_type: DotnetRuntimeTy // this only works in a 64 bit process, otherwise it throws #[cfg(not(target_arch = "x86"))] - let pf64 = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::ProgramFilesX64, co::KF::DONT_UNEXPAND, None)?; + let pf64 = super::known_path::get_program_files_x64()?; // set by WOW64 for x86 processes. https://learn.microsoft.com/windows/win32/winprog64/wow64-implementation-details #[cfg(target_arch = "x86")] diff --git a/src/Rust/src/windows/shortcuts.rs b/src/Rust/src/windows/shortcuts.rs index 090fe832..d56c0121 100644 --- a/src/Rust/src/windows/shortcuts.rs +++ b/src/Rust/src/windows/shortcuts.rs @@ -1,212 +1,635 @@ -use crate::bundle::Manifest; -use crate::shared as util; -use anyhow::{bail, Result}; -use glob::glob; +use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::time::Duration; -use winsafe::{self as w, co}; -use windows::core::{Interface, Result as WindowsResult, GUID, HSTRING, PCWSTR}; -use windows::Win32::Foundation::HWND; +use anyhow::{anyhow, bail, Result}; +use bitflags::bitflags; +use glob::glob; +use same_file::is_same_file; +use windows::core::{Interface, GUID, PCWSTR}; use windows::Win32::Storage::EnhancedStorage::PKEY_AppUserModel_ID; -use windows::Win32::System::Com::StructuredStorage::InitPropVariantFromStringVector; -use windows::Win32::System::Com::{CoCreateInstance, CoInitializeEx, IPersistFile, CLSCTX_ALL, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, STGM_READ}; -use windows::Win32::UI::Shell::PropertiesSystem::IPropertyStore; -use windows::Win32::UI::Shell::{IShellLinkW, ShellLink}; +use windows::Win32::System::Com::{ + CoCreateInstance, CoInitializeEx, CoUninitialize, IPersistFile, StructuredStorage::InitPropVariantFromStringVector, CLSCTX_ALL, + COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE, STGM_READWRITE, +}; +use windows::Win32::UI::Shell::{ + IShellItem, IShellLinkW, IStartMenuPinnedList, PropertiesSystem::IPropertyStore, SHCreateItemFromParsingName, ShellLink, StartMenuPin, +}; + +use crate::bundle::Manifest; +use crate::shared as util; +use crate::windows::{known_path as known, strings::*}; // https://github.com/vaginessa/PWAsForFirefox/blob/fba68dbcc7ca27b970dc5a278ebdad32e0ab3c83/native/src/integrations/implementation/windows.rs#L28 -#[inline] -fn init_com() -> WindowsResult<()> { - let hr = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) }; - if hr.is_err() { - return Err(hr.into()); +bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + struct ShortcutLocationFlags: u32 { + const NONE = 0; + const START_MENU = 1 << 0; + const DESKTOP = 1 << 1; + const STARTUP = 1 << 2; + //const APP_ROOT = 1 << 3, + const START_MENU_ROOT = 1 << 4; + const USER_PINNED = 1 << 5; } - std::thread::sleep(Duration::from_millis(1)); - Ok(()) } -#[inline] -fn create_instance(clsid: &GUID) -> WindowsResult { - unsafe { CoCreateInstance(clsid, None, CLSCTX_ALL) } -} - -pub fn create_default_lnks(root_path: &PathBuf, app: &Manifest) -> Result<()> { - let app = app.clone(); - let current_path = app.get_current_path(root_path); - let main_exe_path = app.get_main_exe_path(root_path); - let t = std::thread::spawn(move || { - init_com()?; - let mut was_error = false; - - info!("Creating desktop shortcut..."); - let desktop = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Desktop, co::KF::DONT_UNEXPAND, None)?; - let desktop_lnk = Path::new(&desktop).join(format!("{}.lnk", &app.title)); - if let Err(e) = _create_lnk(&desktop_lnk.to_string_lossy(), &main_exe_path, ¤t_path, None) { - warn!("Failed to create start menu shortcut: {}", e); - was_error = true; +impl ShortcutLocationFlags { + fn from_string(input: &str) -> ShortcutLocationFlags { + let mut flags = ShortcutLocationFlags::NONE; + for part in input.split(|c| c == ',' || c == ';') { + match part.trim().to_lowercase().as_str() { + "none" => flags |= ShortcutLocationFlags::NONE, + "startmenu" => flags |= ShortcutLocationFlags::START_MENU, + "desktop" => flags |= ShortcutLocationFlags::DESKTOP, + "startup" => flags |= ShortcutLocationFlags::STARTUP, + "startmenuroot" => flags |= ShortcutLocationFlags::START_MENU_ROOT, + _ => warn!("Warning: Unrecognized shortcut flag `{}`", part.trim()), + } } - - std::thread::sleep(Duration::from_millis(1)); - - info!("Creating start menu shortcut..."); - let startmenu = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None)?; - let start_lnk = Path::new(&startmenu).join("Programs").join(format!("{}.lnk", &app.title)); - if let Err(e) = _create_lnk(&start_lnk.to_string_lossy(), &main_exe_path, ¤t_path, None) { - warn!("Failed to create start menu shortcut: {}", e); - was_error = true; - } - - if was_error { - bail!("Failed to create one or both default shortcuts"); - } - - Ok(()) - }); - t.join().unwrap() + flags + } } -#[allow(dead_code)] -fn create_lnk(output: &str, target: &str, work_dir: &str, app_model_id: Option<&str>) -> WindowsResult<()> { - let output = output.to_string(); - let target = target.to_string(); - let work_dir = work_dir.to_string(); - let app_model_id = app_model_id.map(|f| f.to_string()); - let t = std::thread::spawn(move || { - init_com()?; - _create_lnk(&output, &target, &work_dir, app_model_id)?; - Ok(()) - }); - t.join().unwrap() -} - -fn _create_lnk(output: &str, target: &str, work_dir: &str, app_model_id: Option) -> WindowsResult<()> { - let link: IShellLinkW = create_instance(&ShellLink)?; +pub fn create_or_update_manifest_lnks>(root_path: P, next_app: &Manifest, previous_app: Option<&Manifest>) { + let root_path = root_path.as_ref().to_owned().to_path_buf(); + let next_app = next_app.clone(); + let previous_app = previous_app.cloned(); unsafe { - link.SetPath(&HSTRING::from(target))?; - link.SetWorkingDirectory(&HSTRING::from(work_dir))?; + if let Err(e) = unsafe_run_delegate_in_com_context(move || { + unsafe_update_app_manifest_lnks(&root_path, &next_app, previous_app.as_ref())?; + Ok(()) + }) { + warn!("Failed to update shortcuts: {}", e); + } + } +} - // Set app user model ID property - // Docs: https://docs.microsoft.com/windows/win32/properties/props-system-appusermodel-id - if let Some(app_model_id) = app_model_id { - let store: IPropertyStore = link.cast()?; - let hstring = HSTRING::from(app_model_id); - let widearr = hstring.as_wide(); - let id: PCWSTR = PCWSTR(widearr.as_ptr()); - let variant = InitPropVariantFromStringVector(Some(&[id]))?; - store.SetValue(&PKEY_AppUserModel_ID, &variant)?; - store.Commit()?; +pub fn remove_all_shortcuts_for_root_dir>(root_dir: P) { + let root_dir = root_dir.as_ref().to_owned().to_path_buf(); + unsafe { + if let Err(e) = unsafe_run_delegate_in_com_context(move || { + unsafe_remove_all_shortcuts_for_root_dir(&root_dir)?; + Ok(()) + }) { + warn!("Failed to remove shortcuts: {}", e); + } + } +} + +#[inline] +unsafe fn create_instance(clsid: &GUID) -> Result { + Ok(CoCreateInstance(clsid, None, CLSCTX_ALL)?) +} + +fn get_shortcut_filename(app: &Manifest) -> String { + let name = if app.title.is_empty() { app.id.clone() } else { app.title.clone() }; + + let shortcut_file_name = name + ".lnk"; + shortcut_file_name +} + +fn get_path_for_shortcut_location(app: &Manifest, flag: ShortcutLocationFlags) -> Result { + let shortcut_file_name = get_shortcut_filename(app); + match flag { + ShortcutLocationFlags::DESKTOP => Ok(Path::new(&known::get_user_desktop()?).join(shortcut_file_name)), + ShortcutLocationFlags::STARTUP => Ok(Path::new(&known::get_startup()?).join(shortcut_file_name)), + ShortcutLocationFlags::START_MENU_ROOT => Ok(Path::new(&known::get_start_menu()?).join(shortcut_file_name)), + ShortcutLocationFlags::START_MENU => { + if app.authors.is_empty() { + warn!("No authors specified and START_MENU shortcut specified. Using START_MENU_ROOT instead."); + Ok(Path::new(&known::get_start_menu()?).join(shortcut_file_name)) + } else { + Ok(Path::new(&known::get_start_menu()?).join(&app.authors).join(shortcut_file_name)) + } + } + _ => bail!("Invalid shortcut location flag: {:?}", flag), + } +} + +unsafe fn unsafe_update_app_manifest_lnks(root_path: &PathBuf, next_app: &Manifest, previous_app: Option<&Manifest>) -> Result<()> { + let next_locations = ShortcutLocationFlags::from_string(&next_app.shortcut_locations); + let prev_locations = if let Some(prev) = previous_app { + ShortcutLocationFlags::from_string(&prev.shortcut_locations) + } else { + ShortcutLocationFlags::NONE + }; + + info!("Shortcut Previous Locations: {:?} ({:?})", prev_locations, previous_app.map(|a| a.version.clone())); + info!("Shortcut Next Locations: {:?} ({:?})", next_locations, next_app.version); + + // we must end with shortcuts which exist in the next app but not the previous app. + // any shortcuts which exist in both are optional - they could have been deleted by the user, + // and we do not want to re-create them. + let mut new_locations = next_locations - prev_locations - ShortcutLocationFlags::NONE; + + if new_locations.contains(ShortcutLocationFlags::START_MENU_ROOT) && new_locations.contains(ShortcutLocationFlags::START_MENU) { + // if both start menu locations are specified, we prefer ROOT. + new_locations.remove(ShortcutLocationFlags::START_MENU); + } + + let mut app_model_id: Option = None; + if !next_app.shortcut_amuid.is_empty() { + app_model_id = Some(next_app.shortcut_amuid.clone()); + } + + let app_main_exe = next_app.get_main_exe_path(root_path); + let app_work_dir = next_app.get_current_path(root_path); + + info!("App Model ID: {:?}", app_model_id); + let mut current_shortcuts = unsafe_get_shortcuts_for_root_dir(root_path)?; + + // update all existing shortcuts, verify target/workdir/amuid and icon is correct. + info!("Will update all current shortcuts: {:?}", current_shortcuts); + + for (flag, lnk) in current_shortcuts.iter_mut() { + let flag = flag.to_owned(); + info!("Updating existing shortcut '{:?}' ({:?}).", lnk.get_link_path(), flag); + + let target_option = lnk.get_target_path().ok(); + + // set the target path to the main exe if it is missing or incorrect + if target_option.is_none() || !PathBuf::from(target_option.unwrap()).exists() { + warn!("Shortcut {} target does not exist, updating to mainExe and setting workdir to current.", lnk.get_link_path()); + if let Err(e) = lnk.set_target_path(&app_main_exe) { + warn!("Failed to update shortcut target: {}", e); + } + if let Err(e) = lnk.set_working_directory(&app_work_dir) { + warn!("Failed to update shortcut working directory: {}", e); + } } - // Save shortcut to file - let persist: IPersistFile = link.cast()?; - persist.Save(&HSTRING::from(output), true)?; + // force icon refresh by resetting the icon location + if let Err(e) = lnk.set_icon_location(&app_main_exe, 0) { + warn!("Failed to update shortcut icon location: {}", e); + } + + if let Err(e) = lnk.set_aumid(app_model_id.as_deref()) { + warn!("Failed to update shortcut app model ID: {}", e); + } + + if let Err(e) = lnk.save() { + warn!("Failed to save shortcut: {}", e); + } + + // if there is a shortcut in start menu or root, we do not want to create a new one anywhere + if flag == ShortcutLocationFlags::START_MENU_ROOT || flag == ShortcutLocationFlags::START_MENU { + new_locations.remove(ShortcutLocationFlags::START_MENU_ROOT); + new_locations.remove(ShortcutLocationFlags::START_MENU); + } else { + new_locations.remove(flag.to_owned()); + } + } + + // rename existing shortcuts if packTitle has changed + let last_app_name = previous_app.map(|a| a.title.clone()).unwrap_or(next_app.title.clone()); + let shortcuts_to_rename = unsafe_find_best_rename_candidates(&last_app_name, &app_main_exe, current_shortcuts); + for (flag, path) in shortcuts_to_rename { + let shortcut_file_name = get_shortcut_filename(next_app); + + let target_path = if let Some(parent) = path.parent() { + parent.join(shortcut_file_name) + } else { + get_path_for_shortcut_location(next_app, flag)? + }; + + if path != target_path { + info!("Renaming shortcut from '{:?}' to '{:?}'.", path, target_path); + if let Err(e) = std::fs::rename(&path, &target_path) { + warn!("Failed to rename shortcut: {}", e); + } + } + } + + // add new (missing) shortcut locations + for flag in new_locations.iter() { + let path = get_path_for_shortcut_location(next_app, flag)?; + let target = next_app.get_main_exe_path(root_path); + let work_dir = next_app.get_current_path(root_path); + info!("Creating new shortcut for flag '{:?}' ({:?}).", path, flag); + + match Lnk::create_new() { + Ok(mut lnk) => { + if let Err(e) = lnk.set_target_path(&target) { + warn!("Failed to set target path: {}", e); + break; + } + + if let Err(e) = lnk.set_working_directory(&work_dir) { + warn!("Failed to set working directory: {}", e); + break; + } + + if let Err(e) = lnk.set_aumid(app_model_id.as_deref()) { + warn!("Failed to set app model ID: {}", e); + break; + } + + if let Err(e) = lnk.set_icon_location(&target, 0) { + warn!("Failed to set icon location: {}", e); + break; + } + + if let Err(e) = lnk.save_as(&path.to_string_lossy()) { + warn!("Failed to save shortcut: {}", e); + break; + } + } + Err(e) => { + warn!("Failed to create shortcut: {}", e); + } + } } Ok(()) } -pub fn resolve_lnk(link_path: &str) -> WindowsResult<(String, String)> { - let link_path = link_path.to_string(); - let t = std::thread::spawn(move || { - init_com()?; - Ok(_resolve_lnk(&link_path)?) - }); - t.join().unwrap() -} +unsafe fn unsafe_find_best_rename_candidates>( + app_name: &str, + target_path: P, + current_shortcuts: Vec<(ShortcutLocationFlags, Lnk)>, +) -> HashMap { + use strsim::jaro_winkler; -fn _resolve_lnk(link_path: &str) -> WindowsResult<(String, String)> { - let link: IShellLinkW = create_instance(&ShellLink)?; - let persist: IPersistFile = link.cast()?; - - unsafe { - debug!("Loading link: {}", link_path); - persist.Load(&HSTRING::from(link_path), STGM_READ)?; - let flags = 1 | 2 | 1 << 16; - if let Err(e) = link.Resolve(HWND(0), flags) { - // this happens if the target path is missing and the link is broken - warn!("Failed to resolve link {} ({:?})", link_path, e); + // group shortcuts by location flag + let mut groups: HashMap> = HashMap::new(); + for (enum_val, link) in current_shortcuts.iter() { + // filter out shortcuts which have custom arguments + if let Ok(args) = link.get_arguments() { + if !args.is_empty() { + continue; + } } - let mut pszfile = [0u16; 260]; - let mut pszdir = [0u16; 260]; - link.GetPath(&mut pszfile, std::ptr::null_mut(), 0)?; - link.GetWorkingDirectory(&mut pszdir)?; + // filter out shortcuts in user-pinned dir, we're not allowed to rename those + if enum_val == &ShortcutLocationFlags::USER_PINNED { + continue; + } - let target_len = pszfile.iter().position(|&c| c == 0).unwrap_or(pszfile.len()); - let target = HSTRING::from_wide(&pszfile[..target_len])?; - let work_len = pszdir.iter().position(|&c| c == 0).unwrap_or(pszdir.len()); - let work_dir = HSTRING::from_wide(&pszdir[..work_len])?; - Ok((target.to_string(), work_dir.to_string())) + // filter out shortcuts which do not point to our main_exe + if is_same_file(&target_path, PathBuf::from(link.get_target_path().unwrap_or_default())).unwrap_or(false) { + groups.entry(enum_val.to_owned()).or_insert_with(Vec::new).push(link.clone()); + } } + + // Determine the best matching PathBuf for each group + let mut best_matches: HashMap = HashMap::new(); + + for (key, lnk_arr) in groups { + let mut best_path: Option<(PathBuf, f64)> = None; + for lnk in lnk_arr { + let file_path = PathBuf::from(&lnk.my_path); + if let Some(filename) = file_path.file_name().and_then(|n| n.to_str()) { + // we use jaro winkler distance to determine the best match because it + // gives an advantage to strings which share the same prefix + let score = jaro_winkler(filename, app_name); + if best_path.is_none() || best_path.as_ref().unwrap().1 < score { + best_path = Some((file_path, score)); + } + } + } + + if let Some(best) = best_path { + best_matches.insert(key, best.0); + } + } + + best_matches } -pub fn remove_all_shortcuts_for_root_dir>(root_dir: P) -> Result<()> { - let root_dir = root_dir.as_ref().to_owned(); - let t = std::thread::spawn(move || { - init_com()?; - _remove_all_shortcuts_for_root_dir(&root_dir)?; - Ok(()) - }); - t.join().unwrap() -} - -fn _remove_all_shortcuts_for_root_dir>(root_dir: P) -> Result<()> { +unsafe fn unsafe_get_shortcuts_for_root_dir>(root_dir: P) -> Result> { let root_dir = root_dir.as_ref(); info!("Searching for shortcuts containing root: '{}'", root_dir.to_string_lossy()); - let pinned_str = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::RoamingAppData, co::KF::DONT_UNEXPAND, None)?; - let pinned_path = Path::new(&pinned_str).join("Microsoft\\Internet Explorer\\Quick Launch\\User Pinned"); - let search_paths = vec![ - format!("{}/*.lnk", w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Desktop, co::KF::DONT_UNEXPAND, None)?), - format!("{}/*.lnk", w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Startup, co::KF::DONT_UNEXPAND, None)?), - format!("{}/**/*.lnk", w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None)?), - format!("{}/**/*.lnk", pinned_path.to_string_lossy()), + (ShortcutLocationFlags::DESKTOP, format!("{}/*.lnk", known::get_user_desktop()?)), + (ShortcutLocationFlags::STARTUP, format!("{}/*.lnk", known::get_startup()?)), + // (ShortcutLocationFlags::START_MENU_ROOT, format!("{}/*.lnk", known::get_start_menu()?)), + (ShortcutLocationFlags::START_MENU, format!("{}/**/*.lnk", known::get_start_menu()?)), + (ShortcutLocationFlags::USER_PINNED, format!("{}/**/*.lnk", known::get_user_pinned()?)), ]; - for search_glob in search_paths { - info!("Searching for shortcuts in: '{}'", search_glob); - if let Ok(paths) = glob(&search_glob) { - for path in paths { - if let Ok(path) = path { - trace!("Checking shortcut: '{}'", path.to_string_lossy()); - let res = _resolve_lnk(&path.to_string_lossy()); - trace!(" Shortcut resolved: '{:?}'", res); - if let Ok((target, work_dir)) = res { - let target_match = super::is_sub_path(&target, root_dir).unwrap_or(false); - let work_dir_match = super::is_sub_path(&work_dir, root_dir).unwrap_or(false); - if target_match || work_dir_match { - let mstr = if target_match && work_dir_match { - format!("both target ({}) and work dir ({})", target, work_dir) - } else if target_match { - format!("target ({})", target) - } else { - format!("work dir ({})", work_dir) - }; - warn!("Removing shortcut '{}' because {} matched.", path.to_string_lossy(), mstr); - util::retry_io(|| std::fs::remove_file(&path))?; + let mut paths: Vec<(ShortcutLocationFlags, Lnk)> = Vec::new(); + for (flag, search_glob) in search_paths { + info!("Searching for shortcuts in: {:?} ({})", flag, search_glob); + if let Ok(glob_paths) = glob(&search_glob) { + for path in glob_paths.filter_map(Result::ok) { + trace!("Checking shortcut: '{:?}'", path); + match Lnk::open_write(&path) { + Ok(properties) => { + if let Ok(target) = properties.get_target_path() { + if super::is_sub_path(&target, root_dir).unwrap_or(false) { + info!("Selected shortcut for update '{}' because target '{}' matched.", path.to_string_lossy(), target); + paths.push((flag, properties)); + } + } else if let Ok(work_dir) = properties.get_working_directory() { + if super::is_sub_path(&work_dir, root_dir).unwrap_or(false) { + info!("Selected shortcut for update '{}' because work_dir '{}' matched.", path.to_string_lossy(), work_dir); + paths.push((flag, properties)); + } + } else { + warn!("Could not resolve target or work_dir for shortcut '{}'.", path.to_string_lossy()); } } + Err(e) => { + warn!("Failed to load shortcut: {}", e); + } } } } } + + Ok(paths) +} + +unsafe fn unsafe_remove_all_shortcuts_for_root_dir>(root_dir: P) -> Result<()> { + let shortcuts = unsafe_get_shortcuts_for_root_dir(root_dir)?; + for (flag, properties) in shortcuts { + let path = properties.get_link_path(); + info!("Removing shortcut '{}' ({:?}).", path, flag); + let remove_parent_if_empty = flag == ShortcutLocationFlags::START_MENU; + if let Err(e) = unsafe_delete_lnk_file(&path, remove_parent_if_empty) { + warn!("Failed to remove shortcut: {}", e); + } + } Ok(()) } +unsafe fn unsafe_delete_lnk_file>(path: P, remove_parent_if_empty: bool) -> Result<()> { + let path = path.as_ref(); + if !path.exists() { + return Ok(()); + } + + if let Err(e) = unsafe_unpin_lnk_from_start(path) { + warn!("Failed to unpin lnk from start menu: {}", e); + } + + util::retry_io(|| std::fs::remove_file(&path))?; + + // if the parent directory is empty, remove it as well + if remove_parent_if_empty { + if let Some(parent_path) = path.parent() { + if let Ok(entries) = parent_path.read_dir() { + if entries.count() == 0 { + util::retry_io(|| std::fs::remove_dir(&parent_path))?; + } + } + } + } + + Ok(()) +} + +// https://github.com/velopack/velopack/issues/100 +// https://learn.microsoft.com/en-us/windows/win32/api/shobjidl/nf-shobjidl-istartmenupinnedlist-removefromlist +unsafe fn unsafe_unpin_lnk_from_start>(path: P) -> Result<()> { + let path = path.as_ref(); + if !path.exists() { + return Ok(()); + } + + let path = string_to_u16(path.to_string_lossy()); + let item_result: IShellItem = SHCreateItemFromParsingName(PCWSTR(path.as_ptr()), None)?; + let pinned_list: IStartMenuPinnedList = create_instance(&StartMenuPin)?; + pinned_list.RemoveFromList(&item_result)?; + Ok(()) +} + +unsafe fn unsafe_run_delegate_in_com_context(delegate: F) -> Result +where + T: Send + 'static, + F: FnOnce() -> Result + Send + 'static, +{ + let t = std::thread::spawn(move || { + unsafe { + let hr = CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + if hr.is_err() { + return Err(anyhow!(hr)); + } + // This sleep is necessary to ensure that the COM context is fully initialized. + // I don't know why we need it, but if we don't have it then subsequent COM calls + // will break intermittently. + std::thread::sleep(Duration::from_millis(1)); + let result = delegate(); + CoUninitialize(); + result + } + }); + + t.join().map_err(|e| { + e.downcast_ref::>() + .map(|err| anyhow::anyhow!(err.to_string())) // Convert to anyhow using error string + .unwrap_or_else(|| anyhow::anyhow!("Thread panicked or returned an unknown error type")) + })? +} + +#[derive(Clone)] +struct Lnk { + me: IShellLinkW, + pf: IPersistFile, + my_path: String, +} + +#[allow(dead_code)] +impl Lnk { + pub unsafe fn get_link_path(&self) -> String { + self.my_path.clone() + } + + pub unsafe fn get_arguments(&self) -> Result { + let mut pszargs = [0u16; 1024]; + self.me.GetArguments(&mut pszargs)?; + let args = u16_to_string(pszargs)?; + Ok(args) + } + + pub unsafe fn get_description(&self) -> Result { + let mut pszdesc = [0u16; 1024]; + self.me.GetDescription(&mut pszdesc)?; + let desc = u16_to_string(pszdesc)?; + Ok(desc) + } + + pub unsafe fn get_icon_location(&self) -> Result<(String, i32)> { + let mut pszfile = [0u16; 1024]; + let mut pindex = 0; + self.me.GetIconLocation(&mut pszfile, &mut pindex)?; + let icon = u16_to_string(pszfile)?; + Ok((icon, pindex)) + } + + pub unsafe fn get_target_path(&self) -> Result { + let mut pszfile = [0u16; 1024]; + self.me.GetPath(&mut pszfile, std::ptr::null_mut(), 0)?; + let target = u16_to_string(pszfile)?; + Ok(target) + } + + pub unsafe fn get_working_directory(&self) -> Result { + let mut pszdir = [0u16; 1024]; + self.me.GetWorkingDirectory(&mut pszdir)?; + let work_dir = u16_to_string(pszdir)?; + Ok(work_dir) + } + + pub unsafe fn set_arguments(&mut self, path: &str) -> Result<()> { + let args = string_to_u16(path); + let args = PCWSTR(args.as_ptr()); + Ok(self.me.SetArguments(args)?) + } + + pub unsafe fn set_description(&mut self, path: &str) -> Result<()> { + let desc = string_to_u16(path); + let desc = PCWSTR(desc.as_ptr()); + Ok(self.me.SetDescription(desc)?) + } + + pub unsafe fn set_icon_location(&mut self, path: &str, index: i32) -> Result<()> { + let icon = string_to_u16(path); + let icon = PCWSTR(icon.as_ptr()); + Ok(self.me.SetIconLocation(icon, index)?) + } + + pub unsafe fn set_target_path(&mut self, path: &str) -> Result<()> { + let target = string_to_u16(path); + let target = PCWSTR(target.as_ptr()); + Ok(self.me.SetPath(target)?) + } + + pub unsafe fn set_working_directory(&mut self, path: &str) -> Result<()> { + let work_dir = string_to_u16(path); + let work_dir = PCWSTR(work_dir.as_ptr()); + Ok(self.me.SetWorkingDirectory(work_dir)?) + } + + pub unsafe fn set_aumid(&mut self, app_model_id: Option<&str>) -> Result<()> { + // Set app user model ID property + // Docs: https://docs.microsoft.com/windows/win32/properties/props-system-appusermodel-id + let store: IPropertyStore = self.me.cast()?; + if let Some(app_model_id) = app_model_id { + let id = string_to_u16(app_model_id); + let id = PCWSTR(id.as_ptr()); + let variant = InitPropVariantFromStringVector(Some(&[id]))?; + store.SetValue(&PKEY_AppUserModel_ID, &variant)?; + store.Commit()?; + } + // TODO: add clear/remove branch + // else { + // let mut varient = PROPVARIANT::default(); + // VariantToPropVariant(VT_EMPTY, &mut varient); + // store.SetValue(&PKEY_AppUserModel_ID, VT_EMPTY)?; + // VT_EMPTY + // initpropvariant + // } + Ok(()) + } + + pub unsafe fn save(&mut self) -> Result<()> { + Ok(self.pf.Save(None, true)?) + } + + pub unsafe fn save_as(&mut self, path: &str) -> Result<()> { + let output = string_to_u16(path); + let output = PCWSTR(output.as_ptr()); + self.my_path = path.to_string(); + Ok(self.pf.Save(output, true)?) + } + + // const SLR_NO_UI: u32 = 1; + // const SLR_ANY_MATCH: u32 = 2; + // const TIMEOUT_1MS: u32 = 1 << 16; + + pub unsafe fn open_write>(link_path: P) -> Result { + let link_path = link_path.as_ref().to_string_lossy().to_string(); + let link: IShellLinkW = create_instance(&ShellLink)?; + let persist: IPersistFile = link.cast()?; + debug!("Loading link: {}", link_path); + + let link_pcwstr = string_to_u16(&link_path); + let link_pcwstr = PCWSTR(link_pcwstr.as_ptr()); + + persist.Load(link_pcwstr, STGM_READWRITE)?; + + // we don't really want to "resolve" the shortcut in the middle of an update operation + // this can cause Windows to move the target path of a shortcut to one of our temp dirs etc + // let flags = Lnk::SLR_NO_UI | Lnk::SLR_ANY_MATCH | Lnk::TIMEOUT_1MS; + // if let Err(e) = link.Resolve(HWND(0), flags) { + // // this happens if the target path is missing and the link is broken + // warn!("Failed to resolve link {} ({:?})", link_path, e); + // } + Ok(Lnk { me: link, pf: persist, my_path: link_path }) + } + + pub unsafe fn create_new() -> Result { + let link: IShellLinkW = create_instance(&ShellLink)?; + let persist: IPersistFile = link.cast()?; + Ok(Lnk { me: link, pf: persist, my_path: String::new() }) + } +} + +impl std::fmt::Debug for Lnk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Lnk({})", self.my_path) + } +} + +// #[test] +// fn test_shortcut_matching() { +// let shortcuts = vec![ +// (ShortcutLocationFlags::DESKTOP, PathBuf::from("C:\\Users\\Caelan\\Desktop\\Discor.lnk")), +// (ShortcutLocationFlags::DESKTOP, PathBuf::from("C:\\Users\\Caelan\\Desktop\\Discord Hello.lnk")), +// (ShortcutLocationFlags::DESKTOP, PathBuf::from("C:\\Users\\Caelan\\Desktop\\Discord.lnk")), +// (ShortcutLocationFlags::DESKTOP, PathBuf::from("C:\\Users\\Caelan\\Desktop\\Hello Hello.lnk")), +// (ShortcutLocationFlags::DESKTOP, PathBuf::from("C:\\Users\\Caelan\\Desktop\\DiscordDa.lnk")), +// (ShortcutLocationFlags::START_MENU_ROOT, PathBuf::from("C:\\Users\\Caelan\\Desktop\\asdasdasd3Discord2947230947kjsl.lnk")), +// (ShortcutLocationFlags::START_MENU_ROOT, PathBuf::from("C:\\Users\\Caelan\\Desktop\\a2947230947kjsl.lnk")), +// ]; +// +// let matches = unsafe_find_best_shortcut_matches("Discord", shortcuts); +// assert_eq!(matches.len(), 2); +// assert_eq!(matches.get(&ShortcutLocationFlags::DESKTOP).unwrap(), &PathBuf::from("C:\\Users\\Caelan\\Desktop\\Discord.lnk")); +// assert_eq!( +// matches.get(&ShortcutLocationFlags::START_MENU_ROOT).unwrap(), +// &PathBuf::from("C:\\Users\\Caelan\\Desktop\\asdasdasd3Discord2947230947kjsl.lnk") +// ); +// } + +#[test] +#[ignore] +fn test_unpin_shortcut() { + unsafe { + unsafe_run_delegate_in_com_context(|| { + let path = r"C:\Users\Caelan\Desktop\Discord.lnk"; + unsafe_unpin_lnk_from_start(path)?; + Ok(()) + }) + .unwrap(); + } +} #[test] #[ignore] fn test_shortcut_intense_intermittent() { - let startmenu = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None).unwrap(); + let startmenu = known::get_start_menu().unwrap(); let lnk_path = Path::new(&startmenu).join("Programs").join(format!("{}.lnk", "veloshortcuttest")); let target = "C:\\Users\\Caelan\\AppData\\Local\\Discord\\Update.exe"; let work = "C:\\Users\\Caelan\\AppData\\Local\\Discord"; let mut i = 0; while i < 100 { - create_lnk(&lnk_path.to_string_lossy(), &target, &work, None).unwrap(); + unsafe { + let p1 = lnk_path.to_string_lossy().to_string().clone(); + unsafe_run_delegate_in_com_context(move || { + let mut l = Lnk::create_new().unwrap(); + l.set_target_path(&target).unwrap(); + l.set_working_directory(&work).unwrap(); + l.save_as(&p1).unwrap(); + Ok(()) + }) + .unwrap(); + } assert!(lnk_path.exists()); util::retry_io(|| std::fs::remove_file(&lnk_path)).unwrap(); assert!(!lnk_path.exists()); @@ -215,144 +638,107 @@ fn test_shortcut_intense_intermittent() { } #[test] -#[ignore] -fn test_can_resolve_existing_shortcut() { - let link_path = r"C:\Users\Caelan\Desktop\Discord.lnk"; - let (target, _workdir) = resolve_lnk(link_path).unwrap(); - assert_eq!(target, "C:\\Users\\Caelan\\AppData\\Local\\Discord\\Update.exe"); +fn test_bitwise_flags_add_subtract_correctly() { + let prev_locations = ShortcutLocationFlags::DESKTOP | ShortcutLocationFlags::START_MENU_ROOT; + let next_locations = ShortcutLocationFlags::STARTUP | ShortcutLocationFlags::DESKTOP | ShortcutLocationFlags::START_MENU; + + let deleted_locations = prev_locations - next_locations; + let same_locations = prev_locations & next_locations; + let new_locations = next_locations - prev_locations; + + assert_eq!(deleted_locations, ShortcutLocationFlags::START_MENU_ROOT); + assert_eq!(same_locations, ShortcutLocationFlags::DESKTOP); + assert_eq!(new_locations, ShortcutLocationFlags::STARTUP | ShortcutLocationFlags::START_MENU); } #[test] -fn shortcut_full_integration_test() { - let desktop = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Desktop, co::KF::DONT_UNEXPAND, None).unwrap(); - let link_location = Path::new(&desktop).join("testclowd123hi.lnk"); - let target = r"C:\Users\Caelan\AppData\Local\NonExistingAppHello123\current\HelloWorld.exe"; - let work = r"C:\Users\Caelan/appData\Local/NonExistingAppHello123\current"; - let root = r"C:\Users\Caelan/appData\Local/NonExistingAppHello123"; +#[ignore] +fn test_can_resolve_existing_shortcut() { + let link_path = r"C:\Users\Caelan\Desktop\Discord.lnk"; - create_lnk(&link_location.to_string_lossy(), target, work, None).unwrap(); - assert!(link_location.exists()); - - let (target_out, work_out) = resolve_lnk(&link_location.to_string_lossy()).unwrap(); - assert_eq!(target_out, target); - assert_eq!(work_out, work); - - remove_all_shortcuts_for_root_dir(root).unwrap(); - assert!(!link_location.exists()); + unsafe { + unsafe_run_delegate_in_com_context(move || { + let l = Lnk::open_write(link_path).unwrap(); + let target = l.get_target_path().unwrap(); + assert_eq!(target, "C:\\Users\\Caelan\\AppData\\Local\\Discord\\Update.exe"); + Ok(()) + }) + .unwrap(); + } } -// pub struct Lnk { -// me: w::IShellLink, -// pf: w::IPersistFile, -// } +#[test] +fn test_shortcut_full_integration() { + unsafe { + unsafe fn test_create_lnk(link_path: &str, target: &str, work: &str, aumid: Option<&str>) { + let mut l = Lnk::create_new().unwrap(); + l.set_target_path(target).unwrap(); + l.set_working_directory(work).unwrap(); + l.set_aumid(aumid).unwrap(); + l.save_as(link_path).unwrap(); + } -// pub trait ShellLinkReadOnly { -// fn get_arguments(&self) -> w::HrResult; -// fn get_description(&self) -> w::HrResult; -// fn get_icon_location(&self) -> w::HrResult<(String, i32)>; -// fn get_path(&self) -> w::HrResult; -// fn get_show_cmd(&self) -> w::HrResult; -// fn get_working_directory(&self) -> w::HrResult; -// } + unsafe_run_delegate_in_com_context(move || { + let desktop = known::get_user_desktop().unwrap(); + let start_menu = known::get_start_menu().unwrap(); + let start_menu_subfolder = Path::new(&start_menu).join("TestVelopackTest1234"); + let target = r"C:\Users\Caelan\AppData\Local\NonExistingAppHello123\current\HelloWorld.exe"; + let work = r"C:\Users\Caelan/appData\Local/NonExistingAppHello123\current"; + let root = r"C:\Users\Caelan/appData\Local/NonExistingAppHello123"; -// pub trait ShellLinkWriteOnly { -// fn set_arguments(&mut self, path: &str) -> w::HrResult<()>; -// fn set_description(&mut self, path: &str) -> w::HrResult<()>; -// fn set_icon_location(&mut self, path: &str, index: i32) -> w::HrResult<()>; -// fn set_path(&mut self, path: &str) -> w::HrResult<()>; -// fn set_show_cmd(&mut self, show_cmd: co::SW) -> w::HrResult<()>; -// fn set_working_directory(&mut self, path: &str) -> w::HrResult<()>; -// fn save(&self) -> w::HrResult<()>; -// fn save_as(&self, path: &str) -> w::HrResult<()>; -// } + let link1 = Path::new(&desktop).join("testclowd123hi.lnk"); + let link2 = Path::new(&start_menu).join("testclowd123hi.lnk"); + let link3 = start_menu_subfolder.join("testclowd123hi.lnk"); -// impl ShellLinkReadOnly for Lnk { -// fn get_arguments(&self) -> w::HrResult { -// Ok(self.me.GetArguments()?) -// } + std::fs::remove_dir_all(&start_menu_subfolder).unwrap_or_default(); + std::fs::remove_file(&link1).unwrap_or_default(); + std::fs::remove_file(&link2).unwrap_or_default(); + std::fs::remove_file(&link3).unwrap_or_default(); -// fn get_description(&self) -> w::HrResult { -// Ok(self.me.GetDescription()?) -// } + assert!(!link1.exists()); + assert!(!link2.exists()); + assert!(!link3.exists()); + assert!(!start_menu_subfolder.exists()); -// fn get_icon_location(&self) -> w::HrResult<(String, i32)> { -// Ok(self.me.GetIconLocation()?) -// } + std::fs::create_dir_all(&start_menu_subfolder).unwrap(); + assert!(start_menu_subfolder.exists()); -// fn get_path(&self) -> w::HrResult { -// Ok(self.me.GetPath(None, co::SLGP::UNCPRIORITY)?) -// } + let link1_str = link1.to_string_lossy(); + let link2_str = link2.to_string_lossy(); + let link3_str = link3.to_string_lossy(); -// fn get_show_cmd(&self) -> w::HrResult { -// Ok(self.me.GetShowCmd()?) -// } + let aumid = "Some_Test_Rust_Velopack_AUMID"; -// fn get_working_directory(&self) -> w::HrResult { -// Ok(self.me.GetWorkingDirectory()?) -// } -// } + test_create_lnk(&link1_str, target, work, Some(aumid)); + test_create_lnk(&link2_str, target, work, Some(aumid)); + test_create_lnk(&link3_str, target, work, Some(aumid)); -// impl ShellLinkWriteOnly for Lnk { -// fn set_arguments(&mut self, path: &str) -> w::HrResult<()> { -// Ok(self.me.SetArguments(path)?) -// } + assert!(link1.exists()); + assert!(link2.exists()); + assert!(link3.exists()); -// fn set_description(&mut self, path: &str) -> w::HrResult<()> { -// Ok(self.me.SetDescription(path)?) -// } + // let ps_result = Command::new("powershell").raw_arg("Get-StartApps | Sort-Object -Property Name").output()?; + // let ps_output = String::from_utf8_lossy(&ps_result.stdout).to_string(); -// fn set_icon_location(&mut self, path: &str, index: i32) -> w::HrResult<()> { -// Ok(self.me.SetIconLocation(path, index)?) -// } + let shortcuts = unsafe_get_shortcuts_for_root_dir(&root).unwrap(); + assert_eq!(shortcuts.len(), 3); + assert_eq!(shortcuts[0].0, ShortcutLocationFlags::DESKTOP); + assert_eq!(PathBuf::from(&shortcuts[0].1.my_path), link1); + assert_eq!(shortcuts[1].0, ShortcutLocationFlags::START_MENU); + assert_eq!(PathBuf::from(&shortcuts[1].1.my_path), link3); + assert_eq!(shortcuts[2].0, ShortcutLocationFlags::START_MENU); + assert_eq!(PathBuf::from(&shortcuts[2].1.my_path), link2); -// fn set_path(&mut self, path: &str) -> w::HrResult<()> { -// Ok(self.me.SetPath(path)?) -// } + assert_eq!(shortcuts[0].1.get_target_path().unwrap(), target); + assert_eq!(shortcuts[0].1.get_working_directory().unwrap(), work); -// fn set_show_cmd(&mut self, show_cmd: co::SW) -> w::HrResult<()> { -// Ok(self.me.SetShowCmd(show_cmd)?) -// } - -// fn set_working_directory(&mut self, path: &str) -> w::HrResult<()> { -// Ok(self.me.SetWorkingDirectory(path)?) -// } - -// fn save(&self) -> w::HrResult<()> { -// Ok(self.pf.Save(None, true)?) -// } - -// fn save_as(&self, path: &str) -> w::HrResult<()> { -// Ok(self.pf.Save(Some(path), true)?) -// } -// } - -// pub trait ShellLinkReadWrite: ShellLinkReadOnly + ShellLinkWriteOnly {} -// impl ShellLinkReadWrite for Lnk {} - -// impl Lnk { -// pub fn open_read(file_path: &str) -> w::HrResult> { -// let me = w::CoCreateInstance::(&co::CLSID::ShellLink, None, co::CLSCTX::INPROC_SERVER)?; -// let pf = me.QueryInterface::()?; -// pf.Load(file_path, co::STGM::READ)?; -// let flags = (co::SLR::NO_UI | co::SLR::ANY_MATCH).raw() | (1 << 16); -// let flags_with_timeout = unsafe { co::SLR::from_raw(flags) }; -// me.Resolve(&w::HWND::NULL, flags_with_timeout)?; -// Ok(Box::new(Lnk { me, pf })) -// } - -// pub fn open_write(file_path: &str) -> w::HrResult> { -// let me = w::CoCreateInstance::(&co::CLSID::ShellLink, None, co::CLSCTX::INPROC_SERVER)?; -// let pf = me.QueryInterface::()?; -// pf.Load(file_path, co::STGM::READWRITE)?; -// let flags = (co::SLR::NO_UI | co::SLR::ANY_MATCH).raw() | (1 << 16); -// let flags_with_timeout = unsafe { co::SLR::from_raw(flags) }; -// me.Resolve(&w::HWND::NULL, flags_with_timeout)?; -// Ok(Box::new(Lnk { me, pf })) -// } - -// pub fn create_new() -> w::HrResult> { -// let me = w::CoCreateInstance::(&co::CLSID::ShellLink, None, co::CLSCTX::INPROC_SERVER)?; -// let pf = me.QueryInterface::()?; -// Ok(Box::new(Lnk { me, pf })) -// } -// } + unsafe_remove_all_shortcuts_for_root_dir(root).unwrap(); + assert!(!link1.exists()); + assert!(!link2.exists()); + assert!(!link3.exists()); + assert!(!start_menu_subfolder.exists()); + Ok(()) + }) + .unwrap(); + } +} diff --git a/src/Rust/src/windows/splash.rs b/src/Rust/src/windows/splash.rs index 1cac0000..97c5434c 100644 --- a/src/Rust/src/windows/splash.rs +++ b/src/Rust/src/windows/splash.rs @@ -9,8 +9,8 @@ use std::{ sync::mpsc::{self, Receiver, Sender}, thread, }; -use w::WString; -use winsafe::{self as w, co, guard::DeleteObjectGuard, gui, prelude::*}; +use winsafe::guard::DeleteObjectGuard; +use winsafe::{self as w, co, gui, prelude::*, WString}; const TMR_GIF: usize = 1; const MSG_NOMESSAGE: i16 = -99; diff --git a/src/Rust/src/windows/strings.rs b/src/Rust/src/windows/strings.rs new file mode 100644 index 00000000..b5245974 --- /dev/null +++ b/src/Rust/src/windows/strings.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use windows::core::{HSTRING, PCWSTR, PWSTR}; + +pub fn string_to_u16>(input: P) -> Vec { + let input = input.as_ref(); + input.encode_utf16().chain(Some(0)).collect::>() +} + +pub fn pwstr_to_string(input: PWSTR) -> Result { + unsafe { + let hstring = input.to_hstring()?; + let string = hstring.to_string_lossy(); + Ok(string.trim_end_matches('\0').to_string()) + } +} + +pub fn pcwstr_to_string(input: PCWSTR) -> Result { + unsafe { + let hstring = input.to_hstring()?; + let string = hstring.to_string_lossy(); + Ok(string.trim_end_matches('\0').to_string()) + } +} + +pub fn u16_to_string>(input: T) -> Result { + let input = input.as_ref(); + let hstring = HSTRING::from_wide(input)?; + let string = hstring.to_string_lossy(); + Ok(string.trim_end_matches('\0').to_string()) +} diff --git a/src/Rust/src/windows/util.rs b/src/Rust/src/windows/util.rs index 6a5088cc..0ede28b6 100644 --- a/src/Rust/src/windows/util.rs +++ b/src/Rust/src/windows/util.rs @@ -1,22 +1,24 @@ -use crate::shared::{self, runtime_arch::RuntimeArch}; -use anyhow::{anyhow, Result}; -use normpath::PathExt; use std::{ os::windows::process::CommandExt, path::{Path, PathBuf}, process::Command as Process, time::Duration, }; + +use anyhow::{anyhow, Result}; +use normpath::PathExt; use wait_timeout::ChildExt; +use windows::core::PCWSTR; +use windows::Win32::Storage::FileSystem::GetLongPathNameW; +use windows::Win32::System::SystemInformation::{VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_FLAGS}; use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow; -use windows::{ - core::PCWSTR, - Win32::{ - Foundation::{self, GetLastError}, - System::Threading::CreateMutexW, - }, +use windows::Win32::{ + Foundation::{self, GetLastError}, + System::Threading::CreateMutexW, }; -use winsafe::{self as w, co}; + +use crate::shared::{self, runtime_arch::RuntimeArch}; +use crate::windows::strings::{string_to_u16, u16_to_string}; pub fn run_hook(app: &shared::bundle::Manifest, root_path: &PathBuf, hook_name: &str, timeout_secs: u64) -> bool { let sw = simple_stopwatch::Stopwatch::start_new(); @@ -76,16 +78,63 @@ impl Drop for MutexDropGuard { pub fn create_global_mutex(app: &shared::bundle::Manifest) -> Result { let mutex_name = format!("velopack-{}", &app.id); info!("Attempting to open global system mutex: '{}'", &mutex_name); - let encoded = mutex_name.encode_utf16().chain([0u16]).collect::>(); - let pw = PCWSTR(encoded.as_ptr()); - let mutex = unsafe { CreateMutexW(None, true, pw) }?; + let encodedu16 = super::strings::string_to_u16(mutex_name); + let encoded = PCWSTR(encodedu16.as_ptr()); + let mutex = unsafe { CreateMutexW(None, true, encoded) }?; match unsafe { GetLastError() } { Foundation::ERROR_SUCCESS => Ok(MutexDropGuard { mutex }), - Foundation::ERROR_ALREADY_EXISTS => Err(anyhow!("Another installer or updater for this application is running, quit that process and try again.")), + Foundation::ERROR_ALREADY_EXISTS => { + Err(anyhow!("Another installer or updater for this application is running, quit that process and try again.")) + } err => Err(anyhow!("Unable to create global mutex. Error code {:?}", err)), } } +pub fn expand_environment_strings>(input: P) -> Result { + use windows::Win32::System::Environment::ExpandEnvironmentStringsW; + let encoded_u16 = super::strings::string_to_u16(input); + let encoded = PCWSTR(encoded_u16.as_ptr()); + let mut buffer_size = unsafe { ExpandEnvironmentStringsW(encoded, None) }; + if buffer_size == 0 { + return Err(anyhow!(windows::core::Error::from_win32())); + } + + let mut buffer: Vec = vec![0; buffer_size as usize]; + buffer_size = unsafe { ExpandEnvironmentStringsW(encoded, Some(&mut buffer)) }; + if buffer_size == 0 { + return Err(anyhow!(windows::core::Error::from_win32())); + } + + super::strings::u16_to_string(buffer) +} + +#[test] +fn test_expand_environment_strings() { + assert_eq!(expand_environment_strings("%windir%").unwrap(), "C:\\Windows"); + assert_eq!(expand_environment_strings("%windir%\\system32").unwrap(), "C:\\Windows\\system32"); + assert_eq!(expand_environment_strings("%windir%\\system32\\").unwrap(), "C:\\Windows\\system32\\"); +} + +pub fn get_long_path>(str: P) -> Result { + let str = str.as_ref().to_string(); + let str = string_to_u16(str); + let str = PCWSTR(str.as_ptr()); + // SAFETY: str is a valid wide string, this call will return required size of buffer + let len = unsafe { GetLongPathNameW(str, None) }; + if len == 0 { + return Err(anyhow!(windows::core::Error::from_win32())); + } + + let mut vec = vec![0u16; len as usize]; + let len = unsafe { GetLongPathNameW(str, Some(vec.as_mut_slice())) }; + if len == 0 { + return Err(anyhow!(windows::core::Error::from_win32())); + } + + let result = u16_to_string(vec)?; + Ok(result) +} + pub fn is_sub_path, P2: AsRef>(path: P1, parent: P2) -> Result { let path = path.as_ref().to_string_lossy().to_lowercase(); let parent = parent.as_ref().to_string_lossy().to_lowercase(); @@ -104,8 +153,8 @@ pub fn is_sub_path, P2: AsRef>(path: P1, parent: P2) -> Re return Ok(true); } - let path = w::ExpandEnvironmentStrings(&path)?; - let parent = w::ExpandEnvironmentStrings(&parent)?; + let path = expand_environment_strings(&path)?; + let parent = expand_environment_strings(&parent)?; let path = Path::new(&path); let parent = Path::new(&parent); @@ -119,8 +168,24 @@ pub fn is_sub_path, P2: AsRef>(path: P1, parent: P2) -> Re } // calls GetFullPathNameW - let path = path.normalize_virtually()?.as_path().to_string_lossy().to_lowercase(); - let parent = parent.normalize_virtually()?.as_path().to_string_lossy().to_lowercase(); + let path = path.normalize().or_else(|_| path.normalize_virtually())?; + let parent = parent.normalize().or_else(|_| parent.normalize_virtually())?; + + let mut path = path.as_path().to_string_lossy().to_string(); + let mut parent = parent.as_path().to_string_lossy().to_string(); + + // calls GetLongPathNameW + match get_long_path(&path) { + Ok(p) => path = p, + Err(e) => warn!("Failed to get long path for '{}': {}", path, e), + } + match get_long_path(&parent) { + Ok(p) => parent = p, + Err(e) => warn!("Failed to get long path for '{}': {}", parent, e), + } + + path = path.to_lowercase(); + parent = parent.to_lowercase(); let path = PathBuf::from(path); let parent = PathBuf::from(parent); @@ -169,10 +234,7 @@ fn test_is_sub_path_works_with_non_existing_paths() { let path = PathBuf::from(r"C:\AppData\JamLogic"); let parent = PathBuf::from(r"C:\AppData\JamLogicDev"); assert!(!is_sub_path(&path, &parent).unwrap()); - - let path = PathBuf::from(r"C:\AppData\JamLogicDev"); - let parent = PathBuf::from(r"C:\AppData\JamLogic"); - assert!(!is_sub_path(&path, &parent).unwrap()); + assert!(!is_sub_path(&parent, &path).unwrap()); } #[test] @@ -202,15 +264,58 @@ fn test_is_sub_path_works_with_empty_paths() { assert!(!is_sub_path(&path, &parent).unwrap()); } +// Version condition mask constants defined as per Windows SDK +const VER_GREATER_EQUAL: u8 = 3; +const VER_MINORVERSION: VER_FLAGS = VER_FLAGS(0x0000001); +const VER_MAJORVERSION: VER_FLAGS = VER_FLAGS(0x0000002); +const VER_BUILDNUMBER: VER_FLAGS = VER_FLAGS(0x0000004); +const VER_SERVICEPACKMAJOR: VER_FLAGS = VER_FLAGS(0x0000020); + +fn is_os_version_or_greater_internal(major: u16, minor: u16, build: u16, service_pack: u16) -> bool { + let flags = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR; + + unsafe { + let mut mask: u64 = 0; + mask = VerSetConditionMask(mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_BUILDNUMBER, VER_GREATER_EQUAL); + mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + + let mut osvi: OSVERSIONINFOEXW = Default::default(); + osvi.dwMajorVersion = major.into(); + osvi.dwMinorVersion = minor.into(); + osvi.dwBuildNumber = build.into(); + osvi.wServicePackMajor = service_pack.into(); + + VerifyVersionInfoW(&mut osvi, flags, mask).is_ok() + } +} + +pub fn is_windows_10_or_greater() -> bool { + is_os_version_or_greater_internal(10, 0, 0, 0) +} + +pub fn is_windows_7_sp1_or_greater() -> bool { + is_os_version_or_greater_internal(6, 1, 0, 1) +} + +pub fn is_windows_8_or_greater() -> bool { + is_os_version_or_greater_internal(6, 2, 0, 0) +} + +pub fn is_windows_8_1_or_greater() -> bool { + is_os_version_or_greater_internal(6, 3, 0, 0) +} + pub fn is_os_version_or_greater(version: &str) -> Result { let (mut major, mut minor, mut build, _) = shared::parse_version(version)?; if major < 8 { - return Ok(w::IsWindows7OrGreater()?); + return Ok(is_windows_7_sp1_or_greater()); } if major == 8 { - return Ok(if minor >= 1 { w::IsWindows8Point1OrGreater()? } else { w::IsWindows8OrGreater()? }); + return Ok(if minor >= 1 { is_windows_8_or_greater() } else { is_windows_8_1_or_greater() }); } // https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions @@ -222,20 +327,7 @@ pub fn is_os_version_or_greater(version: &str) -> Result { minor = 0; } - if major == 10 && build <= 0 { - return Ok(w::IsWindows10OrGreater()?); - } - - let mut mask: u64 = 0; - mask = w::VerSetConditionMask(mask, co::VER_MASK::MAJORVERSION, co::VER_COND::GREATER_EQUAL); - mask = w::VerSetConditionMask(mask, co::VER_MASK::MINORVERSION, co::VER_COND::GREATER_EQUAL); - mask = w::VerSetConditionMask(mask, co::VER_MASK::BUILDNUMBER, co::VER_COND::GREATER_EQUAL); - - let mut osvi: w::OSVERSIONINFOEX = Default::default(); - osvi.dwMajorVersion = major; - osvi.dwMinorVersion = minor; - osvi.dwBuildNumber = build; - return Ok(w::VerifyVersionInfo(&mut osvi, co::VER_MASK::MAJORVERSION | co::VER_MASK::MINORVERSION | co::VER_MASK::BUILDNUMBER, mask)?); + Ok(is_os_version_or_greater_internal(major.try_into()?, minor.try_into()?, build.try_into()?, 0)) } #[test] diff --git a/src/Rust/tests/commands.rs b/src/Rust/tests/commands.rs index 3b7f4ca5..6c52f47b 100644 --- a/src/Rust/tests/commands.rs +++ b/src/Rust/tests/commands.rs @@ -6,6 +6,7 @@ use std::{fs, path::Path, path::PathBuf}; use tempfile::tempdir; use velopack::*; +use velopack::logging::trace_logger; #[cfg(target_os = "windows")] use winsafe::{self as w, co}; @@ -13,16 +14,26 @@ use winsafe::{self as w, co}; #[test] pub fn test_install_apply_uninstall() { dialogs::set_silent(true); + trace_logger(); + let fixtures = find_fixtures(); let app_id = "AvaloniaCrossPlat"; let pkg_name = "AvaloniaCrossPlat-1.0.11-win-full.nupkg"; - let startmenu = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None).unwrap(); - let lnk_path = Path::new(&startmenu).join("Programs").join(format!("{}.lnk", app_id)); - if lnk_path.exists() { - fs::remove_file(&lnk_path).unwrap(); - } + let start_menu = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None).unwrap(); + let start_menu = Path::new(&start_menu).join("Programs"); + let desktop = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Desktop, co::KF::DONT_UNEXPAND, None).unwrap(); + let desktop = Path::new(&desktop); + + let lnk_start_1 = start_menu.join(format!("{}.lnk", app_id)); + let lnk_desktop_1 = desktop.join(format!("{}.lnk", app_id)); + let lnk_start_2 = start_menu.join(format!("{}.lnk", "AvaloniaCross Updated")); + let lnk_desktop_2 = desktop.join(format!("{}.lnk", "AvaloniaCross Updated")); + let _ = fs::remove_file(&lnk_start_1); + let _ = fs::remove_file(&lnk_desktop_1); + let _ = fs::remove_file(&lnk_start_2); + let _ = fs::remove_file(&lnk_desktop_2); let nupkg = fixtures.join(pkg_name); @@ -30,26 +41,38 @@ pub fn test_install_apply_uninstall() { let tmp_buf = tmp_dir.path().to_path_buf(); commands::install(Some(&nupkg), Some(&tmp_buf)).unwrap(); - assert!(lnk_path.exists()); + assert!(!lnk_desktop_1.exists()); // desktop is created during update + assert!(lnk_start_1.exists()); + assert!(tmp_buf.join("Update.exe").exists()); assert!(tmp_buf.join("current").join("AvaloniaCrossPlat.exe").exists()); assert!(tmp_buf.join("current").join("sq.version").exists()); let (root_dir, app) = shared::detect_manifest_from_update_path(&tmp_buf.join("Update.exe")).unwrap(); assert_eq!(app_id, app.id); - assert!(semver::Version::parse("1.0.11").unwrap() == app.version); + assert_eq!(semver::Version::parse("1.0.11").unwrap(), app.version); let pkg_name_apply = "AvaloniaCrossPlat-1.0.15-win-full.nupkg"; let nupkg_apply = fixtures.join(pkg_name_apply); commands::apply(&root_dir, &app, false, shared::OperationWait::NoWait, Some(&nupkg_apply), None, false).unwrap(); + // shortcuts are renamed, and desktop is created + assert!(!lnk_desktop_1.exists()); + assert!(!lnk_start_1.exists()); + assert!(lnk_desktop_2.exists()); + assert!(lnk_start_2.exists()); + let (root_dir, app) = shared::detect_manifest_from_update_path(&tmp_buf.join("Update.exe")).unwrap(); - assert!(semver::Version::parse("1.0.15").unwrap() == app.version); + assert_eq!(semver::Version::parse("1.0.15").unwrap(), app.version); commands::uninstall(&root_dir, &app, false).unwrap(); assert!(!tmp_buf.join("current").exists()); assert!(tmp_buf.join(".dead").exists()); - assert!(!lnk_path.exists()); + + assert!(!lnk_desktop_1.exists()); + assert!(!lnk_start_1.exists()); + assert!(!lnk_desktop_2.exists()); + assert!(!lnk_start_2.exists()); } #[cfg(target_os = "windows")] diff --git a/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs b/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs index 3cdafa59..927a9466 100644 --- a/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs +++ b/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs @@ -37,7 +37,7 @@ public class WindowsPackCommandRunner : PackageBuilder // add nuspec metadata ExtraNuspecMetadata["runtimeDependencies"] = GetRuntimeDependencies(); ExtraNuspecMetadata["shortcutLocations"] = GetShortcutLocations(); - ExtraNuspecMetadata["shortcutAmuid"] = Utility.CreateGuidFromHash(Options.PackId).ToString(); + ExtraNuspecMetadata["shortcutAmuid"] = Utility.GetAppUserModelId(Options.PackId); // copy files to temp dir, so we can modify them var dir = TempDir.CreateSubdirectory("PreprocessPackDirWin"); diff --git a/src/Velopack.Vpk/Commands/WindowsPackCommand.cs b/src/Velopack.Vpk/Commands/WindowsPackCommand.cs index eadd34cc..ebed80c5 100644 --- a/src/Velopack.Vpk/Commands/WindowsPackCommand.cs +++ b/src/Velopack.Vpk/Commands/WindowsPackCommand.cs @@ -52,12 +52,10 @@ public class WindowsPackCommand : PackCommand .SetHidden() .SetDefault(10); - //AddOption((v) => Shortcuts = v, "--shortcuts") - // .SetDescription("List of locations to install shortcuts to during setup.") - // .SetArgumentHelpName("LOC") - // .SetDefault("Desktop,StartMenuRoot") - // .SetHidden(true); // this argument currently has no effect - Shortcuts = "Desktop,StartMenuRoot"; + AddOption((v) => Shortcuts = v, "--shortcuts") + .SetDescription("List of locations to install shortcuts to during setup.") + .SetArgumentHelpName("LOC") + .SetDefault("Desktop,StartMenuRoot"); if (VelopackRuntimeInfo.IsWindows) { var signParams = AddOption((v) => SignParameters = v, "--signParams", "-n") diff --git a/src/Velopack/Internal/Utility.cs b/src/Velopack/Internal/Utility.cs index f4e611db..1429cca8 100644 --- a/src/Velopack/Internal/Utility.cs +++ b/src/Velopack/Internal/Utility.cs @@ -205,6 +205,7 @@ namespace Velopack if (String.IsNullOrWhiteSpace(channel) || channel.ToLower() == "win") { return "RELEASES"; } + // all other cases the RELEASES file includes the channel name. return $"RELEASES-{channel.ToLower()}"; } @@ -212,10 +213,14 @@ namespace Velopack public static void Retry(this Action block, int retries = 4, int retryDelay = 250, ILogger? logger = null) { - Retry(() => { - block(); - return true; - }, retries, retryDelay, logger); + Retry( + () => { + block(); + return true; + }, + retries, + retryDelay, + logger); } public static T Retry(this Func block, int retries = 4, int retryDelay = 250, ILogger? logger = null) @@ -237,10 +242,14 @@ namespace Velopack public static Task RetryAsync(this Func block, int retries = 4, int retryDelay = 250, ILogger? logger = null) { - return RetryAsync(async () => { - await block().ConfigureAwait(false); - return true; - }, retries, retryDelay, logger); + return RetryAsync( + async () => { + await block().ConfigureAwait(false); + return true; + }, + retries, + retryDelay, + logger); } public static async Task RetryAsync(this Func> block, int retries = 4, int retryDelay = 250, ILogger? logger = null) @@ -276,11 +285,12 @@ namespace Velopack { return Task.WhenAll( from partition in Partitioner.Create(source).GetPartitions(degreeOfParallelism) - select Task.Run(async () => { - using (partition) - while (partition.MoveNext()) - await body(partition.Current).ConfigureAwait(false); - })); + select Task.Run( + async () => { + using (partition) + while (partition.MoveNext()) + await body(partition.Current).ConfigureAwait(false); + })); } /// @@ -299,6 +309,7 @@ namespace Velopack else safeName.Append('_'); } + safeFileName = safeName.ToString(); } @@ -465,13 +476,16 @@ namespace Velopack // retry a few times. if a directory in this tree is open in Windows Explorer, // it might be locked for a little while WE cleans up handles try { - Retry(() => { - try { - deleteMe(); - } catch (DirectoryNotFoundException) { - return; // good! - } - }, retries: 4, retryDelay: 50); + Retry( + () => { + try { + deleteMe(); + } catch (DirectoryNotFoundException) { + return; // good! + } + }, + retries: 4, + retryDelay: 50); } catch (Exception ex) { logger?.Warn(ex, $"Unable to delete child '{fileSystemInfo.FullName}'"); throw; @@ -519,10 +533,9 @@ namespace Velopack // .OrderByDescending(x => x.Version); //} - public static string GetAppUserModelId(string packageId, string exeName) + public static string GetAppUserModelId(string packageId) { - return String.Format("com.velopack.{0}.{1}", packageId.Replace(" ", ""), - exeName.Replace(".exe", "").Replace(" ", "")); + return $"velopack.{packageId}"; } public static bool IsHttpUrl(string urlOrPath) diff --git a/src/Velopack/VelopackApp.cs b/src/Velopack/VelopackApp.cs index 2cd7bfee..deccd042 100644 --- a/src/Velopack/VelopackApp.cs +++ b/src/Velopack/VelopackApp.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -22,6 +23,9 @@ namespace Velopack /// public sealed class VelopackApp { + [DllImport("shell32.dll", SetLastError = true)] + private static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID); + internal static ILogger DefaultLogger { get; private set; } = NullLogger.Instance; internal static IVelopackLocator? DefaultLocator { get; private set; } @@ -165,6 +169,12 @@ namespace Velopack log.Info("Starting Velopack App (Run)."); + if (VelopackRuntimeInfo.IsWindows && locator.AppId != null) { + var appUserModelId = Utility.GetAppUserModelId(locator.AppId); + log.Info($"Setting current process explicit AppUserModelID to '{appUserModelId}'"); + SetCurrentProcessExplicitAppUserModelID(appUserModelId); + } + // first, we run any fast exit hooks VelopackHook defaultBlock = ((v) => { }); var fastExitlookup = new[] { @@ -227,6 +237,7 @@ namespace Velopack if (package.Type == VelopackAssetType.Full && (package.Version == latestLocal?.Version || package.Version == myVersion)) { continue; } + try { log.Info("Removing old package: " + package.FileName); var p = Path.Combine(pkgPath, package.FileName); @@ -245,6 +256,7 @@ namespace Velopack log.Error(ex, $"Error occurred executing user defined Velopack hook. (firstrun)"); } } + if (restarted) { try { _restarted?.Invoke(myVersion); @@ -261,4 +273,4 @@ namespace Velopack } } } -} +} \ No newline at end of file diff --git a/test/Velopack.Packaging.Tests/WindowsPackTests.cs b/test/Velopack.Packaging.Tests/WindowsPackTests.cs index 8465abb0..ecdf4465 100644 --- a/test/Velopack.Packaging.Tests/WindowsPackTests.cs +++ b/test/Velopack.Packaging.Tests/WindowsPackTests.cs @@ -196,6 +196,7 @@ public class WindowsPackTests PackVersion = version, TargetRuntime = RID.Parse("win-x64"), PackDirectory = tmpOutput, + Shortcuts = "Desktop,StartMenuRoot", }; var runner = GetPackRunner(logger); diff --git a/test/fixtures/AvaloniaCrossPlat-1.0.11-win-full.nupkg b/test/fixtures/AvaloniaCrossPlat-1.0.11-win-full.nupkg index 3de16632..88351d21 100644 Binary files a/test/fixtures/AvaloniaCrossPlat-1.0.11-win-full.nupkg and b/test/fixtures/AvaloniaCrossPlat-1.0.11-win-full.nupkg differ diff --git a/test/fixtures/AvaloniaCrossPlat-1.0.15-win-full.nupkg b/test/fixtures/AvaloniaCrossPlat-1.0.15-win-full.nupkg index 3d5217d0..13d98774 100644 Binary files a/test/fixtures/AvaloniaCrossPlat-1.0.15-win-full.nupkg and b/test/fixtures/AvaloniaCrossPlat-1.0.15-win-full.nupkg differ