<commit_before>import gevent.monkey as monkey
monkey.patch_all()

import gevent
import gevent.queue

import sys

import zmq.green as zmq

import binascii
import datetime

import config
import bitcoinrpc.authproxy

def zmqpoller(hash_queue):
    while True:
        msg = socket.recv_multipart()
        topic = str(msg[0])
        body = msg[1]
        hash_queue.put(binascii.hexlify(body))

def rpcrequester(hash_queue, cfg):
    rpcurl = "{}://{}:{}@{}:{}".format(
        cfg["protocol"],
        cfg["rpcuser"],
        cfg["rpcpassword"],
        cfg["rpcip"],
        cfg["rpcport"],
    )
    print "Connecting to bitcoind RPC..."
    handle = bitcoinrpc.authproxy.AuthServiceProxy(rpcurl, None, 500)
    print "Connected. Requesting 'getinfo'..."

    # Check that it's working...
    getinfo = handle.getinfo()
    print "Success."
    print

    total = 0
    start_time = datetime.datetime.utcnow()

    for txhash in hash_queue:
        sys.stdout.write("\r")
        tx = handle.getrawtransaction(txhash, 1)
        try:
            outs = [(vout["scriptPubKey"]["addresses"][0], vout["value"]) for vout in tx["vout"]]
            for out in outs:
                print "{} {} received {}BTC".format(datetime.datetime.utcnow().isoformat(), out[0], out[1])
                total += out[1]
        except KeyError:
            print "odd tx"
            print tx

        total_time = datetime.datetime.utcnow() - start_time
        print
        sys.stdout.write("Total transferred: {}BTC in {}s".format(total, total_time.total_seconds()))
        sys.stdout.flush()

def process_config(cfg):
    assert "rpcuser" in cfg
    assert "rpcpassword" in cfg

    if "rpcip" not in cfg:
        cfg["rpcip"] = "localhost"

    if "rpcport" not in cfg:
        if "testnet" in cfg:
            cfg["rpcport"] = 18332
        else:
            cfg["rpcport"] = 8332

    if "protocol" not in cfg:
        cfg["protocol"] = "http"

    return cfg

if __name__ == "__main__":
    cfg = config.read_file("bitcoin.conf")
    cfg = process_config(cfg)

    context = zmq.Context()
    socket = context.socket(zmq.SUB)
    socket.setsockopt(zmq.SUBSCRIBE, "hashtx")
    socket.connect("tcp://127.0.0.1:10000")

    hash_queue = gevent.queue.Queue()
    zmq_process = gevent.spawn(zmqpoller, hash_queue)

    try:
        rpcrequester(hash_queue, cfg)
    finally:
        socket.close()
        context.term()
<commit_msg>Add some tricked out colours<commit_after>import gevent.monkey as monkey
monkey.patch_all()

import gevent
import gevent.queue

import sys

import zmq.green as zmq

import binascii
import datetime

import config
import bitcoinrpc.authproxy

class termcolors:
    yellow = '\033[33m'
    cyan = '\033[36m'
    reset = '\033[0m'

def zmqpoller(hash_queue):
    while True:
        msg = socket.recv_multipart()
        topic = str(msg[0])
        body = msg[1]
        hash_queue.put(binascii.hexlify(body))

def rpcrequester(hash_queue, cfg):
    rpcurl = "{}://{}:{}@{}:{}".format(
        cfg["protocol"],
        cfg["rpcuser"],
        cfg["rpcpassword"],
        cfg["rpcip"],
        cfg["rpcport"],
    )
    print "Connecting to bitcoind RPC..."
    handle = bitcoinrpc.authproxy.AuthServiceProxy(rpcurl, None, 500)
    print "Connected. Requesting 'getinfo'..."

    # Check that it's working...
    getinfo = handle.getinfo()
    print "Success."
    print

    total = 0
    start_time = datetime.datetime.utcnow()

    for txhash in hash_queue:
        sys.stdout.write("\r")
        tx = handle.getrawtransaction(txhash, 1)
        try:
            outs = [(vout["scriptPubKey"]["addresses"][0], vout["value"]) for vout in tx["vout"]]
            for out in outs:
                print "{}{}{} {} received {}{}BTC{}".format(
                    termcolors.yellow, datetime.datetime.utcnow().isoformat(), termcolors.reset,
                    out[0],
                    termcolors.cyan, out[1], termcolors.reset
                )
                total += out[1]
        except KeyError:
            print "odd tx"
            print tx

        total_time = datetime.datetime.utcnow() - start_time
        print
        sys.stdout.write("Total transferred: {}{}BTC{} in {}s".format(
            termcolors.cyan, total, termcolors.reset,
            total_time.total_seconds()))
        sys.stdout.flush()

def process_config(cfg):
    assert "rpcuser" in cfg
    assert "rpcpassword" in cfg

    if "rpcip" not in cfg:
        cfg["rpcip"] = "localhost"

    if "rpcport" not in cfg:
        if "testnet" in cfg:
            cfg["rpcport"] = 18332
        else:
            cfg["rpcport"] = 8332

    if "protocol" not in cfg:
        cfg["protocol"] = "http"

    return cfg

if __name__ == "__main__":
    cfg = config.read_file("bitcoin.conf")
    cfg = process_config(cfg)

    context = zmq.Context()
    socket = context.socket(zmq.SUB)
    socket.setsockopt(zmq.SUBSCRIBE, "hashtx")
    socket.connect("tcp://127.0.0.1:10000")

    hash_queue = gevent.queue.Queue()
    zmq_process = gevent.spawn(zmqpoller, hash_queue)

    try:
        rpcrequester(hash_queue, cfg)
    finally:
        socket.close()
        context.term()
<|endoftext|>


<commit_before>from __future__ import print_function

import datetime
import pickle

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import *
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.dropdown import DropDown
from kivy.uix.gridlayout import GridLayout
from kivy.base import runTouchApp
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.core.audio import SoundLoader
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import Screen
from kivy.uix.settings import SettingsWithSidebar
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup

from jsonConfig import settingsjson

from gameplay import GamePlayScreen
game = None

class PlayerNamePopup(Popup):

    def __init__(self, value, game):
        super(PlayerNamePopup, self).__init__()
        self.game = game
        self.game.number_of_players = value
        self.game.name_of_players = ['John', 'Sally', 'Sam', 'Joey']
        #Create the screen which allows a user to change names.
        for i in range(self.game.number_of_players):
            button = Button()
            button.text = self.game.name_of_players[i]
            button.value = i
            button.bind(on_press=self.click)
            self.content.add_widget(button)

        self.enter = Button(text='Start Game', size_hint_y=None, height='40dp')
        self.enter.bind(on_press=self.on_press_callback)
        self.children[0].add_widget(self.enter)

    def click(self,button):
        #In here, we create the popup where we request the user's names.
        #On click of the name we want to change, the user can enter a new name.
        i = button.value
        popup = Popup(title="Enter Name here",
              size_hint=(0.25, 0.25),
              on_dismiss=lambda x: self.set_caption(x,i,button))
        box = GridLayout(cols=1)
        box.add_widget(TextInput(focus=True,text=button.text))
        box.children[0].select_all()
        box.add_widget(Button(text="Enter Name",on_press = popup.dismiss) )
        popup.content = box
        popup.open()

    def set_caption(self, popup,i,button):
        #Set the name in the name_of_players array.
        self.game.name_of_players[i] = popup.content.children[1].text
        button.text = self.game.name_of_players[i]

    def on_press_callback(self, obj):
        self.dismiss()
        self.game.children[0].current = 'screen2'

class TutorialScreen(Screen):
    active = BooleanProperty(False)

class EndGameScreen(Screen):
    name_of_players = ListProperty(['','','',''])
    scores_of_players = ListProperty([0, 0, 0, 0])
    screenManager = ObjectProperty()
    number_of_players = NumericProperty(1)
    game = ObjectProperty()

    def on_enter(self,*args):
        self.name_of_players = [x for y,x in sorted(zip(self.game.scores_of_players,self.game.name_of_players))][::-1]
        self.scores_of_players = sorted(self.game.scores_of_players)[::-1]

class PlayerSection(Button):
    myvalue = NumericProperty(4)

    def __init__(self, **kwargs):
        super(PlayerSection, self).__init__(**kwargs)
        self.size = Window.size[0] // 6, Window.size[1] // 6

class GameLayout(FloatLayout):
    ''' This class manages the movements between the various screen and the sound '''

    name_of_players = ListProperty(['Collections found', '', '', ''])
    number_of_players = NumericProperty(1)
    scores_of_players = ListProperty([0, 0, 0, 0])

    # True if there is a game going on
    active = BooleanProperty(False)
    soundActivated = BooleanProperty(False)
    def __init__(self, **kwargs):
        super(GameLayout, self).__init__(**kwargs)
        self.screens = self.ids.screenManager
        self.playscreen = self.screens.get_screen('screen2')
        self.sound = SoundLoader.load('set_song.wav')

    # screen play navigation
    def goToIntro(self, *arg):
        self.screens.current = 'screen1'

    def goToGameScreen(self):
        self.screens.current = 'screen2'

    # Dealing with Sound
    def on_soundActivated(self, obj, value):
        ''' Turn the intro song on or off '''
        if value:
            self.sound.loop = True
            self.sound.play()
        else:
            self.sound.stop()

    def player_name_popup(self, numPlayers):
        '''called after selecting number of players'''
        playername = PlayerNamePopup(numPlayers, self)
        playername.open()

    def restart(self):
        '''reset the scores and everything'''
        self.scores_of_players = [0,0,0,0]
        self.aiScore = 0

    def quit(self):
        ''' You are quiting the current game '''
        self.playscreen.unselectAll()
        self.setupGame()
        self.restart()
        self.goToIntro()
        self.playscreen.stopRotation()
        self.active = False

    def goToTutorial(self):
        self.screens.current = 'tutorialFlow'

    def stopClocks(self):
        Clock.unschedule(self.playscreen.AIplay)
        Clock.unschedule(self.playscreen.displayHint)
        Clock.unschedule(self.playscreen.displayHintSecond)

def boolFromJS(value):
    ''' JSON config returns '1' and '0' for True and False'''
    return True if value == '1' else False

