reverse geocode, icloud monitor
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
__pycache__/*
|
||||
*.pyc
|
||||
cookies/*
|
||||
|
||||
10
.idea/back-end.iml
generated
10
.idea/back-end.iml
generated
@@ -1,13 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.system.id="java-source" type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="jdk" jdkName="uv (pymd3_vue_location_sim)" jdkType="Python SDK" />
|
||||
</component>
|
||||
<module version="4">
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
|
||||
2
.idea/modules.xml
generated
2
.idea/modules.xml
generated
@@ -2,7 +2,7 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/back-end.iml" filepath="$PROJECT_DIR$/.idea/back-end.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/pymd3_vue_location_sim.iml" filepath="$PROJECT_DIR$/.idea/pymd3_vue_location_sim.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
15
.idea/pymd3_vue_location_sim.iml
generated
Normal file
15
.idea/pymd3_vue_location_sim.iml
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.system.id="java-source" type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="jdk" jdkName="uv (pymd3_vue_location_sim)" jdkType="Python SDK" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,24 +1,25 @@
|
||||
#LWP-Cookies-2.0
|
||||
Set-Cookie3: dslang=US-EN; path="/"; domain=.apple.com; path_spec; secure; discard; HttpOnly=None; version=0
|
||||
Set-Cookie3: site=USA; path="/"; domain=.apple.com; path_spec; secure; discard; HttpOnly=None; version=0
|
||||
Set-Cookie3: acn01="vWB+8XG/BRIWimhzMz5URIMyy8lepYRnuuCxqAANNsVFbcxf"; path="/"; domain=.apple.com; path_spec; secure; expires="2027-03-27 09:37:26Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: aasp=90537484E4B81989243BAABCFA1DE44F53876FB5F9C433FD2C4687BD0D409642748545163AD333F3CDBBDDC6128BF84382082E6C0A4E27921C8792F0883DABFBFE8534D699F84266A14F842D6502C6902F2F00DC126EBD3E7D5FF7C490D9FE6740C97EFA884B52031C2E5BB2469A785CBDD236AD7014E383; path="/"; domain=.idmsa.apple.com; path_spec; secure; discard; HttpOnly=None; version=0
|
||||
Set-Cookie3: acn01="CgJEhy4Wbol25G19NM8DS23DOwutDyN0r/4Z1gAMbj5n95Bf"; path="/"; domain=.apple.com; path_spec; secure; expires="2027-04-01 07:24:24Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: aasp=0893DF20A53394DF6264EB0D0F8DB8E09184E1B9350AA9983DC5EB457D7466E0C4F6F402DA6AE23A4F2375865E4D40CEABC7C9BCCF30B7801A1566D157830BE7E3FE1BBC39D1C7027A3909782C03A6C4E39719F3D8C77A528AB04ED28F26A2FCEC7DE40693496A4786EB60DB8BB8F76652B08C8895936DF5; path="/"; domain=.idmsa.apple.com; path_spec; secure; discard; HttpOnly=None; version=0
|
||||
Set-Cookie3: DES580750186337023c50d1415a6e6ca44a2="HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX"; path="/"; domain=.idmsa.apple.com; path_spec; secure; expires="2026-04-21 03:09:12Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-UNIQUE-CLIENT-ID="\"BA==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-LOGIN="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGnGXmMRDmdzLmljbG91ZC5hdXRovQCH_epzZatm6b76myo9LfRsbXIfkwxREvo7U3efqnjYgORbMDpBEJyshI1bhC-Ww30ipqYT87rp7mJxkcXRw4HYHvxJCs1rC9M6JWbFvrmUTu4RuVuaVYSNHRYAz8-DS6bagfD6XUHY-5Xmqf2fnmeGqZfNag~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-VALIDATE="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGnGXmMRDmdzLmljbG91ZC5hdXRovQCH_epzZatm6b76myo9LfRsbXIfkwxREvo7U3efqnjYgORbMDpBEJyshI1bhC-Ww30ipqYT87rp7mJxkcXRw4HYHvxJCs1rC9M6JWbFvrmUTllbuUPSPqBVc1raHL34Cl5lu7SfciATbqjQE4KgZchJLIo6PQ~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-USER="\"v=1:s=1:d=157320350\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-LOGIN="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGnMyCgRDmdzLmljbG91ZC5hdXRovQCKfWIEBypQJL5pYW9WRSJSpnRRYSvdPtLCjiJH_dhIBChjwxje7ma7zbulaTStxnHLbp5oU1erVllk8eusG4zv5fUmpfacRsbFI8Y9UV3jVfOvIQhtJKksZvSyxW-j6Xn3QAtNPBc1WUK1l5TInpMCgNwl3A~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-VALIDATE="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGnMyCgRDmdzLmljbG91ZC5hdXRovQCKfWIEBypQJL5pYW9WRSJSpnRRYSvdPtLCjiJH_dhIBChjwxje7ma7zbulaTStxnHLbp5oU1erVllk8eusG4zv5fUmpfacRsbFI8Y9UV3jVRl05kUtq1dzNVX1bvl3ujJp0xX4p_ZDiqr-9Z1yeC_yxhShKg~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-USER="\"v=1:s=1:d=157320350\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X_APPLE_WEB_KB-FHMLYL_TPMN_3A8D3KIPPI0C_EC="\"v=1:t=BA==BST_IAAAAAAABLwIAAAAAGm_OLQRDmdzLmljbG91ZC5hdXRovQD38nYoxQenHW9WggeFKkoDa8I8zeKoOshv6I4dsZQalR2itry1r6kUZe9d_BZan1W-oKlImTrYi_-Vt5Q4YEJWJITWeqN8QChxvbTXB0o8sQ-wAIzBL1J5sQIRBqMadrtP5U0wslkRg0u0AguK20CM4TGoGg~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-21 00:32:52Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-DS-WEB-SESSION-TOKEN="\"AQHO22bhY5UiUtuYBNJRupGy4tIGYR08ChFW77qgTTPDpPof4ZOPHhm2rGQ7OnoHchQlJLXG77T2BEoZxUO7ZknuRUUoK/j9/XTHYwv92CgFs/STC7oB1aMYmfddwNigbg9L5v40kX3Fmg6teX34bGYuOfqoHKTCuVxhllBNxZsKcPsKLGqRdZ4b3thHu+Y6+uVq5mAT9RnUbYAMD7hYP1Vr4aq+6CYi4+TdtO02SngpGcCRC5BR3PJh2udSG2YSGk5T+SBb4uBx94CzF2116QQWR0WjwSpqJ2K881lENtE3Kvf0pmM5mIw9fLdSgDi6kqP8iJHdni85QYpuTasMzd7ROWih6OqSV0U3O62X8ix+Xf3P1e/Fes7prNxjME+tcORKftTockp3c5U28DKcnZYYYuBRh+BNaAqVIAMg7AW9dNJUuTWdzVtUPhkLgvyTal625iiSh7Q6wsIISa03Cjtueli3NV++Z3R2jFvTQ/mC1lH9U//3ul+UPqblKCBy7SKGi/oph/PIroggTUwx/tvjQxCQf8RVkGg+8T0UE7rj6wDuaYu1i7twt7o1QsBlefEpVU9fYhCK3sq5aydhLRsb47wX42WhftBRTDenq7SwaHJZfxQ3T4NVULg8OsuCeuO1Gejnbylh9BI3Ws+I5k0ZNOaZtAWBlW8dsa5KrEdr+cRG0oV4EKPGTbt/mhvdFA/Kes/ksfI27DkKDmQ0MVj7nTTXQSkxeeQWEuP3T5LSNaYGDl+E6zqn5SsTt1RGxYMj3SHfSzxcLjNk7RoGcNle45L+V1wA5G8=\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-HSA-TRUST="\"28a33818a1dce9a0eecde38e7c8fcc6f080b70bc9feb505599fb2855903a4792_HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-06-25 10:39:31Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Events="\"S2V5QXBwbDoBAAAA8QQnAADHJQyTmjkr1lRgHCHPONwUkefefsNrEGp6R9lQhFZ2hQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Documents="\"S2V5QXBwbDoBAAAAAgQnAAATRltQsTNPTggWr2+h+Ck7taXy1Nfd/0gTtx4/mgZMuQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Photos="\"S2V5QXBwbDoBAAAAAwQnAAC2DQKpt8kGxmIXjouUofkM/o9x9ZcMbMi8+kob3iqjtg==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Cloudkit="\"S2V5QXBwbDoBAAAABAQnAAD32tR1O4Wfr112mDd798bvrPSnGJ9SGs1UAu8ZxpArag==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Safari="\"S2V5QXBwbDoBAAAAFgQnAAAPeaT9obfpF4JkzvnTqn9iKRIFWjCO4Ibdd3ETw14+Kw==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Mail="\"S2V5QXBwbDoBAAAABwQnAADn5vf2KKnwETMPFGonUQAJwl4zr2g3X+iUWawSMgWOEg==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Notes="\"S2V5QXBwbDoBAAAACQQnAAALAg7ubdhjkrtB/lFNTwHudI77mxFx9gsBauT9LBhB6g==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-News="\"S2V5QXBwbDoBAAAACwQnAAA5X6JspeIixJp6c7Uy+R2YynkGzH97NPfDPAkrO7uZ8g==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Sharing="\"S2V5QXBwbDoBAAAADAQnAADz1ui+JqfV6Fy6AI0Z6DScPVidn3wzooDronhTKnsLmQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-26 10:39:35Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-TOKEN="\"v=2:t=BA==BST_IAAAAAAABLwIAAAAAGnGcucRDmdzLmljbG91ZC5hdXRovQBTsTtaO6tyhJbzfEVId4DPvNxvrOsTyKuhGBini_Ov9HBK9EgpLZCsfHxc0R0TWp-8AWGCWsLJSRkerU_Wt4CQG-vY_elLY1vevhKMmIi2CdVVyTBenEo_PyeAkNds0z9SjSi5a5z5rBqJpSysM1UyXNTTcg~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-10 12:07:03Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: xr_3n2093n1a="BW/k2XvBcxLsX55iCOOGgMojIY0Q+/Ey61qJDHU0TQE="; path="/"; domain=p144-fmipweb.icloud.com; path_spec; secure; discard; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-DS-WEB-SESSION-TOKEN="\"AQEaqCRaCv071GuiR0UvtF5IqIvyXlvWeh7aOWEBzPx7Vy4s8hDUySzxI1nO2fkjPPVD5S6w0iH0VDbsHVVw4i89aXs3VaSH4Jl3FjbezwlL+G0lzZfTegOwx3VQyDG4vZoxzA4eo57U+gyfPSk1vj2K7vQr0ypz7gnUnqLL0MnkR//anTy0aidDy5jLoErjYZyHUeh2DU9ked3vaosk6UkZau5bos+QHWiUj4DDp345gTZAQemrQxgYfW3CNF413kMgy45TpIQ9ET6YdP7/6kMbnjPWJXIa/90wsCxfExUx66MWHO2BT0sYTEElc/3yZURTelHk2u0ANxjt+kN29XboudW5pO0/ikIlG39w82gr+fTN2IaBG791uLZg3GIIzqIv2Utt4xFD0W/vq2yhQ4en5dcmb+2JjYyrNhURI3ZssbREohejbnJheQFmWfqjD9BJFk0kO17FgPvrqSlXvU/LK0mpBXjVzWJHNYslxi7kqGIrLhMaMbJgD93p13Atnjqh0nLPuk4h7E3a4xJMWkbOvcjsc6X/2qL46BPaQp8GODmVgwY8qVu3G8rslvPdSWA1s8cw7Ca9Zw36gOjK6Yceka8+5T+g3nywYV/mu6XYc+REiBqdMk/g16VLMRpJ6XwwUP/zfzkhScNg/EAeqyuSTm5B+0tjDoOjSDftJPllvE3R55YymEgs66Yu3qM9m7m33M+hq6Y4fF2qfp3XJi5FX5nqZLmq6izHbAjJKcm9MJ7Dki0mNCALbuO1E+kdnnMG+bufH09VepNo5KPX0E91zURD494agCM=\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-HSA-TRUST="\"28a33818a1dce9a0eecde38e7c8fcc6f080b70bc9feb505599fb2855903a4792_HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-06-30 07:24:24Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Events="\"S2V5QXBwbDoBAAAA8QQtAAAcRMnkLPigzsB/itJ9jaBjf4fy8r1RMlo1yUP2hCDneg==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Documents="\"S2V5QXBwbDoBAAAAAgQtAAAgYberFOMZGUixcIOai9L24ZHNbu5DaZP6HnmRQ1MSvQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Photos="\"S2V5QXBwbDoBAAAAAwQtAACUziNBcSBwEQgghccKEG9k/ygsjsac45cuvR6qqlUkFg==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Cloudkit="\"S2V5QXBwbDoBAAAABAQtAAANeCQ7Cp2T6an+rFbqdrmQ0X4jOkNWxPsx9z1wCl0kTw==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Safari="\"S2V5QXBwbDoBAAAAFgQtAACUhob4CIIW+xGVNeGa7FbKTRx+ZUuIVKj1pNH1kuHAow==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Mail="\"S2V5QXBwbDoBAAAABwQtAABpQkHUvLVS2zGojJeukynTgPUeaeiIOiXhOpK7PRViLA==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Notes="\"S2V5QXBwbDoBAAAACQQtAAAvXaphLtTg6NqJNcDsGV0/3Yz7q9fGYqKTv11baVhQkA==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-News="\"S2V5QXBwbDoBAAAACwQtAAA/aiJIyMlZL5ZfM0T8WFuMnV7U2GLwBEMEEbHvoFEFeA==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-PCS-Sharing="\"S2V5QXBwbDoBAAAADAQtAADjBjUschddlXGLyz3YOcAY3zH0qIO0CaVA3nBRCdP/ZQ==\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-05-01 07:24:27Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-TOKEN="\"v=2:t=BA==BST_IAAAAAAABLwIAAAAAGnNJKERDmdzLmljbG91ZC5hdXRovQBxaNXd8uCVvsFuSORQSJveyszMyViirh-1zzSxcPCoxcnSgwKOEgF3_8tM4gdejba-XwvouYJI1zUjfW3GiydXisVR2Dmv3DBWd4RCYvKYvXZAbCL6gCeDd-n6WbZHVCLgbh_ui1teFpujrhahVPqVS57-FQ~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; expires="2026-04-15 13:58:58Z"; HttpOnly=None; version=0
|
||||
Set-Cookie3: X-APPLE-WEBAUTH-FMIP="\"BA==BST_IAAAAAAABLwIAAAAAGnNJKERDmdzLmljbG91ZC5hdXRovQBxaNXd8uCVvsFuSORQSJveyszMyViirh-1zzSxcPCoxcnSgwKOEgF3_8tM4gdejba-XwvouYJI1zUjfW3GiydXisVR2Dmv3DBWd4RCYvKYvYEkIgdydG0msGrjkyM6ZcaotQmxM0qAZGrGtEOZnjC6QlkocA~~\""; path="/"; domain=.icloud.com; path_spec; domain_dot; secure; discard; HttpOnly=None; version=0
|
||||
Set-Cookie3: xr_3n2093n1a="f16+Jo6W6qPJmRkZzBixaQfuGOyKO+kJ4iSEZyG94A=="; path="/"; domain=p144-fmipweb.icloud.com; path_spec; secure; discard; HttpOnly=None; version=0
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"client_id": "a803c3ce-2586-11f1-a724-8f6777a1d2b5", "session_id": "90537484E4B81989243BAABCFA1DE44F53876FB5F9C433FD2C4687BD0D409642748545163AD333F3CDBBDDC6128BF84382082E6C0A4E27921C8792F0883DABFBFE8534D699F84266A14F842D6502C6902F2F00DC126EBD3E7D5FF7C490D9FE6740C97EFA884B52031C2E5BB2469A785CBDD236AD7014E383", "auth_attributes": "CpWqHR6Y8JEnXubiHUHm2K5yCpOQORPPkclAkiMbW2nsEkSK2oVF24FHI3QNNw4cfBQfJOSEdRvVtGKtbi/Zxzbma2uaF9uD1kAlxlin4oPpxzNH5xKC472p08YUBkvusVaZ4wPqv6Zv+MZefISD/f/5A3vIn2fTZbXgImoKKaTLlwSn5wOcaJYoJYAhWgak+YPWeGLAdm1oGuXvTkCZe3gYtizo8lu6d4SBEjQ18GJ0BAN8D+BRxOb9B1AhxnzUd25aKq4CXzkRmLLCQWX0AA02xVKHQTE=", "scnt": "AAAA-jkwNTM3NDg0RTRCODE5ODkyNDNCQUFCQ0ZBMURFNDRGNTM4NzZGQjVGOUM0MzNGRDJDNDY4N0JEMEQ0MDk2NDI3NDg1NDUxNjNBRDMzM0YzQ0RCQkREQzYxMjhCRjg0MzgyMDgyRTZDMEE0RTI3OTIxQzg3OTJGMDg4M0RBQkZCRkU4NTM0RDY5OUY4NDI2NkExNEY4NDJENjUwMkM2OTAyRjJGMDBEQzEyNkVCRDNFN0Q1RkY3QzQ5MEQ5RkU2NzQwQzk3RUZBODg0QjUyMDMxQzJFNUJCMjQ2OUE3ODVDQkREMjM2QUQ3MDE0RTM4M3wyAAABnS61l867bZUy_24a4kaFbwGNgVJFgWQLrkDm8d_dAuxiqTUQBdnQ9m4C0ia8AA02xTey6hfMfAIuPPlov20tqtmvTh-N23rQ3Q4jQeaNx6uMrtovmQ", "account_country": "USA", "session_token": "idWfvF23GAFDtpDv51Mnga3iu7fJCAdyDaw22SorSHz5JTRc/SottqqYD2KGG/56FLnY9lHAZnJUz8APx24/Y5Udfi45jsW6FmUZ1btGt4ihu0lGg5zIWAxY6B9AHVe4ROZ0mI90mvT5jajGL56Ke3fhL6ncIYP/LTQz2EJfwZtd02MgeQ5j1W0IfF8zmhlDyb9eAn1dC6X61hh7d/DZdLDFfZxxyXkBhemi6dNQKDwAUnYl7YKlShZDLUFNFG0k3m2yiLQANI+InL7fDHPEdyiNaGh9Lu5VLcq7hgNFqIqN6JFjUBAr/cauSeTHHmGPtH9Ry4VWMpl3Ukd+nppFylMCazMj3hvjXpSIkMR7B41pxxRA1FtjgYryeUkexcQXx8vrMYwrkVdtOB4p2xRfX7xXzMGH5VpwpFO75/nO10A4n+YEXg/JaPW31R8zLBvnZd6oL5ElCGIlXQadsPM0mzDdbvM2dXhMQaJ4dLQC9p4XhIrEkGhV7op5YeLWfNiDg3tBDLYn+WVEw7xWmEp6/NRZqFmctEDgJak9AwUV5ostJ4O69UxiaqkL8VgYysHBVcN0Tl6XwVHvh+o5LBL3C9fAsQ1ZIMNb088Sl8iRHbwOxa/UfiQ9RWiTfUhlGzNhd6nxOLIN5SaBV4Z3Xu4asRpP00Ue0igWBzKmn6nTk5lEMABMA+fiDVYdpuN0dNWbscZwLLozBsToayEgfDD/KdYCdqEV8y1mL97z7nSjB1VAfs5x3Skhq6AhKIe9p1Y9AbsOJP8vs+p+G+xHi/sKKzz8r3g27yNpP7emIO6lioYeuHTg341QSGJtPofrq9d+04J08dgnvxJ6dsfyGrYYGjIV9OYidbhMlXkXPofkMVA+1SCjVi1pQOPtlZ8ygzJK4+OiLK8NJJR5tcaMNSZ0l16RIoZw+IdEi2invyN/SXQqjVyie10sEbRarN8cVOJ58AANNsVYFrWa", "trust_eligible": "true", "grant_code": "cca13786ef08a405f94e575e9e8501ab7.0.mrzwz.y2Ef8JBOyp06v2uZhFL20g", "trust_token": "HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX"}
|
||||
{"client_id": "a803c3ce-2586-11f1-a724-8f6777a1d2b5", "session_id": "0893DF20A53394DF6264EB0D0F8DB8E09184E1B9350AA9983DC5EB457D7466E0C4F6F402DA6AE23A4F2375865E4D40CEABC7C9BCCF30B7801A1566D157830BE7E3FE1BBC39D1C7027A3909782C03A6C4E39719F3D8C77A528AB04ED28F26A2FCEC7DE40693496A4786EB60DB8BB8F76652B08C8895936DF5", "auth_attributes": "UKS2E/hPEAScl9xio3Fb55QVB8lO4pEITqYS19Q2eMgmp+MefOgSFUg9Lvpy6wJB76Z+be0kfF5FpnQqoLWy1PEg1L88urm3SlGJy0t9/wXExC7kX2CIkqi/aU9uOjjcjwUZ0FDXtYXBggGhaIuZCPXw9q6QxPL69u6x7JbMMiZStduwKSINPqRph0cCxnEcP2CpLb7V95ckx8fWT/RDM1sJDp4tDJ16gpp68Lq5rmzg76T+R6E4XM1jI/IW/wuD3oLTfgMni7IWcCW1evDbAAxuPnNk2D4=", "scnt": "AAAA-jA4OTNERjIwQTUzMzk0REY2MjY0RUIwRDBGOERCOEUwOTE4NEUxQjkzNTBBQTk5ODNEQzVFQjQ1N0Q3NDY2RTBDNEY2RjQwMkRBNkFFMjNBNEYyMzc1ODY1RTRENDBDRUFCQzdDOUJDQ0YzMEI3ODAxQTE1NjZEMTU3ODMwQkU3RTNGRTFCQkMzOUQxQzcwMjdBMzkwOTc4MkMwM0E2QzRFMzk3MTlGM0Q4Qzc3QTUyOEFCMDRFRDI4RjI2QTJGQ0VDN0RFNDA2OTM0OTZBNDc4NkVCNjBEQjhCQjhGNzY2NTJCMDhDODg5NTkzNkRGNXwyAAABnUf7l4HF2wAVRtQDtvBaNPXuwIi8ILi0sURwYvD0XvQMHlohK0Rw09XdgbnOAAxuPln3yR2rVrA5k3Uvi2iVG6pdntPmgkWZlqF2lHyUJvt31x4_LQ", "account_country": "USA", "session_token": "ObPQlP1J6GPl0T7quRtCSjFkD/Nauze5JFS3oEhcZUQEY1eIaJhrnMzfuE3duncAF/X7fmhVhY1+UEMzv86A0I8YZJ4gHGVcDNGKtyA6ylYYY6BbLnqPzrYs87OHx5elnRjFMtliBzFa9tWxz29sVAhRKjgb4cyi/1LQY51l7MvODVO1/0xTG9sMP/9LCpKq4NSjj4LUs7rrifaAVB94Vz3v9ln5UlnpMMcrQt7TnMWS0UoHKX8rVk1FR9S/mAWdTq+L/4xC1mIspdC/QoqAzdlpFuDWhe/OtJ4xP6DUJBA3nmQM4R7bOfw5DT2iU4QUwnVUbro16wJxkITvwLSJCwpzdLnpU7jTD6ih7EcrnY1MRUA84LE0vjtOo69LqHsVa5XwW2rLiEiyLWfwmbWX7nxRhIJob9aGX/fRsYgcU95/Nyi0PrAUG6vIPoia39YzM6FX6h+AqPx6QPzSsYzGDGeja5a3Itpr3/PIGniWAsaBk5aJ47mEbMH4Kr18+qZDSGTLExB0+rs/t+EY1FBic1lkHIyzMWbzS18+jYjBY2JKgebqwVacY7c67KRn20yqz2t7JdOcX5wPPUtZw36yFsHpFrfc84O3pLQY55ZoLiH08AMJbcV+ARBVXDh8O7gLjlP75HWmgMNtQ0OeW/ihUC/sPokqoRL3Tm6tKUTEXyIIL9o4fLX1MlrUwOxPjkwODPaqw1tVdRVAopbSQXWvugy6os7nS7bOu4qwHBLsrAVYSY6ky8klP8APjBRo6b9txr/B3II6bZGxsxYKC78HrCZURixRrwEx5mU+Uo8CFOIYeh+cml2VGPiWJcL/j+Zgo+fETjuVszi+k0LA0IksVG3d7LoKCGGfEHkPIB7s3kfvw8zVP7XhNhdIZ7/iHFGjc3qW9fAI5NOJSgpIbOsjNPP2x+LCXfmjfVP5nz43KVFVSe19CgpRjlwzxKXQqs1FHQAMbj5417aW", "trust_eligible": "true", "grant_code": "c29b17d808a184d83999908e110e46cf4.0.srzwz.t2kk4FfezvyceWVfQKXZOg", "trust_token": "HSARMTKNSRVXWFlajR2ecD1662phQjqU9vXxnL49ZjypuVYYXHDpA3wTiX6Mf2J4WDlIhZj52z81aDOuz+VC80bVhV41TSNN4ggoPjW8WnsQrjniTQYkgJycPQNnzhkK4hfe2AMrr/bhrJJm8sHHc+Oh1HUckN6T7T4c1bmf2Qg9tRwsdRDNyMMyFH/Ml/cQlWKj39/YHlY=SRVX"}
|
||||
@@ -1,5 +1,5 @@
|
||||
[project]
|
||||
name = "back-end"
|
||||
name = "pymd3_vue_location_sim"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
@@ -7,6 +7,7 @@ dependencies = [
|
||||
"daemonize>=2.5.0",
|
||||
"fastapi==0.135.1",
|
||||
"geopy==2.4.1",
|
||||
"numpy==2.4.3",
|
||||
"pydantic==2.12.5",
|
||||
"pyicloud>=2.4.1",
|
||||
"pymobiledevice3==9.0.0",
|
||||
@@ -18,3 +19,8 @@ dependencies = [
|
||||
"typing==3.10.0.0",
|
||||
"uvicorn==0.41.0",
|
||||
]
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.11.2,<0.12"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
@@ -57,6 +57,7 @@ class FindMyMonitor:
|
||||
self._logged_candidates = False
|
||||
self._no_location_streak = 0
|
||||
self._auth_error_streak = 0
|
||||
self._fetch_lock = asyncio.Lock()
|
||||
|
||||
async def _request_code_from_vue(self, prompt: str) -> str | None:
|
||||
if self.sio is None or self.get_client_sids is None:
|
||||
@@ -287,6 +288,11 @@ class FindMyMonitor:
|
||||
idx = min(self._auth_error_streak - 1, len(schedule) - 1)
|
||||
return max(base_interval, schedule[idx])
|
||||
|
||||
async def refresh_location(self):
|
||||
"""Fetch one location update while serializing iCloud API access."""
|
||||
async with self._fetch_lock:
|
||||
return await self.get_location()
|
||||
|
||||
async def run_monitor(self, interval=60):
|
||||
"""Runs the monitor loop."""
|
||||
if not await self.authenticate():
|
||||
@@ -298,7 +304,7 @@ class FindMyMonitor:
|
||||
while self.running:
|
||||
sleep_seconds = interval
|
||||
try:
|
||||
device_data = await self.get_location()
|
||||
device_data = await self.refresh_location()
|
||||
if device_data is not None:
|
||||
self._no_location_streak = 0
|
||||
self._auth_error_streak = 0
|
||||
|
||||
@@ -98,10 +98,11 @@ class LocationSimulationState:
|
||||
self.loc_id: Optional[str] = None
|
||||
self.longitude: Optional[float] = None
|
||||
self.next_move: Optional[float] = None
|
||||
self.queue: asyncio.Queue = asyncio.Queue()
|
||||
self.queue_data: Dict = {}
|
||||
self.queue_order: list[str] = []
|
||||
self.queue_state: str = "STOPPED"
|
||||
self.simulation_queue: asyncio.Queue = asyncio.Queue()
|
||||
self.simulation_queue_data: Dict = {}
|
||||
self.simulation_queue_order: list[str] = []
|
||||
self.simulation_queue_state: str = "STOPPED"
|
||||
self.simulation_noise: bool = False
|
||||
self.set_location_enabled: bool = True
|
||||
self.simulation_active: bool = False
|
||||
self.simulation_task: Optional[asyncio.Task] = None
|
||||
@@ -441,7 +442,7 @@ class TunneldRunnerSio:
|
||||
"""Start Simulation Queue Worker"""
|
||||
logger.info("Starting location simulation worker...")
|
||||
self.context.simulation_active = True
|
||||
self.context.queue_state = "RUNNING"
|
||||
self.context.simulation_queue_state = "RUNNING"
|
||||
try:
|
||||
if self.context.test_mode:
|
||||
logger.info("Simulation worker: test mode enabled")
|
||||
@@ -544,6 +545,77 @@ class TunneldRunnerSio:
|
||||
self.context.simulation_task = None
|
||||
await end_icloud_monitor()
|
||||
|
||||
async def add_location_to_simulation_queue(data):
|
||||
loc_id = str(uuid.uuid4())
|
||||
latitude = (
|
||||
data.get("latitude")
|
||||
if isinstance(data, dict)
|
||||
else getattr(data, "latitude", None)
|
||||
)
|
||||
longitude = (
|
||||
data.get("longitude")
|
||||
if isinstance(data, dict)
|
||||
else getattr(data, "longitude", None)
|
||||
)
|
||||
delay = (
|
||||
data.get("delay", 0)
|
||||
if isinstance(data, dict)
|
||||
else getattr(data, "delay", 0)
|
||||
)
|
||||
try:
|
||||
delay = parse_delay_seconds(delay)
|
||||
except ValueError as e:
|
||||
return {"status": "error","command": "add", "message": str(e) }
|
||||
if latitude is not None and longitude is not None:
|
||||
logger.info(
|
||||
"Adding location %s (%s, %s) with %s delay to the queue",
|
||||
loc_id,
|
||||
latitude,
|
||||
longitude,
|
||||
delay,
|
||||
)
|
||||
accrued_delay = 0
|
||||
if self.context.simulation_queue_data and len(self.context.simulation_queue_order) > 1:
|
||||
current_index = get_item_index(self.context.loc_id) if self.context.loc_id else 0
|
||||
remaining_items = self.context.simulation_queue_order[current_index + 1:]
|
||||
accrued_delay = sum(parse_delay_seconds(self.context.simulation_queue_data[loc_id]['delay']) for loc_id in remaining_items if loc_id in self.context.simulation_queue_data)
|
||||
accrued_delay = accrued_delay + parse_delay_seconds(self.context.next_move)
|
||||
|
||||
now_time = datetime.now(timezone.utc)
|
||||
new_time = (
|
||||
now_time
|
||||
+ timedelta(seconds=accrued_delay)
|
||||
+ timedelta(seconds=delay)
|
||||
)
|
||||
start_time = new_time.isoformat()
|
||||
coords = f"{latitude}, {longitude}"
|
||||
rev_geocode = self.context.reverse_geocode(coords)
|
||||
if rev_geocode:
|
||||
address = rev_geocode.address
|
||||
else:
|
||||
address = f"{latitude}, {longitude}"
|
||||
location_item = {
|
||||
"loc_id": loc_id,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"delay": delay,
|
||||
"start": start_time,
|
||||
"address": address,
|
||||
}
|
||||
resp = {
|
||||
"status": "OK",
|
||||
"command": "add",
|
||||
"message": f"Location {loc_id} added to the queue",
|
||||
"item": location_item,
|
||||
}
|
||||
await self.context.simulation_queue.put(loc_id)
|
||||
add_item(loc_id, location_item)
|
||||
logger.info("Location %s added to the queue", loc_id)
|
||||
else:
|
||||
logger.error("Invalid location data: %s", data)
|
||||
resp = {"status": "error", "message": "Invalid location data", "data": data}
|
||||
return resp
|
||||
|
||||
async def start_icloud_monitor():
|
||||
"""Start Apple iCloud Find My Monitor to retreive actual reported device location"""
|
||||
logger.info("iCloud monitor start requested")
|
||||
@@ -579,6 +651,44 @@ class TunneldRunnerSio:
|
||||
finally:
|
||||
self.context.fmf_queue.task_done()
|
||||
|
||||
async def refresh_icloud_location() -> dict:
|
||||
"""Fetch one iCloud location update, with or without monitor loop running."""
|
||||
logger.info("iCloud monitor refresh requested")
|
||||
try:
|
||||
updated_location = await self.context.icloud_monitor.refresh_location()
|
||||
except Exception as e:
|
||||
logger.exception("Failed to refresh iCloud location: %s", e)
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "Failed to refresh iCloud location",
|
||||
"error": type(e).__name__,
|
||||
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
|
||||
"icloud_monitor_running": is_icloud_monitor_running(),
|
||||
}
|
||||
|
||||
if updated_location is None:
|
||||
return {
|
||||
"status": "ok",
|
||||
"location_found": False,
|
||||
"location_updated": False,
|
||||
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
|
||||
"icloud_monitor_running": is_icloud_monitor_running(),
|
||||
}
|
||||
|
||||
location_updated = self.context.fmf_location != updated_location
|
||||
self.context.fmf_location = updated_location
|
||||
await self.context.sio.emit(
|
||||
"fmf_update", updated_location.model_dump(), namespace="/"
|
||||
)
|
||||
return {
|
||||
"status": "ok",
|
||||
"location_found": True,
|
||||
"location_updated": location_updated,
|
||||
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
|
||||
"icloud_monitor_running": is_icloud_monitor_running(),
|
||||
"fmf_location": updated_location,
|
||||
}
|
||||
|
||||
async def end_icloud_monitor():
|
||||
logger.info("iCloud monitor stop requested")
|
||||
self.context.icloud_monitor_enabled = False
|
||||
@@ -604,29 +714,42 @@ class TunneldRunnerSio:
|
||||
|
||||
async def pause_simulation_queue():
|
||||
"""Pauses asyncio.Queue playback"""
|
||||
self.context.queue_state = "PAUSED"
|
||||
self.context.simulation_queue_state = "PAUSED"
|
||||
|
||||
async def resume_simulation_queue():
|
||||
"""Resumes asyncio.Queue playback"""
|
||||
self.context.queue_state = "RUNNING"
|
||||
"""Resumes asyncio.simulation_queue playback"""
|
||||
self.context.simulation_queue_state = "RUNNING"
|
||||
update_queue_times()
|
||||
|
||||
def update_queue_times():
|
||||
current_index = get_item_index(self.context.loc_id)
|
||||
remaining_items = self.context.queue_order[current_index + 1:]
|
||||
remaining_items = self.context.simulation_queue_order[current_index + 1:]
|
||||
new_delay = self.context.next_move or 0
|
||||
now_time = datetime.now(timezone.utc)
|
||||
for item in remaining_items:
|
||||
item_delay = parse_delay_seconds(self.context.queue_data[item].get("delay")) or 0
|
||||
item_delay = parse_delay_seconds(self.context.simulation_queue_data[item].get("delay")) or 0
|
||||
new_delay += item_delay
|
||||
new_time = (now_time + timedelta(seconds=new_delay)).isoformat()
|
||||
self.context.queue_data[item].start = new_time
|
||||
self.context.simulation_queue_data[item].start = new_time
|
||||
update_queue_data()
|
||||
|
||||
def update_queue_data():
|
||||
data = {
|
||||
"simulation_queue": {
|
||||
"active": self.context.simulation_active,
|
||||
"data": self.context.simulation_queue_data,
|
||||
"order": self.context.simulation_queue_order,
|
||||
"state": self.context.simulation_queue_state,
|
||||
"worker_task": self.context.simulation_task.get_name() if self.context.simulation_task else None,
|
||||
}
|
||||
}
|
||||
self.context.sio.emit("queue_data_update", { "data": data }, namespace="/" )
|
||||
|
||||
|
||||
async def empty_simulation_queue():
|
||||
"""Empties all items from an asyncio.Queue."""
|
||||
logger.info("Clearing location simulation queue...")
|
||||
q = self.context.queue
|
||||
q = self.context.simulation_queue
|
||||
self.context.set_location_enabled = False
|
||||
while not q.empty():
|
||||
try:
|
||||
@@ -639,38 +762,38 @@ class TunneldRunnerSio:
|
||||
|
||||
|
||||
def add_item(item_id, payload):
|
||||
self.context.queue_data[item_id] = payload
|
||||
self.context.queue_order.append(item_id)
|
||||
self.context.simulation_queue_data[item_id] = payload
|
||||
self.context.simulation_queue_order.append(item_id)
|
||||
|
||||
def remove_item(item_id):
|
||||
if item_id in self.context.queue_order:
|
||||
# self.context.queue_order.remove(item_id)
|
||||
self.context.queue_data[item_id]["status"] = "deleted"
|
||||
if item_id in self.context.simulation_queue_order:
|
||||
# self.context.simulation_queue_order.remove(item_id)
|
||||
self.context.simulation_queue_data[item_id]["status"] = "deleted"
|
||||
|
||||
def clear_item(item_id):
|
||||
if item_id in self.context.queue_order:
|
||||
self.context.queue_order.remove(item_id)
|
||||
del self.context.queue_data[item_id]
|
||||
if item_id in self.context.simulation_queue_order:
|
||||
self.context.simulation_queue_order.remove(item_id)
|
||||
del self.context.simulation_queue_data[item_id]
|
||||
|
||||
def clear_items():
|
||||
self.context.queue_data = {}
|
||||
self.context.queue_order = []
|
||||
self.context.simulation_queue_data = {}
|
||||
self.context.simulation_queue_order = []
|
||||
|
||||
def get_item(item_id):
|
||||
return self.context.queue_data[item_id]
|
||||
return self.context.simulation_queue_data[item_id]
|
||||
|
||||
def update_item(item_id, **updates):
|
||||
if item_id in self.context.queue_data:
|
||||
self.context.queue_data[item_id].update(updates)
|
||||
if item_id in self.context.simulation_queue_data:
|
||||
self.context.simulation_queue_data[item_id].update(updates)
|
||||
|
||||
def get_item_index(item_id):
|
||||
return self.context.queue_order.index(item_id)
|
||||
return self.context.simulation_queue_order.index(item_id)
|
||||
|
||||
def get_item_id_by_index(index):
|
||||
return self.context.queue_order[index]
|
||||
return self.context.simulation_queue_order[index]
|
||||
|
||||
def get_items_in_order():
|
||||
return [self.context.queue_data[i] for i in self.context.queue_order if self.context.queue_data[i].get("status") != "deleted"]
|
||||
return [self.context.simulation_queue_data[i] for i in self.context.simulation_queue_order if self.context.simulation_queue_data[i].get("status") != "deleted"]
|
||||
|
||||
def parse_delay_seconds(raw_delay) -> int:
|
||||
if raw_delay is None:
|
||||
@@ -695,9 +818,9 @@ class TunneldRunnerSio:
|
||||
"""Ends asyncio.Queue playback and closes tunnel"""
|
||||
logger.info("End location simulation request")
|
||||
try:
|
||||
q = self.context.queue
|
||||
q = self.context.simulation_queue
|
||||
self.context.set_location_enabled = False
|
||||
self.context.queue_state = "SHUTDOWN"
|
||||
self.context.simulation_queue_state = "SHUTDOWN"
|
||||
|
||||
# Drain pending queue entries.
|
||||
while not q.empty():
|
||||
@@ -731,23 +854,26 @@ class TunneldRunnerSio:
|
||||
|
||||
self.context.simulation_active = False
|
||||
self.context.simulation_task = None
|
||||
self.context.simulation_state = "ENDED"
|
||||
self.context.set_location_enabled = True
|
||||
self.context.next_move = None
|
||||
self.context.loc_id = None
|
||||
self.context.latitude = None
|
||||
self.context.longitude = None
|
||||
self.context.queue_state = "STOPPED"
|
||||
self.context.simulation_queue_state = "STOPPED"
|
||||
# Recreate queue to discard sentinel wakeup items and unblock clean restarts.
|
||||
self.context.queue = asyncio.Queue()
|
||||
self.context.simulation_queue = asyncio.Queue()
|
||||
await end_icloud_monitor()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error ending simulation queue: {e}")
|
||||
return False
|
||||
|
||||
def toggle_test_mode() -> dict:
|
||||
self.context.test_mode = not self.context.test_mode
|
||||
return {"test_mode": self.context.test_mode }
|
||||
|
||||
def get_status() -> dict:
|
||||
current_item = self.context.queue_data.get(self.context.loc_id) if self.context.loc_id else None
|
||||
current_item = self.context.simulation_queue_data.get(self.context.loc_id) if self.context.loc_id else None
|
||||
current_start = (
|
||||
current_item.get("start")
|
||||
if isinstance(current_item, dict)
|
||||
@@ -774,9 +900,9 @@ class TunneldRunnerSio:
|
||||
"next_move": self.context.next_move,
|
||||
"simulation_queue": {
|
||||
"active": self.context.simulation_active,
|
||||
"data": self.context.queue_data,
|
||||
"order": self.context.queue_order,
|
||||
"state": self.context.queue_state,
|
||||
"data": self.context.simulation_queue_data,
|
||||
"order": self.context.simulation_queue_order,
|
||||
"state": self.context.simulation_queue_state,
|
||||
"worker_task": self.context.simulation_task.get_name() if self.context.simulation_task else None,
|
||||
},
|
||||
"set_location_enable": self.context.set_location_enabled,
|
||||
@@ -786,6 +912,27 @@ class TunneldRunnerSio:
|
||||
}
|
||||
return data
|
||||
|
||||
import numpy as np
|
||||
def add_gps_noise(lat, lon, std_dev_meters=5):
|
||||
"""
|
||||
Simulates GPS noise by adding Gaussian noise to coordinates.
|
||||
1 degree of latitude ~ 111,000 meters.
|
||||
1 degree of longitude ~ 111,000 * cos(latitude) meters.
|
||||
"""
|
||||
# Earth's radius, meters
|
||||
earth_radius = 6378137
|
||||
|
||||
# Convert meters to degrees
|
||||
lat_diff = (std_dev_meters / earth_radius) * (180 / np.pi)
|
||||
lon_diff = (std_dev_meters / (earth_radius * np.cos(np.radians(lat)))) * (180 / np.pi)
|
||||
|
||||
# Generate Gaussian noise
|
||||
noised_lat = lat + np.random.normal(0, lat_diff)
|
||||
noised_lon = lon + np.random.normal(0, lon_diff)
|
||||
|
||||
return noised_lat, noised_lon
|
||||
|
||||
|
||||
""" FastAPI HTTP Functions"""
|
||||
|
||||
def generate_http_response(
|
||||
@@ -923,7 +1070,7 @@ class TunneldRunnerSio:
|
||||
task=task, udid=udid
|
||||
)
|
||||
created_task = True
|
||||
except ConnectionFailedError, InvalidServiceError, MuxException:
|
||||
except (ConnectionFailedError, InvalidServiceError, MuxException):
|
||||
pass
|
||||
if connection_type in ("usb", None):
|
||||
for rsd in await get_rsds(udid=udid):
|
||||
@@ -1041,6 +1188,11 @@ class TunneldRunnerSio:
|
||||
}
|
||||
return generate_http_response(data)
|
||||
|
||||
@self._app.get("/icloud-monitor/refresh")
|
||||
async def app_refresh_icloud_monitor() -> fastapi.Response:
|
||||
data = await refresh_icloud_location()
|
||||
return generate_http_response(data)
|
||||
|
||||
|
||||
"""Simulation Functions"""
|
||||
""" start, add, clear, pause, resume, end, status """
|
||||
@@ -1055,77 +1207,7 @@ class TunneldRunnerSio:
|
||||
async def app_add_location(data: SimulationRequestData) -> fastapi.Response:
|
||||
"""Add a location to the simulation queue"""
|
||||
logger.info("Request to add new location to queue")
|
||||
|
||||
loc_id = str(uuid.uuid4())
|
||||
latitude = (
|
||||
data.get("latitude")
|
||||
if isinstance(data, dict)
|
||||
else getattr(data, "latitude", None)
|
||||
)
|
||||
longitude = (
|
||||
data.get("longitude")
|
||||
if isinstance(data, dict)
|
||||
else getattr(data, "longitude", None)
|
||||
)
|
||||
delay = (
|
||||
data.get("delay", 0)
|
||||
if isinstance(data, dict)
|
||||
else getattr(data, "delay", 0)
|
||||
)
|
||||
try:
|
||||
delay = parse_delay_seconds(delay)
|
||||
except ValueError as e:
|
||||
return generate_http_response(
|
||||
{"status": "error", "message": str(e)},
|
||||
status_code=400,
|
||||
)
|
||||
if latitude is not None and longitude is not None:
|
||||
logger.info(
|
||||
"Adding location %s (%s, %s) with %s delay to the queue",
|
||||
loc_id,
|
||||
latitude,
|
||||
longitude,
|
||||
delay,
|
||||
)
|
||||
accrued_delay = 0
|
||||
if self.context.queue_data:
|
||||
current_index = get_item_index(self.context.loc_id)
|
||||
remaining_items = self.context.queue_order[current_index + 1:]
|
||||
accrued_delay = sum(parse_delay_seconds(self.context.queue_data[loc_id]['delay']) for loc_id in remaining_items if loc_id in self.context.queue_data)
|
||||
accrued_delay = accrued_delay + parse_delay_seconds(self.context.next_move)
|
||||
|
||||
now_time = datetime.now(timezone.utc)
|
||||
new_time = (
|
||||
now_time
|
||||
+ timedelta(seconds=accrued_delay)
|
||||
+ timedelta(seconds=delay)
|
||||
)
|
||||
start_time = new_time.isoformat()
|
||||
coords = f"{latitude}, {longitude}"
|
||||
rev_geocode = self.context.reverse_geocode(coords)
|
||||
if rev_geocode:
|
||||
address = rev_geocode.address
|
||||
else:
|
||||
address = f"{latitude}, {longitude}"
|
||||
location_item = {
|
||||
"loc_id": loc_id,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"delay": delay,
|
||||
"start": start_time,
|
||||
"address": address,
|
||||
}
|
||||
resp = {
|
||||
"status": "added",
|
||||
"message": f"Location {loc_id} added to the queue",
|
||||
"item": location_item,
|
||||
}
|
||||
await self.context.queue.put(loc_id)
|
||||
add_item(loc_id, location_item)
|
||||
logger.info("Location %s added to the queue", loc_id)
|
||||
else:
|
||||
logger.error("Invalid location data: %s", data)
|
||||
resp = {"status": "error", "message": "Invalid location data", "data": data}
|
||||
resp = await add_location_to_simulation_queue(data)
|
||||
return generate_http_response(resp)
|
||||
|
||||
@self._app.get("/simulation/clear")
|
||||
@@ -1160,12 +1242,12 @@ class TunneldRunnerSio:
|
||||
async def app_simulation_test_mode() -> fastapi.Response:
|
||||
"""Enable test mode for the simulation queue"""
|
||||
before_toggle = self.context.test_mode
|
||||
self.context.test_mode = not self.context.test_mode
|
||||
data = {"status": "Ok", "message": f"Test mode toggled from {before_toggle} to {self.context.test_mode}"}
|
||||
data = toggle_test_mode()
|
||||
data['status'] = "OK"
|
||||
data['message'] = f"Test mode toggled from {before_toggle} to {self.context.test_mode}"
|
||||
return generate_http_response(data)
|
||||
|
||||
|
||||
|
||||
"""Status Functions"""
|
||||
|
||||
@self._app.get("/status/rsd")
|
||||
@@ -1289,88 +1371,19 @@ class TunneldRunnerSio:
|
||||
)
|
||||
try:
|
||||
match command:
|
||||
case "test-mode":
|
||||
data = toggle_test_mode()
|
||||
return {
|
||||
"command": command,
|
||||
"status": "OK",
|
||||
"message": "test-mode toggled",
|
||||
"data": data
|
||||
}
|
||||
|
||||
case "add":
|
||||
""" Add a location to the simulation queue"""
|
||||
loc_id = str(uuid.uuid4())
|
||||
latitude = (
|
||||
data.get("latitude")
|
||||
if isinstance(data, dict)
|
||||
else getattr(data, "latitude", None)
|
||||
)
|
||||
longitude = (
|
||||
data.get("longitude")
|
||||
if isinstance(data, dict)
|
||||
else getattr(data, "longitude", None)
|
||||
)
|
||||
delay = (
|
||||
data.get("delay", 0)
|
||||
if isinstance(data, dict)
|
||||
else getattr(data, "delay", 0)
|
||||
)
|
||||
try:
|
||||
delay = parse_delay_seconds(delay)
|
||||
except ValueError as e:
|
||||
return {
|
||||
"command": command,
|
||||
"status": "error",
|
||||
"message": str(e),
|
||||
}
|
||||
if latitude is not None and longitude is not None:
|
||||
logger.info(
|
||||
"Adding location %s (%s, %s) with %s delay to the queue",
|
||||
loc_id,
|
||||
latitude,
|
||||
longitude,
|
||||
delay,
|
||||
)
|
||||
accrued_delay = 0
|
||||
if self.context.queue_data:
|
||||
current_index = get_item_index(self.context.loc_id)
|
||||
remaining_items = self.context.queue_order[current_index + 1:]
|
||||
accrued_delay = sum(parse_delay_seconds(self.context.queue_data[loc_id]['delay']) for loc_id in remaining_items if loc_id in self.context.queue_data)
|
||||
accrued_delay = accrued_delay + parse_delay_seconds(self.context.next_move)
|
||||
|
||||
now_time = datetime.now(timezone.utc)
|
||||
new_time = (
|
||||
now_time
|
||||
+ timedelta(seconds=accrued_delay)
|
||||
+ timedelta(seconds=delay)
|
||||
)
|
||||
start_time = new_time.isoformat()
|
||||
coords = f"{latitude}, {longitude}"
|
||||
rev_geocode = self.context.reverse_geocode(coords)
|
||||
if rev_geocode:
|
||||
address = rev_geocode.address
|
||||
else:
|
||||
address = f"{latitude}, {longitude}"
|
||||
location_item = {
|
||||
"loc_id": loc_id,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"delay": delay,
|
||||
"start": start_time,
|
||||
"address": address,
|
||||
}
|
||||
ack = {
|
||||
"command": command,
|
||||
"status": "added",
|
||||
"message": f"Location {loc_id} added to the queue",
|
||||
"item": location_item,
|
||||
}
|
||||
await self.context.queue.put(loc_id)
|
||||
add_item(loc_id, location_item)
|
||||
logger.info("Location %s added to the queue", loc_id)
|
||||
return ack
|
||||
else:
|
||||
logger.warning(
|
||||
"Invalid location data received from %s: %s", sid, data
|
||||
)
|
||||
return {
|
||||
"command": command,
|
||||
"status": "error",
|
||||
"message": "Invalid location data",
|
||||
"data": data,
|
||||
}
|
||||
resp = await add_location_to_simulation_queue(data)
|
||||
return resp
|
||||
case "clear":
|
||||
""" Clear the simulation queue"""
|
||||
await empty_simulation_queue()
|
||||
@@ -1448,6 +1461,10 @@ class TunneldRunnerSio:
|
||||
"icloud_monitor_enabled": self.context.icloud_monitor_enabled,
|
||||
"icloud_monitor_running": is_icloud_monitor_running(),
|
||||
}
|
||||
case "refresh":
|
||||
data = await refresh_icloud_location()
|
||||
data["command"] = command
|
||||
return data
|
||||
case _:
|
||||
return {
|
||||
"command": command,
|
||||
@@ -1583,111 +1600,175 @@ class LocationSimulationQueue(LocationSimulation):
|
||||
def __init__(self, dvt, context: LocationSimulationState):
|
||||
super().__init__(dvt)
|
||||
self.context = context
|
||||
self._noise_task: Optional[asyncio.Task] = None
|
||||
self._noise_loc_id: Optional[str] = None
|
||||
|
||||
@staticmethod
|
||||
def _add_gps_noise(lat: float, lon: float, std_dev_meters: float = 5.0) -> tuple[float, float]:
|
||||
"""Apply Gaussian jitter in meters and convert to lat/lon deltas."""
|
||||
earth_radius = 6378137.0
|
||||
lat_sigma_deg = (std_dev_meters / earth_radius) * (180.0 / math.pi)
|
||||
cos_lat = math.cos(math.radians(lat))
|
||||
if abs(cos_lat) < 1e-6:
|
||||
cos_lat = 1e-6
|
||||
lon_sigma_deg = (std_dev_meters / (earth_radius * cos_lat)) * (180.0 / math.pi)
|
||||
noised_lat = lat + random.gauss(0.0, lat_sigma_deg)
|
||||
noised_lon = lon + random.gauss(0.0, lon_sigma_deg)
|
||||
return noised_lat, noised_lon
|
||||
|
||||
async def _stop_noise_task(self) -> None:
|
||||
if self._noise_task is not None:
|
||||
self._noise_task.cancel()
|
||||
with suppress(asyncio.CancelledError):
|
||||
await self._noise_task
|
||||
self._noise_task = None
|
||||
self._noise_loc_id = None
|
||||
|
||||
async def _noise_loop(self, loc_id: str, base_latitude: float, base_longitude: float) -> None:
|
||||
while True:
|
||||
await asyncio.sleep(random.randint(45, 180))
|
||||
if not self.context.simulation_active:
|
||||
break
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
break
|
||||
if not self.context.set_location_enabled:
|
||||
continue
|
||||
if not self.context.simulation_noise:
|
||||
continue
|
||||
if self.context.loc_id != loc_id:
|
||||
break
|
||||
noised_latitude, noised_longitude = self._add_gps_noise(
|
||||
base_latitude, base_longitude
|
||||
)
|
||||
await self.set(noised_latitude, noised_longitude)
|
||||
logger.info(
|
||||
"Applied simulation noise to active location loc_id=%s sent=%s,%s base=%s,%s",
|
||||
loc_id,
|
||||
noised_latitude,
|
||||
noised_longitude,
|
||||
base_latitude,
|
||||
base_longitude,
|
||||
)
|
||||
|
||||
def _start_noise_task(self, loc_id: str, base_latitude: float, base_longitude: float) -> None:
|
||||
self._noise_loc_id = loc_id
|
||||
self._noise_task = asyncio.create_task(
|
||||
self._noise_loop(loc_id, base_latitude, base_longitude),
|
||||
name=f"simulation-noise-{loc_id}",
|
||||
)
|
||||
|
||||
async def play_queue(
|
||||
self, disable_sleep: bool = False, timing_randomness_range: int = 0
|
||||
) -> None:
|
||||
while True:
|
||||
if self.context.queue_state == "PAUSED":
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
break
|
||||
loc_id = await self.context.queue.get()
|
||||
if loc_id is None:
|
||||
self.context.queue.task_done()
|
||||
break
|
||||
location_item = self.context.queue_data.get(loc_id)
|
||||
if location_item is None:
|
||||
logger.warning(
|
||||
"Simulation queue item missing for loc_id=%s; skipping stale entry",
|
||||
loc_id,
|
||||
try:
|
||||
while True:
|
||||
if self.context.simulation_queue_state == "PAUSED":
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
break
|
||||
loc_id = await self.context.simulation_queue.get()
|
||||
if loc_id is None:
|
||||
self.context.simulation_queue.task_done()
|
||||
break
|
||||
location_item = self.context.simulation_queue_data.get(loc_id)
|
||||
if location_item is None:
|
||||
logger.warning(
|
||||
"Simulation queue item missing for loc_id=%s; skipping stale entry",
|
||||
loc_id,
|
||||
)
|
||||
self.context.simulation_queue.task_done()
|
||||
continue
|
||||
new_latitude = location_item.get("latitude")
|
||||
new_longitude = location_item.get("longitude")
|
||||
new_delay = location_item.get("delay")
|
||||
new_delay = 0 if new_delay is None else new_delay
|
||||
new_start = location_item.get("start")
|
||||
|
||||
current_location_item = self.context.simulation_queue_data.get(self.context.loc_id)
|
||||
current_latitude = (
|
||||
current_location_item.get("latitude")
|
||||
if isinstance(current_location_item, dict)
|
||||
else self.context.latitude
|
||||
)
|
||||
current_longitude = (
|
||||
current_location_item.get("longitude")
|
||||
if isinstance(current_location_item, dict)
|
||||
else self.context.longitude
|
||||
)
|
||||
current_start = (
|
||||
current_location_item.get("start")
|
||||
if isinstance(current_location_item, dict)
|
||||
else None
|
||||
)
|
||||
self.context.queue.task_done()
|
||||
continue
|
||||
new_latitude = location_item.get("latitude")
|
||||
new_longitude = location_item.get("longitude")
|
||||
new_delay = location_item.get("delay")
|
||||
new_delay = 0 if new_delay is None else new_delay
|
||||
new_start = location_item.get("start")
|
||||
|
||||
current_location_item = self.context.queue_data.get(self.context.loc_id)
|
||||
current_latitude = (
|
||||
current_location_item.get("latitude")
|
||||
if isinstance(current_location_item, dict)
|
||||
else self.context.latitude
|
||||
)
|
||||
current_longitude = (
|
||||
current_location_item.get("longitude")
|
||||
if isinstance(current_location_item, dict)
|
||||
else self.context.longitude
|
||||
)
|
||||
current_start = (
|
||||
current_location_item.get("start")
|
||||
if isinstance(current_location_item, dict)
|
||||
else None
|
||||
)
|
||||
|
||||
if self.context.set_location_enabled:
|
||||
if new_delay > 0 and not disable_sleep:
|
||||
countdown_delay = int(round(float(new_delay)))
|
||||
if timing_randomness_range > 0:
|
||||
new_delay = new_delay + random.uniform(
|
||||
-timing_randomness_range, timing_randomness_range
|
||||
)
|
||||
if self.context.set_location_enabled:
|
||||
if new_delay > 0 and not disable_sleep:
|
||||
countdown_delay = int(round(float(new_delay)))
|
||||
for i in range(max(0, countdown_delay), 0, -1):
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
break
|
||||
while self.context.queue_state == "PAUSED":
|
||||
await asyncio.sleep(0.1)
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
if timing_randomness_range > 0:
|
||||
new_delay = new_delay + random.uniform(
|
||||
-timing_randomness_range, timing_randomness_range
|
||||
)
|
||||
countdown_delay = int(round(float(new_delay)))
|
||||
for i in range(max(0, countdown_delay), 0, -1):
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
break
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
while self.context.simulation_queue_state == "PAUSED":
|
||||
await asyncio.sleep(0.1)
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
break
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
break
|
||||
self.context.next_move = i
|
||||
await self.context.sio.emit(
|
||||
"simulation_status",
|
||||
{
|
||||
"status": self.context.simulation_active,
|
||||
"loc_id": self.context.loc_id,
|
||||
"latitude": current_latitude,
|
||||
"longitude": current_longitude,
|
||||
"start": current_start,
|
||||
"next_move": i,
|
||||
},
|
||||
namespace="/",
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
self.context.simulation_queue.task_done()
|
||||
break
|
||||
self.context.next_move = i
|
||||
await self.context.sio.emit(
|
||||
"simulation_status",
|
||||
{
|
||||
"status": self.context.simulation_active,
|
||||
"loc_id": self.context.loc_id,
|
||||
"latitude": current_latitude,
|
||||
"longitude": current_longitude,
|
||||
"start": current_start,
|
||||
"next_move": i,
|
||||
},
|
||||
namespace="/",
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
self.context.queue.task_done()
|
||||
break
|
||||
self.context.queue_data[loc_id]["start"] = datetime.now(timezone.utc).isoformat()
|
||||
if self.context.loc_id is not None:
|
||||
self.context.queue_data[self.context.loc_id]["end"] = datetime.now(timezone.utc).isoformat()
|
||||
await self.set(new_latitude, new_longitude)
|
||||
self.context.loc_id = loc_id
|
||||
self.context.latitude = new_latitude
|
||||
self.context.longitude = new_longitude
|
||||
await self.context.sio.emit(
|
||||
"simulation_status",
|
||||
{
|
||||
"status": self.context.simulation_active,
|
||||
"loc_id": self.context.loc_id,
|
||||
"latitude": self.context.latitude,
|
||||
"longitude": self.context.longitude,
|
||||
"start": new_start,
|
||||
"next_move": None,
|
||||
},
|
||||
namespace="/",
|
||||
)
|
||||
logger.info(
|
||||
"Set simulated location to %s, %s after %ss delay",
|
||||
new_latitude,
|
||||
new_longitude,
|
||||
new_delay,
|
||||
)
|
||||
self.context.queue.task_done()
|
||||
self.context.simulation_queue_data[loc_id]["start"] = datetime.now(timezone.utc).isoformat()
|
||||
if self.context.loc_id is not None:
|
||||
self.context.simulation_queue_data[self.context.loc_id]["end"] = datetime.now(timezone.utc).isoformat()
|
||||
update_queue_data()
|
||||
|
||||
await self._stop_noise_task()
|
||||
await self.set(new_latitude, new_longitude)
|
||||
self.context.loc_id = loc_id
|
||||
self.context.latitude = new_latitude
|
||||
self.context.longitude = new_longitude
|
||||
if self.context.simulation_noise:
|
||||
self._start_noise_task(loc_id, new_latitude, new_longitude)
|
||||
await self.context.sio.emit(
|
||||
"simulation_status",
|
||||
{
|
||||
"status": self.context.simulation_active,
|
||||
"loc_id": self.context.loc_id,
|
||||
"latitude": self.context.latitude,
|
||||
"longitude": self.context.longitude,
|
||||
"start": new_start,
|
||||
"next_move": None,
|
||||
},
|
||||
namespace="/",
|
||||
)
|
||||
logger.info(
|
||||
"Set simulated location to %s, %s after %ss delay",
|
||||
new_latitude,
|
||||
new_longitude,
|
||||
new_delay,
|
||||
)
|
||||
self.context.simulation_queue.task_done()
|
||||
finally:
|
||||
await self._stop_noise_task()
|
||||
|
||||
|
||||
class LocationSimulationTestQueue(LocationSimulationBase):
|
||||
@@ -1706,9 +1787,9 @@ class LocationSimulationTestQueue(LocationSimulationBase):
|
||||
logger.info("Simulated location set to %s, %s", latitude, longitude)
|
||||
|
||||
async def clear(self) -> None:
|
||||
q = self.context.queue
|
||||
q = self.context.simulation_queue
|
||||
self.context.set_location_enabled = False
|
||||
self.context.queue_state = "SHUTDOWN"
|
||||
self.context.simulation_queue_state = "SHUTDOWN"
|
||||
while not q.empty():
|
||||
try:
|
||||
item = q.get_nowait()
|
||||
@@ -1726,36 +1807,36 @@ class LocationSimulationTestQueue(LocationSimulationBase):
|
||||
with suppress(asyncio.CancelledError):
|
||||
await self.context.simulation_task
|
||||
self.context.simulation_active = False
|
||||
self.context.queue_state = "SHUTDOWN"
|
||||
self.context.simulation_queue_state = "SHUTDOWN"
|
||||
|
||||
async def play_queue(
|
||||
self, disable_sleep: bool = False, timing_randomness_range: int = 0
|
||||
) -> None:
|
||||
while True:
|
||||
if self.context.queue_state == "PAUSED":
|
||||
if self.context.simulation_queue_state == "PAUSED":
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
break
|
||||
loc_id = await self.context.queue.get()
|
||||
loc_id = await self.context.simulation_queue.get()
|
||||
if loc_id is None:
|
||||
self.context.queue.task_done()
|
||||
self.context.simulation_queue.task_done()
|
||||
break
|
||||
location_item = self.context.queue_data.get(loc_id)
|
||||
location_item = self.context.simulation_queue_data.get(loc_id)
|
||||
if location_item is None:
|
||||
logger.warning(
|
||||
"Test simulation queue item missing for loc_id=%s; skipping stale entry",
|
||||
loc_id,
|
||||
)
|
||||
self.context.queue.task_done()
|
||||
self.context.simulation_queue.task_done()
|
||||
continue
|
||||
new_latitude = location_item.get("latitude")
|
||||
new_longitude = location_item.get("longitude")
|
||||
new_delay = location_item.get("delay")
|
||||
new_delay = 0 if delay is None else delay
|
||||
new_delay = 0 if new_delay is None else new_delay
|
||||
new_start = location_item.get("start")
|
||||
|
||||
current_location_item = self.context.queue_data.get(self.context.loc_id)
|
||||
current_location_item = self.context.simulation_queue_data.get(self.context.loc_id)
|
||||
current_latitude = (
|
||||
current_location_item.get("latitude")
|
||||
if isinstance(current_location_item, dict)
|
||||
@@ -1781,13 +1862,13 @@ class LocationSimulationTestQueue(LocationSimulationBase):
|
||||
)
|
||||
countdown_delay = int(round(float(new_delay)))
|
||||
for i in range(max(0, countdown_delay), 0, -1):
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
break
|
||||
while self.context.queue_state == "PAUSED":
|
||||
while self.context.simulation_queue_state == "PAUSED":
|
||||
await asyncio.sleep(0.1)
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
break
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
break
|
||||
self.context.next_move = i
|
||||
await self.context.sio.emit(
|
||||
@@ -1803,14 +1884,15 @@ class LocationSimulationTestQueue(LocationSimulationBase):
|
||||
namespace="/",
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
self.context.queue.task_done()
|
||||
if self.context.simulation_queue_state == "SHUTDOWN":
|
||||
self.context.simulation_queue.task_done()
|
||||
break
|
||||
self.context.queue_data[loc_id]["start"] = datetime.now(timezone.utc).isoformat()
|
||||
self.context.simulation_queue_data[loc_id]["start"] = datetime.now(timezone.utc).isoformat()
|
||||
if self.context.loc_id is not None:
|
||||
self.context.queue_data[self.context.loc_id]["end"] = datetime.now(timezone.utc).isoformat()
|
||||
self.context.simulation_queue_data[self.context.loc_id]["end"] = datetime.now(timezone.utc).isoformat()
|
||||
await self.set(new_latitude, new_longitude)
|
||||
self.context.loc_id = loc_id
|
||||
self.context.loc_id = loc_id
|
||||
self.context.latitude = new_latitude
|
||||
self.context.longitude = new_longitude
|
||||
await self.context.sio.emit(
|
||||
@@ -1822,14 +1904,13 @@ class LocationSimulationTestQueue(LocationSimulationBase):
|
||||
"longitude": self.context.longitude,
|
||||
"start": new_start,
|
||||
"next_move": None,
|
||||
"next_move": None,
|
||||
},
|
||||
namespace="/",
|
||||
)
|
||||
logger.info(
|
||||
"Set simulated location to %s, %s after %ss delay",
|
||||
latitude,
|
||||
longitude,
|
||||
delay,
|
||||
new_latitude,
|
||||
new_longitude,
|
||||
new_delay,
|
||||
)
|
||||
self.context.queue.task_done()
|
||||
self.context.simulation_queue.task_done()
|
||||
|
||||
109
uv.lock
generated
109
uv.lock
generated
@@ -88,45 +88,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "back-end"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "daemonize" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "geopy" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyicloud" },
|
||||
{ name = "pymobiledevice3" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-socketio" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "sqlalchemy-orm" },
|
||||
{ name = "typer" },
|
||||
{ name = "typing" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.3.1" },
|
||||
{ name = "daemonize", specifier = ">=2.5.0" },
|
||||
{ name = "fastapi", specifier = "==0.135.1" },
|
||||
{ name = "geopy", specifier = "==2.4.1" },
|
||||
{ name = "pydantic", specifier = "==2.12.5" },
|
||||
{ name = "pyicloud", specifier = ">=2.4.1" },
|
||||
{ name = "pymobiledevice3", specifier = "==9.0.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
||||
{ name = "python-socketio", specifier = "==5.16.1" },
|
||||
{ name = "sqlalchemy", specifier = ">=2.0.48" },
|
||||
{ name = "sqlalchemy-orm", specifier = ">=1.2.10" },
|
||||
{ name = "typer", specifier = ">=0.24.1" },
|
||||
{ name = "typing", specifier = "==3.10.0.0" },
|
||||
{ name = "uvicorn", specifier = "==0.41.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bidict"
|
||||
version = "0.23.1"
|
||||
@@ -802,6 +763,35 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.4.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opack2"
|
||||
version = "0.0.1"
|
||||
@@ -1125,6 +1115,47 @@ version = "0.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/dc/9ae75ede398b7adf538f2d1dca0f96c645c4e96789f8039340a0ed6a8a8f/pylzss-0.3.4.tar.gz", hash = "sha256:16818631742488e53a34fda0d402d80edb2b812e11877801e21a9e5ce9b9db1c", size = 25144, upload-time = "2023-11-24T00:26:37.787Z" }
|
||||
|
||||
[[package]]
|
||||
name = "pymd3-vue-location-sim"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "daemonize" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "geopy" },
|
||||
{ name = "numpy" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyicloud" },
|
||||
{ name = "pymobiledevice3" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-socketio" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "sqlalchemy-orm" },
|
||||
{ name = "typer" },
|
||||
{ name = "typing" },
|
||||
{ name = "uvicorn" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "click", specifier = ">=8.3.1" },
|
||||
{ name = "daemonize", specifier = ">=2.5.0" },
|
||||
{ name = "fastapi", specifier = "==0.135.1" },
|
||||
{ name = "geopy", specifier = "==2.4.1" },
|
||||
{ name = "numpy", specifier = "==2.4.3" },
|
||||
{ name = "pydantic", specifier = "==2.12.5" },
|
||||
{ name = "pyicloud", specifier = ">=2.4.1" },
|
||||
{ name = "pymobiledevice3", specifier = "==9.0.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
||||
{ name = "python-socketio", specifier = "==5.16.1" },
|
||||
{ name = "sqlalchemy", specifier = ">=2.0.48" },
|
||||
{ name = "sqlalchemy-orm", specifier = ">=1.2.10" },
|
||||
{ name = "typer", specifier = ">=0.24.1" },
|
||||
{ name = "typing", specifier = "==3.10.0.0" },
|
||||
{ name = "uvicorn", specifier = "==0.41.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pymobiledevice3"
|
||||
version = "9.0.0"
|
||||
|
||||
Reference in New Issue
Block a user