from django.db import models
from django.db.models import Q
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver

# Create your models here.

SEX_CHOICES = (
	('M', 'Male'),
	('F', 'Female')
)

class MarriageManager(models.Manager):
    def marriage_with(self, family_member):
        marriages = self.all()
        marriages = marriages.filter(\
        					Q(descendant=family_member.id) | \
        					Q(in_law=family_member.id))
        return marriages

    def marriage_of(self, family_member1, family_member2):
        marriages = self.all()
        marriages = marriages.filter(\
        						Q(descendant=family_member1.id) | \
        						Q(in_law=family_member1.id))

        marriages = marriages.filter(\
        						Q(descendant=family_member2.id) | \
        						Q(in_law=family_member2.id))

        if len(marriages) != 1:
            return None
        else:
            return marriages[0]

class Marriage(models.Model):
    descendant = models.ForeignKey('FamilyMember', models.DO_NOTHING,
    												related_name='descendant')
    in_law = models.ForeignKey('FamilyMember', models.DO_NOTHING,
    													related_name='in_law')

    date_of_marriage = models.DateField(null=True, blank=True)
    is_divorced = models.BooleanField()

    objects = MarriageManager()

    def _get_descendant_children_id_list(self):
        descendant_children = self.descendant.children.all()
        children_ids = list()
        for child in descendant_children :
            children_ids.append(child.pk)
        return children_ids

    def get_children(self):
        children_ids = self._get_descendant_children_id_list()
        return self.in_law.children.all().filter(pk__in=children_ids).order_by('date_of_birth')

    def get_step_children(self):
        children_ids = self._get_descendant_children_id_list()
        return self.in_law.children.all().exclude(pk__in=children_ids).order_by('date_of_birth')

    @property
    def marriage_title(self):
        return f'{self.descendant.full_name} & {self.in_law.full_name}'

    def __str__(self):
        return self.marriage_title

class FamilyMemberManager(models.Manager):
    def search_members(self, first=None, middle=None, last=None, maiden=None):
        querySet = self.all()
        if last:
            querySet = querySet.filter(last_name__icontains=last)
        if first:
            querySet = querySet.filter(first_name__icontains=first)
        if middle:
            querySet = querySet.filter(middle_name__icontains=middle)
        if maiden:
            querySet = querySet.filter(maiden_name__icontains=maiden)
        return querySet

    def find_exact_members(self, first=None, middle=None, last=None, maiden=None):
        querySet = self.all()
        if last:
            querySet = querySet.filter(last_name__iexact=last)
        if first:
            querySet = querySet.filter(first_name__iexact=first)
        if middle:
            querySet = querySet.filter(middle_name__iexact=middle)
        if maiden:
            querySet = querySet.filter(maiden_name__iexact=maiden)
        return querySet

    def find_person(self, first=None, middle=None, last=None, maiden=None):
        if first == '':
            first = None
        if middle == '':
            middle = None
        if last == '':
            last = None
        if maiden == '':
            maiden = None
        firstTry = self.search_members(first, middle, last, maiden)
        if len(firstTry) != 1:
            secondTry = self.find_exact_members(first, middle, last, maiden)
            if len(secondTry) != 1:
                return None
            return secondTry[0]
        return firstTry[0]

class FamilyMember(models.Model):
    #Name
    first_name = models.CharField(max_length=50)
    middle_name = models.CharField(max_length=50, blank=True)
    last_name = models.CharField(max_length=50)
    maiden_name = models.CharField(max_length=50, blank=True)

    #Personal Info
    sex = models.CharField(max_length=1, choices=SEX_CHOICES, blank=True)
    is_step_child = models.BooleanField()

    date_of_birth = models.DateField(null=True, blank=True)
    date_of_death = models.DateField(null=True, blank=True)

    #Relationships
    parents = models.ManyToManyField('self', null=True, blank=True,
    								related_name='ps', symmetrical=False)
    children = models.ManyToManyField('self', null=True, blank=True,
									related_name='cs', symmetrical=False)

    #Manager Objects
    objects = FamilyMemberManager()

    def get_children(self):
        children_ids = self._get_descendant_children_id_list()
        return self.in_law.children.all().filter(pk__in=children_ids).order_by('date_of_birth')

    @property
    def full_name(self):
        return f'{self.first_name} {self.middle_name} {self.last_name}'

    @property
    def parents_marriage(self):
        parents = self.parents.all()

        if len(parents) == 2:
            parents_marriage = Marriage.objects.marriage_of(parents[0],parents[1])
        else:
            parents_marriage = None

        return parents_marriage

    class Meta:
        ordering = ('first_name','last_name')
        verbose_name = "Family Member"
        verbose_name_plural = "Family Members"

    def __str__(self):
        return self.full_name

@receiver(post_save, sender=FamilyMember)
def add_parent(sender, instance, **kwargs):
	print("ADDING PARENT")
	for parent in instance.parents.all():
		try:
			me = parent.children.get(id=instance.pk)
		except:
			parent.children.add(instance)

@receiver(post_save, sender=FamilyMember)
def add_children(sender, instance, **kwargs):
	print("ADDING CHILD")
	for child in instance.children.all():
		try:
			me = child.parents.get(id=instance.pk)
		except Exception as e:
			child.parents.add(instance)


# （1）拡張モジュールのインポート
import numpy as np                  # 配列を扱う数値計算ライブラリNumPy
import matplotlib.pyplot as plt     # グラフ描画ライブラリmatplotlib
import japanize_matplotlib          # matplotlibの日本語化
import math
import random
# （2）時間変数tの導入
T = 700                  # 変数tの範囲 0≦t<T（日）（250,150,150,700と値を変えてシミュレーションを行う）
n = 10*T                 # 変数tの範囲をn等分   n=T/h=T/0.1=10*T （T=250のときはn=2500）
h = 0.1                  # 等差数列の公差:0.1 固定
time = np.arange(0,T,h)     # 0から公差dtでTを超えない範囲で等差数列を生成 t[0],...,t[n-1] 要素数n個
c=np.zeros((10,10,10,10))
# （3）SIRモデル
# 3-1パラメータ
N=np.zeros((10,10))
m=np.zeros((10,10))                # 1日1人あたり接触する人数（人）（10,50,100,5と値を変えてシミュレーションを行う）
p = 0.02               #5接触ごとに感染が生じる1日あたりの確率
d = 14                   # 感染者の回復平均日数（日）
beta = np.zeros((10,10))           # 接触あたりの感染率
beta_p=np.zeros((10,10,10,10))
gamma = 1/d              # 回復率（隔離率）
alpha=0.01                #重症化率
# 3-2初期値

for i in range(10):
  for j in range(10):
    N[i][j]=random.randrange(10000,100000)

    m[i][j]=random.randrange(10,100)
    beta[i][j]=m[i][j]*p/N[i][j]

for i in range(10):
  for j in range(10):
    for s in range(10):
      for t in range(10):
        if i!=s or j!=t:
          beta_p[i][j][s][t]=(beta[i][j]+beta[s][t])/(100*math.sqrt((i-s)**2+(t-j)**2))

# 3-4数値積分変数S,I,Rをリストとして生成
S = np.zeros((10,10,n))          # S[0],...,S[n-1] 要素数n個
Im = np.zeros((10,10,n))          # I[0],...,I[n-1] 要素数n個
Is = np.zeros((10,10,n))          # I[0],...,I[n-1] 要素数n個
R = np.zeros((10,10,n))          # R[0],...,R[n-1] 要素数n個
Is_sum=np.zeros(n)
Im_sum=np.zeros(n)
# 3-5初期値代入
for i in range(10):
  for j in range(10):
      #Im [i][j][0]=10*random.random()
      Im[i][j][0]=0
      Is[i][j][0]=0
      R[i][j][0]=0
      S[i][j][0]=N[i][j]-Im[i][j][0]
Im[5][5][0]=100
S[5][5][0]=N[5][5]-Im[5][5][0]

for i in range(10):
  for j in range(10):
    for s in range(10):
      for t in range(10):
        if i==s and j==t:
          c[i][j][s][t]=0
        elif (i==s and abs(j-t)==1) or (abs(i-s)==1 and j==t):
          c[i][j][s][t]=1
        elif 1-math.exp(-(math.sqrt((i-s)**2+(j-t)**2)))<random.random():
          c[i][j][s][t]=1

       # print("c[{}][{}][{}][{}]=".format(i,j,s,t))
        #print(c[5][5][5][6])

for t in range(n-1):
  for i in range(10):
    for j in range(10):
      Su=0
      for k in range(10):
        for s in range(10):
            Su=Su+c[i][j][k][s]*beta_p[i][j][k][s]*S[i][j][t]*Im[k][s][t]
      S[i][j][t+1]=S[i][j][t]-h*beta[i][j]*S[i][j][t]*Im[i][j][t]-h*Su
      Im[i][j][t+1]=Im[i][j][t]+h*Su+h*beta[i][j]*S[i][j][t]*Im[i][j][t]-h*alpha*Im[i][j][t]
      Is[i][j][t+1]=Is[i][j][t]+h*alpha*Im[i][j][t]-h*gamma*Is[i][j][t]
      R[i][j][t+1]=R[i][j][t]+h*gamma*(Im[i][j][t]+Is[i][j][t])
for t in range(n):
  for i in range(10):
    for j in range(10):
      Im_sum[t]=Im_sum[t]+Im[i][j][t]
      Is_sum[t]=Is_sum[t]+Is[i][j][t]

# （4）数値積分 4次ルンゲ-クッタ法 4th-Order Runge–Kutta Methods

# （5）結果表示 データプロットによるグラフ表示
# 点(t,S),点(t,I),点(t,R) それぞれ要素数n個のプロット
for i in range(10):
  for j in range(10):
    plt.plot(time, Is[i][j], color = "red", linewidth = 1.0)
# グラフの見た目設定
plt.title('各ノードIs')  # グラフタイトル パラメータmとTの値表示
#plt.yticks(np.arange(0,N+0.1,N/10))    # y軸 目盛りの配分 0からN（=1000万）までを10等分 N/10（=100万）刻み Nを含めるためNをN+0.1としておく
#plt.gca().set_yticklabels(['{:.0f}%'.format(y/((N)/100)) for y in plt.gca().get_yticks()])   # y軸目盛りを％表示に変更
plt.xlabel('時間（日）')                              # 横軸ラベル
plt.ylabel('人数')      # 縦軸ラベル
plt.grid(True)                                        # グリッド表示
plt.legend()                                          # 凡例表示
# 設定反映しプロット描画
plt.show()

for i in range(10):
  for j in range(10):
    plt.plot(time, Im[i][j], color = "blue", linewidth = 1.0)
# グラフの見た目設定
plt.title('各ノードのIm')  # グラフタイトル パラメータmとTの値表示
#plt.yticks(np.arange(0,N+0.1,N/10))    # y軸 目盛りの配分 0からN（=1000万）までを10等分 N/10（=100万）刻み Nを含めるためNをN+0.1としておく
#plt.gca().set_yticklabels(['{:.0f}%'.format(y/((N)/100)) for y in plt.gca().get_yticks()])   # y軸目盛りを％表示に変更
plt.xlabel('時間（日）')                              # 横軸ラベル
plt.ylabel('人数')      # 縦軸ラベル
plt.grid(True)                                        # グリッド表示
plt.legend()                                          # 凡例表示
# 設定反映しプロット描画
plt.show()