class CollectionApp(App):
    active = BooleanProperty(False)

    def build(self):
        Clock.max_iteration = 50
        # The following line will be uncommented in the beta
        # For now, it gives us access to various kivy settings we can play with
        self.use_kivy_settings = False
        self.gamelayout = GameLayout()
        self.settings_cls = SettingsWithSidebar
        self.loadSettings()
        self.gamelayout.bind(active=self.changeActive)
        return self.gamelayout

    def changeActive(self,instance,value):
        # This doesn't work.. crashes if the build_settings wasn't launched first
        #self.quitButton.disabled = not self.gamelayout.active
        pass

    def loadSettings(self):
        # Load the values already stored into the file
        self.gamelayout.playscreen.hintActivated = boolFromJS(
            self.config.get('settings', 'hint'))
        speedSettings = {'slow':10, 'normal':5, 'fast':1}
        self.gamelayout.playscreen.displayHintTimer = speedSettings[
            self.config.get('settings', 'hintspeed')]
        self.gamelayout.soundActivated = boolFromJS(
            self.config.get('settings', 'sound'))
        self.gamelayout.playscreen.aiActivated = boolFromJS(
            self.config.get('settings', 'ai'))

    def build_config(self, config):
        config.setdefaults('settings', {'hint': True,
                                        'sound': False,
                                        'ai': False,
                                        'hintspeed': 'fast'})

    def build_settings(self, settings):
        print(settings)
        self.settings = settings
        settings.add_json_panel('Settings', self.config, data=settingsjson)
        settingsCloseButton = settings.interface.ids.menu.ids.button
        self.settingsCloseButton = settingsCloseButton
        settingsCloseButton.on_press = self.leaveSettingsPanel
        settings.interface.ids.menu.add_widget(Button(text="Tutorial",
                                                      size_hint = (None, None),
                                                      x= settingsCloseButton.x,
                                                      y = settingsCloseButton.top + 10,
                                                      size = settingsCloseButton.size,
                                                      on_press= self.moveToTutorial))

        self.quitButton = Button(text="Quit Current Game",
                              background_color = [1,0,0,1],
                              size_hint = (None, None),
                              x= settingsCloseButton.x,
                              y = settingsCloseButton.top + settingsCloseButton.height + 20,
                              size = settingsCloseButton.size,
                              disabled = False,
                              on_press= self.quit)

        settings.interface.ids.menu.add_widget(self.quitButton)
        settings.on_close = self.quit

    def quit(self, *arg):
        self.gamelayout.quit()
        self.settingsCloseButton.trigger_action()

    def moveToTutorial(self, buttonInstance):
        self.gamelayout.goToTutorial()
        self.settingsCloseButton.trigger_action()

    def leaveSettingsPanel(self, *arg):
        ''' activated when you exit the setting panels'''
        self.gamelayout.playscreen.setUpHint()
        self.gamelayout.playscreen.setUpAI()

    def on_config_change(self, config, section, key, value):
        if key == 'hint':
            self.gamelayout.hintActivated = boolFromJS(value)
        if key == 'sound':
            self.gamelayout.soundActivated = boolFromJS(value)
        if key == 'hintspeed':
            speedSettings = {'slow':10, 'normal':5, 'fast':1}
            self.gamelayout.displayHintTimer = speedSettings[value]
        if key == 'ai':
            self.gamelayout.aiActivated = boolFromJS(value)

# To test the screen size you can use:
# kivy main.py -m screen:ipad3

if __name__ == '__main__':
    CollectionApp().run()
<commit_msg>Moved a tiny bit of the PlayerNamePopup to the kivy file<commit_after>from __future__ import print_function

import datetime
import pickle

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import *
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.dropdown import DropDown
from kivy.uix.gridlayout import GridLayout
from kivy.base import runTouchApp
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.core.audio import SoundLoader
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import Screen
from kivy.uix.settings import SettingsWithSidebar
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup

from jsonConfig import settingsjson

from gameplay import GamePlayScreen
game = None

class PlayerNamePopup(Popup):

    def __init__(self, value, game):
        super(PlayerNamePopup, self).__init__()
        self.game = game
        self.game.number_of_players = value
        self.game.name_of_players = ['John', 'Sally', 'Sam', 'Joey']
        #Create the screen which allows a user to change names.
        for i in range(self.game.number_of_players):
            button = Button(value=i)
            button.text = self.game.name_of_players[i]
            #button.value = i
            button.bind(on_press=self.click)
            self.content.add_widget(button)

        self.enter = Button(text='Start Game', size_hint_y=None, height='40dp')
        self.enter.bind(on_press=self.on_press_callback)
        self.children[0].add_widget(self.enter)

    def click(self,button):
        #In here, we create the popup where we request the user's names.
        #On click of the name we want to change, the user can enter a new name.
        i = button.value
        popup = Popup(title="Enter Name here",
              size_hint=(0.25, 0.25),
              on_dismiss=lambda x: self.set_caption(x,i,button))
        box = GridLayout(cols=1)
        box.add_widget(TextInput(focus=True,text=button.text))
        box.children[0].select_all()
        box.add_widget(Button(text="Enter Name",on_press = popup.dismiss) )
        popup.content = box
        popup.open()

    def set_caption(self, popup,i,button):
        #Set the name in the name_of_players array.
        self.game.name_of_players[i] = popup.content.children[1].text
        button.text = self.game.name_of_players[i]

    def on_press_callback(self, obj):
        self.dismiss()
        self.game.children[0].current = 'screen2'

class TutorialScreen(Screen):
    active = BooleanProperty(False)

class EndGameScreen(Screen):
    name_of_players = ListProperty(['','','',''])
    scores_of_players = ListProperty([0, 0, 0, 0])
    screenManager = ObjectProperty()
    number_of_players = NumericProperty(1)
    game = ObjectProperty()

    def on_enter(self,*args):
        self.name_of_players = [x for y,x in sorted(zip(self.game.scores_of_players,self.game.name_of_players))][::-1]
        self.scores_of_players = sorted(self.game.scores_of_players)[::-1]

class PlayerSection(Button):
    myvalue = NumericProperty(4)

    def __init__(self, **kwargs):
        super(PlayerSection, self).__init__(**kwargs)
        self.size = Window.size[0] // 6, Window.size[1] // 6

class GameLayout(FloatLayout):
    ''' This class manages the movements between the various screen and the sound '''

    name_of_players = ListProperty(['Collections found', '', '', ''])
    number_of_players = NumericProperty(1)
    scores_of_players = ListProperty([0, 0, 0, 0])

    # True if there is a game going on
    active = BooleanProperty(False)
    soundActivated = BooleanProperty(False)
    def __init__(self, **kwargs):
        super(GameLayout, self).__init__(**kwargs)
        self.screens = self.ids.screenManager
        self.playscreen = self.screens.get_screen('screen2')
        self.sound = SoundLoader.load('set_song.wav')

    # screen play navigation
    def goToIntro(self, *arg):
        self.screens.current = 'screen1'

    def goToGameScreen(self):
        self.screens.current = 'screen2'

    # Dealing with Sound
    def on_soundActivated(self, obj, value):
        ''' Turn the intro song on or off '''
        if value:
            self.sound.loop = True
            self.sound.play()
        else:
            self.sound.stop()

    def player_name_popup(self, numPlayers):
        '''called after selecting number of players'''
        playername = PlayerNamePopup(numPlayers, self)
        playername.open()

    def restart(self):
        '''reset the scores and everything'''
        self.scores_of_players = [0,0,0,0]
        self.aiScore = 0

    def quit(self):
        ''' You are quiting the current game '''
        self.playscreen.unselectAll()
        self.setupGame()
        self.restart()
        self.goToIntro()
        self.playscreen.stopRotation()
        self.active = False

    def goToTutorial(self):
        self.screens.current = 'tutorialFlow'

    def stopClocks(self):
        Clock.unschedule(self.playscreen.AIplay)
        Clock.unschedule(self.playscreen.displayHint)
        Clock.unschedule(self.playscreen.displayHintSecond)

def boolFromJS(value):
    ''' JSON config returns '1' and '0' for True and False'''
    return True if value == '1' else False

class CollectionApp(App):
    active = BooleanProperty(False)

    def build(self):
        Clock.max_iteration = 50
        # The following line will be uncommented in the beta
        # For now, it gives us access to various kivy settings we can play with
        self.use_kivy_settings = False
        self.gamelayout = GameLayout()
        self.settings_cls = SettingsWithSidebar
        self.loadSettings()
        self.gamelayout.bind(active=self.changeActive)
        return self.gamelayout

    def changeActive(self,instance,value):
        # This doesn't work.. crashes if the build_settings wasn't launched first
        #self.quitButton.disabled = not self.gamelayout.active
        pass

    def loadSettings(self):
        # Load the values already stored into the file
        self.gamelayout.playscreen.hintActivated = boolFromJS(
            self.config.get('settings', 'hint'))
        speedSettings = {'slow':10, 'normal':5, 'fast':1}
        self.gamelayout.playscreen.displayHintTimer = speedSettings[
            self.config.get('settings', 'hintspeed')]
        self.gamelayout.soundActivated = boolFromJS(
            self.config.get('settings', 'sound'))
        self.gamelayout.playscreen.aiActivated = boolFromJS(
            self.config.get('settings', 'ai'))

    def build_config(self, config):
        config.setdefaults('settings', {'hint': True,
                                        'sound': False,
                                        'ai': False,
                                        'hintspeed': 'fast'})

    def build_settings(self, settings):
        print(settings)
        self.settings = settings
        settings.add_json_panel('Settings', self.config, data=settingsjson)
        settingsCloseButton = settings.interface.ids.menu.ids.button
        self.settingsCloseButton = settingsCloseButton
        settingsCloseButton.on_press = self.leaveSettingsPanel
        settings.interface.ids.menu.add_widget(Button(text="Tutorial",
                                                      size_hint = (None, None),
                                                      x= settingsCloseButton.x,
                                                      y = settingsCloseButton.top + 10,
                                                      size = settingsCloseButton.size,
                                                      on_press= self.moveToTutorial))

        self.quitButton = Button(text="Quit Current Game",
                              background_color = [1,0,0,1],
                              size_hint = (None, None),
                              x= settingsCloseButton.x,
                              y = settingsCloseButton.top + settingsCloseButton.height + 20,
                              size = settingsCloseButton.size,
                              disabled = False,
                              on_press= self.quit)

        settings.interface.ids.menu.add_widget(self.quitButton)
        settings.on_close = self.quit

    def quit(self, *arg):
        self.gamelayout.quit()
        self.settingsCloseButton.trigger_action()

    def moveToTutorial(self, buttonInstance):
        self.gamelayout.goToTutorial()
        self.settingsCloseButton.trigger_action()

    def leaveSettingsPanel(self, *arg):
        ''' activated when you exit the setting panels'''
        self.gamelayout.playscreen.setUpHint()
        self.gamelayout.playscreen.setUpAI()

    def on_config_change(self, config, section, key, value):
        if key == 'hint':
            self.gamelayout.hintActivated = boolFromJS(value)
        if key == 'sound':
            self.gamelayout.soundActivated = boolFromJS(value)
        if key == 'hintspeed':
            speedSettings = {'slow':10, 'normal':5, 'fast':1}
            self.gamelayout.displayHintTimer = speedSettings[value]
        if key == 'ai':
            self.gamelayout.aiActivated = boolFromJS(value)

# To test the screen size you can use:
# kivy main.py -m screen:ipad3

if __name__ == '__main__':
    CollectionApp().run()
<|endoftext|>


<commit_before>###########################
#
# Python rewrite of multilateration technique by André Andersen in his [blog post](http://blog.andersen.im/2012/07/signal-emitter-positioning-using-multilateration).
#
############################

from numpy import *
from numpy.linalg import *
import json
#speed of sound in medium
v = 3450
numOfDimensions = 3
nSensors = 5
region = 3
sensorRegion = 2

#choose a random sensor location
emitterLocation = region * ( random.random_sample(numOfDimensions) - 0.5 )
sensorLocations = [ sensorRegion * ( random.random_sample(numOfDimensions)-0.5 ) for n in range(nSensors) ]
p = matrix( sensorLocations ).T

#Time from emitter to each sensor
sensorTimes = [ sqrt( dot(location-emitterLocation,location-emitterLocation) ) / v for location in sensorLocations ]

c = argmin(sensorTimes)
cTime = sensorTimes[c]

