コード例 #1
0
ファイル: bookstab.py プロジェクト: Dark-on/pms-labs
class BookInfoScreen(MDScreen):
    """Contains layout with detailed information about the choosen
    book.

    Also containstoolbar with buttons for getting back to the books list
    and deleting the choosen book from the list.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        scroll_view = ScrollView()
        self.layout = MDBoxLayout(orientation="vertical", size_hint=(1, 1.2))
        scroll_view.add_widget(self.layout)
        self.add_widget(scroll_view)

    def load_screen(self, book):
        """Loads all elements for the detailed info.

        Created as separate method because on every book we need to reload the
        information.
        """
        self.book = book

        toolbar = MDToolbar(type="top")
        toolbar.left_action_items = [["arrow-left", self.go_back]]
        toolbar.right_action_items = [["delete", self.delete_item]]

        content = BookInfoContent(book)

        self.layout.add_widget(toolbar)
        self.layout.add_widget(content)

    def go_back(self, touch):
        self.layout.clear_widgets()
        self.manager.transition.direction = "right"
        self.manager.switch_to(BooksTab.screens["books_list"])

    def delete_item(self, touch):
        BooksTab.screens["books_list"].books.remove(self.book)
        books_list = BooksTab.screens["books_list"].books
        BooksTab.screens["books_list"].load_books_list(books_list)
        self.go_back(touch)
コード例 #2
0
class Main(MDApp):
    def __init__(self):
        super(Main, self).__init__()
        self.theme_cls.primary_palette = "Amber"

        self.title = "Four in a row"
        self.root = Builder.load_file("style.kv")
        self.current_widget = MDBoxLayout()

        self.start_widget = StartWidget(self.start_game, self.change_theme)
        self.theme_widget = ThemeWidget(self, self.return_back)
        self.current_widget.add_widget(self.start_widget)

    def change_theme(self):
        self.current_widget.clear_widgets()
        self.current_widget.add_widget(self.theme_widget)

    def start_game(self):
        self.current_widget.clear_widgets()
        self.current_widget.add_widget(GameWidget(self.return_back))

    def return_back(self):
        self.current_widget.clear_widgets()
        self.current_widget.add_widget(self.start_widget)

    def build(self):
        return self.current_widget
コード例 #3
0
class BookAdderScreen(MDScreen):
    """Contains layout with functionality for adding new books to the list.

    Also contains toolbar with buttons for getting back to the books list
    and saving new book.
    """
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.layout = MDBoxLayout(orientation="vertical")
        self.load_content()

        self.add_widget(self.layout)

    def load_content(self):
        self.layout.clear_widgets()

        toolbar = MDToolbar(type="top")
        toolbar.left_action_items = [["arrow-left", self.go_back]]
        toolbar.right_action_items = [["plus", self.add_book]]

        title_label = MDLabel(
            text="Title: ",
            halign="left",
            valign="top",
        )
        subtitle_label = MDLabel(
            text="Subtitle: ",
            halign="left",
            valign="top",
        )
        price_label = MDLabel(
            text="Price: ",
            halign="left",
            valign="top",
        )

        self.title_input = MDTextField()
        self.subtitle_input = MDTextField()
        self.price_input = MDTextField()

        self.layout.add_widget(toolbar)
        self.layout.add_widget(title_label)
        self.layout.add_widget(self.title_input)
        self.layout.add_widget(subtitle_label)
        self.layout.add_widget(self.subtitle_input)
        self.layout.add_widget(price_label)
        self.layout.add_widget(self.price_input)

    def go_back(self, touch):
        self.layout.clear_widgets()
        self.manager.transition.direction = "right"
        self.manager.switch_to(BooksTab.screens["books_list"])

    def add_book(self, touch):
        book = Book(title=self.title_input.text,
                    subtitle=self.subtitle_input.text,
                    price=self.price_input.text)
        BooksTab.screens["books_list"].books.append(book)
        books_list = BooksTab.screens["books_list"].books
        BooksTab.screens["books_list"].load_books_list(books_list)
        self.go_back(touch)
コード例 #4
0
class DiscoveryMixin():
    def makeDiscoveryPage(self):

        # Discovery Page

        screen = Screen(name='Discovery')
        self.discoveryScreen = screen

        layout = BoxLayout(orientation='vertical', spacing=10)
        screen.add_widget(layout)

        label = Label(
            size_hint=(1, None),
            halign="center",
            text=
            'Browsing your local network.\nWarning: anyone on your network\ncan advertise a site with any title they want.'
        )

        layout.add_widget(self.makeBackButton())

        layout.add_widget(label)

        self.discoveryScroll = ScrollView(size_hint=(1, 1))

        self.discoveryListbox = BoxLayout(orientation='vertical',
                                          size_hint=(1, None))
        self.discoveryListbox.bind(
            minimum_height=self.discoveryListbox.setter('height'))

        self.discoveryScroll.add_widget(self.discoveryListbox)
        layout.add_widget(self.discoveryScroll)

        return screen

    def goToDiscovery(self, *a):
        "Go to the local network discovery page"
        self.discoveryListbox.clear_widgets()

        try:
            hardline.discoveryPeer.search('', n=1)
            time.sleep(2)
            for i in hardline.getAllDiscoveries():
                info = i

                self.discoveryListbox.add_widget(
                    MDToolbar(title=str(info.get('title', 'no title'))))
                l = StackLayout(adaptive_size=True,
                                spacing=8,
                                size_hint=(1, None))

                #Need to capture that info in the closures
                def scope(info):
                    btn = Button(text="Open in Browser")

                    def f(*a):
                        self.openInBrowser("http://" + info['hash'] +
                                           ".localhost:7009")

                    btn.bind(on_press=f)

                    l.add_widget(btn)

                    btn = Button(text="Copy URL")

                    def f(*a):
                        try:
                            from kivy.core.clipboard import Clipboard
                            Clipboard.copy("http://" + info['hash'] +
                                           ".localhost:7009")
                        except:
                            logging.exception("Could not copy to clipboard")

                    btn.bind(on_press=f)

                    self.localServicesListBox.add_widget(
                        MDToolbar(title=str(info.get('title', 'no title'))))

                    l.add_widget(btn)

                scope(info)

                self.discoveryListbox.add_widget(l)
                lb = self.saneLabel("Hosted By: " + info.get("from_ip", ""),
                                    self.discoveryListbox)
                self.discoveryListbox.add_widget(lb)
                lb = self.saneLabel("ID: " + info['hash'],
                                    self.discoveryListbox)
                self.discoveryListbox.add_widget(lb)

        except Exception:
            logging.info(traceback.format_exc())

        self.screenManager.current = "Discovery"
コード例 #5
0
class ToolsAndSettingsMixin():
    def goToSettings(self, *a):
        self.screenManager.current = "Settings"

    def goToGlobalSettings(self, *a):
        if os.path.exists(hardline.globalSettingsPath):
            with open(hardline.globalSettingsPath) as f:
                globalConfig = toml.load(f)
        else:
            globalConfig = {}

        self.localSettingsBox.clear_widgets()

        self.localSettingsBox.add_widget(
            Label(size_hint=(1, 6), halign="center", text='OpenDHT Proxies'))
        self.localSettingsBox.add_widget(
            Label(size_hint=(1, None),
                  text='Proxies are tried in order from 1-3'))

        self.localSettingsBox.add_widget(
            self.settingButton(globalConfig, "DHTProxy", 'server1'))
        self.localSettingsBox.add_widget(
            self.settingButton(globalConfig, "DHTProxy", 'server2'))
        self.localSettingsBox.add_widget(
            self.settingButton(globalConfig, "DHTProxy", 'server3'))

        self.localSettingsBox.add_widget(
            Label(size_hint=(1, 6), halign="center", text='Stream Server'))
        self.localSettingsBox.add_widget(
            Label(
                size_hint=(1, None),
                text=
                'To allow others to sync to this node as a DrayerDB Stream server, set a server title to expose a service'
            ))

        self.localSettingsBox.add_widget(
            self.settingButton(globalConfig, "DrayerDB", 'serverName'))

        btn1 = Button(text='Save')

        def save(*a):
            with open(hardline.globalSettingsPath, 'w') as f:
                toml.dump(globalConfig, f)
            if platform == 'android':
                self.stop_service()
                self.start_service()
            else:
                daemonconfig.loadDrayerServerConfig()

            self.screenManager.current = "Main"

        btn1.bind(on_press=save)
        self.localSettingsBox.add_widget(btn1)
        self.screenManager.current = "GlobalSettings"

    def makeGlobalSettingsPage(self):

        screen = Screen(name='GlobalSettings')
        layout = BoxLayout(orientation='vertical', spacing=10)
        screen.add_widget(layout)

        layout.add_widget(self.makeBackButton())

        self.localSettingsScroll = ScrollView(size_hint=(1, 1))
        self.localSettingsBox = BoxLayout(orientation='vertical',
                                          size_hint=(1, None),
                                          spacing=10)
        self.localSettingsBox.bind(
            minimum_height=self.localSettingsBox.setter('height'))

        self.localSettingsScroll.add_widget(self.localSettingsBox)

        layout.add_widget(self.localSettingsScroll)

        return screen

    def makeSettingsPage(self):
        page = Screen(name='Settings')

        layout = BoxLayout(orientation='vertical')
        page.add_widget(layout)
        label = MDToolbar(title="Settings and Tools")
        layout.add_widget(label)

        layout.add_widget(self.makeBackButton())

        log = Button(text='System Logs')

        btn1 = Button(text='Local Services')
        label1 = Label(halign="center",
                       text='Share a local webservice with the world')

        log.bind(on_release=self.gotoLogs)
        btn1.bind(on_press=self.goToLocalServices)
        layout.add_widget(log)

        layout.add_widget(btn1)
        layout.add_widget(label1)

        btn = Button(text='Global Settings')

        btn.bind(on_press=self.goToGlobalSettings)
        layout.add_widget(btn)

        # Start/Stop
        btn3 = Button(text='Stop')
        btn3.bind(on_press=self.stop_service)
        label3 = Label(
            size_hint=(1, None),
            halign="center",
            text=
            'Stop the background process.  It must be running to acess hardline sites.  Starting may take a few seconds.'
        )
        layout.add_widget(btn3)
        layout.add_widget(label3)

        btn4 = Button(text='Start or Restart.')
        btn4.bind(on_press=self.start_service)
        label4 = Label(
            size_hint=(1, None),
            halign="center",
            text='Restart the process. It will show in your notifications.')
        layout.add_widget(btn4)
        layout.add_widget(label4)

        layout.add_widget(Widget())

        return page

    def makeLogsPage(self):
        screen = Screen(name='Logs')
        self.servicesScreen = screen

        layout = BoxLayout(orientation='vertical', spacing=10)
        screen.add_widget(layout)

        layout.add_widget(MDToolbar(title="System Logs"))

        layout.add_widget(self.makeBackButton())

        self.logsListBoxScroll = ScrollView(size_hint=(1, 1))

        self.logsListBox = BoxLayout(orientation='vertical',
                                     size_hint=(1, None),
                                     spacing=10)
        self.logsListBox.bind(minimum_height=self.logsListBox.setter('height'))

        self.logsListBoxScroll.add_widget(self.logsListBox)

        layout.add_widget(self.logsListBoxScroll)

        return screen

    def gotoLogs(self, *a):
        self.logsListBox.clear_widgets()
        try:
            from kivy.logger import LoggerHistory
            for i in LoggerHistory.history:
                self.logsListBox.add_widget(
                    MDTextField(text=str(i.getMessage()),
                                multiline=True,
                                size_hint=(1, None),
                                mode="rectangle"))

            self.screenManager.current = "Logs"
        except Exception as e:
            logging.info(traceback.format_exc())
コード例 #6
0
class StreamsMixin():

    #Reuse the same panel for editStream, the main hub for accessing the stream,
    #and it's core settings
    def editStream(self, name):
        if not name in daemonconfig.userDatabases:
            self.goToStreams()
        db = daemonconfig.userDatabases[name]
        c = db.config
        try:
            c.add_section("Service")
        except:
            pass
        try:
            c.add_section("Info")
        except:
            pass

        self.streamEditPanel.clear_widgets()
        topbar = BoxLayout(size_hint=(1, None),
                           adaptive_height=True,
                           spacing=5)

        stack = StackLayout(size_hint=(1, None),
                            adaptive_height=True,
                            spacing=5)

        def upOne(*a):
            self.goToStreams()

        btn1 = Button(text='Up')

        btn1.bind(on_press=upOne)

        topbar.add_widget(btn1)

        topbar.add_widget(self.makeBackButton())

        self.streamEditPanel.add_widget(topbar)
        self.streamEditPanel.add_widget(MDToolbar(title=name))

        def goHere():
            self.editStream(name)

        self.backStack.append(goHere)
        self.backStack = self.backStack[-50:]

        btn2 = Button(text='Notebook View')

        def goPosts(*a):
            self.gotoStreamPosts(name)

        btn2.bind(on_press=goPosts)
        stack.add_widget(btn2)

        btn2 = Button(text='Feed View')

        def goPosts(*a):
            self.gotoStreamPosts(name, parent=None)

        btn2.bind(on_press=goPosts)
        stack.add_widget(btn2)

        btn2 = Button(text='Stream Settings')

        def goSettings(*a):
            self.editStreamSettings(name)

        btn2.bind(on_press=goSettings)
        stack.add_widget(btn2)

        if name.startswith('file:'):
            btn2 = Button(text='Close Stream')

            def close(*a):
                daemonconfig.closeUserDatabase(name)
                self.goToStreams()

            btn2.bind(on_press=close)
            stack.add_widget(btn2)

        importData = Button(text="Import Data File")

        def promptSet(*a):
            try:
                #Needed for android
                self.getPermission('files')
            except:
                logging.exception("cant ask permission")

            def f(selection):
                if selection:

                    def f2(x):
                        if x:
                            with daemonconfig.userDatabases[name]:
                                with open(selection) as f:
                                    daemonconfig.userDatabases[
                                        name].importFromToml(f.read())
                            daemonconfig.userDatabases[name].commit()

                    self.askQuestion("Really import?", "yes", cb=f2)
                self.openFM.close()

            from .kivymdfmfork import MDFileManager
            from . import directories
            self.openFM = MDFileManager(select_path=f)

            if os.path.exists("/storage/emulated/0/Documents") and os.access(
                    "/storage/emulated/0/Documents", os.W_OK):
                self.openFM.show("/storage/emulated/0/Documents")
            elif os.path.exists(
                    os.path.expanduser("~/Documents")) and os.access(
                        os.path.expanduser("~/Documents"), os.W_OK):
                self.openFM.show(os.path.expanduser("~/Documents"))
            else:
                self.openFM.show(directories.externalStorageDir
                                 or directories.settings_path)

        importData.bind(on_release=promptSet)
        stack.add_widget(importData)

        export = Button(text="Export All Posts")

        def promptSet(*a):
            from .kivymdfmfork import MDFileManager
            from .. import directories
            try:
                #Needed for android
                self.getPermission('files')
            except:
                logging.exception("cant ask permission")

            def f(selection):
                if selection:
                    if not selection.endswith(".toml"):
                        selection = selection + ".toml"

                    def g(a):
                        if a == 'yes':

                            r = daemonconfig.userDatabases[
                                name].getDocumentsByType('post', parent='')
                            data = daemonconfig.userDatabases[
                                name].exportRecordSetToTOML(
                                    [i['id'] for i in r])

                            logging.info("Exporting data to:" + selection)
                            with open(selection, 'w') as f:
                                f.write(data)
                        self.openFM.close()

                    if os.path.exists(selection):
                        self.askQuestion("Overwrite?", 'yes', g)
                    else:
                        g('yes')

            #Autocorrect had some fun with the kivymd devs
            self.openFM = MDFileManager(select_path=f,
                                        save_mode=(name + '.toml'))

            if os.path.exists("/storage/emulated/0/Documents") and os.access(
                    "/storage/emulated/0/Documents", os.W_OK):
                self.openFM.show("/storage/emulated/0/Documents")
            elif os.path.exists(
                    os.path.expanduser("~/Documents")) and os.access(
                        os.path.expanduser("~/Documents"), os.W_OK):
                self.openFM.show(os.path.expanduser("~/Documents"))
            else:
                self.openFM.show(directories.externalStorageDir
                                 or directories.settings_path)

        export.bind(on_release=promptSet)

        stack.add_widget(export)

        self.streamEditPanel.add_widget(stack)

        #Show recent changes no matter where they are in the tree.
        #TODO needs to be hideable for anti-spoiler purposes in fiction.
        self.streamEditPanel.add_widget(MDToolbar(title="Recent Changes:"))

        for i in daemonconfig.userDatabases[name].getDocumentsByType(
                'post', orderBy='arrival DESC', limit=5):
            x = self.makePostWidget(name, i, includeParent=True)
            self.streamEditPanel.add_widget(x)

        self.screenManager.current = "EditStream"

    def showSharingCode(self, name, c, wp=True):
        if daemonconfig.ddbservice[0]:
            try:
                localServer = daemonconfig.ddbservice[0].getSharableURL()
            except:
                logging.exception("wtf")
        else:
            localServer = ''

        d = {
            'sv': c['Sync'].get('server', '') or localServer,
            'vk': c['Sync'].get("syncKey", ''),
            'n': name[:24]
        }
        if wp:
            d['sk'] = c['Sync'].get('writePassword', '')
        else:
            d['sk'] = ''

        import json
        d = json.dumps(d, indent=0, separators=(',', ':'))
        if wp:
            self.showQR(d, "Stream Code(full access)")
        else:
            self.showQR(d, "Stream Code(readonly)")

    def editStreamSettings(self, name):
        db = daemonconfig.userDatabases[name]
        c = db.config

        self.streamEditPanel.clear_widgets()

        self.streamEditPanel.add_widget(
            Label(size_hint=(1, None), halign="center", text=name))
        self.streamEditPanel.add_widget(
            Label(size_hint=(1, None),
                  halign="center",
                  text="file:" + db.filename))

        self.streamEditPanel.add_widget(self.makeBackButton())

        def save(*a):
            logging.info("SAVE BUTTON WAS PRESSED")
            # On android this is the bg service's job
            db.saveConfig()

            if platform == 'android':
                self.stop_service()
                self.start_service()
            else:
                db.close()
                daemonconfig.loadUserDatabases(
                    None,
                    only=name,
                    callbackFunction=self.onDrayerRecordChange)

        def delete(*a):
            def f(n):
                if n and n == name:
                    daemonconfig.delDatabase(None, n)
                    if platform == 'android':
                        self.stop_service()
                        self.start_service()
                    self.goToStreams()

            self.askQuestion("Really delete?", name, f)

        self.streamEditPanel.add_widget(
            Label(size_hint=(1, None),
                  halign="center",
                  font_size="24sp",
                  text='Sync'))

        self.streamEditPanel.add_widget(
            keyBox := self.settingButton(c, "Sync", "syncKey"))

        self.streamEditPanel.add_widget(
            pBox := self.settingButton(c, "Sync", "writePassword"))

        self.streamEditPanel.add_widget(
            Label(
                size_hint=(1, None),
                halign="center",
                font_size="12sp",
                text=
                'Keys have a special format, you must use the generator to change them.'
            ))

        def promptNewKeys(*a, **k):
            def makeKeys(a):
                if a == 'yes':
                    import base64
                    vk, sk = libnacl.crypto_sign_keypair()
                    vk = base64.b64encode(vk).decode()
                    sk = base64.b64encode(sk).decode()
                    keyBox.text = vk
                    pBox.text = sk

            self.askQuestion("Overwrite with random keys?", 'yes', makeKeys)

        keyButton = Button(text='Generate New Keys')
        keyButton.bind(on_press=promptNewKeys)
        self.streamEditPanel.add_widget(keyButton)

        self.streamEditPanel.add_widget(
            serverBox := self.settingButton(c, "Sync", "server"))

        self.streamEditPanel.add_widget(
            Label(size_hint=(1, None),
                  halign="center",
                  text='Do not include the http:// '))

        self.streamEditPanel.add_widget(
            self.settingButton(c, "Sync", "serve", 'yes'))

        self.streamEditPanel.add_widget(
            Label(size_hint=(1, None),
                  halign="center",
                  text='Set serve=no to forbid clients to sync'))

        self.streamEditPanel.add_widget(
            Label(size_hint=(1, None),
                  halign="center",
                  font_size="24sp",
                  text='Application'))

        self.streamEditPanel.add_widget(
            self.settingButton(c, "Application", "notifications", 'no'))

        def f(*a):
            def g(a):
                try:
                    import json
                    a = json.loads(a)
                    serverBox.text = c['Sync'][
                        'server'] = a['sv'] or c['Sync']['server']
                    keyBox.text = c['Sync']['syncKey'] = a['vk']
                    pBox.text = c['Sync']['writePassword'] = a['sk']

                except:
                    pass

            self.askQuestion("Enter Sharing Code", cb=g, multiline=True)

        keyButton = Button(text='Load from Code')
        keyButton.bind(on_press=f)
        self.streamEditPanel.add_widget(keyButton)

        def f(*a):
            self.showSharingCode(name, c)

        keyButton = Button(text='Show Sharing Code')
        keyButton.bind(on_press=f)
        self.streamEditPanel.add_widget(keyButton)

        def f(*a):
            self.showSharingCode(name, c, wp=False)

        keyButton = Button(text='Readonly Sharing Code')
        keyButton.bind(on_press=f)
        self.streamEditPanel.add_widget(keyButton)

        btn1 = Button(text='Save Changes')

        btn1.bind(on_press=save)
        self.streamEditPanel.add_widget(btn1)

        btn2 = Button(text='Delete this stream')

        btn2.bind(on_press=delete)
        self.streamEditPanel.add_widget(btn2)

        def gotoOrphans(*a, **k):
            self.gotoStreamPosts(name, orphansMode=True)

        oButton = Button(text='Show Unreachable Garbage')
        oButton.bind(on_press=gotoOrphans)
        self.streamEditPanel.add_widget(oButton)

        noSpreadsheet = Button(text="Spreadsheet on/off")

        def promptSet(*a):
            from .kivymdfmfork import MDFileManager
            from .. import directories
            try:
                #Needed for android
                self.getPermission('files')
            except:
                logging.exception("cant ask permission")

            def f(selection):
                if selection == 'on':
                    daemonconfig.userDatabases[
                        name].enableSpreadsheetEval = True
                else:
                    daemonconfig.userDatabases[
                        name].enableSpreadsheetEval = False

            if hasattr(daemonconfig.userDatabases[name],
                       'enableSpreadsheetEval'):
                esf = daemonconfig.userDatabases[name].enableSpreadsheetEval
            else:
                esf = True

            self.askQuestion("Allow Spreadsheet Functions?",
                             'on' if esf else 'off', f)

        noSpreadsheet.bind(on_release=promptSet)
        self.streamEditPanel.add_widget(noSpreadsheet)
        self.streamEditPanel.add_widget(
            self.saneLabel(
                "Disabling only takes effect for this session. Use this feature if a stream is loading too slowly, to allow you to fix the offending expression.",
                self.streamEditPanel))

        self.screenManager.current = "EditStream"

    def makeStreamsPage(self):
        "Prettu much just an empty page filled in by the specific goto functions"

        screen = Screen(name='Streams')
        self.servicesScreen = screen

        self.streamsEditPanelScroll = ScrollView(size_hint=(1, 1))

        self.streamsEditPanel = BoxLayout(orientation='vertical',
                                          adaptive_height=True,
                                          spacing=5,
                                          size_hint=(1, None))
        self.streamsEditPanel.bind(
            minimum_height=self.streamsEditPanel.setter('height'))

        self.streamsEditPanelScroll.add_widget(self.streamsEditPanel)

        screen.add_widget(self.streamsEditPanelScroll)

        return screen

    def goToStreams(self, *a):

        "Go to a page wherein we can list user-modifiable services."
        self.streamsEditPanel.clear_widgets()

        layout = self.streamsEditPanel

        bar = BoxLayout(spacing=10, adaptive_height=True, size_hint=(1, None))

        stack = StackLayout(spacing=10,
                            adaptive_height=True,
                            size_hint=(1, None))
        layout.add_widget(bar)
        layout.add_widget(MDToolbar(title="My Streams"))

        layout.add_widget(stack)

        def upOne(*a):
            self.gotoMainScreen()

        btn1 = Button(text='Up')

        btn1.bind(on_press=upOne)

        bar.add_widget(btn1)
        bar.add_widget(self.makeBackButton())

        btn2 = Button(text='Create a Stream')

        btn2.bind(on_press=self.promptAddStream)
        stack.add_widget(btn2)

        def f(selection):
            if selection:
                dn = 'file:' + os.path.basename(selection)
                while dn in daemonconfig.userDatabases:
                    dn = dn + '2'
                try:
                    daemonconfig.loadUserDatabase(selection, dn)
                    self.editStream(dn)
                except:
                    logging.exception(dn)

            self.openFM.close()

        #This lets us view notebook files that aren't installed.
        def promptOpen(*a):

            try:
                #Needed for android
                self.getPermission('files')
            except:
                logging.exception("cant ask permission")

            from .kivymdfmfork import MDFileManager
            from . import directories
            self.openFM = MDFileManager(select_path=f)

            if os.path.exists("/storage/emulated/0/Documents") and os.access(
                    "/storage/emulated/0/Documents", os.W_OK):
                self.openFM.show("/storage/emulated/0/Documents")
            elif os.path.exists(
                    os.path.expanduser("~/Documents")) and os.access(
                        os.path.expanduser("~/Documents"), os.W_OK):
                self.openFM.show(os.path.expanduser("~/Documents"))
            else:
                self.openFM.show(directories.externalStorageDir
                                 or directories.settings_path)

        btn1 = Button(text='Open Book File')

        btn1.bind(on_press=promptOpen)

        stack.add_widget(btn1)

        def goHere():
            self.screenManager.current = "Streams"

        self.backStack.append(goHere)
        self.backStack = self.backStack[-50:]

        layout.add_widget(MDToolbar(title="Open Streams:"))

        try:
            s = daemonconfig.userDatabases
            time.sleep(0.5)
            for i in s:
                layout.add_widget(self.makeButtonForStream(i))
                try:
                    for j in daemonconfig.userDatabases[i].connectedServers:
                        if daemonconfig.userDatabases[i].connectedServers[
                                j] > (time.time() - (10 * 60)):
                            w = 'online'
                        else:
                            w = 'idle/offline'
                        layout.add_widget(
                            self.saneLabel(j[:28] + ": " + w, layout))
                except:
                    logging.exception("Error showing node status")

        except Exception:
            logging.info(traceback.format_exc())

        self.screenManager.current = "Streams"

    def makeButtonForStream(self, name):
        "Make a button that, when pressed, edits the stream in the title"

        btn = Button(text=name)

        def f(*a):
            self.editStream(name)

        btn.bind(on_press=f)
        return btn

    def promptAddStream(self, *a, **k):
        def f(v):
            if v:
                daemonconfig.makeUserDatabase(None, v)
                self.editStream(v)

        self.askQuestion("New Stream Name?", cb=f)

    def makeStreamEditPage(self):
        "Prettu much just an empty page filled in by the specific goto functions"

        screen = Screen(name='EditStream')
        self.servicesScreen = screen

        self.streamEditPanelScroll = ScrollView(size_hint=(1, 1))

        self.streamEditPanel = BoxLayout(orientation='vertical',
                                         adaptive_height=True,
                                         spacing=5,
                                         size_hint=(1, None))
        self.streamEditPanel.bind(
            minimum_height=self.streamEditPanel.setter('height'))

        self.streamEditPanelScroll.add_widget(self.streamEditPanel)

        screen.add_widget(self.streamEditPanelScroll)

        return screen
コード例 #7
0
class ServiceApp(MDApp, uihelpers.AppHelpersMixin, tools.ToolsAndSettingsMixin, servicesUI.ServicesMixin, discovery.DiscoveryMixin, tables.TablesMixin, posts.PostsMixin, streams.StreamsMixin):

    def stop_service(self, foo=None):
        if self.service:
            self.service.stop()
            self.service = None
        else:
            hardline.stop()

    
    def on_location(self, **kwargs):
        Logger.info("Called on_location")
        Logger.info(kwargs)
        self.location = kwargs


    def onDrayerRecordChange(self,db,record,sig):
        if self.currentPageNewRecordHandler:
            self.currentPageNewRecordHandler(db,record,sig)
        
        #Only deleting or changing as data row can affect this
        if record['type'] in ('null','row'):
            self.clearSpreadsheetCache()

    def start_service(self, foo=None):
        
        try:
            self.service.stop()
            self.service = None
        except:
            logging.exception("Likely no need to stop nonexistent service")

        try:
            self.getPermission('location')
            from plyer import gps
            gps.configure(
                on_location=self.on_location
            )

            gps.start()
        except:
            logging.exception("Could not start location service")


        if platform == 'android':
            from android import AndroidService
            logging.info("About to start Android service")
            service = AndroidService('HardlineP2P Service', 'running')
            service.start('service started')
            self.service = service
            # On android the service that will actually be handling these databases is in the background in a totally separate
            # process.  So we open an SECOND drayer database object for each, with the same physical storage, using the first as the server.
            # just for use in the foreground app.

            # Because of this, two connections to the same DB file is a completetely supported use case that drayerDB has optimizations for.
            daemonconfig.loadUserDatabases(None, forceProxy='localhost:7004',callbackFunction=self.onDrayerRecordChange)
        else:
            def f():
                # Ensure stopped
                hardline.stop()

                loadedServices = daemonconfig.loadUserServices(
                    None)

                daemonconfig.loadDrayerServerConfig()
                self.currentPageNewRecordHandler=None
                db = daemonconfig.loadUserDatabases(
                    None,callbackFunction=self.onDrayerRecordChange)
                hardline.start(7009)
                # Unload them at exit because we will be loading them again on restart
                for i in loadedServices:
                    loadedServices[i].close()
            t = threading.Thread(target=f, daemon=True)
            t.start()

    def build(self):
        self.service = None

        self.start_service()

        # Create the manager
        sm = ScreenManager()
        self.currentPageNewRecordHandler=None
        sm.add_widget(self.makeMainScreen())
        sm.add_widget(self.makeDiscoveryPage())
        sm.add_widget(self.makeSettingsPage())

        sm.add_widget(self.makeLocalServiceEditPage())
        sm.add_widget(self.makeLocalServicesPage())
        sm.add_widget(self.makeGlobalSettingsPage())
        sm.add_widget(self.makeStreamsPage())
        sm.add_widget(self.makeStreamEditPage())
        sm.add_widget(self.makeLogsPage())
        sm.add_widget(self.makePostMetaDataPage())
        sm.add_widget(self.makeQuestionPage())

        from kivy.base import EventLoop
        EventLoop.window.bind(on_keyboard=self.hook_keyboard)
        import kivymd
        self.theme_cls.colors=kivymd.color_definitions.colors

        import kivy.clock
        kivy.clock.Clock.max_iteration = 10

        #Horid hacks for material design
        self.theme_cls.colors['Brown']['900']='050200'
        self.theme_cls.colors['Green']['600']='43694e'
        self.theme_cls.colors['Light']['Background']='E3DFDA'
        self.theme_cls.primary_palette = "Green"
        self.theme_cls.theme_style = "Light"
        self.theme_cls.primary_hue='600'
        self.theme_cls.accent_hue='900'
        self.theme_cls.accent_palette='Brown'


        self.backStack = []

        # Call this to save whatever unsaved data. Also acts as a flag.
        self.unsavedDataCallback = None

        self.screenManager = sm


        Clock.schedule_interval(self.flushUnsaved, 60*5)
        self.gotoMainScreen()

        return sm

    # Here is our autosave
    def on_pause(self):
        self.flushUnsaved()
        return True

    def on_stop(self):
        self.flushUnsaved()

    def on_destroy(self):
        self.flushUnsaved()

    def flushUnsaved(self, *a):
        if self.unsavedDataCallback:
            self.unsavedDataCallback()
            self.unsavedDataCallback = None

    def makeMainScreen(self):
        mainScreen = Screen(name='Main')

        mainscroll = ScrollView(size_hint=(1, 1))

        self.mainScreenlayout = BoxLayout(orientation='vertical',
                           spacing=10, size_hint=(1, 1),adaptive_height=True)

        mainscroll.add_widget(self.mainScreenlayout)
        mainScreen.add_widget(mainscroll)
        return mainScreen


    def gotoMainScreen(self):
        self.mainScreenlayout.clear_widgets()
        layout=self.mainScreenlayout

    
        label = MDToolbar(title="Drayer Journal")
        label.icon=os.path.join(os.path.dirname(os.path.abspath("__file__")),'assets','icons',"Craftpix.net",'medival','cart.jpg')
        layout.add_widget(label)

        stack = StackLayout(size_hint=(1,None),adaptive_height=True,spacing=5)

      
        l = self.saneLabel("Notice: streams may be stored on the SD card. Some other apps may be able to read them",layout)
        layout.add_widget(l)
            
        btn1 = Button(text='My Streams')

        stack.add_widget(btn1)

        btn1.bind(on_press=self.goToStreams)

        btn1 = Button(text='Discover Services')
     

        btn1.bind(on_press=self.goToDiscovery)
        stack.add_widget(btn1)

        btn5 = Button(text='Settings+Tools')

        btn5.bind(on_press=self.goToSettings)

        stack.add_widget(btn5)


        btn6 = Button(text='Help')

        btn6.bind(on_press=self.goToHelp)

        stack.add_widget(btn6)

        layout.add_widget(stack)

        label = MDToolbar(title="Bookmarks")
        layout.add_widget(label)

        for i in sorted(list(daemonconfig.getBookmarks().keys())):
            bw =BoxLayout(orientation='horizontal',
                           spacing=10, size_hint=(1, None),adaptive_height=True)
            b = Button(text=i[:32])
            bd = Button(text="Del")


            def dlbm(*a,i=i):
                def f(a):
                    if a:
                        daemonconfig.setBookmark(a,None,None)
                        self.gotoMainScreen()
                self.askQuestion("Delete Bookmark?",i,f)
            bd.bind(on_press=dlbm)

            def bm(*a,i=i):
                self.gotoBookmark(i)
            b.bind(on_press=bm)
            bw.add_widget(b)
            bw.add_widget(bd)
            layout.add_widget(bw)

        self.screenManager.current = "Main"





    def goBack(self,*a):
        def f(d):
            try:
                self.openFM.close()
            except:
                pass
            if d:
                self.currentPageNewRecordHandler=None
                self.unsavedDataCallback = False
                # Get rid of the first one representing the current page
                if self.backStack:
                    self.backStack.pop()

                # Go to the previous page, if that page left an instruction for how to get back to it
                if self.backStack:
                    self.backStack.pop()()
                else:
                    self.gotoMainScreen()

        # If they have an unsaved post, ask them if they really want to leave.
        if self.unsavedDataCallback:
            self.askQuestion("Discard unsaved data?", 'yes', cb=f)
        else:
            f(True)

    def makeBackButton(self,width=1):
        btn1 = Button(text='Back')

        btn1.bind(on_press=self.goBack)
        return btn1
    
    def hook_keyboard(self, window, key, *largs):
        if key == 27:
            self.goBack()
            return True 

    def goToHelp(self,*a):
        dn = "builtin:help"
        if not dn in daemonconfig.userDatabases:
            daemonconfig.loadUserDatabase(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),'Drayer Documentation.toml'),dn)
        self.editStream(dn)

    def getPermission(self, type='all'):
        """
        Since API 23, Android requires permission to be requested at runtime.
        This function requests permission and handles the response via a
        callback.
        The request will produce a popup if permissions have not already been
        been granted, otherwise it will do nothing.
        """
        if platform == "android":
            from android.permissions import request_permissions, Permission

            if type == 'all':
                plist = [Permission.ACCESS_COARSE_LOCATION,
                         Permission.ACCESS_FINE_LOCATION, Permission.MANAGE_EXTERNAL_STORAGE]
            if type == 'location':
                plist = [Permission.ACCESS_COARSE_LOCATION,
                         Permission.ACCESS_FINE_LOCATION]
            if type == 'files':
                plist = [Permission.MANAGE_EXTERNAL_STORAGE]

            def callback(permissions, results):
                """
                Defines the callback to be fired when runtime permission
                has been granted or denied. This is not strictly required,
                but added for the sake of completeness.
                """
                if all([res for res in results]):
                    print("callback. All permissions granted.")
                else:
                    print("callback. Some permissions refused.")

            request_permissions(plist, callback)
コード例 #8
0
class ServicesMixin():
    def makeLocalServiceEditPage(self):

        screen = Screen(name='EditLocalService')
        self.servicesScreen = screen

        layout = BoxLayout(orientation='vertical', spacing=10)
        screen.add_widget(layout)
        self.localServiceEditorName = Label(size_hint=(1, None),
                                            halign="center",
                                            text="??????????")

        layout.add_widget(self.makeBackButton())

        self.localServiceEditPanelScroll = ScrollView(size_hint=(1, 1))

        self.localServiceEditPanel = BoxLayout(orientation='vertical',
                                               size_hint=(1, None))
        self.localServiceEditPanel.bind(
            minimum_height=self.localServiceEditPanel.setter('height'))

        self.localServiceEditPanelScroll.add_widget(self.localServiceEditPanel)

        layout.add_widget(self.localServiceEditPanelScroll)

        return screen

    def editLocalService(self, name, c=None):
        if not c:
            c = configparser.RawConfigParser(
                dict_type=cidict.CaseInsensitiveDict)

        try:
            c.add_section("Service")
        except:
            pass
        try:
            c.add_section("Info")
        except:
            pass

        self.localServiceEditPanel.clear_widgets()

        self.localServiceEditorName.text = name

        def save(*a):
            logging.info("SAVE BUTTON WAS PRESSED")
            # On android this is the bg service's job
            daemonconfig.makeUserService(
                None,
                name,
                title=c['Info'].get("title", 'Untitled'),
                service=c['Service'].get("service", ""),
                port=c['Service'].get("port", ""),
                cacheInfo=c['Cache'],
                noStart=(platform == 'android'),
                useDHT=c['Access'].get("useDHT", "yes"))
            if platform == 'android':
                self.stop_service()
                self.start_service()

            self.goToLocalServices()

        def delete(*a):
            def f(n):
                if n and n == name:
                    daemonconfig.delUserService(None, n)
                    if platform == 'android':
                        self.stop_service()
                        self.start_service()
                    self.goToLocalServices()

            self.askQuestion("Really delete?", name, f)

        self.localServiceEditPanel.add_widget(
            Label(size_hint=(1, None), halign="center", text='Service'))

        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Service", "service"))
        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Service", "port"))
        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Info", "title"))

        self.localServiceEditPanel.add_widget(
            Label(size_hint=(1, None), halign="center", text='Cache'))
        self.localServiceEditPanel.add_widget(
            Label(size_hint=(1, None),
                  text='Cache mode only works for static content'))

        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Cache", "directory"))

        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Cache", "maxAge"))

        self.localServiceEditPanel.add_widget(
            Label(size_hint=(1, None),
                  text='Try to refresh after maxAge seconds(default 1 week)'))

        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Cache", "maxSize", '256'))

        self.localServiceEditPanel.add_widget(
            Label(size_hint=(1, None),
                  text='Max size to use for the cache in MB'))

        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Cache", "downloadRateLimit", '1200'))

        self.localServiceEditPanel.add_widget(
            Label(size_hint=(1, None), text='Max MB per hour to download'))

        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Cache", "dynamicContent", 'no'))

        self.localServiceEditPanel.add_widget(
            Label(
                size_hint=(1, None),
                text=
                'Allow executing code in protected @mako files in the cache dir. yes to enable. Do not use with untrusted @mako'
            ))

        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Cache", "allowListing", 'no'))

        self.localServiceEditPanel.add_widget(
            Label(size_hint=(1, None),
                  text='Allow directory listing of cached content'))

        self.localServiceEditPanel.add_widget(
            Label(
                size_hint=(1, None),
                text=
                'Directory names are subfolders within the HardlineP2P cache folder,\nand can also be used to share\nstatic files by leaving the service blank.'
            ))

        self.localServiceEditPanel.add_widget(
            Label(size_hint=(1, None), halign="center",
                  text='Access Settings'))
        self.localServiceEditPanel.add_widget(
            Label(size_hint=(1, None),
                  text='Cache mode only works for static content'))

        self.localServiceEditPanel.add_widget(
            self.settingButton(c, "Access", "useDHT", 'yes'))

        self.localServiceEditPanel.add_widget(
            Label(
                size_hint=(1, None),
                text=
                'DHT Discovery uses a proxy server on Android. \nDisabling this saves bandwidth but makes access from outside your network\nunreliable.'
            ))

        btn1 = Button(text='Save Changes')

        btn1.bind(on_press=save)
        self.localServiceEditPanel.add_widget(btn1)

        btn2 = Button(text='Delete this service')

        btn2.bind(on_press=delete)
        self.localServiceEditPanel.add_widget(btn2)

        self.screenManager.current = "EditLocalService"

    def makeButtonForLocalService(self, name, c=None):
        "Make a button that, when pressed, edits the local service in the title"

        btn = Button(text=name)

        def f(*a):
            self.editLocalService(name, c)

        btn.bind(on_press=f)

        return btn

    def makeLocalServicesPage(self):

        screen = Screen(name='LocalServices')
        self.servicesScreen = screen

        layout = BoxLayout(orientation='vertical', spacing=10)
        screen.add_widget(layout)

        label = Label(
            size_hint=(1, None),
            halign="center",
            text=
            'WARNING: Running a local service may use a lot of data and battery.\nChanges may require service restart.'
        )

        labelw = Label(
            size_hint=(1, None),
            halign="center",
            text=
            'WARNING 2: This app currently prefers the external SD card for almost everything including the keys.'
        )

        layout.add_widget(self.makeBackButton())

        layout.add_widget(label)
        layout.add_widget(labelw)

        btn2 = Button(text='Create a service')

        btn2.bind(on_press=self.promptAddService)
        layout.add_widget(btn2)

        self.localServicesListBoxScroll = ScrollView(size_hint=(1, 1))

        self.localServicesListBox = BoxLayout(orientation='vertical',
                                              size_hint=(1, None),
                                              spacing=10)
        self.localServicesListBox.bind(
            minimum_height=self.localServicesListBox.setter('height'))

        self.localServicesListBoxScroll.add_widget(self.localServicesListBox)

        layout.add_widget(self.localServicesListBoxScroll)

        return screen

    def promptAddService(self, *a, **k):
        def f(v):
            if v:
                self.editLocalService(v)

        self.askQuestion("New Service filename?", cb=f)

    def goToLocalServices(self, *a):
        "Go to a page wherein we can list user-modifiable services."
        self.localServicesListBox.clear_widgets()

        try:
            s = daemonconfig.listServices(None)
            time.sleep(0.5)
            for i in s:
                self.localServicesListBox.add_widget(
                    self.makeButtonForLocalService(i, s[i]))

        except Exception:
            logging.info(traceback.format_exc())

        self.screenManager.current = "LocalServices"
コード例 #9
0
class CardArray(MDBoxLayout):
    def __init__(self, **kwargs):
        super(CardArray, self).__init__(**kwargs)
        self.popup_container = MDBoxLayout(orientation='vertical',
                                           spacing=2,
                                           padding=2)
        container = MDBoxLayout(orientation='vertical', spacing=2, padding=2)
        popup_close_button = MDRoundFlatButton(text='Exit Panel',
                                               size_hint_x=.5,
                                               on_press=self.close_popup,
                                               pos_hint={
                                                   "center_x": .5,
                                                   "center_y": .5
                                               },
                                               md_bg_color=(1, 0, 0, 1),
                                               text_color=(1, 0, 1, 1),
                                               font_size='18sp')
        container.add_widget(self.popup_container)
        container.add_widget(popup_close_button)
        container.add_widget(MDLabel(text=''))
        self.popup = Popup(size_hint=(.4, .3), content=container)

    def close_popup(self, *args):
        self.popup.dismiss()

    def open_popup(self, title='', label_text='', app=None, *args):
        self.popup.title = title
        if label_text in ('OFF', 'ON'):
            self.create_switch_control(app=app, text=label_text)
        else:
            self.create_numeric_control(app=app, text=label_text)
        self.popup.open()

    def create_numeric_control(self, app, text=''):
        grid = MDGridLayout(spacing=3, padding=3, cols=3)
        decrease_number_btn = MDFloatingActionButton(
            icon="minus",
            md_bg_color=app.theme_cls.primary_color,
            on_release=self.numeric_decrement)
        self.parameter_label = LabelButton(
            text=text,
            font_size=40,
            halign='center',
            theme_text_color="Custom",
            text_color=(1, .2, 1, 1),
            on_release=self.commit_parameter_value)
        increase_number_btn = MDFloatingActionButton(
            icon="plus",
            md_bg_color=app.theme_cls.primary_color,
            on_release=self.numeric_increment)
        self.popup_container.clear_widgets()
        grid.add_widget(decrease_number_btn)
        grid.add_widget(self.parameter_label)
        grid.add_widget(increase_number_btn)
        self.popup_container.add_widget(grid)

    def create_switch_control(self, text, app):
        self.switch_state = True if text == 'ON' else False
        grid = MDGridLayout(spacing=3, padding=3, cols=3)
        self.switch = MySwitch(width=dp(64), active=self.switch_state)
        self.switch.bind(on_press=self.switch_callback)

        self.parameter_label = LabelButton(
            text=text,
            font_size=40,
            halign='center',
            theme_text_color="Custom",
            text_color=(1, 0, 0, 1) if not self.switch.active else
            (0, 1, 0, 1),
            on_release=self.commit_parameter_value)
        commit_btn = MDRoundFlatIconButton(
            text="Commit",
            md_bg_color=app.theme_cls.primary_color,
            on_release=self.commit_parameter_value)
        self.popup_container.clear_widgets()
        grid.add_widget(self.parameter_label)
        grid.add_widget(self.switch)
        grid.add_widget(commit_btn)
        self.popup_container.add_widget(grid)

    def _numeric_buttons_callback(self, *args):
        widget = self.parameter_label
        value_char = ''
        if widget.text.isdigit():
            value_digit = eval(widget.text)
        else:
            values = widget.text.split(':')
            value_char = values[0] + ':'
            value_digit = eval(values[-1])
        return value_digit, value_char

    def numeric_decrement(self, *args):
        widget = self.parameter_label
        value_digit, value_char = self._numeric_buttons_callback()
        value_digit -= 1
        widget.text = value_char + str(value_digit)

    def numeric_increment(self, *args):
        widget = self.parameter_label
        value_digit, value_char = self._numeric_buttons_callback()
        value_digit += 1
        widget.text = value_char + str(value_digit)

    def commit_parameter_value(self, *args):
        print('value sent to the backend')

    def switch_callback(self, *args):
        print('switch function is called')
        self.switch_state = self.switch.active
        self.parameter_label.text = 'ON' if self.switch_state else 'OFF'
        self.parameter_label.text_color = (1, 0, 0,
                                           1) if not self.switch_state else (0,
                                                                             1,
                                                                             0,
                                                                             1)