plt.plot(time,Im_sum,color="green",linewidth=1.0)

plt.title('Imの和')  # グラフタイトル パラメータmとTの値表示
#plt.yticks(np.arange(0,N+0.1,N/10))    # y軸 目盛りの配分 0からN（=1000万）までを10等分 N/10（=100万）刻み Nを含めるためNをN+0.1としておく
#plt.gca().set_yticklabels(['{:.0f}%'.format(y/((N)/100)) for y in plt.gca().get_yticks()])   # y軸目盛りを％表示に変更
plt.xlabel('時間（日）')                              # 横軸ラベル
plt.ylabel('人数')      # 縦軸ラベル
plt.grid(True)                                        # グリッド表示
plt.legend()                                          # 凡例表示
# 設定反映しプロット描画
plt.show()

plt.plot(time,Is_sum,color="black",linewidth=1.0)

plt.title('Isの和')  # グラフタイトル パラメータmとTの値表示
#plt.yticks(np.arange(0,N+0.1,N/10))    # y軸 目盛りの配分 0からN（=1000万）までを10等分 N/10（=100万）刻み Nを含めるためNをN+0.1としておく
#plt.gca().set_yticklabels(['{:.0f}%'.format(y/((N)/100)) for y in plt.gca().get_yticks()])   # y軸目盛りを％表示に変更
plt.xlabel('時間（日）')                              # 横軸ラベル
plt.ylabel('人数')      # 縦軸ラベル
plt.grid(True)                                        # グリッド表示
plt.legend()                                          # 凡例表示
# 設定反映しプロット描画
plt.show()

virus-sim - コピー/複雑ネットワーク/sir_複雑ネットワーク.py

sir_複雑ネットワーク.py


import dlib
import cv2
import numpy as np
import imutils
import requests
import pyttsx3
import threading
import urllib
import time

engine = pyttsx3.init()

# webcam = cv2.VideoCapture('http://192.168.225.139/mjpeg/1')
stream = urllib.request.urlopen('http://192.168.225.139/mjpeg/1')
bytes = bytes()

forward_detector = dlib.simple_object_detector('model/forward.svm')
left_detector = dlib.simple_object_detector('model/left.svm')
right_detector = dlib.simple_object_detector('model/right.svm')
stop_detector = dlib.simple_object_detector('model/stop.svm')
uturn_detector = dlib.simple_object_detector('model/uturn.svm')

count = 0
label = ""

last_label = ""

def doReq(param):
    requests.request(url=f"http://192.168.225.184/{param}", method="GET")

# def sendCommand(param):
#     threading.Thread(target=doReq, args=(param,), daemon=True).start()

def speak1(thing):
    try:
        engine.say(thing)
        engine.runAndWait()
    except:
        pass

def speak(thing):
    threading.Thread(target=speak1, args=(thing,), daemon=True).start()

def check_label(lab):
    global count, label
    if lab == label:
        label = lab
        count += 1
    else:
        count = 1
        label = lab