#sensors delta time relative to sensor c
t = sensorDeltaTimes = [ sensorTime - cTime for sensorTime in sensorTimes ]

ijs = range(nSensors)
del ijs[c]

A = zeros([nSensors-1,numOfDimensions])
b = zeros([nSensors-1,1])
iRow = 0
rankA = 0

for i in ijs:
	for j in ijs:
		A[iRow,:] = 2*( v*(t[j])*(p[:,i]-p[:,c]).T - v*(t[i])*(p[:,j]-p[:,c]).T )
		b[iRow] = v*(t[i])*(v*v*(t[j])**2-p[:,j].T*p[:,j]) + \
		(v*(t[i])-v*(t[j]))*p[:,c].T*p[:,c] + \
		v*(t[j])*(p[:,i].T*p[:,i]-v*v*(t[i])**2)
		rankA = matrix_rank(A)
		if rankA >= numOfDimensions :
			break
		iRow += 1
	if rankA >= numOfDimensions:
		break

calculatedLocation = asarray( lstsq(A,b)[0] )[:,0]

print json.dumps( A.tolist() )
print json.dumps( b.tolist() )

print "Emitter location: %s " % emitterLocation
print "Calculated position of emitter: %s " % calculatedLocation
<commit_msg>Removed json debug info<commit_after>###########################
#
# Python rewrite of multilateration technique by André Andersen in his [blog post](http://blog.andersen.im/2012/07/signal-emitter-positioning-using-multilateration).
#
############################

from numpy import *
from numpy.linalg import *
import json
#speed of sound in medium
v = 3450
numOfDimensions = 3
nSensors = 5
region = 3
sensorRegion = 2

#choose a random sensor location
emitterLocation = region * ( random.random_sample(numOfDimensions) - 0.5 )
sensorLocations = [ sensorRegion * ( random.random_sample(numOfDimensions)-0.5 ) for n in range(nSensors) ]
p = matrix( sensorLocations ).T

#Time from emitter to each sensor
sensorTimes = [ sqrt( dot(location-emitterLocation,location-emitterLocation) ) / v for location in sensorLocations ]

c = argmin(sensorTimes)
cTime = sensorTimes[c]

#sensors delta time relative to sensor c
t = sensorDeltaTimes = [ sensorTime - cTime for sensorTime in sensorTimes ]

ijs = range(nSensors)
del ijs[c]

A = zeros([nSensors-1,numOfDimensions])
b = zeros([nSensors-1,1])
iRow = 0
rankA = 0

for i in ijs:
	for j in ijs:
		A[iRow,:] = 2*( v*(t[j])*(p[:,i]-p[:,c]).T - v*(t[i])*(p[:,j]-p[:,c]).T )
		b[iRow] = v*(t[i])*(v*v*(t[j])**2-p[:,j].T*p[:,j]) + \
		(v*(t[i])-v*(t[j]))*p[:,c].T*p[:,c] + \
		v*(t[j])*(p[:,i].T*p[:,i]-v*v*(t[i])**2)
		rankA = matrix_rank(A)
		if rankA >= numOfDimensions :
			break
		iRow += 1
	if rankA >= numOfDimensions:
		break

calculatedLocation = asarray( lstsq(A,b)[0] )[:,0]

print "Emitter location: %s " % emitterLocation
print "Calculated position of emitter: %s " % calculatedLocation
<|endoftext|>


<commit_before>import signal
import argparse
import json
import re
import sys
from urllib.parse import urlunparse

import lxml.html as html
import asyncio
import aiohttp
import tqdm

# Restore default Ctrl-C handler for faster process shutdown
signal.signal(signal.SIGINT, signal.SIG_DFL)

RECIPE_LIST_URL = "http://na.finalfantasyxiv.com/lodestone/playguide/db/recipe/"

LANG_HOSTS = {
    "en": "na.finalfantasyxiv.com",
    "ja": "jp.finalfantasyxiv.com",
    "fr": "fr.finalfantasyxiv.com",
    "de": "de.finalfantasyxiv.com",
}

CLASSES = [
    "Carpenter",
    "Blacksmith",
    "Armorer",
    "Goldsmith",
    "Leatherworker",
    "Weaver",
    "Alchemist",
    "Culinarian",
]

# recipe level -> [ 0-star adjustment, 1-star adjustment, ... ]
LEVEL_DIFF = {
    # verified against app
    50: [ 0, 5, 20, 40, 60 ], # 50, 55, 70, 90, 110
    # unconfirmed from desynthesis results
    51: [ 69 ], # 120
    52: [ 73 ], # 125
    53: [ 77 ], # 130
    54: [ 79 ], # 133
    55: [ 81 ], # 136
    56: [ 83 ], # 139
    57: [ 85 ], # 142
    58: [ 87 ], # 145
    59: [ 89 ], # 148
    60: [ 90, 100, 120, 150, 190 ], # 150, 160, 180, 210, 250
    61: [ 199 ], # 260
    62: [ 203 ], # 265
    63: [ 207 ], # 270
    64: [ 209 ], # 273
    65: [ 211 ], # 276
    66: [ 213 ], # 279
    67: [ 215 ], # 282
    68: [ 217 ], # 285
    69: [ 219 ], # 288
    70: [ 220, 230, 250, 280, 320 ], # 290, 300, 320, 350, 390
}

LEVEL_RANGE = ["{0}-{1}".format(start, start + 4) for start in range(1, 70, 5)]
NUM_LEVEL_RANGES = len(LEVEL_RANGE)

EMBED_CODE_RE = re.compile("\\[db:recipe=([0-9a-f]+)]")

ASPECT_RE = re.compile("Aspect: (.+)")

FETCH_SEMAPHORE: asyncio.Semaphore

def logInfo(msg):
    print(msg, end="\n", file=sys.stderr)

def logError(msg):
    print(f"\nERROR: {msg}", end="\n", file=sys.stderr)

async def wait_with_progress(coros: list, desc: str = None, unit: str = None):
    for f in tqdm.tqdm(asyncio.as_completed(coros), total=len(coros), desc=desc, unit=unit):
        yield await f

async def fetch(session: aiohttp.ClientSession, url: str, **kwargs):
    err_count = 0
    while err_count < 5:
        try:
            async with FETCH_SEMAPHORE:
                async with session.get(url, **kwargs) as res:
                    if res.status == 429:
                        retry_after = int(res.headers["retry-after"] or '5')
                        asyncio.sleep(retry_after)
                        continue
                    elif res.status != 200:
                        raise Exception(f"{res.status} {res.reason}")
                    return await res.text()
        except SystemExit:
            raise
        except:
            err_count += 1
            asyncio.sleep(5)
            pass
    logError(f"Could not load page after 5 tries: {url}")
    raise SystemExit

def parse_recipe_links_page(text: str):
    tree = html.fromstring(text)
    rel_links = tree.xpath("//div/@data-ldst-href")
    links = map(str, rel_links)
    show_end = int(tree.xpath("//span[@class='show_end']/text()")[0])
    total = int(tree.xpath("//span[@class='total']/text()")[0])
    return links, show_end, total

async def fetch_recipe_links_page(session: aiohttp.ClientSession, cls: str, level_range: int, page: int):
    params = {
        "category2": CLASSES.index(cls),
        "category3": level_range,
        "page": page,
    }
    return parse_recipe_links_page(await fetch(session, RECIPE_LIST_URL, params=params))

async def fetch_recipe_links_range(session: aiohttp.ClientSession, cls: str, level_range: int):
    links = []
    page = 1
    while True:
        page_links, show_end, total = await fetch_recipe_links_page(session, cls, level_range, page)
        links += page_links
        if show_end < total:
            page += 1
        else:
            break
    return links

async def fetch_recipe_links(session: aiohttp.ClientSession, cls: str):
    results = wait_with_progress(
        [fetch_recipe_links_range(session, cls, level_range) for level_range in range(0, NUM_LEVEL_RANGES)],
        desc=f"Fetching {cls} links",
        unit=""
    )

    links = []
    async for r in results:
        links.extend(r)

    return links

def make_recipe_url(lang: str, rel_link: str):
    return urlunparse(("http", LANG_HOSTS[lang], rel_link, "", "", ""))

async def fetch_recipe_pages(session: aiohttp.ClientSession, rel_link: str):
    pages = {}

    for lang in LANG_HOSTS:
        pages[lang] = html.fromstring(await fetch(session, make_recipe_url(lang, rel_link)))

    return pages

async def fetch_recipe(session: aiohttp.ClientSession, rel_link: str):
    pages = await fetch_recipe_pages(session, rel_link)

    tree = pages["en"]

    embed_code = tree.xpath("//div[@class='embed_code_txt']//div[contains(text(), 'db:recipe')]/text()")[0]
    match = EMBED_CODE_RE.match(embed_code)
    if match is None:
        raise Exception("recipe id not found")
    recipe_id = match.group(1)

    detail_box = tree.xpath("//div[@class='recipe_detail item_detail_box']")[0]
    base_level = int(detail_box.xpath("//span[@class='db-view__item__text__level__num']/text()")[0])
    stars = None
    level_adjustment = 0
    if base_level in LEVEL_DIFF:
        stars = len(detail_box.xpath("//div[@class='db-view__item__text__level']//span[contains(@class, 'star')]"))
        try:
            level_adjustment = LEVEL_DIFF[base_level][stars]
        except IndexError:
            logError(f"Unsupported number of stars ({stars}) for level {base_level}.")
            raise SystemExit

    level = base_level + level_adjustment

    craft_data = tree.xpath("//ul[@class='db-view__recipe__craftdata']")[0]
    difficulty = int(craft_data.xpath("li[span='Difficulty']/text()")[0])
    durability = int(craft_data.xpath("li[span='Durability']/text()")[0])
    maxQuality = int(craft_data.xpath("li[span='Maximum Quality']/text()")[0])

    # Base level 51 recipes of difficulty 169 or 339 are adjusted to level 115
    # instead of the default 120 that other level 51 recipes are adjusted to.

    if ((base_level == 51 and (difficulty == 169 or difficulty == 339)) or
            (base_level == 61 and (difficulty == 1116 or difficulty == 558))):
        level -= 5
    if base_level == 60 and stars == 3 and difficulty == 1764:
        level += 10

    aspect = None

    craft_conditions = tree.xpath("//dl[@class='db-view__recipe__crafting_conditions']")[0]
    for characteristic in craft_conditions.xpath("dt[text()='Characteristics']/../dd/text()"):
        characteristic = characteristic.strip()
        match = ASPECT_RE.match(characteristic)
        if not match is None:
            aspect = match.group(1)

    recipe = {
        "id": recipe_id,
        "name": {},
        "baseLevel": base_level,
        "level": level,
        "difficulty": difficulty,
        "durability": durability,
        "maxQuality": maxQuality,
    }

    if stars:
        recipe["stars"] = stars

    if aspect:
        recipe["aspect"] = aspect

    for lang in LANG_HOSTS:
        tree = pages[lang]
        recipe["name"][lang] = str(tree.xpath("//h2[contains(@class,'db-view__item__text__name')]/text()")[0]).strip()

    return recipe

async def fetch_recipes(session: aiohttp.ClientSession, cls: str, links: list):
    recipes = wait_with_progress(
        [fetch_recipe(session, link) for link in links],
        desc=f"Fetching {cls} recipes",
        unit=""
    )

    return [r async for r in recipes]

