prim-ctrl 0.6.0__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {prim_ctrl-0.6.0 → prim_ctrl-0.6.1}/PKG-INFO +1 -1
- {prim_ctrl-0.6.0 → prim_ctrl-0.6.1}/prim_ctrl/__main__.py +75 -50
- {prim_ctrl-0.6.0 → prim_ctrl-0.6.1}/pyproject.toml +1 -1
- {prim_ctrl-0.6.0 → prim_ctrl-0.6.1}/LICENSE +0 -0
- {prim_ctrl-0.6.0 → prim_ctrl-0.6.1}/README.md +0 -0
- {prim_ctrl-0.6.0 → prim_ctrl-0.6.1}/prim_ctrl/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: prim-ctrl
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Primitive Ctrl - Remote control of your phone's Primitive FTPd Android SFTP server and optionally Tailscale VPN.
|
|
5
5
|
Home-page: https://github.com/lmagyar/prim-ctrl
|
|
6
6
|
License: Apache-2.0
|
|
@@ -62,6 +62,15 @@ class Logger(logging.Logger):
|
|
|
62
62
|
if self.level == logging.NOTSET:
|
|
63
63
|
self.setLevel(logging.WARNING if silent else logging.INFO)
|
|
64
64
|
|
|
65
|
+
def exception_or_error(self, e: Exception, args):
|
|
66
|
+
if not args or args.debug:
|
|
67
|
+
logger.exception(e)
|
|
68
|
+
else:
|
|
69
|
+
if hasattr(e, '__notes__'):
|
|
70
|
+
logger.error("%s: %s", LazyStr(repr, e), LazyStr(", ".join, e.__notes__))
|
|
71
|
+
else:
|
|
72
|
+
logger.error(LazyStr(repr, e))
|
|
73
|
+
|
|
65
74
|
def error(self, msg, *args, **kwargs):
|
|
66
75
|
self.exitcode = 1
|
|
67
76
|
super().error(msg, *args, **kwargs)
|
|
@@ -171,8 +180,11 @@ class Pingable:
|
|
|
171
180
|
logger.debug("Waiting for %s to be %s (timeout is %ds)", LazyStr(self.get_class_name), LazyStr(Pingable.get_state_name, available), int(timeout))
|
|
172
181
|
async with asyncio.timeout(timeout):
|
|
173
182
|
while await self.ping(available) != available:
|
|
174
|
-
|
|
175
|
-
|
|
183
|
+
await self._sleep_while_wait(available)
|
|
184
|
+
|
|
185
|
+
async def _sleep_while_wait(self, available: bool):
|
|
186
|
+
if not available:
|
|
187
|
+
await asyncio.sleep(1)
|
|
176
188
|
|
|
177
189
|
class Manager:
|
|
178
190
|
@abstractmethod
|
|
@@ -653,6 +665,9 @@ class LocalTailscale(Manageable):
|
|
|
653
665
|
logger.debug("Pinging %s", LazyStr(self.get_class_name))
|
|
654
666
|
return await async_tailscale_is_online()
|
|
655
667
|
|
|
668
|
+
async def _sleep_while_wait(self, available: bool):
|
|
669
|
+
await asyncio.sleep(0.250)
|
|
670
|
+
|
|
656
671
|
########
|
|
657
672
|
|
|
658
673
|
class WideHelpFormatter(argparse.RawTextHelpFormatter):
|
|
@@ -715,18 +730,30 @@ class Control:
|
|
|
715
730
|
raise ValueError("The --restore-state option can be enabled only for the stop intent")
|
|
716
731
|
|
|
717
732
|
async def execute(self, args, local: Local, phone: Phone):
|
|
718
|
-
async def _stop(restore_state: dict | None):
|
|
733
|
+
async def _stop(restore_state: dict | None, stop_only_started: bool = False):
|
|
719
734
|
if local.vpn and phone.vpn and phone.remote_sftp and await local.vpn.test() and await phone.vpn.test():
|
|
720
|
-
if (restore_state is None or not restore_state.get(Control.PHONE_SFTP,
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
735
|
+
if (restore_state is None or not restore_state.get(Control.PHONE_SFTP, stop_only_started)) and await phone.remote_sftp.test():
|
|
736
|
+
try:
|
|
737
|
+
await phone.remote_sftp.stop(10, 30)
|
|
738
|
+
except Exception as e:
|
|
739
|
+
logger.exception_or_error(e, args)
|
|
740
|
+
if restore_state is None or not restore_state.get(Control.PHONE_VPN, stop_only_started):
|
|
741
|
+
try:
|
|
742
|
+
await phone.vpn.stop(10, 60)
|
|
743
|
+
except Exception as e:
|
|
744
|
+
logger.exception_or_error(e, args)
|
|
724
745
|
else:
|
|
725
|
-
if (restore_state is None or not restore_state.get(Control.PHONE_SFTP,
|
|
726
|
-
|
|
746
|
+
if (restore_state is None or not restore_state.get(Control.PHONE_SFTP, stop_only_started)):
|
|
747
|
+
try:
|
|
748
|
+
await phone.zeroconf_sftp.stop(10, 30)
|
|
749
|
+
except Exception as e:
|
|
750
|
+
logger.exception_or_error(e, args)
|
|
727
751
|
if local.vpn:
|
|
728
|
-
if restore_state is None or not restore_state.get(Control.LOCAL_VPN,
|
|
729
|
-
|
|
752
|
+
if restore_state is None or not restore_state.get(Control.LOCAL_VPN, stop_only_started):
|
|
753
|
+
try:
|
|
754
|
+
await local.vpn.stop(10, 30)
|
|
755
|
+
except Exception as e:
|
|
756
|
+
logger.exception_or_error(e, args)
|
|
730
757
|
match args.intent:
|
|
731
758
|
case 'test':
|
|
732
759
|
if local.vpn and phone.vpn and phone.remote_sftp and phone.state:
|
|
@@ -739,29 +766,31 @@ class Control:
|
|
|
739
766
|
case 'start':
|
|
740
767
|
if local.vpn and phone.vpn and phone.remote_sftp:
|
|
741
768
|
state = dict()
|
|
742
|
-
zeroconf_accessible = False
|
|
743
|
-
remote_accessible = False
|
|
744
|
-
|
|
745
|
-
# gather local state info
|
|
746
|
-
local_vpn_state = await local.vpn.test()
|
|
747
|
-
state[Control.LOCAL_VPN] = local_vpn_state
|
|
748
|
-
# start changing local state - we need a local vpn to be able to access the state of the remote vpn and optionally the phone
|
|
749
|
-
await local.vpn.start(10, 30)
|
|
750
|
-
|
|
751
|
-
# gather remote state info
|
|
752
|
-
if phone.state:
|
|
753
|
-
phone_state, phone_vpn_state = await gather_with_taskgroup(phone.state.get(10, 30), phone.vpn.test())
|
|
754
|
-
state[Control.PHONE_WIFI] = phone_state[PhoneState.WIFI]
|
|
755
|
-
state[Control.PHONE_VPN] = phone_vpn_state
|
|
756
|
-
state[Control.PHONE_SFTP] = phone_state[PhoneState.PFTPD]
|
|
757
|
-
if not state[Control.PHONE_WIFI] and not args.accept_cellular:
|
|
758
|
-
raise RuntimeError(f"Phone is not on Wi-Fi network")
|
|
759
|
-
else:
|
|
760
|
-
state[Control.PHONE_VPN] = await phone.vpn.test()
|
|
761
|
-
if state[Control.PHONE_VPN]:
|
|
762
|
-
state[Control.PHONE_SFTP] = remote_accessible = await phone.remote_sftp.test()
|
|
763
|
-
# start changing remote state
|
|
764
769
|
try:
|
|
770
|
+
# gather local state info
|
|
771
|
+
local_vpn_state = await local.vpn.test()
|
|
772
|
+
state[Control.LOCAL_VPN] = local_vpn_state
|
|
773
|
+
|
|
774
|
+
# start changing local state - we need a local vpn to be able to access the state of the remote vpn and optionally the phone
|
|
775
|
+
await local.vpn.start(10, 30)
|
|
776
|
+
|
|
777
|
+
zeroconf_accessible = False
|
|
778
|
+
remote_accessible = False
|
|
779
|
+
|
|
780
|
+
# gather phone state info
|
|
781
|
+
if phone.state:
|
|
782
|
+
phone_state, phone_vpn_state = await gather_with_taskgroup(phone.state.get(10, 30), phone.vpn.test())
|
|
783
|
+
state[Control.PHONE_WIFI] = phone_state[PhoneState.WIFI]
|
|
784
|
+
state[Control.PHONE_VPN] = phone_vpn_state
|
|
785
|
+
state[Control.PHONE_SFTP] = phone_state[PhoneState.PFTPD]
|
|
786
|
+
if not state[Control.PHONE_WIFI] and not args.accept_cellular:
|
|
787
|
+
raise RuntimeError(f"Phone is not on Wi-Fi network")
|
|
788
|
+
else:
|
|
789
|
+
state[Control.PHONE_VPN] = await phone.vpn.test()
|
|
790
|
+
if state[Control.PHONE_VPN]:
|
|
791
|
+
state[Control.PHONE_SFTP] = remote_accessible = await phone.remote_sftp.test()
|
|
792
|
+
|
|
793
|
+
# start changing phone state
|
|
765
794
|
if phone.state:
|
|
766
795
|
if not state[Control.PHONE_SFTP]:
|
|
767
796
|
if not state[Control.PHONE_VPN]:
|
|
@@ -799,20 +828,22 @@ class Control:
|
|
|
799
828
|
zeroconf_accessible = await phone.zeroconf_sftp.test()
|
|
800
829
|
if not zeroconf_accessible and not remote_accessible:
|
|
801
830
|
raise RuntimeError(f"Even when {phone.vpn.get_class_name()} and {phone.remote_sftp.get_class_name()} is started, {phone.remote_sftp.get_class_name()} is still not accessible")
|
|
831
|
+
|
|
832
|
+
# print out result on stdout
|
|
833
|
+
if not args.backup_state:
|
|
834
|
+
state = dict()
|
|
835
|
+
state[Control.CONNECTED] = Control.ZEROCONF if zeroconf_accessible else Control.REMOTE
|
|
836
|
+
print(StateSerializer.dumps(state))
|
|
802
837
|
except:
|
|
803
|
-
await _stop(state)
|
|
838
|
+
await _stop(state, stop_only_started = True)
|
|
804
839
|
raise
|
|
805
|
-
if not args.backup_state:
|
|
806
|
-
state = dict()
|
|
807
|
-
state[Control.CONNECTED] = Control.ZEROCONF if zeroconf_accessible else Control.REMOTE
|
|
808
|
-
print(StateSerializer.dumps(state))
|
|
809
840
|
else:
|
|
810
|
-
|
|
811
|
-
|
|
841
|
+
try:
|
|
842
|
+
if not await phone.zeroconf_sftp.test():
|
|
812
843
|
await phone.zeroconf_sftp.start(10, 30)
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
844
|
+
except:
|
|
845
|
+
await _stop(None)
|
|
846
|
+
raise
|
|
816
847
|
case 'stop':
|
|
817
848
|
await _stop(StateSerializer.loads(args.restore_state) if args.restore_state else None)
|
|
818
849
|
|
|
@@ -912,13 +943,7 @@ async def main():
|
|
|
912
943
|
await args.ctor().run(args)
|
|
913
944
|
|
|
914
945
|
except Exception as e:
|
|
915
|
-
|
|
916
|
-
logger.exception(e)
|
|
917
|
-
else:
|
|
918
|
-
if hasattr(e, '__notes__'):
|
|
919
|
-
logger.error("%s: %s", LazyStr(repr, e), LazyStr(", ".join, e.__notes__))
|
|
920
|
-
else:
|
|
921
|
-
logger.error(LazyStr(repr, e))
|
|
946
|
+
logger.exception_or_error(e, args)
|
|
922
947
|
|
|
923
948
|
return logger.exitcode
|
|
924
949
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "prim-ctrl"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.1"
|
|
4
4
|
description = "Primitive Ctrl - Remote control of your phone's Primitive FTPd Android SFTP server and optionally Tailscale VPN."
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
authors = ["Laszlo Magyar <lmagyar1973@gmail.com>"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|