You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Comrad/komrade/frontend/main.py

473 lines
14 KiB
Python

## CONFIG
# change this to your external ip address for your server
#(needs to be external to allow tor routing)
from config import *
# monkeypatching the things that asyncio needs
import subprocess
subprocess.PIPE = -1 # noqa
subprocess.STDOUT = -2 # noqa
subprocess.DEVNULL = -3 # noqa
import asyncio
import os
os.environ['KIVY_EVENTLOOP'] = 'asyncio'
# loop = asyncio.get_event_loop()
# loop.set_debug(True)
# imports
4 years ago
from kivy.uix.screenmanager import Screen,ScreenManager
from kivymd.app import MDApp
4 years ago
from kivymd.uix.button import MDFillRoundFlatButton, MDIconButton
from kivymd.uix.toolbar import MDToolbar
from kivymd.uix.screen import MDScreen
4 years ago
from kivymd.uix.dialog import MDDialog
4 years ago
from kivy.lang import Builder
4 years ago
from kivy.uix.boxlayout import BoxLayout
from kivymd.theming import ThemeManager
from kivy.properties import ObjectProperty,ListProperty
import time,os
4 years ago
from collections import OrderedDict
from functools import partial
from kivy.uix.screenmanager import NoTransition
from kivymd.uix.label import MDLabel
from kivy.uix.widget import Widget
4 years ago
from kivymd.uix.list import OneLineListItem
from kivymd.uix.card import MDCard, MDSeparator
from kivymd.uix.boxlayout import MDBoxLayout
4 years ago
from kivy.uix.gridlayout import GridLayout
from kivy.metrics import dp
from kivy.properties import NumericProperty
4 years ago
from kivymd.uix.list import * #MDList, ILeftBody, IRightBody, ThreeLineAvatarListItem, TwoLineAvatarListItem, BaseListItem, ImageLeftWidget
from kivy.uix.image import Image, AsyncImage
4 years ago
import requests,json
from kivy.storage.jsonstore import JsonStore
from kivy.core.window import Window
4 years ago
from kivy.core.text import LabelBase
4 years ago
import shutil,sys
from kivy.uix.image import Image
import sys
sys.path.append("..") # Adds higher directory to python modules path.
4 years ago
from p2p.api import *
from p2p.persona import *
from kivy.event import EventDispatcher
import threading,asyncio,sys
# raise Exception(str(Window.size))
Window.size = WINDOW_SIZE
# Window.fullscreen = True #'auto'
4 years ago
# with open('log.txt','w') as of:
# of.write('### LOG ###\n')
def rgb(r,g,b,a=1):
return (r/255,g/255,b/255,a)
class MyLayout(MDBoxLayout):
4 years ago
scr_mngr = ObjectProperty(None)
post_id = ObjectProperty()
4 years ago
4 years ago
@property
def app(self):
if not hasattr(self,'_app'):
from kivy.app import App
self._app = App.get_running_app()
return self._app
def rgb(self,r,g,b,a=1):
return rgb(r,g,b,a=a)
4 years ago
def change_screen(self, screen, *args):
self.scr_mngr.current = screen
4 years ago
def change_screen_from_uri(self,uri,*args):
4 years ago
self.app.uri=uri
4 years ago
screen_name = route(uri)
self.app.screen = screen_name
4 years ago
self.app.log(f'routing to {screen_name}')
self.scr_mngr.current = screen_name
def view_post(self,post_id):
self.post_id=post_id
self.change_screen('view')
4 years ago
4 years ago
class ProgressPopup(MDDialog): pass
class MessagePopup(MDDialog): pass
class MyBoxLayout(MDBoxLayout): pass
class MyLabel(MDLabel): pass
4 years ago
class MyToolbar(MDToolbar):
action_icon_color = ListProperty()
def update_action_bar(self, action_bar, action_bar_items):
action_bar.clear_widgets()
new_width = 0
for item in action_bar_items:
new_width += dp(48)
action_bar.add_widget(
MDIconButton(
icon=item[0],
on_release=item[1],
opposite_colors=True,
text_color=(self.specific_text_color if not self.action_icon_color else self.action_icon_color),
theme_text_color="Custom",
)
)
action_bar.width = new_width
def update_action_bar_text_colors(self, instance, value):
for child in self.ids["left_actions"].children:
if not self.action_icon_color:
child.text_color = self.specific_text_color
else:
child.text_color = self.action_icon_color
for child in self.ids["right_actions"].children:
if not self.action_icon_color:
child.text_color = self.specific_text_color
else:
child.text_color = self.action_icon_color
4 years ago
4 years ago
4 years ago
4 years ago
def get_tor_proxy_session():
session = requests.session()
# Tor uses the 9050 port as the default socks port
session.proxies = {'http': 'socks5://127.0.0.1:9150',
'https': 'socks5://127.0.0.1:9150'}
4 years ago
return session
def get_async_tor_proxy_session():
from requests_futures.sessions import FuturesSession
session = FuturesSession()
# Tor uses the 9050 port as the default socks port
session.proxies = {'http': 'socks5://127.0.0.1:9150',
'https': 'socks5://127.0.0.1:9150'}
return session
4 years ago
def get_tor_python_session():
from torpy.http.requests import TorRequests
with TorRequests() as tor_requests:
with tor_requests.get_session() as s:
return s
def draw_background(widget, img_fn='assets/bg.png'):
from kivy.core.image import Image as CoreImage
from kivy.graphics import Color, Rectangle
widget.canvas.before.clear()
with widget.canvas.before:
Color(.4, .4, .4, 1)
texture = CoreImage(img_fn).texture
texture.wrap = 'repeat'
nx = float(widget.width) / texture.width
ny = float(widget.height) / texture.height
Rectangle(pos=widget.pos, size=widget.size, texture=texture,
tex_coords=(0, 0, nx, 0, nx, ny, 0, ny))
#### LOOPER
4 years ago
def route(uri):
4 years ago
if not '/' in uri: return None
prefix=uri.split('/')[1] #,channel,rest = uri.split('/',3)
4 years ago
mapd = {
'inbox':'feed',
'outbox':'feed',
'login':'login',
}
return mapd.get(prefix,None)
4 years ago
# DEFAULT_SCREEN = route(DEFAULT_URI)
4 years ago
class MainApp(MDApp):
title = 'Komrade'
logged_in=False
4 years ago
# store = JsonStore('../p2p/.keys.json')
# store_global = JsonStore('../p2p/.keys.global.json')
store = JsonStore('app.json')
login_expiry = 60 * 60 * 24 * 7 # once a week
texture = ObjectProperty()
4 years ago
uri = '/inbox/world'
# def connect(self):
# # connect to kad?
# self.node = p2p.connect()
def rgb(self,*_): return rgb(*_)
4 years ago
def change_screen(self, screen, *args):
self.screen=screen
self.root.change_screen(screen,*args)
4 years ago
@property
def channel(self):
if not hasattr(self,'uri'): return None
if self.uri.count('/')<2: return None
return self.uri.split('/')[2]
4 years ago
def change_screen_from_uri(self,uri,*args):
self.uri=uri
4 years ago
self.log('CHANGING SCREEN',uri,'??')
4 years ago
return self.root.change_screen_from_uri(uri,*args)
@property
def logger(self):
if not hasattr(self,'_logger'):
import logging
handler = logging.StreamHandler()
4 years ago
formatter = logging.Formatter('[%(asctime)s]\n%(message)s\n')
handler.setFormatter(formatter)
4 years ago
self._logger = logger = logging.getLogger(__file__)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
return self._logger
4 years ago
def log(self,*args,**msgs):
4 years ago
line = ' '.join(str(x) for x in args)
4 years ago
self.logger.debug(line)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.event_loop_worker = None
self.loop=asyncio.get_event_loop()
# load json storage
self.username=''
self.load_store()
4 years ago
self.uri=DEFAULT_URI
# connect to API
4 years ago
self.api = Api(log=self.log)
4 years ago
@property
async def node(self):
return await self.api.node
4 years ago
4 years ago
def get_username(self):
if hasattr(self,'username'): return self.username
self.load_store()
if hasattr(self,'username'): return self.username
return ''
4 years ago
def build(self):
4 years ago
# bind
4 years ago
global app,root
4 years ago
app = self
# load root
4 years ago
self.root = root = Builder.load_file('root.kv')
draw_background(self.root)
4 years ago
# edit logo
toolbar=root.ids.toolbar
toolbar.md_bg_color = root.rgb(*COLOR_TOOLBAR)
toolbar.action_icon_color=root.rgb(*COLOR_ICON)
logo=toolbar.ids.label_title
4 years ago
logo.font_name='assets/Strengthen.ttf'
logo.font_size='58dp'
logo.pos_hint={'center_y':0.43}
logo.text_color=root.rgb(*COLOR_LOGO)
4 years ago
self.root.change_screen_from_uri(self.uri if self.uri else DEFAULT_URI)
4 years ago
return self.root
4 years ago
4 years ago
def load_store(self):
if not self.store.exists('user'): return
userd=self.store.get('user')
4 years ago
if not userd: return
4 years ago
4 years ago
self.username = userd.get('username','')
async def upload(self,filename,file_id=None):
self.log('uploading filename:',filename)
rdata=await self.api.upload(filename,file_id=file_id)
self.log('upload result:',rdata)
if rdata is not None:
rdata['success']='File uploaded'
return rdata
return {'error':'Upload failed'}
async def download(self,file_id,output_fn=None):
self.log('downloading:',file_id)
file_dat = await self.api.download(file_id)
self.log('file_dat =',file_dat)
if not output_fn:
file_id=file_dat['id']
file_ext=file_dat['ext']
output_fn=os.path.join('cache',file_id[:3]+'/'+file_id[3:]+'.'+file_ext)
output_dir=os.path.dirname(output_fn)
if not os.path.exists(output_dir): os.makedirs(output_dir)
with open(output_fn,'wb') as of:
for data_piece in file_dat['parts_data']:
if data_piece is not None:
of.write(data_piece)
4 years ago
async def post(self, content='', file_id=None, file_ext=None, anonymous=False,channel='world'):
#timestamp=time.time()
jsond={}
#jsond['timestamp']=
if content: jsond['content']=str(content)
if file_id: jsond['file_id']=str(file_id)
if file_ext: jsond['file_ext']=str(file_ext)
4 years ago
if channel and channel[0]=='@': channel=channel[1:]
4 years ago
self.log(f'''app.post(
4 years ago
content={content},
file_id={file_id},
file_ext={file_ext},
anonymous={anonymous},
4 years ago
channel={channel},
4 years ago
[username={self.username}]'''
)
if not anonymous and self.username:
jsond['author']=self.username
4 years ago
#jsond['channel']=channel
self.log('posting:',jsond)
4 years ago
res=await self.api.post(jsond,channel = channel)
if 'success' in res:
self.root.change_screen('feed')
return {'post_id':res['post_id']}
4 years ago
4 years ago
@property
def keys(self):
return self.api.keys
4 years ago
async def get_post(self,post_id):
return await self.api.get_post(post_id)
async def get_posts(self,uri=b'/inbox/world'):
return await self.persona.read_inbox(uri)
# if uri.count('/')<2: raise Exception('not a URI: '+uri)
# if 'login' in uri:
# raise Exception('!!!! '+uri)
# self.log(f'app.get_posts(uri={uri} -> ...')
# data = await self.api.get_posts(uri)
# self.log(f'app.get_posts() got back from api.get_posts() a {type(data)}')
# newdata=[]
# for d in data:
# # self.log('data d:',d)
# if not 'val' in d: continue
# newdict = dict(d['val'].items())
# newdict['timestamp']=float(d['time'])
# newdict['to_name']=d['channel']
# newdata.append(newdict)
4 years ago
# # return index
# return newdata
4 years ago
async def get_channel_posts(self,channel,prefix='inbox'):
# am I allowed to?
if not channel in self.keys:
self.log('!! tsk tsk dont be nosy')
return
4 years ago
return await self.get_posts(uri='/'+os.path.join(prefix,channel))
4 years ago
async def get_channel_inbox(self,channel):
return await self.get_channel_posts(channel=channel,prefix='inbox')
async def get_channel_outbox(self,channel):
return await self.get_channel_posts(channel=channel,prefix='outbox')
async def get_my_posts(self):
return await self.persona.read_outbox()
4 years ago
### SYNCHRONOUS?
def app_func(self):
'''This will run both methods asynchronously and then block until they
are finished
'''
# self.other_task = asyncio.ensure_future(self.waste_time_freely())
self.other_task = asyncio.ensure_future(self.api.connect_forever())
async def run_wrapper():
# we don't actually need to set asyncio as the lib because it is
# the default, but it doesn't hurt to be explicit
await self.async_run() #async_lib='asyncio')
print('App done')
self.other_task.cancel()
return asyncio.gather(run_wrapper(), self.other_task)
4 years ago
4 years ago
def open_dialog(self,msg):
if not hasattr(self,'dialog') or not self.dialog:
self.dialog = ProgressPopup()
# raise Exception(self.dialog, msg)
self.dialog.text=msg
self.dialog.open()
#stop
def open_msg_dialog(self,msg):
from screens.post.post import MessagePopup,ProgressPopup
if not hasattr(self,'msg_dialog') or not self.msg_dialog:
self.msg_dialog = MessagePopup()
self.msg_dialog.ids.msg_label.text=msg
self.msg_dialog.open()
def close_dialog(self):
if hasattr(self,'dialog'):
self.dialog.dismiss()
def close_msg_dialog(self):
if hasattr(self,'msg_dialog'):
self.msg_dialog.dismiss()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(MainApp().app_func())
loop.close()
# def main():
# # start_logger()
# App = MainApp()
# App.run()
# if __name__ == '__main__':
# # loop = asyncio.get_event_loop()
# # asyncio.set_event_loop(loop)
# # loop.run_until_complete(main())
# # loop.close()
# main()