async def fetch_class(session: aiohttp.ClientSession, additional_languages: dict, cls: str):
    links = await fetch_recipe_links(session, cls)
    recipes = await fetch_recipes(session, cls, links)
    recipes.sort(key=lambda r: (r['level'], r['name']['en']))
    for recipe in recipes:
        for lang in additional_languages.keys():
            names = additional_languages[lang]
            english_name = recipe['name']['en']
            recipe['name'][lang] = names.get(english_name) or english_name
    return recipes

async def scrape_to_file(session: aiohttp.ClientSession, additional_languages: dict, cls: str):
    recipes = await fetch_class(session, additional_languages, cls)
    with open("out/" + cls + ".json", mode="wt", encoding="utf-8") as db_file:
        json.dump(recipes, db_file, indent=2, sort_keys=True, ensure_ascii=False)

async def scrape_classes(session: aiohttp.ClientSession, additional_languages: dict):
    for cls in CLASSES:
        await scrape_to_file(session, additional_languages, cls)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-c",
        "--concurrency",
        help="Max number of concurrent requests to Lodestone servers. [Default: 8]",
        default=8,
        metavar="N"
    )
    parser.add_argument(
        "-l",
        "--lang-file",
        help="Language code and path to file that defines mappings from English recipe names to another language.",
        metavar="LANG=FILE",
        action="append"
    )
    args = parser.parse_args()

    # Load additional language files
    additional_languages = {}
    if args.lang_file:
        for f in args.lang_file:
            lang, path = f.split("=", 2)
            with open(path, mode="rt", encoding="utf-8") as fp:
                print(f"Loading additional language '{lang}' from: {path}")
                additional_languages[lang] = json.load(fp)

    loop = asyncio.get_event_loop()

    global FETCH_SEMAPHORE
    FETCH_SEMAPHORE = asyncio.Semaphore(int(args.concurrency), loop=loop)
    session = aiohttp.ClientSession(loop=loop)

    try:
        loop.run_until_complete(scrape_classes(session, additional_languages))
    except KeyboardInterrupt:
        pass
    finally:
        session.close()
        loop.close()

if __name__ == '__main__':
    main()
<commit_msg>Level 70 4-star mapping is actually 380.<commit_after>import signal
import argparse
import json
import re
import sys
from urllib.parse import urlunparse

import lxml.html as html
import asyncio
import aiohttp
import tqdm

# Restore default Ctrl-C handler for faster process shutdown
signal.signal(signal.SIGINT, signal.SIG_DFL)

RECIPE_LIST_URL = "http://na.finalfantasyxiv.com/lodestone/playguide/db/recipe/"

LANG_HOSTS = {
    "en": "na.finalfantasyxiv.com",
    "ja": "jp.finalfantasyxiv.com",
    "fr": "fr.finalfantasyxiv.com",
    "de": "de.finalfantasyxiv.com",
}

CLASSES = [
    "Carpenter",
    "Blacksmith",
    "Armorer",
    "Goldsmith",
    "Leatherworker",
    "Weaver",
    "Alchemist",
    "Culinarian",
]

# recipe level -> [ 0-star adjustment, 1-star adjustment, ... ]
LEVEL_DIFF = {
    # verified against app
    50: [ 0, 5, 20, 40, 60 ], # 50, 55, 70, 90, 110
    # unconfirmed from desynthesis results
    51: [ 69 ], # 120
    52: [ 73 ], # 125
    53: [ 77 ], # 130
    54: [ 79 ], # 133
    55: [ 81 ], # 136
    56: [ 83 ], # 139
    57: [ 85 ], # 142
    58: [ 87 ], # 145
    59: [ 89 ], # 148
    60: [ 90, 100, 120, 150, 190 ], # 150, 160, 180, 210, 250
    61: [ 199 ], # 260
    62: [ 203 ], # 265
    63: [ 207 ], # 270
    64: [ 209 ], # 273
    65: [ 211 ], # 276
    66: [ 213 ], # 279
    67: [ 215 ], # 282
    68: [ 217 ], # 285
    69: [ 219 ], # 288
    70: [ 220, 230, 250, 280, 310 ], # 290, 300, 320, 350, 380
}

LEVEL_RANGE = ["{0}-{1}".format(start, start + 4) for start in range(1, 70, 5)]
NUM_LEVEL_RANGES = len(LEVEL_RANGE)

EMBED_CODE_RE = re.compile("\\[db:recipe=([0-9a-f]+)]")

ASPECT_RE = re.compile("Aspect: (.+)")

FETCH_SEMAPHORE: asyncio.Semaphore

def logInfo(msg):
    print(msg, end="\n", file=sys.stderr)

def logError(msg):
    print(f"\nERROR: {msg}", end="\n", file=sys.stderr)

async def wait_with_progress(coros: list, desc: str = None, unit: str = None):
    for f in tqdm.tqdm(asyncio.as_completed(coros), total=len(coros), desc=desc, unit=unit):
        yield await f

async def fetch(session: aiohttp.ClientSession, url: str, **kwargs):
    err_count = 0
    while err_count < 5:
        try:
            async with FETCH_SEMAPHORE:
                async with session.get(url, **kwargs) as res:
                    if res.status == 429:
                        retry_after = int(res.headers["retry-after"] or '5')
                        asyncio.sleep(retry_after)
                        continue
                    elif res.status != 200:
                        raise Exception(f"{res.status} {res.reason}")
                    return await res.text()
        except SystemExit:
            raise
        except:
            err_count += 1
            asyncio.sleep(5)
            pass
    logError(f"Could not load page after 5 tries: {url}")
    raise SystemExit

def parse_recipe_links_page(text: str):
    tree = html.fromstring(text)
    rel_links = tree.xpath("//div/@data-ldst-href")
    links = map(str, rel_links)
    show_end = int(tree.xpath("//span[@class='show_end']/text()")[0])
    total = int(tree.xpath("//span[@class='total']/text()")[0])
    return links, show_end, total

async def fetch_recipe_links_page(session: aiohttp.ClientSession, cls: str, level_range: int, page: int):
    params = {
        "category2": CLASSES.index(cls),
        "category3": level_range,
        "page": page,
    }
    return parse_recipe_links_page(await fetch(session, RECIPE_LIST_URL, params=params))

async def fetch_recipe_links_range(session: aiohttp.ClientSession, cls: str, level_range: int):
    links = []
    page = 1
    while True:
        page_links, show_end, total = await fetch_recipe_links_page(session, cls, level_range, page)
        links += page_links
        if show_end < total:
            page += 1
        else:
            break
    return links

async def fetch_recipe_links(session: aiohttp.ClientSession, cls: str):
    results = wait_with_progress(
        [fetch_recipe_links_range(session, cls, level_range) for level_range in range(0, NUM_LEVEL_RANGES)],
        desc=f"Fetching {cls} links",
        unit=""
    )

    links = []
    async for r in results:
        links.extend(r)

    return links

def make_recipe_url(lang: str, rel_link: str):
    return urlunparse(("http", LANG_HOSTS[lang], rel_link, "", "", ""))

async def fetch_recipe_pages(session: aiohttp.ClientSession, rel_link: str):
    pages = {}

    for lang in LANG_HOSTS:
        pages[lang] = html.fromstring(await fetch(session, make_recipe_url(lang, rel_link)))

    return pages

async def fetch_recipe(session: aiohttp.ClientSession, rel_link: str):
    pages = await fetch_recipe_pages(session, rel_link)

    tree = pages["en"]

    embed_code = tree.xpath("//div[@class='embed_code_txt']//div[contains(text(), 'db:recipe')]/text()")[0]
    match = EMBED_CODE_RE.match(embed_code)
    if match is None:
        raise Exception("recipe id not found")
    recipe_id = match.group(1)

    detail_box = tree.xpath("//div[@class='recipe_detail item_detail_box']")[0]
    base_level = int(detail_box.xpath("//span[@class='db-view__item__text__level__num']/text()")[0])
    stars = None
    level_adjustment = 0
    if base_level in LEVEL_DIFF:
        stars = len(detail_box.xpath("//div[@class='db-view__item__text__level']//span[contains(@class, 'star')]"))
        try:
            level_adjustment = LEVEL_DIFF[base_level][stars]
        except IndexError:
            logError(f"Unsupported number of stars ({stars}) for level {base_level}.")
            raise SystemExit

    level = base_level + level_adjustment

    craft_data = tree.xpath("//ul[@class='db-view__recipe__craftdata']")[0]
    difficulty = int(craft_data.xpath("li[span='Difficulty']/text()")[0])
    durability = int(craft_data.xpath("li[span='Durability']/text()")[0])
    maxQuality = int(craft_data.xpath("li[span='Maximum Quality']/text()")[0])

    # Base level 51 recipes of difficulty 169 or 339 are adjusted to level 115
    # instead of the default 120 that other level 51 recipes are adjusted to.

    if ((base_level == 51 and (difficulty == 169 or difficulty == 339)) or
            (base_level == 61 and (difficulty == 1116 or difficulty == 558))):
        level -= 5
    if base_level == 60 and stars == 3 and difficulty == 1764:
        level += 10

    aspect = None

    craft_conditions = tree.xpath("//dl[@class='db-view__recipe__crafting_conditions']")[0]
    for characteristic in craft_conditions.xpath("dt[text()='Characteristics']/../dd/text()"):
        characteristic = characteristic.strip()
        match = ASPECT_RE.match(characteristic)
        if not match is None:
            aspect = match.group(1)

    recipe = {
        "id": recipe_id,
        "name": {},
        "baseLevel": base_level,
        "level": level,
        "difficulty": difficulty,
        "durability": durability,
        "maxQuality": maxQuality,
    }

    if stars:
        recipe["stars"] = stars

    if aspect:
        recipe["aspect"] = aspect

    for lang in LANG_HOSTS:
        tree = pages[lang]
        recipe["name"][lang] = str(tree.xpath("//h2[contains(@class,'db-view__item__text__name')]/text()")[0]).strip()

    return recipe

async def fetch_recipes(session: aiohttp.ClientSession, cls: str, links: list):
    recipes = wait_with_progress(
        [fetch_recipe(session, link) for link in links],
        desc=f"Fetching {cls} recipes",
        unit=""
    )

    return [r async for r in recipes]

async def fetch_class(session: aiohttp.ClientSession, additional_languages: dict, cls: str):
    links = await fetch_recipe_links(session, cls)
    recipes = await fetch_recipes(session, cls, links)
    recipes.sort(key=lambda r: (r['level'], r['name']['en']))
    for recipe in recipes:
        for lang in additional_languages.keys():
            names = additional_languages[lang]
            english_name = recipe['name']['en']
            recipe['name'][lang] = names.get(english_name) or english_name
    return recipes

async def scrape_to_file(session: aiohttp.ClientSession, additional_languages: dict, cls: str):
    recipes = await fetch_class(session, additional_languages, cls)
    with open("out/" + cls + ".json", mode="wt", encoding="utf-8") as db_file:
        json.dump(recipes, db_file, indent=2, sort_keys=True, ensure_ascii=False)