while True:
    # _, image = webcam.read()
    ok = False
    bytes += stream.read(1024)
    a = bytes.find(b'\xff\xd8')
    b = bytes.find(b'\xff\xd9')
    if a != -1 and b != -1:
        jpg = bytes[a:b + 2]
        bytes = bytes[b + 2:]
        i = cv2.imdecode(np.fromstring(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
        # cv2.imshow('i', i)

        image = imutils.resize(i, width=400)
        bgray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
        gray = clahe.apply(bgray)
        nimage = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

        fd = forward_detector(nimage)
        ld = left_detector(nimage)
        rd = right_detector(nimage)
        sd = stop_detector(nimage)
        ud = uturn_detector(nimage)

        print('last_label:', last_label)

        for b in ud:
            if not ok:
                ok = True
            else:
                break

            if last_label != "uturn":
                last_label = "uturn"
            else:
                break
            (x, y, w, h) = (b.left(), b.top(), b.right(), b.bottom())
            cv2.putText(image, 'uturn', (x - 10, y - 10), cv2.FONT_HERSHEY_PLAIN, 1.2, (0, 255, 0), 2)
            cv2.rectangle(image, (x, y), (w, h), (0, 255, 0), 2)
            speak('uturn')

        for b in fd:
            if not ok:
                ok = True
            else:
                break
            if last_label != "forward":
                last_label = "forward"
            else:
                break
            (x, y, w, h) = (b.left(), b.top(), b.right(), b.bottom())
            cv2.putText(image, 'forward', (x - 10, y - 10), cv2.FONT_HERSHEY_PLAIN, 1.2, (0, 255, 0), 2)
            cv2.rectangle(image, (x, y), (w, h), (0, 255, 0), 2)
            speak('forward')
            doReq("F")

        for b in ld:
            if not ok:
                ok = True
            else:
                break
            if last_label != "left":
                last_label = "left"
            else:
                break
            (x, y, w, h) = (b.left(), b.top(), b.right(), b.bottom())
            cv2.putText(image, 'left', (x - 10, y - 10), cv2.FONT_HERSHEY_PLAIN, 1.2, (0, 255, 0), 2)
            cv2.rectangle(image, (x, y), (w, h), (0, 255, 0), 2)
            speak('left')
            doReq("L")
            # time.sleep(0.05)
            # doReq("F")
            # time.sleep(2)
            # doReq("S")

        for b in rd:
            if not ok:
                ok = True
            else:
                break
            if last_label != "right":
                last_label = "right"
            else:
                break
            (x, y, w, h) = (b.left(), b.top(), b.right(), b.bottom())
            cv2.putText(image, 'right', (x - 10, y - 10), cv2.FONT_HERSHEY_PLAIN, 1.2, (0, 255, 0), 2)
            cv2.rectangle(image, (x, y), (w, h), (0, 255, 0), 2)
            speak('right')
            doReq("R")
            # time.sleep(0.05)
            # doReq("F")
            # time.sleep(2)
            # doReq("S")

        for b in sd:
            if not ok:
                ok = True
            else:
                break
            if last_label != "stop":
                last_label = "stop"
            else:
                break
            (x, y, w, h) = (b.left(), b.top(), b.right(), b.bottom())
            cv2.putText(image, 'stop', (x - 10, y - 10), cv2.FONT_HERSHEY_PLAIN, 1.2, (0, 255, 0), 2)
            cv2.rectangle(image, (x, y), (w, h), (0, 255, 0), 2)
            speak('stop')
            doReq("S")

        cv2.imshow("Image", np.hstack([image]))
        key = cv2.waitKey(1) & 0xFF
        if key == ord('q'):
            print("Exiting..")
            cv2.destroyAllWindows()
            # webcam.release()
            break


import xlrd
import matplotlib.pyplot as plt
# 打开excel表格
data_excel = xlrd.open_workbook('2.xlsx')
table = data_excel.sheets()[0]
#划定区域
b_row1=9  #开始行
e_row1=14  #结束行
b_col1=11   #开始列
e_col1=17  #结束列
#读取标签
list_values1=[]
for i in range(b_row1, e_row1):
    col = table.col_values(i)[1]
    list_values1.append(col)
print(list_values1)
#提取单元格内容
for x in range(b_col1,e_col1):
    values = []
    col = table.row_values(x)
    col1=table.row_values(x)[1]
    # print(col1)
    for i in range(b_row1, e_row1):
        values.append(col[i])
    # print(values)
    plt.rcParams['font.sans-serif'] = ['SimHei']  # 正常显示中文标签
    labels = values
    sizes = values
    # explode = (0, 0.1, 0, 0)
    fig1, ax1 = plt.subplots()
    ax1.bar(list_values1, values)
    for x, y in enumerate(values):
        plt.text(x, y + 4, y, ha='center', va='bottom')
    plt.title(col1+"情况")
    plt.savefig("C:/Users/LLLLL/Desktop/作图任务/{}情况.png".format(col1))
    plt.show()


import os
import json
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from log_module import ProjectLogger
from flight_data import FlightData

class DataManager:
    """
        This class is responsible for talking to the Google Spreadsheet.
    """
    API_SCOPES = ["https://www.googleapis.com/auth/spreadsheets"]
    API_SERVICE_NAME = "sheets"
    API_VERSION = "v4"
    AVAILABLE_RANGE = 'A:B'

    def __init__(self):
        # create new logger for DataManager
        self._logger = ProjectLogger().get_module_logger('DataManager')
        self._config = self._check_if_config_folder_exists()
        self._logger.debug('Initializing DataManager...')
        self._spreadsheet_id = self._config['google_sheet_id']
        self._handle_credentials()
        try:
            # Build the Google service
            service = build(DataManager.API_SERVICE_NAME, DataManager.API_VERSION, credentials=self._creds)
            # Call the Sheets API
            self._sheet = service.spreadsheets()
            self._logger.debug('Google Sheet service created')
        except HttpError as err:
            self._logger.error(err)

        self._logger.debug('DataManager initialized')

    @staticmethod
    def _prepare_range(sheet_id: str, cells: str) -> str:
        """
        This method prepares the range for the Google Sheet API
        :param sheet_id: Sheet id to be updated e.g. 'Sheet1'
        :param cells: Cells to be updated e.g. 'A1:B2'
        :return: Range e.g. 'Sheet1!A1:B2'
        """
        return f'\'{sheet_id}\'!{cells}'

    @staticmethod
    def _prepare_sheet_id(start_location: str) -> str:
        """
        This method prepares the sheet id for the Google Sheet API
        :param start_location: start location IATA code
        :return: Ready to use sheet id for application e.g. 'FROM_GDA'
        """
        return f'FROM_{start_location}'

    @staticmethod
    def get_flight_dict(start_location: str, destination: str, price: int) -> dict:
        """
        This method creates the flight data dictionary
        :param start_location: start location IATA code
        :param destination: destination IATA code
        :param price: price of the flight
        :return: proper data dictionary
        """
        return {'origin_airport': start_location, 'destination_airport': destination, 'price': price}

    def _handle_credentials(self) -> None:
        """
        This method handles the Google credentials for the application.
        It checks if the token file exists, if it does, it loads the credentials from file.
        If there are no credentials, it creates new ones.
        :return: None
        """
        # Check if token file exists, if it does, load credentials from file
        if os.path.exists(self._config['google_token_file']):
            self._creds = Credentials.from_authorized_user_file(self._config['google_token_file'],
                                                                DataManager.API_SCOPES)
            self._logger.debug('Google credentials loaded from file')

        # If there are no (valid) credentials available, let the user log in using OAuth.
        if not self._creds or not self._creds.valid:
            if self._creds and self._creds.expired and self._creds.refresh_token:
                # If credentials are expired, but refresh token is available, refresh credentials
                self._logger.debug('Credentials expired, refreshing...')
                self._creds.refresh(Request())
                self._logger.debug('Credentials refreshed')
            else:
                # If credentials are not available, create new credentials
                self._logger.debug('Credentials not available, creating new...')
                flow = InstalledAppFlow.from_client_secrets_file(self._config['google_credentials_file'],
                                                                 DataManager.API_SCOPES)
                self._creds = flow.run_local_server(port=0)
                self._logger.debug('Credentials created')
            # Save the credentials for the next run
            with open(self._config['google_token_file'], 'w+') as token:
                token.write(self._creds.to_json())
                self._logger.debug('Credentials saved to file')

    def _check_if_config_folder_exists(self) -> dict:
        """
        This function checks if config folder exists. If not, it creates it.
        :return: config dictionary
        """
        config = {
            'tequila_api_key': '',
            'google_token_file': '.config/token.json',
            'google_credentials_file': '.config/client_secret.json',
            'google_sheet_id': ''
        }

        if not os.path.exists('.config/'):
            self._logger.warning('Config folder does not exist, creating...')
            os.mkdir('.config')

        if not os.path.exists('.config/config.json'):
            self._logger.warning('Config file does not exist, creating...')

            with open('.config/config.json', 'w+') as f:
                f.write(json.dumps(config))

        else:
            with open('.config/config.json', 'r') as f:
                config = json.load(f)

        if not os.path.exists(config['google_token_file']):
            self._logger.warning('Google token file does not exist, creating...')

            with open(config['google_token_file'], 'w+') as f:
                f.write('')

        if not os.path.exists(config['google_credentials_file']):
            self._logger.error(
                'Google credentials file does not exist! Please download it from Google Cloud Platform and put it in .config folder. Name it `client_secret.json` and try again.')

            raise FileNotFoundError('Google credentials file not found! More info in log file.')

        return config

    def get_config(self) -> dict:
        """
        This method returns the json setup for the application
        :return: config dictionary with `tequila_api_key`, `google_token_file`, `google_credentials_file` and `google_sheet_id`
        """
        return self._config

    def _create_sheet(self, sheet_id: str) -> bool:
        """
        This method creates the sheet in the connected Google spreadsheet
        :param sheet_id: Sheet id to be created e.g. 'Sheet1'
        :return: True if sheet was created, False otherwise
        """
        status = False

        self._logger.debug(f'Creating sheet {sheet_id}')

        try:
            # Call the Sheets API to create the sheet
            self._logger.debug(f'Calling Sheets API to create sheet {sheet_id}')
            self._sheet.batchUpdate(spreadsheetId=self._spreadsheet_id, body={
                'requests': [
                    {
                        'addSheet': {
                            'properties': {
                                'title': sheet_id
                            }
                        }
                    }
                ]
            }).execute()
            status = True
        except HttpError as err:
            self._logger.error(err)

        return status

    def _check_if_sheet_exists(self, sheet_id: str) -> bool:
        """
        This method checks if the sheet exists in the connected Google spreadsheet
        :param sheet_id: Sheet id to be checked e.g. 'Sheet1'
        :return: True if a sheet exists, False otherwise
        """
        status = False

        self._logger.debug(f'Checking if sheet {sheet_id} exists')

        # Call the Sheets API to get the list of sheets
        sheets = self.get_start_locations()

        # Check if sheet exists
        for sheet in sheets:
            if f'FROM_{sheet}' == sheet_id:
                status = True
                break

        return status

    def _append_data(self, sheet_id: str, new_values: list) -> bool:
        """
        This method appends the data to the connected Google spreadsheet
        :param sheet_id: Sheet id to be updated e.g. 'Sheet1'
        :param new_values: 2D list of values to append e.g. [['New Value A', 'New Value B'], ['New Value C', 'New Value D']]
        :return: True if append was successful, False otherwise
        """
        status = False

        self._logger.debug(f'Appending values {new_values} to sheet {sheet_id}')

        # Add sheet id to the range, so it looks like 'Sheet1!A1:B2'
        data_range = self._prepare_range(sheet_id, DataManager.AVAILABLE_RANGE)

        self._logger.debug(f'Calling Sheets API to append data to {data_range}')

        try:
            # Call the Sheets API to append the data
            self._sheet.values().append(spreadsheetId=self._spreadsheet_id, range=data_range,
                                        valueInputOption='USER_ENTERED', insertDataOption='INSERT_ROWS',
                                        body={'values': new_values}).execute()
            status = True
        except HttpError as err:
            self._logger.error(err)

        return status

    def _update_data(self, new_values: list, cells_range: str) -> bool:
        """
        This method updates the data in the connected Google spreadsheet
        :param new_values: 2D list of values to update [['New Value A', 'New Value B'], ['New Value C', 'New Value D']]
        :param cells_range: Range to update in Sheet!A1:B2 format --> 'My Custom Sheet!A1:B2'
        :return: True if update was successful, False otherwise
        """
        status = False
        self._logger.debug(f'Updating range `{cells_range}` with values {new_values}')
        try:
            # Call the Sheets API to update the data
            result = self._sheet.values().update(spreadsheetId=self._spreadsheet_id, range=cells_range,
                                                 valueInputOption='RAW',
                                                 body={'values': new_values}).execute()
            # Check if any cells were updated
            status = result.get('updatedCells') > 0
            if status:
                self._logger.debug(f'{result.get("updatedCells")} cells updated')
            else:
                self._logger.debug(f'No cells updated')
        except HttpError as err:
            self._logger.error(err)

        return status

    def _read_data(self, cells_range: str = None) -> list:
        """
        This method reads the data from the connected Google spreadsheet
        :param cells_range: Range to read in Sheet!A1:B2 format e.g. 'My Custom Sheet!A1:B2'
        :return: 2D list of values [['Value A', 'Value B'], ['Value C', 'Value D']]
        """
        # Check if range is specified
        if cells_range is None:
            self._logger.error('No range specified, using default')
            cells_range = DataManager.AVAILABLE_RANGE

        try:
            # Call the Sheets API to read the data
            self._logger.debug('Calling Sheets API to access data')

            # Check if sheet exists
            sheet_id = cells_range.split("!")[0].replace('\'', '')
            if not self._check_if_sheet_exists(sheet_id):
                self._logger.error(f'Sheet `{sheet_id}` does not exist')
                return []

            result = self._sheet.values().get(spreadsheetId=self._spreadsheet_id, range=cells_range).execute()
            values = result.get('values', [])
        except HttpError as err:
            self._logger.error(err)
            values = []
        return values

    def _get_flights_amount(self, start_location: str) -> int:
        """
        This method reads the amount of flights from the connected Google spreadsheet
        :param start_location: start location IATA code
        :return: amount of flights
        """
        self._logger.debug(f'Reading flights amount for {start_location}...')
        # Call the Sheets API to read the data
        sheet_id = self._prepare_sheet_id(start_location)
        read_range = self._prepare_range(sheet_id, DataManager.AVAILABLE_RANGE)
        flights = self._read_flights(read_range)

        # Return amount of flights
        self._logger.debug(f'Amount of flights for {start_location}: {len(flights)}')

        return len(flights)

    def _read_flights(self, start_location: str) -> dict:
        """
        This method reads all flights data from the connected Google spreadsheet
        :param start_location: start location IATA code
        :return: dictionary of flights data {'iataCode1': 'lowestPrice1', 'iataCode2': 'lowestPrice2'}
        """
        self._logger.debug(f'Reading flights data...')
        # Call the Sheets API to read the data
        sheet_id = self._prepare_sheet_id(start_location)
        read_range = self._prepare_range(sheet_id, DataManager.AVAILABLE_RANGE)
        values = self._read_data(read_range)

        self._logger.debug(f'Collected all flights data')

        self._logger.debug(values)

        # Return dictionary of flight data {'iataCode1': 'lowestPrice1', 'iataCode2': 'lowestPrice2'}
        flights = {f'{row[0]}': int(row[1]) for row in values if len(row) == 2}

        return flights

    def _add_flight(self, flight: FlightData) -> None:
        """
        This method adds the new flight data to the connected Google spreadsheet
        :param flight: Flight data to add e.g. {'start_iata_code': 'ABC', dest_iata_code': 'ABC', 'lowestPrice': 123}
        :return: True if data added, False otherwise
        """
        self._logger.debug(
            f'Adding flight data: {flight.origin_airport}->{flight.destination_airport} with price {flight.price}$'
        )

        # Check if there is data to add
        if flight is None:
            self._logger.error('add_flight: No data to add')
            return

        # Check if sheet exists
        sheet_id = self._prepare_sheet_id(flight.origin_airport)
        if not self._check_if_sheet_exists(sheet_id):
            self._create_sheet(sheet_id)

        # Call the Sheets API to add the data
        status = self._append_data(sheet_id, [[flight.destination_airport, flight.price]])

        if status:
            self._logger.debug(f'Flight data added')
        else:
            self._logger.error(f'Flight data not added')

    def get_start_locations(self) -> list:
        """
        This method reads all start_locations from the connected Google spreadsheet
        :return: list of start locations IATA codes
        """
        # Call the Sheets API to get the list of sheets
        try:
            self._logger.debug(f'Calling Sheets API to get the list of sheets')
            result = self._sheet.get(spreadsheetId=self._spreadsheet_id).execute()
            sheets = result.get('sheets', [])
            self._logger.debug(f'{len(sheets)} sheets found')
        except HttpError as err:
            self._logger.error(err)
            sheets = []

        return [sheet['properties']['title'].replace('FROM_', '') for sheet in sheets]

    def get_destination_airports(self, start_location: str) -> list:
        """
        This method reads all destinations for given start location from the connected Google spreadsheet
        :param start_location: start location IATA code
        :return: list of destinations IATA codes
        """
        self._logger.debug(f'Reading destinations for {start_location}...')
        # Call the Sheets API to read the data
        sheet_id = self._prepare_sheet_id(start_location)
        read_range = self._prepare_range(sheet_id, DataManager.AVAILABLE_RANGE)
        values = self._read_data(read_range)
        destinations = [row[0] for row in values]

        # Return list of destinations
        self._logger.debug(f'Destinations for {start_location}: {destinations}')
        return destinations

    def get_flight_data(self, data: FlightData) -> dict:
        """
        This method reads the flight data (by iata code) from the connected Google sheet
        :param data: See FlightData class for more information.
        :return: dictionary of certain flight data {'iataCode': 'lowestPrice'}
        :raises ValueError: if there is no flight data for given start and destination
        """
        flights = self._read_flights(start_location=data.origin_airport)

        if data.destination_airport not in flights.keys():
            self._logger.error(f'No flight data found for {data.origin_airport}->{data.destination_airport}')
            return {}

        return flights.get(data.destination_airport)

    def update_flight(self, flight: FlightData) -> int:
        """
        This method updates the flight data in the connected Google sheet
        :param flight: See FlightData class for more information.
        :return: status code (0 - updated with lower price, 1 - not updated)
        :raises ValueError: if error occurred during update process
        """
        status = False

        self._logger.info(
            f'Updating flight data: {flight.origin_airport}->{flight.destination_airport} with price {flight.price}'
        )

        # Check if start location exists in the sheet
        if not self._check_if_sheet_exists(self._prepare_sheet_id(flight.origin_airport)):
            self._logger.debug(
                f'update_flight: There is no sheet for iata code `{flight.origin_airport}`, creating new')
            self._add_flight(flight)
            return 0

        # Get range to update
        row_number = -1
        existing_values = self._read_flights(flight.origin_airport)
        for nr, row in enumerate(existing_values.keys()):
            if row == flight.destination_airport:
                self._logger.debug(f'Found existing entry for `{flight.destination_airport}`')
                row_number = nr + 1
                break

        # Add new data if there is no data for this destination
        if row_number == -1:
            self._logger.debug(f'update_flight: No data for iata code `{flight.destination_airport}`')
            self._add_flight(flight)
            return 0

        # Call the Sheets API to update the data
        sheet_id = self._prepare_sheet_id(flight.origin_airport)
        update_range = self._prepare_range(sheet_id, f'B{row_number}')

        # Check if price is lower than existing one
        if flight.price < existing_values[flight.destination_airport]:
            self._logger.debug(f'update_flight: New price is lower than existing one, updating...')
            status = self._update_data([[flight.price]], update_range)
        else:
            self._logger.debug(f'update_flight: New price is higher than existing one, not updating...')

        return 0 if status else 1


import os
import re
import sys
import json
import base64
from multiprocessing import Process, Manager
from mcstatus import MinecraftServer
from PyQt5.QtWidgets import QWidget, QListWidget, QListWidgetItem, QVBoxLayout
from PyQt5.QtWidgets import QMenu, QPushButton, QMessageBox, QGridLayout
from PyQt5.QtWidgets import QInputDialog, QSpinBox, QLabel
from PyQt5.QtGui import QIcon, QPixmap, QImage
from PyQt5.QtCore import Qt, QSize, QTimer
from core.api import Widget, WidgetInfo
from core.gui.help import TextViewer
from core.paths import RES, RELOAD, SETTINGS, ERROR, DELETE, HELP
from core.utils import LogLevel, try_except, print_stack_trace

def get_description(desc) -> str:
    if desc['text']:
        return desc['text']
    result = ''
    if 'extra' in desc:
        for part in desc['extra']:
            result += part['text']
    return result

class Info(WidgetInfo):
    def __init__(self, lang):
        WidgetInfo.__init__(self, lang)
        self.lang = lang['MINECRAFT']
        self.NAME = 'Minecraft Servers Monitoring'
        self.DESCRIPTION = self.lang['description']
        self.HELP = self.lang['help']
        self.AUTHOR = 'InterVi'
        self.EMAIL = 'intervionly@gmail.com'
        self.URL = 'https://github.com/InterVi/DeWidgets'
        self.ICON = QIcon(os.path.join(sys.path[0], 'res', 'minecraft',
                                       'minecraft.png'))

class Main(Widget, QWidget):
    def __init__(self, widget_manager, info):
        Widget.__init__(self, widget_manager, info)
        QWidget.__init__(self)
        self.servers = []
        self.timer_interval = 30000
        self.lang = info.lang
        self.show_more = None
        self.settings_win = None
        # setup stylesheet
        with open(os.path.join(RES, 'minecraft', 'style.css'), encoding='utf-8'
                  ) as file:
            style = file.read()
            self.setStyleSheet(style)
        # setup list
        self.list = QListWidget(self)
        self.list.setStyleSheet(style)
        self.list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.list.customContextMenuRequested.connect(self._show_list_menu)
        self.list.itemDoubleClicked.connect(self._list_double_click)
        self.list.setIconSize(QSize(64, 64))
        self.list.setWordWrap(True)
        self.list.setSelectionMode(QListWidget.NoSelection)
        # setup list menu
        self.list_menu = QMenu(self)
        reload_action = self.list_menu.addAction(QIcon(RELOAD),
                                                 self.widget_manager.lang[
                                                     'MINECRAFT']['reload'])
        reload_action.triggered.connect(self._list_fill)
        # setup v box layout
        self.v_box = QVBoxLayout(self)
        self.v_box.addWidget(self.list)
        self.v_box.setContentsMargins(0, 0, 0, 0)
        # setup timer
        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self._list_fill)
        self.start_timer = QTimer(self)
        self.start_timer.timeout.connect(self._list_fill)
        # buffer
        self.__manager = Manager()
        self.list_buffer = self.__manager.dict()
        # other
        self.__procs = []

    def boot(self):
        self._fill_settings()
        self._list_fill()
        self.start_timer.setSingleShot(True)
        self.start_timer.start(1000)

    def place(self):
        self.start_timer.setSingleShot(True)
        self.start_timer.start(1)

    def remove(self):
        self.update_timer.stop()
        self._kill_procs()

    def purge(self):
        self.update_timer.stop()
        self._kill_procs()

    @try_except()
    def show_settings(self):
        self.settings_win = Settings(self)

    @try_except()
    def _list_fill(self, checked=False):
        self._pool_ping()
        self.list.clear()
        for addr in self.servers:
            item = QListWidgetItem(self.list)
            try:
                if addr not in self.list_buffer:
                    item.setText(addr)
                    continue
                text, favicon, tooltip = json.loads(self.list_buffer[addr])
                favicon = base64.b64decode(favicon)
                item.setIcon(QIcon(QPixmap.fromImage(QImage.fromData(favicon)))
                             )
                item.setText(text)
                font = item.font()
                font.setPixelSize(10)
                font.setBold(True)
                item.setFont(font)
                item.setToolTip(tooltip)
            except:
                item.setText(addr)
                print_stack_trace()()

    def _fill_settings(self):
        section = self.widget_manager.get_config(self.info.NAME)
        if 'servers' in section:
            self.servers = json.loads(section['servers'])
        if 'timer' in section:
            self.timer_interval = int(section['timer'])
            if self.timer_interval > 0:
                self.update_timer.start(self.timer_interval)

    @try_except()
    def _show_list_menu(self, point):
        self.list_menu.exec(self.list.mapToGlobal(point))

    @try_except()
    def _list_double_click(self, item):
        self.show_more = ShowMore(self)

    def _kill_procs(self):
        for p in self.__procs:
            try:
                if p and p.is_alive():
                    p.terminate()
            except:
                print_stack_trace()()
        self.__procs.clear()

    def _pool_ping(self):
        @try_except(level=LogLevel.DEBUG)
        def ping(addr):
            status = MinecraftServer.lookup(addr).status()
            favicon = ''
            if status.favicon:
                favicon = status.favicon[status.favicon.find(',') + 1:]
            tooltip = ''
            if status.players.sample:
                for player in status.players.sample:
                    tooltip += player.name + ', '
            text = '[' + str(status.players.online) + ' / ' + \
                   str(status.players.max) + '] ' + \
                   self.lang['ping'].format(
                       str(status.latency)) + \
                   '\n[' + status.version.name + ']\n' + \
                   re.sub('§+[a-zA-Z0-9]', '',
                          get_description(status.description))
            self.list_buffer[addr] = json.dumps((text, favicon, tooltip[:-2]))

        self._kill_procs()
        for addr in self.servers:
            proc = Process(target=ping, args=(addr,))
            proc.start()
            self.__procs.append(proc)

class ShowMore(TextViewer):
    def __init__(self, main):
        self.main = main
        self.lang = main.widget_manager.lang['MINECRAFT']
        title = main.list.item(main.list.currentRow()).text()
        for i in range(4):
            title = title[title.find(']\n'):]
        super().__init__(
            title[2:].strip(), self.lang['close_button'],
            self.lang['close_button_tt']
        )
        self.setWindowIcon(main.list.item(main.list.currentRow()).icon())
        self.exit_button.clicked.connect(self.exit)
        self.text.setHtml(self.lang['wait'])
        self.__manager = Manager()
        self.__info_buffer = self.__manager.dict()
        self.__proc = None
        self._ping_server()
        self.timer = QTimer(self)
        self.timer.setSingleShot(True)
        self.timer.timeout.connect(self.print_info)
        self.timer.start(100)
        self.show()

    def _ping_server(self):
        @try_except(level=LogLevel.DEBUG)
        def ping():
            self.__info_buffer['online'] = ''
            self.__info_buffer['max'] = ''
            self.__info_buffer['version'] = ''
            self.__info_buffer['protocol'] = ''
            self.__info_buffer['players'] = ''
            self.__info_buffer['ping'] = ''
            self.__info_buffer['description'] = ''
            self.__info_buffer['map'] = ''
            self.__info_buffer['brand'] = ''
            self.__info_buffer['plugins'] = ''
            config = self.main.widget_manager.config.config
            if 'servers' not in config[self.main.info.NAME]:
                return
            servers = json.loads(config[self.main.info.NAME]['servers'])
            if not servers:
                return
            addr = servers[self.main.list.currentRow()]
            try:
                status = MinecraftServer.lookup(addr).status()
            except:
                print_stack_trace(LogLevel.DEBUG)()
                return
            self.__info_buffer['online'] = str(status.players.online)
            self.__info_buffer['max'] = str(status.players.max)
            self.__info_buffer['version'] = status.version.name
            self.__info_buffer['protocol'] = status.version.protocol
            self.__info_buffer['ping'] = str(status.latency)
            players = ''
            if status.players.sample:
                for p in status.players.sample:
                    players += p.name + '(' + p.id + ')' + ',
'
            self.__info_buffer['players'] = players[:-7]
            self.__info_buffer['description'] = get_description(
                status.description)
            try:
                query = MinecraftServer.lookup(addr).query()
            except:
                print_stack_trace(LogLevel.DEBUG)()
                return
            self.__info_buffer['map'] = query.map
            self.__info_buffer['brand'] = query.software.brand
            plugins = ''
            for p in query.software.plugins:
                plugins += p + ', '
            self.__info_buffer['plugins'] = plugins[:-2]
            for p in query.players.names:
                if p not in players:
                    players += p + ',
'
            self.__info_buffer['players'] = players[:-7]

        if self.__proc and self.__proc.is_alive():
            self.__proc.terminate()
        self.__proc = Process(target=ping)
        self.__proc.start()

    @try_except()
    def print_info(self):
        self.text.setHtml(self.lang['info'].format(
            **self.__info_buffer))
        if self.__proc and self.__proc.is_alive():
            self.timer.start(100)

    @try_except()
    def exit(self, checked=False):
        self.timer.stop()
        try:
            if self.__proc and self.__proc.is_alive():
                self.__proc.terminate()
                self.__proc = None
        except:
            print_stack_trace()()
        self.close()

class Settings(QWidget):
    def __init__(self, main):
        super().__init__()
        self.main = main
        self.lang = main.widget_manager.lang['MINECRAFT']
        # setup window
        self.setWindowTitle(self.lang['settings_title'])
        self.setWindowIcon(QIcon(SETTINGS))
        self.resize(400, 460)
        # setup list
        self.list = QListWidget(self)
        self.list.setIconSize(QSize(64, 64))
        self.list.itemClicked.connect(self.__change_enabeld)
        self.list.setWordWrap(True)
        self._list_fill()
        # setup 'Update' label
        self.update_label = QLabel(self.lang['time_label'], self)
        self.update_label.setAlignment(Qt.AlignCenter)
        # setup 'Time' spinbox
        self.time_edit = QSpinBox(self)
        self.time_edit.setMinimum(0)
        self.time_edit.setMaximum(1000000000)
        self.time_edit.setValue(int(self.main.timer_interval/1000))
        self.time_edit.setToolTip(self.lang['time_input_tt'])
        self.time_edit.setAlignment(Qt.AlignCenter)
        self.time_edit.valueChanged.connect(self._time_changed)
        # setup 'Up' button
        self.up_button = QPushButton(self.lang['up_button'], self)
        self.up_button.setToolTip(self.lang['up_button_tt'])
        self.up_button.clicked.connect(self._up)
        # setup 'Down' button
        self.down_button = QPushButton(self.lang['down_button'], self)
        self.down_button.setToolTip(self.lang['down_button_tt'])
        self.down_button.clicked.connect(self._down)
        # setup 'Delete' button
        self.delete_button = QPushButton(self.lang['delete_button'], self)
        self.delete_button.setToolTip(self.lang['delete_button_tt'])
        self.delete_button.clicked.connect(self._delete)
        # setup 'Add' button
        self.add_button = QPushButton(self.lang['add_button'], self)
        self.add_button.setToolTip(self.lang['add_button_tt'])
        self.add_button.clicked.connect(self._add)
        # setup 'Close' button
        self.close_button = QPushButton(
            self.lang['settings_close_button'], self
        )
        self.close_button.setToolTip(self.lang['settings_close_button_tt'])
        self.close_button.clicked.connect(self.close)
        # setup layout
        self.grid = QGridLayout(self)
        self.grid.addWidget(self.list, 0, 0, 1, 2)
        self.grid.addWidget(self.update_label, 1, 0)
        self.grid.addWidget(self.time_edit, 1, 1)
        self.grid.addWidget(self.up_button, 2, 0)
        self.grid.addWidget(self.down_button, 2, 1)
        self.grid.addWidget(self.delete_button, 3, 0, 1, 2)
        self.grid.addWidget(self.add_button, 4, 0, 1, 2)
        self.grid.addWidget(self.close_button, 5, 0, 1, 2)
        self.setLayout(self.grid)
        # show
        self.__change_enabeld()
        self.show()

    def __change_enabeld(self):
        item = self.list.currentItem()
        if item:
            self.delete_button.setEnabled(True)
        else:
            self.delete_button.setEnabled(False)
        if item and self.list.count() > 1:
            self.up_button.setEnabled(True)
            self.down_button.setEnabled(True)
        else:
            self.up_button.setEnabled(False)
            self.down_button.setEnabled(False)

    @try_except()
    def _list_fill(self):
        self.list.clear()
        for i in range(self.main.list.count()):
            m_item = self.main.list.item(i)
            item = QListWidgetItem(self.list)
            item.setText(m_item.text())
            item.setIcon(m_item.icon())
            self.list.addItem(item)

    @try_except()
    def _time_changed(self, value):
        self.main.update_timer.stop()
        if value > 0:
            self.main.timer_interval = value * 1000
            self.main.update_timer.start(self.main.timer_interval)
        else:
            self.main.timer_interval = 0
        self.main.widget_manager.config.config[
            self.main.info.NAME]['timer'] = str(self.main.timer_interval)
        self.main.widget_manager.config.save()

    @try_except()
    def _move(self, up=True):
        row = self.list.currentRow()
        if up and row - 1 >= 0:
            self.main.servers.insert(row - 1, self.main.servers.pop(row))
            row -= 1
        elif not up and row + 1 < self.list.count():
            self.main.servers.insert(row + 1, self.main.servers.pop(row))
            row += 1
        else:
            return
        self.main.widget_manager.config.config[self.main.info.NAME][
            'servers'] = json.dumps(self.main.servers)
        self.main.widget_manager.config.save()
        self.main._list_fill()
        self._list_fill()
        self.list.setCurrentRow(row)

    def _up(self, checked):
        self._move()

    def _down(self, checked):
        self._move(False)

    @try_except()
    def _delete(self, checked):
        if not self.list.count():
            return
        mbox = QMessageBox(QMessageBox.Question, self.lang['confirm_title'],
                           self.lang['confirm_text'],
                           QMessageBox.Ok | QMessageBox.Cancel, self)
        mbox.setWindowIcon(QIcon(DELETE))
        mbox.setInformativeText(self.lang['confirm_inf'].format(
            self.list.currentItem().text()))
        ok = mbox.button(QMessageBox.Ok)
        ok.setText(self.lang['confirm_ok_button'])
        ok.setToolTip(self.lang['confirm_ok_button_tt'])
        cancel = mbox.button(QMessageBox.Cancel)
        cancel.setText(self.lang['confirm_cancel_button'])
        cancel.setToolTip(self.lang['confirm_cancel_button_tt'])
        if mbox.exec() == QMessageBox.Ok:
            del self.main.servers[self.list.currentRow()]
            self.main.widget_manager.config.config[self.main.info.NAME][
                'servers'] = json.dumps(self.main.servers)
            self.main.widget_manager.config.save()
            self.main._list_fill()
            self._list_fill()
            self.__change_enabeld()

    @try_except()
    def _add(self, checked):
        id = QInputDialog(self)
        id.setWindowIcon(QIcon(HELP))
        id.setWindowTitle(self.lang['input_title'])
        id.setLabelText(self.lang['input_text'])
        id.setTextValue('s.vomine.ru:25565')
        id.setOkButtonText(self.lang['input_ok_button'])
        id.setCancelButtonText(self.lang['input_cancel_button'])
        if id.exec() == QInputDialog.Accepted:
            text = id.textValue()
            if not text or len(text.split(':')) != 2 \
                    or not text.split(':')[1].isdigit():
                self._show_error()
            else:
                self.main.servers.append(text)
                self.main.widget_manager.config.config[
                    self.main.info.NAME]['servers'] = json.dumps(
                    self.main.servers)
                self.main.widget_manager.config.save()
                self.main._list_fill()
                self._list_fill()
                self.__change_enabeld()
                self.list.setCurrentRow(self.list.count() - 1)

    @try_except()
    def _show_error(self):
        mbox = QMessageBox(QMessageBox.Critical, self.lang['error_title'],
                           self.lang['error_text'], QMessageBox.Ok, self)
        mbox.setWindowIcon(QIcon(ERROR))
        ok = mbox.button(QMessageBox.Ok)
        ok.setText(self.lang['error_ok_button'])
        ok.setToolTip(self.lang['error_ok_button_tt'])
        mbox.exec()


import tkinter as Tk
from random import randint
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

class Window:

    def __init__(self,master):
        frame = Tk.Frame(master)

        self.fig = plt.figure(figsize=(14, 4.5), dpi=100)

        self.ax = self.fig.add_subplot(1,1,1)
        self.ax.set_ylim(0, 100)
        self.line, = self.ax.plot(xar, yar)

        self.canvas = FigureCanvasTkAgg(self.fig, master=master)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
        frame.pack()

    def animate(self,i):
        xar = []
        yar = []
        for j in range (1,10):
            xar.append (j)
            yar.append (randint (1,100))
        self.ax.clear()
        self.ax.plot (xar, yar)

xar = []
yar = []

root = Tk.Tk()
app = Window(root)
ani = animation.FuncAnimation(app.fig, app.animate, interval=1000, blit=False)
root.mainloop()


import json
import time

import boto3
import cfnresponse

from cfn_utils import Utils, LOGGER

class Greengrassv2Handler:
    """
    Handles the create and delete events for the Greengrass Group resources.
    """

    GGV2_CLIENT = boto3.client("greengrassv2")
    COMPONENT_SUFFIX = ".GoldVIP.Telemetry"
    RETRIES = 10
    WAIT_TIME = 5

    @staticmethod
    def __attach_service_role(event):
        """
        Check if a GreenGrass service role is associated to the account,
        if not we associate the service role created by the stack to the account.
        The service role is set to persist after the stack deletion.
        """
        service_role_arn = event['ResourceProperties']['ServiceRoleArn']
        service_role_name = event['ResourceProperties']['ServiceRoleName']
        service_role_policy_name = event['ResourceProperties']['ServiceRolePolicyName']

        ggv1_client = boto3.client("greengrass")
        iam_client = boto3.client('iam')

        # Check if service role is associated
        try:
            ggv1_client.get_service_role_for_account()
        except: # pylint: disable=bare-except
            ggv1_client.associate_service_role_to_account(
                RoleArn=service_role_arn
            )

            return

        # Deleting the service role when it is not needed.
        iam_client.delete_role_policy(
            RoleName=service_role_name,
            PolicyName=service_role_policy_name
        )

        iam_client.delete_role(
            RoleName=service_role_name
        )

    @staticmethod
    def __create_telemetry_component(event, ids):
        """
        Creates a greengrass v2 component from a lambda function.
        :param event: The MQTT message in json format.
        :param ids: Dictionary of resource ids.
        """

        stack_name = event['ResourceProperties']['StackName']
        telemetry_topic = event['ResourceProperties']['TelemetryTopic']
        lambda_arn = event['ResourceProperties']['TelemetryLambdaArn']

        component = Greengrassv2Handler.GGV2_CLIENT.create_component_version(
            lambdaFunction={
                'lambdaArn': lambda_arn,
                'componentName': stack_name + Greengrassv2Handler.COMPONENT_SUFFIX,
                'componentVersion': '1.0.0',
                'componentLambdaParameters': {
                    'eventSources': [
                        {
                            'topic': telemetry_topic + '/config',
                            'type': 'IOT_CORE'
                        },
                        {
                            'topic': telemetry_topic + '/config',
                            'type': 'PUB_SUB'
                        }
                    ],
                    'pinned': True,
                    'environmentVariables': {
                        'telemetryTopic': telemetry_topic,
                        'AppDataTopicSuffix': "/app_data"
                    },
                    'linuxProcessParams': {
                        'isolationMode': 'NoContainer',
                    }
                }
            }
        )

        ids["telemetry_component"] = component["arn"]

    @staticmethod
    def create(event):
        """
        Initiates the creation of the Greengrass resources.
        :param event: The MQTT message in json dictionary format.
        """
        ids = {'thing_name': event['ResourceProperties']['CoreThingName'], \
                'thing_arn': event['ResourceProperties']['CoreThingArn']}

        Greengrassv2Handler.__attach_service_role(event)
        Greengrassv2Handler.__create_telemetry_component(event, ids)

        return ids

    @staticmethod
    def check_deployment(deployment_id, status):
        """
        Waits for the deployment to have a certain status.
        :param deployment_id: Id of the deployment to check.
        :param status: list of statuses to check the deployment against.
        """
        for _ in range(Greengrassv2Handler.RETRIES):
            time.sleep(Greengrassv2Handler.WAIT_TIME)
            new_deployment_status = Greengrassv2Handler.GGV2_CLIENT.get_deployment(
                deploymentId=deployment_id
            )['deploymentStatus']
            if new_deployment_status in status:
                return True
        return False

    @staticmethod
    def delete(event):
        """
        Initiates the deletion of the Greengrass resources.
        :param event: The MQTT message in json dictionary format.
        """
        ids = Utils.decode_ids(event['PhysicalResourceId'])
        component_arn = ids["telemetry_component"]
        thing_name = ids['thing_name']
        thing_arn = ids['thing_arn']
        deployment_name = f"{event['ResourceProperties']['StackName'].replace('-','_')}_clean_deployment"

        Utils.set_cloudwatch_retention(
            event['ResourceProperties']['StackName'],
            event['ResourceProperties']['Region'])

        # Delete GoldVIP telemetry component
        Greengrassv2Handler.GGV2_CLIENT.delete_component(
            arn=component_arn
        )
        LOGGER.info("Greengrass telemetry component deleted.")

        # Get a list of deployments associated with the core thing.
        # These deployments are created when we provision the board.
        # We are compiling the list of deployments before creating the new one
        # which will uninstall the telemetry component on the board.
        deployment_ids = []
        paginator = Greengrassv2Handler.GGV2_CLIENT.get_paginator('list_deployments')
        for page in paginator.paginate(targetArn=thing_arn):
            for deployment in page['deployments']:
                deployment_ids.append(deployment['deploymentId'])

        # Creates a Greengrass v2 deployment with minimal Greengrass components.
        # This will uninstall unwanted components on the board
        new_deployment_id = Greengrassv2Handler.GGV2_CLIENT.create_deployment(
            targetArn=thing_arn,
            deploymentName=deployment_name,
            components={}
        )['deploymentId']

        # Append the new deployment to the list of deployments pending deletion.
        deployment_ids.append(new_deployment_id)

        # Wait for deployment status
        if Greengrassv2Handler.check_deployment(new_deployment_id, ['COMPLETED']):
            LOGGER.info("Uninstalled components.")
        else:
            LOGGER.error("Failed to uninstall deployed components!")

        for deployment_id in deployment_ids:
            try:
                # Cancel deployment
                Greengrassv2Handler.GGV2_CLIENT.cancel_deployment(
                    deploymentId=deployment_id)

                if Greengrassv2Handler.check_deployment(deployment_id, ['CANCELED', 'INACTIVE']):
                    LOGGER.info("Deleting deployment: with id: %s",
                                deployment_id)
                    # Delete deployment
                    Greengrassv2Handler.GGV2_CLIENT.delete_deployment(
                        deploymentId=deployment_id)
                else:
                    LOGGER.error("Failed to delete deployment with id: %s",
                                 deployment_id)
            except Exception as err:
                LOGGER.error("Failed to delete deployment with id %s, error: %s",
                    deployment_id, err)

        try:
            Greengrassv2Handler.GGV2_CLIENT.delete_core_device(
                coreDeviceThingName=thing_name
            )
            LOGGER.info("Core device deleted.")
        except Greengrassv2Handler.GGV2_CLIENT.exceptions.ResourceNotFoundException:
            LOGGER.info("Core device not found.")

def lambda_handler(event, context):
    """
    Handler function for the custom resource function.
    :param event: The MQTT message in json format.
    :param context: A Lambda context object, it provides information.
    """
    LOGGER.info('Greengrass custom function handler got event %s', json.dumps(event))

    try:
        response_data = {}

        if event['RequestType'] == 'Create':
            ids = Greengrassv2Handler.create(event)

            cfnresponse.send(
                event, context, cfnresponse.SUCCESS,
                response_data, physical_resource_id=Utils.encode_ids(ids))
        elif event['RequestType'] == 'Update':
            cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
        elif event['RequestType'] == 'Delete':
            Greengrassv2Handler.delete(event)
            cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
        else:
            LOGGER.info('Unexpected Request Type!')
            cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
    # pylint: disable=broad-exception-caught
    except Exception as err:
        LOGGER.error("Greengrass custom function handler error: %s", err)
        response_data = {"Data": str(err)}
        cfnresponse.send(event, context, cfnresponse.FAILED, response_data)


# coding: utf-8

import scrapy
import json
import re
from ..items import FreshItem, Fresh
from ..const import FreshSource

'''
多点爬虫
'''

class DmallSpider(scrapy.Spider):
    name = 'dmall'
    host = 'https://appapi.dmall.com/app/'
    headers = {'Cookie': '_digger_uuid=a67bf8018a22211b; cookie_id=1521601952508a67bf; inited=true; updateTime=1520518007000; addr=%E5%8C%97%E4%BA%AC%E5%B8%82%E6%B5%B7%E6%B7%80%E5%8C%BA%E5%BA%B7%E5%81%A5%E5%AE%9D%E7%9B%9B%E5%B9%BF%E5%9C%BA; addrId=; appVersion=3.8.5; areaId=110108; bigdata=; community=%E5%BA%B7%E5%81%A5%E5%AE%9D%E7%9B%9B%E5%B9%BF%E5%9C%BA; lat=40.036456; lng=116.372483; platform=IOS; storeGroup=0-148-1,1-148-1,2-148-1; store_id=148; ticketName=; token=; uuid=6928a6385e8876946849f622ad60bc46042aa787; vender_id=1;',
               'storeGroup': '1-148-1;2-148-1', 'platform': 'IOS', 'storeId': 148, 'venderId': 1}

    def start_requests(self):
        url = '{host}wareCategory/list'.format(host=self.host)
        yield scrapy.Request(url, callback=self.parse_category, headers=self.headers)

    def parse_category(self, response):
        if response.body:
            data = json.loads(response.body)
            stores = data.get('data', {}).get('wareCategory')
            for store in stores:
                category_list = store.get('categoryList')
                for top_category in category_list:
                    for category in top_category.get('childCategoryList', []):
                        yield category

                for top_category in category_list:
                    for category in top_category.get('childCategoryList', []):
                        category_id = category.get('categoryId')
                        url = '{host}search/wareSearch'.format(host=self.host)

                        param = {
                            "sortRule": 0,
                            "globalSelection": False,
                            "pos": 0,
                            "categoryId": str(category_id),
                            "pageNum": 1,
                            "src": 0,
                            "noResultSearch": 0,
                            "from": 1,
                            "erpStoreId": "148",
                            "categoryLevel": 2,
                            "sortKey": 0,
                            "businessCode": 1,
                            "venderId": "1",
                            "categoryType": 1,
                            "sort": 0,
                            "pageSize": 20}

                        yield scrapy.FormRequest(url, callback=self.parse_goods_in_category, headers=self.headers,
                                                 method='POST', formdata={'param': json.dumps(param)},
                                                 meta={'category': {'id': category_id, 'name': category.get('categoryName')}, 'page': 1})

    def parse_goods_in_category(self, response):
        if response.body:
            data = json.loads(response.body).get('data', {})
            product_list = data.get('wareList', [])
            for product in product_list:
                yield product

            for product in product_list:
                sku = product.get('sku')
                # print '>>>>>>>>>>>>>>>', sku, response.meta.get('page')
                if sku:
                    body_param = {
                        "sku": str(sku),
                        "lng": 116.372483,
                        "lat": 40.036456000000001,
                        "erpStoreId": "148",
                        "venderId": "1"}

                    url = 'https://detail.dmall.com/app/wareDetail/baseinfo'.format(host=self.host)
                    yield scrapy.FormRequest(url, callback=self.parse_goods_info, method='POST',
                                             formdata={'param': json.dumps(body_param)},
                                             meta=response.meta, headers=self.headers)

                    # 更新销量
                    url = 'https://detail.dmall.com/app/wareDetail/extendinfo'.format(host=self.host)
                    meta = response.meta
                    meta.update(sku=sku)
                    yield scrapy.FormRequest(url, callback=self.update_volume, method='POST',
                                             formdata={'param': json.dumps(body_param)},
                                             meta=meta, headers=self.headers)

            # 翻页
            meta = response.meta
            if meta.get('page') == 1:
                total = data.get('pageInfo', {}).get('total')
                page = total / 20 + int((total % 20) > 0)
                for i in (1, page + 1):
                    meta.update(page=i)
                    param = {
                        "sortRule": 0,
                        "globalSelection": False,
                        "pos": 0,
                        "categoryId": str(meta.get('category').get('id')),
                        "pageNum": i,
                        "src": 0,
                        "noResultSearch": 0,
                        "from": 1,
                        "erpStoreId": "148",
                        "categoryLevel": 2,
                        "sortKey": 0,
                        "businessCode": 1,
                        "venderId": "1",
                        "categoryType": 1,
                        "sort": 0,
                        "pageSize": 20}

                    yield scrapy.FormRequest(response.url, callback=self.parse_goods_in_category, headers=self.headers,
                                             method='POST', formdata={'param': json.dumps(param)}, meta=meta)

    def parse_goods_info(self, response):
        if response.body:
            data = json.loads(response.body).get('data', {})

            unit = ''
            region = ''
            brand = ''
            for d in data.get('introduceList'):
                if d.get('name') == u'规格':
                    unit = d.get('value')

                if d.get('name') == u'产地':
                    region = d.get('value')

                if d.get('name') == u'品牌':
                    brand = d.get('value')

            yield FreshItem(
                name=data.get('wareName'),
                sku=str(data.get('sku')),
                unit=unit,
                price=data.get('promotionWareVO', {}).get('unitProPrice', 0) / float(100),
                price_origin=data.get('promotionWareVO', {}).get('origPrice', 0) / float(100),
                promotion='',
                volume=0,
                region=region,
                brand=brand,
                source=FreshSource.dmall,
                coupon_exception=0,
                sellout='',
                category_id=response.meta.get('category', {}).get('id'),
                category_name=response.meta.get('category', {}).get('name'),
            )

    def update_volume(self, response):
        if response.body:
            data = json.loads(response.body).get('data', {}).get('sellOutCount', '')
            volume = re.findall(u'已售(.*?)件', data)
            volume = volume[0] if volume else 0

            sku = response.meta.get('sku')
            Fresh.update_volume(FreshSource.dmall, sku, volume)


import logging
import signal
import sys
import webbrowser
from copy import deepcopy
from pathlib import Path
from threading import Timer
from time import time

from .client import Client
from .enums import ItemType
from .exceptions import AuthTokenExtractionError, MissingAuthTokenError, EndpointError, RequestError, LogNotFoundError

class Server:
    _heartbeat_timeout = 90  # seconds to shutdown after receiving no heartbeat
    _static_headers = { 'Cache-Control': 'no-store' }

    def __init__(self, bottle, port):
        self._bottle = bottle
        self._client = Client()
        self._app = self._bottle.Bottle()

        self._app.route('/', callback=self._index)
        self._app.route('/static/<filename>.js', callback=self._static_files_javascript)
        self._app.route('/static/<filename>', callback=self._static_files)
        self._app.route('/wishing-well', callback=self._identify)
        self._app.route('/data', callback=self._get_data)
        self._app.route('/update-wish-history', method='POST', callback=self._update_wish_history)
        self._app.route('/heartbeat', method='POST', callback=self._heartbeat)

        self._server = self._bottle.WSGIRefServer(host='localhost', port=port)
        self._last_heartbeat = time()
        self._shutdown_timer = Timer(self._heartbeat_timeout, self._shutdown)
        self._shutdown_timer.start()

        # try to handle sigint
        signal.signal(signal.SIGINT, self._handle_sigint)

        webbrowser.open(f'http://localhost:{port}')
        self._bottle.run(self._app, server=self._server)

    def _shutdown(self):
        logging.debug('No longer receiving heartbeats. Shutting down.')
        self._server.srv.shutdown()

    def _handle_sigint(self, *args):
        self._shutdown_timer.cancel()
        Timer(0.1, self._shutdown).start()

    def _index(self):
        return self._bottle.static_file('index.html', root=Path(sys.path[0]) / 'frontend', headers=self._static_headers)

    def _static_files(self, filename):
        return self._bottle.static_file(filename, root=Path(sys.path[0]) / 'frontend', headers=self._static_headers)

    def _static_files_javascript(self, filename):
        return self._bottle.static_file(filename + '.js', root=Path(sys.path[0]) / 'frontend', headers=self._static_headers, mimetype='text/javascript')

    # a simple 204 on a fixed endpoint name;
    # used to identify ourselves to check if there's already an
    # instance of wishing well running when we start it
    def _identify(self):
        self._bottle.response.status = 204

    # heartbeat. if this isn't called every X seconds, shut down
    def _heartbeat(self):
        time_ = time()
        logging.debug('Last heartbeat: %fs ago', time_ - self._last_heartbeat)
        self._last_heartbeat = time_
        self._shutdown_timer.cancel()
        self._shutdown_timer = Timer(self._heartbeat_timeout, self._shutdown)
        self._shutdown_timer.start()

    # calculate stats and pity. also returns pity the
    # given wish/reward was obtained at, if applicable
    def _calculate_stats_and_pity(self, wish, stats, pity, low_pity):
        current_pity = None
        banner_type = wish['banner_type']

        # 5 star
        if wish['rarity'] == 5:
            current_pity = pity[banner_type]['pity5'] + 1
            if wish['type'] is ItemType.CHARACTER:
                stats['characters5']['total'] += 1
                stats['characters5']['averagePity'].append(current_pity)
            else:
                stats['weapons5']['total'] += 1
                stats['weapons5']['averagePity'].append(current_pity)

            low_pity.append({
                'name': wish['name'],
                'pity': current_pity
            })
            pity[banner_type]['pity5'] = 0
        else:
            pity[banner_type]['pity5'] += 1

        # 4 star
        if wish['rarity'] == 4:
            current_pity = pity[banner_type]['pity4'] + 1
            if wish['type'] is ItemType.CHARACTER:
                stats['characters4']['total'] += 1
                stats['characters4']['averagePity'].append(current_pity)
            else:
                stats['weapons4']['total'] += 1
                stats['weapons4']['averagePity'].append(current_pity)

            pity[banner_type]['pity4'] = 0
        else:
            pity[banner_type]['pity4'] += 1

        # 3 star
        if wish['rarity'] == 3:
            stats['weapons3']['total'] += 1

        return current_pity

    # return all data required by the frontend
    def _get_data(self):
        # banner types
        banner_types = self._client.get_banner_types()

        # pity template
        pity_template = {}
        for key, name in banner_types.items():
            pity_template[key] = {
                'name': name,
                'pity4': 0,
                'pity5': 0
            }

        uids = {}
        for uid in self._client.get_uids():
            uids[uid] = {
                'statistics': {
                    'characters5': { 'total': 0, 'averagePity': [] },
                    'weapons5': { 'total': 0, 'averagePity': [] },
                    'characters4': { 'total': 0, 'averagePity': [] },
                    'weapons4': { 'total': 0, 'averagePity': [] },
                    'weapons3': { 'total': 0 }
                },
                'pity': deepcopy(pity_template),
                'lowPity': [],
                'wishHistory': []
            }

            # shorthands
            data = uids[uid]
            stats = data['statistics']
            pity = data['pity']
            low_pity = data['lowPity']
            wish_history = data['wishHistory']

            for wish in self._client.get_wish_history(uid):
                current_pity = self._calculate_stats_and_pity(wish, stats, pity, low_pity)

                # insert wish copy into frontend-ready history
                # wish_copy = wish.copy()
                wish['type'] = 'Character' if wish['type'] is ItemType.CHARACTER else 'Weapon'
                wish['bannerType'] = wish['banner_type']
                wish['bannerTypeName'] = banner_types[wish['banner_type']]
                wish['rarityText'] = '★' * wish['rarity']
                wish['pity'] = current_pity
                del wish['banner_type']
                wish_history.append(wish)

            # calculate average pity
            for _, category in stats.items():
                if 'averagePity' in category:
                    category['averagePity'] = (
                        sum(category['averagePity']) / len(category['averagePity'])
                        if len(category['averagePity']) > 0 else 0
                    )

            # transform pity into a list
            if 100 in pity:
                del pity[100]  # remove novice wishes
            pity = [ banner for _, banner in pity.items() ]

            # sort and clamp low pity
            low_pity.sort(key=lambda reward: reward['pity'])
            data['lowPity'] = low_pity[:5]

            # re-sort the history now that all banner types are
            # merged, then reverse it for display in the frontend
            wish_history.sort(key=lambda wish: wish['id'])
            for wish in wish_history:
                del wish['id']
            wish_history.reverse()

            data['totalWishes'] = len(wish_history)
            # uid loop end

        return {
            'bannerTypes': banner_types,
            'uids': uids
        }

    def _update_wish_history(self):
        body = self._bottle.request.json

        try:
            if 'url' in body:
                region, auth_token = Client.extract_region_and_auth_token(body['url'])
            else:
                region, auth_token = Client.extract_region_and_auth_token_from_file()
        except (AuthTokenExtractionError, LogNotFoundError) as e:
            self._bottle.response.status = 400
            return {
                'message': str(e)
            }

        self._client.set_region_and_auth_token(region, auth_token)
        try:
            self._client.fetch_and_store_banner_types()
            new_wishes_count = self._client.fetch_and_store_wish_history()
        except (MissingAuthTokenError, RequestError, EndpointError) as e:
            self._bottle.response.status = 500
            return {
                'message': str(e)
            }

        return {
            'message': f'Retrieved {new_wishes_count} new {"wish" if new_wishes_count == 1 else "wishes"}.'
        }


from typing import Dict, List

from dindex_store.common import EdgeType
from dindex_store.profile_index_duckdb import ProfileIndexDuckDB
from dindex_store.graph_index_duckdb import GraphIndexDuckDB
from dindex_store.graph_index_kuzu import GraphIndexKuzu
from dindex_store.fulltext_index_duckdb import FTSIndexDuckDB
from dindex_store.content_similarity_index_minhash import SimpleMHIndex
from dindex_store.content_similarity_index_minhash import MHIndex

def load_dindex(config: Dict):
    dindex = DiscoveryIndex(config, load=True)
    return dindex

class DiscoveryIndex:
    """DiscoveryIndex stores profiles generated by the profiler, a graph that
    describes relationships between profiles, and a full text search index on
    the original input data.

    :param profile_index: ProfileIndex
    :param graph_idnex: GraphIndex
    :param fts_index: FullTextSearchIndex
    """

    """
    Mapping of index type and implementations
    """
    profile_index_mapping = {
        "duckdb": ProfileIndexDuckDB,
    }

    fts_index_mapping = {
        "duckdb": FTSIndexDuckDB,
    }

    content_similarity_index_mapping = {
        "simpleminhash": SimpleMHIndex,
        "minhash": MHIndex
    }

    graph_index_mapping = {
        "kuzu": GraphIndexKuzu,
        "duckdb": GraphIndexDuckDB
    }

    def __init__(self, config: Dict, load=False, force=False) -> None:
        # TODO: Validate config in a consistent way
        profile_index = config["profile_index"]
        print(f"Initializing profile index: {profile_index}...")
        self.__profile_index = DiscoveryIndex.profile_index_mapping[profile_index](config, load=load, force=force)
        content_similarity_index = config["content_index"]
        print(f"Initializing content similarity index: {content_similarity_index}...")
        self.__content_similarity_index = DiscoveryIndex.content_similarity_index_mapping[content_similarity_index](config, load=load)
        fts_index = config["fts_index"]
        print(f"Initializing FTS index: {fts_index}...")
        self.__fts_index = DiscoveryIndex.fts_index_mapping[fts_index](config, load=load, force=force)
        graph_index = config["graph_index"]
        print(f"Initializing Graph index: {graph_index}...")
        self.__graph_index = DiscoveryIndex.graph_index_mapping[graph_index](config, load=load, force=force)

    def get_content_similarity_index(self):
        return self.__content_similarity_index

    def refresh_fts_index(self, table_name, index_column, force=False):
        self.__fts_index.create_fts_index(table_name, index_column, force=force)

    # ----------------------------------------------------------------------
    # Modify Methods

    def add_profile(self, profile: Dict) -> bool:
        profile_id = profile["id"]

        success_profile = self.__profile_index.add_profile(profile)
        if not success_profile:
            return False
        success_graph = self.__graph_index.add_node(profile_id)
        if not success_graph:
            return False
        # if there's a minhash for this column, then we insert it in the content index as well
        if profile['minhash']:
            success_content_similarity = self.__content_similarity_index.add_profile(profile_id, profile['minhash'])
            if not success_content_similarity:
                return False
        return True

    def add_text_content(self, profile_id, dbName, path, sourceName, columnName, data) -> bool:
        return self.__fts_index.insert(profile_id, dbName, path, sourceName, columnName, data)

    def add_edge(
            self,
            source_node_id: int,
            target_node_id: int,
            type: EdgeType,
            properties: Dict) -> bool:
        return self.__graph_index.add_edge(
            source_node_id, target_node_id, type, properties)

    def add_undirected_edge(
            self,
            source_node_id: int,
            target_node_id: int,
            type: EdgeType,
            properties: Dict) -> bool:
        return self.__graph_index.add_undirected_edge(
            source_node_id, target_node_id, type, properties)

    # ----------------------------------------------------------------------
    # Query Methods

    def get_filtered_profiles_from_table(self, table_name, desired_attributes):
        results = self.__profile_index.get_filtered_profiles_from_table(table_name, desired_attributes)
        return results

    def get_filtered_profiles_from_nids(self, nids, desired_attributes):
        results = self.__profile_index.get_filtered_profiles_from_nids(nids, desired_attributes)
        return results

    def get_minhashes(self) -> Dict:
        return self.__profile_index.get_minhashes()

    def find_neighborhood(self, node_id: int, relation_type, hops: int = 1, desired_attributes=None):
        neighbors_ids = self.__graph_index.find_neighborhood(node_id, relation_type, hops)
        if len(neighbors_ids) == 0:
            return []
        # now, for each neighbor_id, retrieve the desired attributes
        if desired_attributes is not None:
            return self.__profile_index.get_filtered_profiles_from_nids(neighbors_ids, desired_attributes)
        else:
            return self.__profile_index.get_profile(neighbors_ids)

    def find_path(
            self,
            source_node_id: int,
            target_node_id: int,
            max_len: int = 3):
        return self.__graph_index.find_path(
            source_node_id, target_node_id, max_len)

    def fts_query(self, keywords, search_domain, max_results, exact_search=False) -> List:
        return self.__fts_index.fts_query(keywords, search_domain, max_results, exact_search)

if __name__ == "__main__":
    print("Discovery index")


import requests
from phe import paillier
import argparse
import numpy as np

parser = argparse.ArgumentParser()
parser.add_argument('--part', action="store", dest="part")
args = parser.parse_args()
def do_a():
    return args.part in ['a', 'ab', 'ba', None]
def do_b():
    return args.part in ['b', 'ab', 'ba', None]

def main():
    if not do_a() and not do_b():
        print(f'Only part a and b are accepted')
        return

    email = 'gregoire.clement@epfl.ch'
    base_url = 'http://com402.epfl.ch/hw5/ex3/'

    public_key, private_key = paillier.generate_paillier_keypair()

    def predict(enc_x, model, public_key=public_key):
        url_predict = base_url + 'securehealth/prediction_service'
        payload_predict = {
            'email': email,
            'pk': public_key.n,
            'encrypted_input': enc_x,
            'model': model,
        }
        resp = requests.post(
            url = url_predict,
            json = payload_predict,
        )
        return resp.json()['encrypted_prediction']

    def encrypt(x, public_key=public_key):
        if type(x) is list:
            return [encrypt(e) for e in x]
        else:
            return public_key.encrypt(x).ciphertext()

    def decrypt(x, private_key=private_key):
        if type(x) is list:
            return [decrypt(e) for e in x]
        else:
            x = paillier.EncryptedNumber(public_key=public_key, ciphertext=x)
            return private_key.decrypt(x)

    def get_input(email=email):
        url_get_input = base_url + 'get_input'
        payload_get_inp = {
            'email': email,
        }
        resp = requests.post(
            url = url_get_input,
            json = payload_get_inp
        )
        return resp.json()['x']

    ## Part a
    if do_a():
        def get_token_a(prediction):
            url_token_a = base_url + 'get_token_1'
            payload_token_a = {
                'email': email,
                'prediction': prediction
            }
            resp = requests.post(
                url = url_token_a,
                json=payload_token_a,
            )
            if resp.status_code == 200:
                token = resp.json()['token']
                print(f'ex3a = "{token}"')
            else:
                print(f'ex3a token request failed with error_code {resp.stats_code}')

        x = get_input()
        pred_enc = predict(encrypt(x), model=1)
        pred = decrypt(pred_enc)
        get_token_a(pred)

    ## Part b
    if do_b():
        def find_bias():
            x = [0]*12
            pred_enc = predict(encrypt(x), model=2)
            pred = decrypt(pred_enc)
            return pred

        def find_weight(i, bias):
            x = [0]*12
            x[i] = 1
            pred_enc = predict(encrypt(x), model=2)
            pred = decrypt(pred_enc)
            return pred - bias

        def get_token_b(weights, bias):
            url_token_b = base_url + 'get_token_2'
            payload_token_b = {
                'email': email,
                'weights': weights,
                'bias': bias,
            }
            resp = requests.post(
                url = url_token_b,
                json=payload_token_b,
            )
            if resp.status_code == 200:
                token = resp.json()['token']
                print(f'ex3b = "{token}"')
            else:
                print(f'ex3b token request failed with error_code {resp.stats_code}')

        bias = find_bias()
        weights = [find_weight(i, bias) for i in range(12)]

        get_token_b(weights, bias)

main()


from subnet import (
    IPv4Address,
    IPv6Address,
    ip_address,
)

from .constants import (
    MAX_ADDRESS_RETRIES,
    MAX_PRIVKEY_RETRIES,
)
from .config import ServerConfig
from .peer import Peer
from .utils import generate_key, public_key, find_ip_and_subnet

INHERITABLE_OPTIONS = [
    'dns',
    'interface',
    'keepalive',
    'mtu',
    'port',
    'preshared_key',
]

class Server(Peer):
    """
    The WireGuard Server

    While not required to have a server<->client setup, this class simplifies doing so
    """

    ipv4_subnet = None
    ipv6_subnet = None

    def __init__(self,
                 description,
                 subnet,
                 **kwargs
            ):  # pylint: disable=too-many-branches

        if not isinstance(subnet, (list, set, tuple,)):
            subnet = [subnet]

        if len(subnet) > 2:
            raise ValueError('You cannot set more than 2 core subnets: 1 IPv4 + 1 IPv6. '
                             'Use AllowedIPs instead.')

        addresses_from_subnets = []
        for value in subnet:
            ip, net = find_ip_and_subnet(value)  # pylint: disable=invalid-name

            if net is None:
                raise ValueError(f"'{value}' does not appear to be an IPv4 or IPv6 network")

            if ip is not None:
                if 'address' in kwargs and kwargs['address'] is not None:
                    raise ValueError(
                        'You cannot provide both an address AND a subnet with host bits set!'
                    )

                addresses_from_subnets.append(ip)

            if net.prefixlen == net.max_prefixlen:
                raise ValueError('You cannot use an IPv4 `/32` subnet, nor an IPv6 '
                                 '`/128` subnet as that only gives you 1 IP address '
                                 'to use, and therefore you cannot have any peers!')

            if net.version == 4:
                if self.ipv4_subnet:
                    raise ValueError('You cannot set 2 IPv4 core subnets.')

                self.ipv4_subnet = net

            elif net.version == 6:
                if self.ipv6_subnet:
                    raise ValueError('You cannot set 2 IPv6 core subnets.')

                self.ipv6_subnet = net

        if 'address' not in kwargs:
            if addresses_from_subnets:
                kwargs.update({'address': addresses_from_subnets})
            else:
                kwargs.update({'address': self.unique_address()})

        if 'config_cls' not in kwargs:
            kwargs.update({'config_cls': ServerConfig})

        if 'allowed_ips' not in kwargs or not kwargs['allowed_ips']:
            kwargs.update({'allowed_ips': []})
        elif not isinstance(kwargs['allowed_ips'], list):
            kwargs['allowed_ips'] = list(kwargs['allowed_ips'])

        if self.ipv4_subnet:
            kwargs['allowed_ips'].append(self.ipv4_subnet)
        if self.ipv6_subnet:
            kwargs['allowed_ips'].append(self.ipv6_subnet)

        super().__init__(
            description,
            **kwargs
        )

    def __repr__(self):
        """
        A simplistic representation of this object
        """

        return (f'<{self.__class__.__name__} iface={self.interface} ipv4={self.ipv4_subnet} '
                f'ipv6={self.ipv6_subnet} address={self.address}>')

    def __iter__(self):
        """
        Iterates through this server's useful attributes
        """

        subnets = []
        if self.ipv4_subnet:
            subnets.append(self.ipv4_subnet)
        if self.ipv6_subnet:
            subnets.append(self.ipv6_subnet)

        yield from {'subnet': subnets}.items()
        yield from super().__iter__()

    def pubkey_exists(self, item):
        """
        Checks a public key against the public keys already used by this server and it's peers
        """

        if item == self.public_key:
            return True

        return item in self.peers_pubkeys

    def address_exists_ipv4(self, item):
        """
        Checks an IPv4 address against the addresses already used by this server and it's peers
        """

        if not isinstance(item, IPv4Address):
            item = ip_address(item)

        if item == self.ipv4:
            return True

        return item in self.peers_addresses_ipv4

    def address_exists_ipv6(self, item):
        """
        Checks an IPv6 address against the addresses already used by this server and it's peers
        """

        if not isinstance(item, IPv6Address):
            item = ip_address(item)

        if item == self.ipv6:
            return True

        return item in self.peers_addresses_ipv6

    @property
    def peers_addresses_ipv4(self):
        """
        Returns all the IPv4 addresses for the peers attached to this server
        """

        if not self.peers:
            return []
        return [peer.ipv4 for peer in self.peers]

    @property
    def peers_addresses_ipv6(self):
        """
        Returns all the IPv6 addresses for the peers attached to this server
        """

        if not self.peers:
            return []
        return [peer.ipv6 for peer in self.peers]

    @property
    def peers_pubkeys(self):
        """
        Returns all the public keys for the peers attached to this server
        """

        if not self.peers:
            return []
        return [peer.public_key for peer in self.peers]

    def unique_address(self, max_address_retries=None):
        """
        Return unused addresses from this server's subnets (1 IPv4 + 1 IPv6, if applicable)
        """

        addresses = []

        if self.ipv4_subnet:
            addresses.append(self.unique_address_ipv4(max_address_retries))

        if self.ipv6_subnet:
            addresses.append(self.unique_address_ipv6(max_address_retries))

        return addresses

    def unique_address_ipv4(self, max_address_retries=None):
        """
        Return an unused address from this server's IPv4 subnet
        """

        if max_address_retries in [None, True]:
            max_address_retries = MAX_ADDRESS_RETRIES

        address = self.ipv4_subnet.random_ip()
        tries = 0

        while self.address_exists_ipv4(address):
            if tries >= max_address_retries:
                raise ValueError('Too many retries to obtain an unused IPv4 address')

            address = self.ipv4_subnet.random_ip()
            tries += 1

        return address

    def unique_address_ipv6(self, max_address_retries=None):
        """
        Return an unused address from this server's IPv6 subnet
        """

        if max_address_retries in [None, True]:
            max_address_retries = MAX_ADDRESS_RETRIES

        address = self.ipv6_subnet.random_ip()
        tries = 0

        while self.address_exists_ipv6(address):
            if tries >= max_address_retries:
                raise ValueError('Too many retries to obtain an unused IPv6 address')

            address = self.ipv6_subnet.random_ip()
            tries += 1

        return address

    def unique_privkey(self, max_privkey_retries=None):
        """
        Returns a private key that is not already in use among this server's peers
        """

        if max_privkey_retries in [None, True]:
            max_privkey_retries = MAX_PRIVKEY_RETRIES

        private_key = generate_key()
        tries = 0

        while self.pubkey_exists(public_key(private_key)):
            if tries >= max_privkey_retries:
                raise ValueError('Too many retries to obtain an unique private key')

            private_key = generate_key()
            tries += 1

        return private_key

    def peer(self,
             description,
             *,
             peer_cls=None,
             **kwargs
        ):
        """
        Returns a peer that is prepopulated with values appropriate for this server
        """

        if peer_cls in [None, False]:
            peer_cls = Peer
        elif not callable(peer_cls):
            raise ValueError('Invalid value given for peer_cls')

        if 'address' not in kwargs:
            kwargs.update({'address': self.unique_address()})

        # These are keys that should be propagated from the server to a remote peer, only
        # if they are not already being explicitly set at peer creation
        for key in INHERITABLE_OPTIONS:
            if key not in kwargs:
                kwargs.update({key: getattr(self, key, None)})
        if kwargs['mtu'] != self.mtu:
            raise ValueError('MTU cannot be different between different peers')

        peer = peer_cls(
            description,
            **kwargs
        )

        self.add_peer(
            peer,
            max_address_retries=(kwargs.get('address') is None),
            max_privkey_retries=(kwargs.get('private_key') is None),
        )
        return peer

    def add_peer(self, peer, max_address_retries=None, max_privkey_retries=None):
        """
        Adds a peer to this server, checking for a unique IP address + unique private key
        and optionally updating the peer's data to obtain uniqueness
        """

        if self.ipv4_subnet and peer.ipv4:
            if self.address_exists_ipv4(peer.ipv4):
                try:
                    if max_address_retries in [False, 0]:
                        raise ValueError('Not allowed to change the peer IP address due to'
                                         ' max_address_retries=False (or 0)')
                    peer.ipv4 = self.unique_address_ipv4(max_address_retries)
                except ValueError as exc:
                    raise ValueError(
                        'Could not add peer to this server. It is not unique.') from exc

        if self.ipv6_subnet and peer.ipv6:
            if self.address_exists_ipv6(peer.ipv6):
                try:
                    if max_address_retries in [False, 0]:
                        raise ValueError('Not allowed to change the peer IP address due to'
                                         ' max_address_retries=False (or 0)')
                    peer.ipv6 = self.unique_address_ipv6(max_address_retries)
                except ValueError as exc:
                    raise ValueError(
                        'Could not add peer to this server. It is not unique.') from exc

        if self.pubkey_exists(peer.public_key):
            try:
                if max_privkey_retries in [False, 0]:
                    raise ValueError('Not allowed to change the peer private key due to'
                                     ' max_privkey_retries=False (or 0)')
                peer.private_key = self.unique_privkey(max_privkey_retries)
            except ValueError as exc:
                raise ValueError(
                        'Could not add peer to this server. It is not unique.') from exc

        peer.peers.add(self)  # This server needs to be a peer of the new peer
        self.peers.add(peer)  # The peer needs to be attached to this server