async def scrape_classes(session: aiohttp.ClientSession, additional_languages: dict):
    for cls in CLASSES:
        await scrape_to_file(session, additional_languages, cls)

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-c",
        "--concurrency",
        help="Max number of concurrent requests to Lodestone servers. [Default: 8]",
        default=8,
        metavar="N"
    )
    parser.add_argument(
        "-l",
        "--lang-file",
        help="Language code and path to file that defines mappings from English recipe names to another language.",
        metavar="LANG=FILE",
        action="append"
    )
    args = parser.parse_args()

    # Load additional language files
    additional_languages = {}
    if args.lang_file:
        for f in args.lang_file:
            lang, path = f.split("=", 2)
            with open(path, mode="rt", encoding="utf-8") as fp:
                print(f"Loading additional language '{lang}' from: {path}")
                additional_languages[lang] = json.load(fp)

    loop = asyncio.get_event_loop()

    global FETCH_SEMAPHORE
    FETCH_SEMAPHORE = asyncio.Semaphore(int(args.concurrency), loop=loop)
    session = aiohttp.ClientSession(loop=loop)

    try:
        loop.run_until_complete(scrape_classes(session, additional_languages))
    except KeyboardInterrupt:
        pass
    finally:
        session.close()
        loop.close()

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before>from common import  emailIp

<commit_msg>changed Gmail account<commit_after>print "!!!!!!!!!!!!!!!!!!"

from common import  emailIp

<|endoftext|>


<commit_before>#!/usr/bin/env python
import RPi.GPIO as GPIO
import datetime
import logging
import requests
import settings
import time
import threading

class Pin(object):
    URL = settings.API_URL + settings.NAME + '/'

    def post(self, data):
            logging.debug('Ready to send a POST request for {url} with data {data}'.format(url=self.relative_url, data=data))
            data['api_key'] = settings.API_KEY
            r = requests.post(self.URL + self.relative_url, data=data)
            logging.debug('POST Request sent with response {response}'.format(response=r.text))

class Coffee(Pin):
    pots = 0

    relative_url = 'coffee'

    def __init__(self, notipi, PIN):
        self.notipi = notipi
        self.PIN = PIN

        self.day = datetime.date.today()
        GPIO.setup(self.PIN, GPIO.IN, pull_up_down=settings.COFFEE_PUD)
        # Running in it's own thread
        GPIO.add_event_detect(self.PIN, GPIO.RISING, callback=self.update, bouncetime=5000)
        logging.info('Coffee button is ready')

    def update(self, signal):
        today = datetime.date.today()
        if today > self.day:
            self.pots = 0
            self.day = today
        self.pots += 1
        self.notipi.blink(2)
        # Date formatted like '06. October 2014 13:13:19'
        coffee_date = datetime.datetime.now().strftime('%d. %B %Y %H:%M:%S')
        self.post({'pots': self.pots, 'datetime': coffee_date})
        time.sleep(1)
        self.notipi.blink(2)
        logging.info('New coffee pot at {date}'.format(date=datetime.datetime.now()))

class Light(Pin):
    relative_url = 'status'
    interval = 60 * 30  # 30min

    def __init__(self, notipi, PIN):
        self.notipi = notipi
        self.PIN = PIN
        self.status = None

        GPIO.setup(self.PIN, GPIO.IN)
        # Running in it's own thread
        GPIO.add_event_detect(self.PIN, GPIO.BOTH, callback=self.update)
        # Update once every hour too
        self.periodic_update()
        logging.info('Light sensor is ready')

    def update(self, signal=0):
        time.sleep(0.2)
        if GPIO.input(self.PIN) == settings.LIGHT_DIRECTION:
            status = 'true'
        else:
            status = 'false'
        # Only update if status has changed
        if self.status != status:
            self.status = status
            self.post({'status': status})
            self.notipi.blink()
            logging.debug('Light status changed to {status}'.format(status=self.status))

    def periodic_update(self):
        self.update()
        threading.Timer(self.interval, self.periodic_update).start()

class Led(Pin):
    def __init__(self, notipi, PIN):
        self.notipi = notipi
        self.PIN = PIN

        GPIO.setup(self.PIN, GPIO.OUT)
        logging.info('LED is ready')

    def blink(self, n=1):
        for _ in range(n):
            GPIO.output(self.PIN, False)
            time.sleep(0.3)
            GPIO.output(self.PIN, True)
            time.sleep(0.3)
        logging.debug('LED blinked {times} time(s)'.format(times=n))

class Notipi(object):
    def __init__(self):
        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        self.led = Led(self, settings.LED_PIN)
        self.coffee = Coffee(self, settings.BUTTON_PIN)
        self.light = Light(self, settings.LIGHT_PIN)
        self.blink(5)

    def blink(self, *args, **kwargs):
        self.led.blink(*args, **kwargs)

def main():
    # Logging
    log_level = logging.DEBUG if settings.DEBUG else logging.INFO
    logging.basicConfig(format='%(asctime)s %(message)s', level=log_level)

    logging.info('Starting NotiPi')
    notipi = Notipi()
    logging.info('NotPi handlers started')
    # Wait forever
    while True:
        time.sleep(1)

if __name__ == '__main__':
    main()
<commit_msg>Remove legacy coffee code<commit_after>#!/usr/bin/env python
import RPi.GPIO as GPIO
import datetime
import logging
import requests
import settings
import time
import threading

class Pin(object):
    URL = settings.API_URL + settings.NAME + '/'

    def post(self, data=None):
            logging.debug('Ready to send a POST request for {url} with data {data}'.format(url=self.relative_url, data=data))
            data['api_key'] = settings.API_KEY
            r = requests.post(self.URL + self.relative_url, data=data)
            logging.debug('POST Request sent with response {response}'.format(response=r.text))

class Coffee(Pin):
    pots = 0

    relative_url = 'coffee'

    def __init__(self, notipi, PIN):
        self.notipi = notipi
        self.PIN = PIN

        GPIO.setup(self.PIN, GPIO.IN, pull_up_down=settings.COFFEE_PUD)
        # Running in it's own thread
        GPIO.add_event_detect(self.PIN, GPIO.RISING, callback=self.update, bouncetime=5000)
        logging.info('Coffee button is ready')

    def update(self, signal):
        self.notipi.blink(2)
        self.post()
        time.sleep(1)
        self.notipi.blink(2)
        logging.info('New coffee pot at {date}'.format(date=datetime.datetime.now()))

class Light(Pin):
    relative_url = 'status'
    interval = 60 * 30  # 30min

    def __init__(self, notipi, PIN):
        self.notipi = notipi
        self.PIN = PIN
        self.status = None

        GPIO.setup(self.PIN, GPIO.IN)
        # Running in it's own thread
        GPIO.add_event_detect(self.PIN, GPIO.BOTH, callback=self.update)
        # Update once every hour too
        self.periodic_update()
        logging.info('Light sensor is ready')

    def update(self, signal=0):
        time.sleep(0.2)
        if GPIO.input(self.PIN) == settings.LIGHT_DIRECTION:
            status = 'true'
        else:
            status = 'false'
        # Only update if status has changed
        if self.status != status:
            self.status = status
            self.post({'status': status})
            self.notipi.blink()
            logging.debug('Light status changed to {status}'.format(status=self.status))

    def periodic_update(self):
        self.update()
        threading.Timer(self.interval, self.periodic_update).start()

class Led(Pin):
    def __init__(self, notipi, PIN):
        self.notipi = notipi
        self.PIN = PIN

        GPIO.setup(self.PIN, GPIO.OUT)
        logging.info('LED is ready')

    def blink(self, n=1):
        for _ in range(n):
            GPIO.output(self.PIN, False)
            time.sleep(0.3)
            GPIO.output(self.PIN, True)
            time.sleep(0.3)
        logging.debug('LED blinked {times} time(s)'.format(times=n))

class Notipi(object):
    def __init__(self):
        GPIO.setmode(GPIO.BCM)
        GPIO.setwarnings(False)
        self.led = Led(self, settings.LED_PIN)
        self.coffee = Coffee(self, settings.BUTTON_PIN)
        self.light = Light(self, settings.LIGHT_PIN)
        self.blink(5)

    def blink(self, *args, **kwargs):
        self.led.blink(*args, **kwargs)

def main():
    # Logging
    log_level = logging.DEBUG if settings.DEBUG else logging.INFO
    logging.basicConfig(format='%(asctime)s %(message)s', level=log_level)

    logging.info('Starting NotiPi')
    notipi = Notipi()
    logging.info('NotPi handlers started')
    # Wait forever
    while True:
        time.sleep(1)

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before>#!/usr/bin/env python3
#
# Copyright 2016 Red Hat, Inc.
#
# Authors:
#     Fam Zheng <famz@redhat.com>
#
# This work is licensed under the MIT License.  Please see the LICENSE file or
# http://opensource.org/licenses/MIT.

import email
import email.utils
import email.header
import datetime
import re

def _parse_header(header):
    r = ''
    for h, c in email.header.decode_header(header):
        if isinstance(h, bytes):
            h = h.decode(c or 'utf-8')
        r += h
    if '\n' in r:
        r = " ".join([x.strip() for x in r.splitlines()])
    return r

def parse_address(addr_str):
    name, addr = email.utils.parseaddr(_parse_header(addr_str))
    return name, addr

def _addr_fmt_text(name, addr):
    if name:
        return "%s <%s>" % (name, addr)
    else:
        return addr

class MboxMessage(object):
    """ Helper class to process mbox """
    def __init__(self, m):
        self._m = email.message_from_string(m)
        self._status = {}
        self._mbox = m

    def get_mbox(self):
        return self._mbox

    def get_subject(self, upper=False, strip_tags=False, suppress_re=None):
        """Process and return subject of the message.
           upper: convert subject to upper case.
           strip_tags: remove all the leading [xxx] tags
           suppress_re: a subject str to compare to, if ours is the same or
           only to prepend Re:, return an empty str"""
        def do_strip_tags(t):
            diff_stats = t.strip()
            while t.startswith("[") and "]" in t:
                t = t[t.find("]") + 1:].strip()
            return t
        r = _parse_header(self._m['subject'])
        if upper:
            r = r.upper()
        if strip_tags:
            r = do_strip_tags(r)

        if suppress_re:
            while len(suppress_re) < len(r) and r.upper().startswith("RE:"):
                r = r[3:].strip()
            if suppress_re == r:
                return "Re: ..."
        return r

    def get_from(self, text=False):
        name, addr = parse_address(self._m['from'])
        name = name or addr
        if text:
            return _addr_fmt_text(name, addr)
        return name, addr

    def _get_addr_list(self, field, text):
        ret = []
        f = self._m.get_all(field, [])
        addrs = email.utils.getaddresses(f)
        for name, addr in addrs:
            name = name or addr
            if text:
                ret.append(_addr_fmt_text(name, addr))
            else:
                ret.append((name, addr))
        if text:
            ret = ", ".join(ret)
        return ret

    def get_to(self, text=False):
        return self._get_addr_list("to", text)

    def get_cc(self, text=False):
        return self._get_addr_list("cc", text)

    def trim_message_id(self, msgid):
        if msgid and msgid.startswith("<") and msgid.endswith(">"):
            msgid = msgid[1:-1]
        return msgid

    def get_in_reply_to(self):
        return self.trim_message_id(self._m['in-reply-to'])

    def get_date(self, timestamp=False):
        tup = email.utils.parsedate_tz(self._m['date'])
        if tup:
            stamp = email.utils.mktime_tz(tup)
            if timestamp:
                return stamp
            return datetime.datetime.utcfromtimestamp(stamp)

    def get_message_id(self):
        return self.trim_message_id(self._m['message-id'])

    def get_prefixes(self, upper=False):
        """Return tags extracted from the leading "[XXX] [YYY ZZZ]... in subject"""
        r = []
        s = self.get_subject(upper=upper)
        while s.startswith('[') and ']' in s:
            t = s[1:s.find(']')]
            for k in t.split(' '):
                r.append(k)
            if ']' in s:
                s = s[s.find(']') + 1:].strip()
        return r

    def get_version(self):
        v = 1
        for tag in self.get_prefixes(True):
            if tag.startswith("V"):
                try:
                    v = int(tag[1:])
                except:
                    pass
        return v

    def find_tags(self, *tags):
        """Return intersection of *tags is present in this message"""
        s = set([x.upper() for x in tags])
        return s.intersection(self.get_prefixes(upper=True))

    def get_body(self):
        def decode_payload(payload, charset):
            try:
                return payload.decode(charset or 'utf-8')
            except:
                if charset != 'utf-8':
                    # Still fall back from non-utf-8 to utf-8
                    return payload.decode('utf-8')
                else:
                    raise
        payload = self._m.get_payload(decode=not self._m.is_multipart())
        body = ''
        if isinstance(payload, bytes):
            body = decode_payload(payload, self._m.get_content_charset())
        elif isinstance(payload, list):
            for p in payload:
                if p.get_content_type() == "text/plain":
                    body += decode_payload(p.get_payload(decode=True),
                                           p.get_content_charset())
        else:
            return "<Error while getting message body: %s>" % payload
        return body

    def get_preview(self, maxchar=1000):
        r = ""
        quote = False
        for l in self.get_body().splitlines():
            if l.startswith(">"):
                if not quote:
                    r += "..."
                    quote = True
                continue
            quote = False
            r += l
            if len(r) >= 1000:
                break
        return r

    def _find_line(self, pattern):
        rexp = re.compile(pattern)
        for l in self.get_body().splitlines():
            if rexp.match(l):
                return l

    def get_reviewed_by(self):
        """Try to find a "Reviewed-by:" line in message body"""
        prefix = "Reviewed-by:"
        r = self._find_line("^" + prefix + ".*>$")
        if r:
            return email.utils.parseaddr(r[len(prefix):].strip())
        else:
            return None

    def get_num(self):
        cur, total = None, None
        for tag in self.get_prefixes():
            if '/' in tag:
                n, m = tag.split('/')
                try:
                    cur, total = int(n), int(m)
                    break
                except:
                    pass
        return cur, total

    def is_reply(self):
        return self.get_subject(upper=True, strip_tags=True).startswith("RE:")

    def set_status(self, st, val):
        self._status[st] = val

    def get_status(self, st, default=None):
        return self._status.get(st, default)

    def get_status_by_prefix(self, pref):
        return dict([(k, v) for k, v in self._status.items() if k.startswith(pref)])

    def _has_lines(self, text, *lines):
        i = 0
        for l in text.splitlines():
            if i == len(lines):
                break
            if l.startswith(lines[i]):
                i += 1
        return i == len(lines)

    def is_patch(self):
        """ Return true if the email body is a patch """
        body = self.get_body()
        if self.get_subject().startswith("Re:"):
            return False
        return self._has_lines(body,
                               "---",
                               "diff ",
                               "index ",
                               "---",
                               "+++",
                               "@@")

    def is_series_head(self):
        """Create and return a Series from Message if it is one, otherwise
        return None"""
        c, t = self.get_num()
        if (c, t) == (None, None) and self.is_patch():
            return True
        if c == 0:
            return True
        return False
<commit_msg>mbox: Tolerate invalid bytes in the message<commit_after>#!/usr/bin/env python3
#
# Copyright 2016 Red Hat, Inc.
#
# Authors:
#     Fam Zheng <famz@redhat.com>
#
# This work is licensed under the MIT License.  Please see the LICENSE file or
# http://opensource.org/licenses/MIT.

import email
import email.utils
import email.header
import datetime
import re

def _parse_header(header):
    r = ''
    for h, c in email.header.decode_header(header):
        if isinstance(h, bytes):
            h = h.decode(c or 'utf-8')
        r += h
    if '\n' in r:
        r = " ".join([x.strip() for x in r.splitlines()])
    return r

def parse_address(addr_str):
    name, addr = email.utils.parseaddr(_parse_header(addr_str))
    return name, addr

def _addr_fmt_text(name, addr):
    if name:
        return "%s <%s>" % (name, addr)
    else:
        return addr

class MboxMessage(object):
    """ Helper class to process mbox """
    def __init__(self, m):
        self._m = email.message_from_string(m)
        self._status = {}
        self._mbox = m

    def get_mbox(self):
        return self._mbox

    def get_subject(self, upper=False, strip_tags=False, suppress_re=None):
        """Process and return subject of the message.
           upper: convert subject to upper case.
           strip_tags: remove all the leading [xxx] tags
           suppress_re: a subject str to compare to, if ours is the same or
           only to prepend Re:, return an empty str"""
        def do_strip_tags(t):
            diff_stats = t.strip()
            while t.startswith("[") and "]" in t:
                t = t[t.find("]") + 1:].strip()
            return t
        r = _parse_header(self._m['subject'])
        if upper:
            r = r.upper()
        if strip_tags:
            r = do_strip_tags(r)

        if suppress_re:
            while len(suppress_re) < len(r) and r.upper().startswith("RE:"):
                r = r[3:].strip()
            if suppress_re == r:
                return "Re: ..."
        return r

    def get_from(self, text=False):
        name, addr = parse_address(self._m['from'])
        name = name or addr
        if text:
            return _addr_fmt_text(name, addr)
        return name, addr

    def _get_addr_list(self, field, text):
        ret = []
        f = self._m.get_all(field, [])
        addrs = email.utils.getaddresses(f)
        for name, addr in addrs:
            name = name or addr
            if text:
                ret.append(_addr_fmt_text(name, addr))
            else:
                ret.append((name, addr))
        if text:
            ret = ", ".join(ret)
        return ret

    def get_to(self, text=False):
        return self._get_addr_list("to", text)

    def get_cc(self, text=False):
        return self._get_addr_list("cc", text)

    def trim_message_id(self, msgid):
        if msgid and msgid.startswith("<") and msgid.endswith(">"):
            msgid = msgid[1:-1]
        return msgid

    def get_in_reply_to(self):
        return self.trim_message_id(self._m['in-reply-to'])

    def get_date(self, timestamp=False):
        tup = email.utils.parsedate_tz(self._m['date'])
        if tup:
            stamp = email.utils.mktime_tz(tup)
            if timestamp:
                return stamp
            return datetime.datetime.utcfromtimestamp(stamp)

    def get_message_id(self):
        return self.trim_message_id(self._m['message-id'])

    def get_prefixes(self, upper=False):
        """Return tags extracted from the leading "[XXX] [YYY ZZZ]... in subject"""
        r = []
        s = self.get_subject(upper=upper)
        while s.startswith('[') and ']' in s:
            t = s[1:s.find(']')]
            for k in t.split(' '):
                r.append(k)
            if ']' in s:
                s = s[s.find(']') + 1:].strip()
        return r

    def get_version(self):
        v = 1
        for tag in self.get_prefixes(True):
            if tag.startswith("V"):
                try:
                    v = int(tag[1:])
                except:
                    pass
        return v

    def find_tags(self, *tags):
        """Return intersection of *tags is present in this message"""
        s = set([x.upper() for x in tags])
        return s.intersection(self.get_prefixes(upper=True))

    def get_body(self):
        def decode_payload(payload, charset):
            try:
                return payload.decode(charset or 'utf-8',
                                      errors='backslashreplace')
            except:
                if charset != 'utf-8':
                    # Still fall back from non-utf-8 to utf-8
                    return payload.decode('utf-8')
                else:
                    raise
        payload = self._m.get_payload(decode=not self._m.is_multipart())
        body = ''
        if isinstance(payload, bytes):
            body = decode_payload(payload, self._m.get_content_charset())
        elif isinstance(payload, list):
            for p in payload:
                if p.get_content_type() == "text/plain":
                    body += decode_payload(p.get_payload(decode=True),
                                           p.get_content_charset())
        else:
            return "<Error while getting message body: %s>" % payload
        return body

    def get_preview(self, maxchar=1000):
        r = ""
        quote = False
        for l in self.get_body().splitlines():
            if l.startswith(">"):
                if not quote:
                    r += "..."
                    quote = True
                continue
            quote = False
            r += l
            if len(r) >= 1000:
                break
        return r

    def _find_line(self, pattern):
        rexp = re.compile(pattern)
        for l in self.get_body().splitlines():
            if rexp.match(l):
                return l

    def get_reviewed_by(self):
        """Try to find a "Reviewed-by:" line in message body"""
        prefix = "Reviewed-by:"
        r = self._find_line("^" + prefix + ".*>$")
        if r:
            return email.utils.parseaddr(r[len(prefix):].strip())
        else:
            return None

    def get_num(self):
        cur, total = None, None
        for tag in self.get_prefixes():
            if '/' in tag:
                n, m = tag.split('/')
                try:
                    cur, total = int(n), int(m)
                    break
                except:
                    pass
        return cur, total

    def is_reply(self):
        return self.get_subject(upper=True, strip_tags=True).startswith("RE:")

    def set_status(self, st, val):
        self._status[st] = val

    def get_status(self, st, default=None):
        return self._status.get(st, default)

    def get_status_by_prefix(self, pref):
        return dict([(k, v) for k, v in self._status.items() if k.startswith(pref)])

    def _has_lines(self, text, *lines):
        i = 0
        for l in text.splitlines():
            if i == len(lines):
                break
            if l.startswith(lines[i]):
                i += 1
        return i == len(lines)

    def is_patch(self):
        """ Return true if the email body is a patch """
        body = self.get_body()
        if self.get_subject().startswith("Re:"):
            return False
        return self._has_lines(body,
                               "---",
                               "diff ",
                               "index ",
                               "---",
                               "+++",
                               "@@")

    def is_series_head(self):
        """Create and return a Series from Message if it is one, otherwise
        return None"""
        c, t = self.get_num()
        if (c, t) == (None, None) and self.is_patch():
            return True
        if c == 0:
            return True
        return False
<|endoftext|>


<commit_before># Copyright (c) 2017 Austin Bowen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

'''mcdl.py - A script for downloading pre-built Minecraft software'''

__filename__ = 'mcdl.py'
__version__  = '0.3.0'
__author__   = 'Austin Bowen <austin.bowen.314@gmail.com>'

import os
import requests
from hashlib import sha1
from packaging.version import parse as parse_version
from progress.bar import IncrementalBar
from six import print_
from terminaltables import AsciiTable
from time import time as wall_time

# Return codes
SUCCESS = 0
WARN_FILE_NOT_NEW     =  1
ERROR_INVALID_ARGS    = -1
ERROR_FILE_PERMS      = -2
ERROR_DOWNLOAD_FAILED = -3

HTTP_USER_AGENT = __filename__+'/'+__version__

def cmd_get(*args):
    '''Handles command: get <project> <file> [dest]'''
    # Get the project name
    try:
        project = args[0]
    except IndexError:
        print_('ERROR: No project given\n')
        print_projects()
        return ERROR_INVALID_ARGS

    # Get the project file name
    try:
        project_file_name = args[1]
    except IndexError:
        print_('ERROR: No project file given')
        return ERROR_INVALID_ARGS

    # Get the project file destination
    try:
        file_dest = args[2]
    except IndexError:
        file_dest = os.curdir
    if os.path.isdir(file_dest):
        file_dest = os.path.join(file_dest, project_file_name)

    # Get project files
    project_files = get_project_files(project)
    # Project DNE?
    if (project_files == None):
        print_('ERROR: Project "'+project+'" does not exist\n')
        print_projects()
        return ERROR_INVALID_ARGS

    # Get project file
    project_file = get_project_file_named(project_file_name, project_files)
    # Project file DNE?
    if (project_file == None):
        print_('ERROR: {} file "{}" does not exist'.format(
            project, project_file_name))
        return ERROR_INVALID_ARGS

    # Download and save the project file and return the result
    return download_project_file(project_file, file_dest)

def cmd_list(*args):
    '''Handles command: list <project>'''
    # Get project
    try:
        project = args[0]
    except IndexError:
        print_('ERROR: No project given\n')
        print_projects()
        return ERROR_INVALID_ARGS

    # Get project files
    project_files = get_project_files(project)
    # Project DNE?
    if (project_files == None):
        print_('ERROR: Project "'+project+'" does not exist\n')
        print_projects()
        return ERROR_INVALID_ARGS

    # Sort project files by version ascending
    project_files = sorted(project_files,
        key=lambda pf: parse_version(pf['name']))

    # Build and print table of files
    rows = [[
        '{} Files'.format(project.title()),
        'MC Ver.',
        'Size',
    ]]
    for project_file in project_files:
        rows.append([
            project_file['name'],
            project_file['version']['minecraft'],
            project_file['size']['human'],
        ])
    table = AsciiTable(rows)
    table.outer_border = False
    table.padding_left = table.padding_right = 2
    print_(table.table)

    return SUCCESS

def download_project_file(project_file, file_dest):
    '''Downloads the project file content and saves it to the destination.
    If the destination is a directory and not a file name, then the project
    file content is saved to a file named using the project file name.
    If the project file is not newer than the file at the destination, then
    the project file content is not downloaded.

    Prints progress and errors.

    Returns: SUCCESS, ERROR_FILE_PERMS, or ERROR_DOWNLOAD_FAILED.
    '''
    # File destination is a directory?  Append the project file name.
    if os.path.isdir(file_dest):
        file_dest = os.path.join(file_dest, project_file['name'])

    # Do not have write permission for the file destination?
    if not os.access(os.path.dirname(os.path.abspath(file_dest)), os.W_OK):
        print_('ERROR: Do not have write permission for destination "{}"'.format(
            file_dest))
        return ERROR_FILE_PERMS

    # Return if the hashes match, implying no change
    try:
        # Get hash of local file
        with open(file_dest, 'rb') as f:
            file_hash = sha1(f.read()).hexdigest().casefold()

        # Hashes match?
        if (file_hash == project_file['hashes']['sha1'].casefold()):
            print_('File "'+file_dest+'" is already up-to-date')
            return WARN_FILE_NOT_NEW
        del file_hash
    except FileNotFoundError:
        pass

    # Set up HTTP request headers
    headers = {
        'User-Agent': HTTP_USER_AGENT,
    }

    # Start downloading the project file data
    print_('Downloading {} file "{}"...  '.format(
        project_file['project'], project_file['name']))
    req = requests.get(project_file['urls']['free'],
        headers=headers, stream=True)
    del headers

    # Failed to create request?
    if not req:
        print_('ERROR: Download failed (HTTP status code '+\
            req.status_code+')')
        return ERROR_DOWNLOAD_FAILED

    # Print the progress
    project_file_data = bytearray()
    bar = IncrementalBar(' ', max=project_file['size']['bytes'],
        suffix='%(percent)d%% of '+project_file['size']['human']+\
            ' (ETA %(eta_td)s)')
    t0 = 0
    for chunk in req.iter_content(chunk_size=8192):
        # Add new chunk to the project file data
        project_file_data.extend(chunk)

        # Update progress bar every 0.5 seconds or at end of download
        t1 = wall_time()
        if ((t1-t0) >= 0.5 or len(chunk) < 8192):
            bar.goto(len(project_file_data))
            t0 = t1
    bar.finish()
    req.close()
    del bar, req, t0, t1

    # Make sure the downloaded project file hash matches the expected hash
    actual_hash   = sha1(project_file_data).hexdigest().casefold()
    expected_hash = project_file['hashes']['sha1'].casefold()
    if (actual_hash != expected_hash):
        print_('WARNING: Downloaded file\'s SHA-1 hash value does not match'+\
            ' the expected hash value')
    del actual_hash, expected_hash

    # Save the project file to the destination
    print_('Saving to file "'+file_dest+'"...  ', end='', flush=True)
    with open(file_dest, 'wb') as f:
        f.write(project_file_data)
    print_('Done.')

    return SUCCESS

def get_project_file_named(name, project_files):
    '''Returns the project file for the given project, or None if either
    the project or project file does not exist.
    '''
    for project_file in project_files:
        if (project_file['name'] == name): return project_file
    return None

def get_project_files(project):
    '''Returns a list of files available for the given project,
    or None if the project does not exist.

    Example:
    >>> get_project_files('spigot')
    [
        {
            'name'   : 'spigot-latest.jar',
            'project': 'Spigot',
            'version': {
                'minecraft': '...'
            },
            'size': {
                'human': '23.40MB',
                'bytes': 24539208
            },
            'date': {
                'human': 'March 24, 2017',
                'epoch': 1493893504,
            },
            'urls': {
                'paid': 'http://...',
                'free': 'http://...'
            }
        },
        ...,
    ]
    '''
    # Project DNE?
    if not project_exists(project): return None

    # Download list of project files
    headers = {'User-Agent': HTTP_USER_AGENT}
    req = requests.get('https://yivesmirror.com/api/'+project, headers=headers)
    req_json = req.json()
    req.close(); del req

    # Build and return list of project files
    project_files = []
    for entry in req_json:
        project_file = list(entry.values())[0]
        project_file['project'] = project
        project_files.append(project_file)
    return project_files

def get_projects():
    '''Returns the list of available projects.'''
    # Return previously-existing list of projects, if it exists
    global projects
    try:
        return projects
    except NameError:
        pass

    # Download list of projects
    headers = {'User-Agent': HTTP_USER_AGENT}
    req = requests.get('https://yivesmirror.com/api/invalid', headers=headers)
    req_json = req.json()
    req.close(); del req

    # Read list of projects
    projects = req_json['validSoftwares']
    projects.sort()
    return projects

def print_projects():
    print_('Projects: {}'.format(' '.join(get_projects())))

def project_exists(project):
    '''Returns True if the given project is available.'''
    return project.casefold() in {p.casefold() for p in get_projects()}

def main():
    import sys

    # Get command
    try:
        cmd = sys.argv[1].casefold()
    except IndexError:
        cmd = None

    # Command not given or not recognized?
    if cmd not in {'get', 'list'}:
        # Command not recognized?
        if (cmd != None): print_('ERROR: Unrecognized command "'+cmd+'"')

        # Print usage
        filename = os.path.basename(sys.argv[0])
        print_('Usage:')
        print_('  {} get  <project> <file> [dest]'.format(filename) +\
               '  Download the project file')
        print_('  {} list <project>              '.format(filename) +\
               '  List the project files')

        print_()
        print_projects()

        print_('\nExample: Downloading a Spigot 1.12 snapshot')
        print_('  $ {} get spigot spigot-1.12-R0.1-SNAPSHOT-b1372.jar'.format(
            filename))

        print_('\nReturn Codes:')
        return_codes = (
            (SUCCESS,               'Success'),
            (WARN_FILE_NOT_NEW,     'Download unnecessary'),
            (ERROR_INVALID_ARGS,    'Invalid arguments'),
            (ERROR_FILE_PERMS,      'File access permission error'),
            (ERROR_DOWNLOAD_FAILED, 'Download failed'),
        )
        for return_code in return_codes:
            print_('{:4}: {}'.format(*return_code))
        del return_codes

        print_('\nDownloads hosted by '+\
            'Yive\'s Mirror (no affiliation): https://yivesmirror.com/')
        print_('View project source on GitHub: https://github.com/SaltyHash/mcdl')

        sys.exit(SUCCESS if (cmd == None) else ERROR_INVALID_ARGS)

    # Get the handler function for the command and execute it
    try:
        cmd_func = globals()['cmd_'+cmd.replace('-', '_')]
    except KeyError:
        raise RuntimeError(
            'Failed to find handler function for command "'+cmd+'"')
    result = cmd_func(*sys.argv[2:])
    sys.exit(result)

if (__name__ == '__main__'):
    main()
<commit_msg>Version bump to 0.4.0<commit_after># Copyright (c) 2017 Austin Bowen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

'''mcdl.py - A script for downloading pre-built Minecraft software'''

__filename__ = 'mcdl.py'
__version__  = '0.4.0'
__author__   = 'Austin Bowen <austin.bowen.314@gmail.com>'

import os
import requests
from hashlib import sha1
from packaging.version import parse as parse_version
from progress.bar import IncrementalBar
from six import print_
from terminaltables import AsciiTable
from time import time as wall_time

# Return codes
SUCCESS = 0
WARN_FILE_NOT_NEW     =  1
ERROR_INVALID_ARGS    = -1
ERROR_FILE_PERMS      = -2
ERROR_DOWNLOAD_FAILED = -3

HTTP_USER_AGENT = __filename__+'/'+__version__

def cmd_get(*args):
    '''Handles command: get <project> <file> [dest]'''
    # Get the project name
    try:
        project = args[0]
    except IndexError:
        print_('ERROR: No project given\n')
        print_projects()
        return ERROR_INVALID_ARGS

    # Get the project file name
    try:
        project_file_name = args[1]
    except IndexError:
        print_('ERROR: No project file given')
        return ERROR_INVALID_ARGS

    # Get the project file destination
    try:
        file_dest = args[2]
    except IndexError:
        file_dest = os.curdir
    if os.path.isdir(file_dest):
        file_dest = os.path.join(file_dest, project_file_name)

    # Get project files
    project_files = get_project_files(project)
    # Project DNE?
    if (project_files == None):
        print_('ERROR: Project "'+project+'" does not exist\n')
        print_projects()
        return ERROR_INVALID_ARGS

    # Get project file
    project_file = get_project_file_named(project_file_name, project_files)
    # Project file DNE?
    if (project_file == None):
        print_('ERROR: {} file "{}" does not exist'.format(
            project, project_file_name))
        return ERROR_INVALID_ARGS

    # Download and save the project file and return the result
    return download_project_file(project_file, file_dest)

def cmd_list(*args):
    '''Handles command: list <project>'''
    # Get project
    try:
        project = args[0]
    except IndexError:
        print_('ERROR: No project given\n')
        print_projects()
        return ERROR_INVALID_ARGS

    # Get project files
    project_files = get_project_files(project)
    # Project DNE?
    if (project_files == None):
        print_('ERROR: Project "'+project+'" does not exist\n')
        print_projects()
        return ERROR_INVALID_ARGS

    # Sort project files by version ascending
    project_files = sorted(project_files,
        key=lambda pf: parse_version(pf['name']))

    # Build and print table of files
    rows = [[
        '{} Files'.format(project.title()),
        'MC Ver.',
        'Size',
    ]]
    for project_file in project_files:
        rows.append([
            project_file['name'],
            project_file['version']['minecraft'],
            project_file['size']['human'],
        ])
    table = AsciiTable(rows)
    table.outer_border = False
    table.padding_left = table.padding_right = 2
    print_(table.table)

    return SUCCESS

def download_project_file(project_file, file_dest):
    '''Downloads the project file content and saves it to the destination.
    If the destination is a directory and not a file name, then the project
    file content is saved to a file named using the project file name.
    If the project file is not newer than the file at the destination, then
    the project file content is not downloaded.

    Prints progress and errors.

    Returns: SUCCESS, ERROR_FILE_PERMS, or ERROR_DOWNLOAD_FAILED.
    '''
    # File destination is a directory?  Append the project file name.
    if os.path.isdir(file_dest):
        file_dest = os.path.join(file_dest, project_file['name'])

    # Do not have write permission for the file destination?
    if not os.access(os.path.dirname(os.path.abspath(file_dest)), os.W_OK):
        print_('ERROR: Do not have write permission for destination "{}"'.format(
            file_dest))
        return ERROR_FILE_PERMS

    # Return if the hashes match, implying no change
    try:
        # Get hash of local file
        with open(file_dest, 'rb') as f:
            file_hash = sha1(f.read()).hexdigest().casefold()

        # Hashes match?
        if (file_hash == project_file['hashes']['sha1'].casefold()):
            print_('File "'+file_dest+'" is already up-to-date')
            return WARN_FILE_NOT_NEW
        del file_hash
    except FileNotFoundError:
        pass

    # Set up HTTP request headers
    headers = {
        'User-Agent': HTTP_USER_AGENT,
    }

    # Start downloading the project file data
    print_('Downloading {} file "{}"...  '.format(
        project_file['project'], project_file['name']))
    req = requests.get(project_file['urls']['free'],
        headers=headers, stream=True)
    del headers

    # Failed to create request?
    if not req:
        print_('ERROR: Download failed (HTTP status code '+\
            req.status_code+')')
        return ERROR_DOWNLOAD_FAILED

    # Print the progress
    project_file_data = bytearray()
    bar = IncrementalBar(' ', max=project_file['size']['bytes'],
        suffix='%(percent)d%% of '+project_file['size']['human']+\
            ' (ETA %(eta_td)s)')
    t0 = 0
    for chunk in req.iter_content(chunk_size=8192):
        # Add new chunk to the project file data
        project_file_data.extend(chunk)

        # Update progress bar every 0.5 seconds or at end of download
        t1 = wall_time()
        if ((t1-t0) >= 0.5 or len(chunk) < 8192):
            bar.goto(len(project_file_data))
            t0 = t1
    bar.finish()
    req.close()
    del bar, req, t0, t1

    # Make sure the downloaded project file hash matches the expected hash
    actual_hash   = sha1(project_file_data).hexdigest().casefold()
    expected_hash = project_file['hashes']['sha1'].casefold()
    if (actual_hash != expected_hash):
        print_('WARNING: Downloaded file\'s SHA-1 hash value does not match'+\
            ' the expected hash value')
    del actual_hash, expected_hash

    # Save the project file to the destination
    print_('Saving to file "'+file_dest+'"...  ', end='', flush=True)
    with open(file_dest, 'wb') as f:
        f.write(project_file_data)
    print_('Done.')

    return SUCCESS

def get_project_file_named(name, project_files):
    '''Returns the project file for the given project, or None if either
    the project or project file does not exist.
    '''
    for project_file in project_files:
        if (project_file['name'] == name): return project_file
    return None

def get_project_files(project):
    '''Returns a list of files available for the given project,
    or None if the project does not exist.

    Example:
    >>> get_project_files('spigot')
    [
        {
            'name'   : 'spigot-latest.jar',
            'project': 'Spigot',
            'version': {
                'minecraft': '...'
            },
            'size': {
                'human': '23.40MB',
                'bytes': 24539208
            },
            'date': {
                'human': 'March 24, 2017',
                'epoch': 1493893504,
            },
            'urls': {
                'paid': 'http://...',
                'free': 'http://...'
            }
        },
        ...,
    ]
    '''
    # Project DNE?
    if not project_exists(project): return None

    # Download list of project files
    headers = {'User-Agent': HTTP_USER_AGENT}
    req = requests.get('https://yivesmirror.com/api/'+project, headers=headers)
    req_json = req.json()
    req.close(); del req

    # Build and return list of project files
    project_files = []
    for entry in req_json:
        project_file = list(entry.values())[0]
        project_file['project'] = project
        project_files.append(project_file)
    return project_files

def get_projects():
    '''Returns the list of available projects.'''
    # Return previously-existing list of projects, if it exists
    global projects
    try:
        return projects
    except NameError:
        pass

    # Download list of projects
    headers = {'User-Agent': HTTP_USER_AGENT}
    req = requests.get('https://yivesmirror.com/api/invalid', headers=headers)
    req_json = req.json()
    req.close(); del req

    # Read list of projects
    projects = req_json['validSoftwares']
    projects.sort()
    return projects

def print_projects():
    print_('Projects: {}'.format(' '.join(get_projects())))

def project_exists(project):
    '''Returns True if the given project is available.'''
    return project.casefold() in {p.casefold() for p in get_projects()}

def main():
    import sys

    # Get command
    try:
        cmd = sys.argv[1].casefold()
    except IndexError:
        cmd = None

    # Command not given or not recognized?
    if cmd not in {'get', 'list'}:
        # Command not recognized?
        if (cmd != None): print_('ERROR: Unrecognized command "'+cmd+'"')

        # Print usage
        filename = os.path.basename(sys.argv[0])
        print_('Usage:')
        print_('  {} get  <project> <file> [dest]'.format(filename) +\
               '  Download the project file')
        print_('  {} list <project>              '.format(filename) +\
               '  List the project files')

        print_()
        print_projects()

        print_('\nExample: Downloading a Spigot 1.12 snapshot')
        print_('  $ {} get spigot spigot-1.12-R0.1-SNAPSHOT-b1372.jar'.format(
            filename))

        print_('\nReturn Codes:')
        return_codes = (
            (SUCCESS,               'Success'),
            (WARN_FILE_NOT_NEW,     'Download unnecessary'),
            (ERROR_INVALID_ARGS,    'Invalid arguments'),
            (ERROR_FILE_PERMS,      'File access permission error'),
            (ERROR_DOWNLOAD_FAILED, 'Download failed'),
        )
        for return_code in return_codes:
            print_('{:4}: {}'.format(*return_code))
        del return_codes

        print_('\nDownloads hosted by '+\
            'Yive\'s Mirror (no affiliation): https://yivesmirror.com/')
        print_('View project source on GitHub: https://github.com/SaltyHash/mcdl')

        sys.exit(SUCCESS if (cmd == None) else ERROR_INVALID_ARGS)

    # Get the handler function for the command and execute it
    try:
        cmd_func = globals()['cmd_'+cmd.replace('-', '_')]
    except KeyError:
        raise RuntimeError(
            'Failed to find handler function for command "'+cmd+'"')
    result = cmd_func(*sys.argv[2:])
    sys.exit(result)

if (__name__ == '__main__'):
    main()
<|endoftext|>


<commit_before>import urllib.parse

mailto_edge = {
    "color": {"color": "lightblue"},
    "title": "Mailto Parsing Functions",
    "label": "📧",
}

def run(unfurl, node):
    if node.data_type == "url" and node.value.startswith("mailto:"):

        raw = node.value.split(":", 1)

        if len(raw[1]) == 0:
            return

        nodes = []

        if "?" in raw[1]:
            split_qs = raw[1].split("?", 1)

            if len(split_qs[0]) > 0:
                nodes.append({"k": "to", "v": split_qs[0]})

            # rest of the string for node value, parse as 'url.query;'
            for pair in split_qs[1].replace("&", "&").split("&"):
                r = pair.split("=", 1)
                nodes.append({"k": r[0], "v": r[1]})
        else:
            nodes.append({"k": "to", "v": raw[1]})

        for n in nodes:
            k = n["k"]
            v = n["v"]
            unfurl_val = None
            if k in ["to", "cc", "bcc"]:
                if "," in v:
                    data_type = "mailto.email.multiple"
                else:
                    data_type = "mailto.email"
                unfurl_val = v
            else:
                data_type = "url.query"
                unfurl_val = f"{k}={v}"

            unfurl.add_to_queue(
                data_type=data_type,
                key=k,
                value=unfurl_val,
                label=f"{k}: {v}",
                hover="Mailto link is a type of URL that opens the default mail client for sending an email, per "
                'RFC2368',
                parent_id=node.node_id,
                incoming_edge_config=mailto_edge,
            )

            print("-" * 20)

    if node.data_type == "mailto.email.multiple":
        for raw_email in node.value.split(","):
            email = raw_email.replace("%20", " ").strip()
            unfurl.add_to_queue(
                data_type="email",
                key=None,
                value=email,
                label=f"{node.key}: {email}",
                hover="Mailto link is a type of URL that opens the default mail client for sending an email, per "
                'RFC2368',
                parent_id=node.node_id,
                incoming_edge_config=mailto_edge,
            )
<commit_msg>Change single mailto node to email<commit_after>import urllib.parse

mailto_edge = {
    "color": {"color": "lightblue"},
    "title": "Mailto Parsing Functions",
    "label": "📧",
}

def run(unfurl, node):
    if node.data_type == "url" and node.value.startswith("mailto:"):

        raw = node.value.split(":", 1)

        if len(raw[1]) == 0:
            return

        nodes = []

        if "?" in raw[1]:
            split_qs = raw[1].split("?", 1)

            if len(split_qs[0]) > 0:
                nodes.append({"k": "to", "v": split_qs[0]})

            # rest of the string for node value, parse as 'url.query;'
            for pair in split_qs[1].replace("&", "&").split("&"):
                r = pair.split("=", 1)
                nodes.append({"k": r[0], "v": r[1]})
        else:
            nodes.append({"k": "to", "v": raw[1]})

        for n in nodes:
            k = n["k"]
            v = n["v"]
            unfurl_val = None
            if k in ["to", "cc", "bcc"]:
                if "," in v:
                    data_type = "mailto.email.multiple"
                else:
                    data_type = "email"
                unfurl_val = v
            else:
                data_type = "url.query"
                unfurl_val = f"{k}={v}"

            unfurl.add_to_queue(
                data_type=data_type,
                key=k,
                value=unfurl_val,
                label=f"{k}: {v}",
                hover="Mailto link is a type of URL that opens the default mail client for sending an email, per "
                'RFC2368',
                parent_id=node.node_id,
                incoming_edge_config=mailto_edge,
            )

            print("-" * 20)

    if node.data_type == "mailto.email.multiple":
        for raw_email in node.value.split(","):
            email = raw_email.replace("%20", " ").strip()
            unfurl.add_to_queue(
                data_type="email",
                key=None,
                value=email,
                label=f"{node.key}: {email}",
                hover="Mailto link is a type of URL that opens the default mail client for sending an email, per "
                'RFC2368',
                parent_id=node.node_id,
                incoming_edge_config=mailto_edge,
            )
<|endoftext|>