Zen mind · Enlightenment

preface

There is a built-in memo on the Mac that feels quite useful. Then I also want to make a similar Remember gadget to play with.

Tool type: Fat server, thin client mode. The general scenario is that the client just sends the items that it wants to be reminded to the server, and then with its own local scan, the memo will be pop-up reminded if it meets the requirements.

Recently, I have been fascinated by Redis and moved by its elegant and efficient design. Although the support for search is not very good, but search words use professional search services. I personally believe in the Unix toolset tenet: a tool that does only one thing, and that’s what Redis currently embodies.

The server side

For the original design of the server side, is a “fat”, do most of the work of the form, but do do, found that the client side to do a lot of work. The current tasks on the server side are:

  • Accept user registration
  • CRUD support for Memo
  • “Security screening” of requests

The directory structure

➜  server ll *.py
-rw-r--r--  1 changba164  staff   346B Oct 28 14:53 datastructor.py # Common data structure beans
-rw-r--r--  1 changba164  staff   2.6K Oct 28 18:22 redishelper.py  # redis operations related to the utility class
-rw-r--r--  1 changba164  staff   4.6K Oct 28 18:42 server.py   # Provide service support for clientsCopy the code

The code is relatively simple, so let’s take a look at the details.

datastructor.py

#! /usr/bin python
# coding: utf8
# file: .py

import sys

reload(sys)
sys.setdefaultencoding('utf8')
import json

class ResponseCode(object):
    """Service Response Code"""
    def __init__(self, code, msg):
        self.code = code
        self.msg = msg

    def getcode(self):
        return json.dumps({"code":self.code, "msg":self.msg})Copy the code

redishelper.py

#! /usr/bin python
# coding: utf8
# file: .py

import sys

reload(sys)
sys.setdefaultencoding('utf8')

import redis
import hashlib
import uuid
import time

class RedisHelper(object):
    """Redis operation related utility classes. """
    def __init__(self):
        self.rs = redis.Redis(host="localhost", port=6379, db=3, encoding="utf8", charset="utf8")
        # related key
        self.uids = "userids:set"
        self.rank = "unfinished:zset:"
        self.unfinished = "unfinished:hash:"
        self.finished = "finished:hash:"

    def check_request_valid(self, uid, securitycode):
        if uid is None:
            return False
        if securitycode == hashlib.md5(uid).hexdigest():
            return True
        return False

    def register(self, uid):
        if uid is None:
            return False
        self.rs.sadd(self.uids, uid)
        return True

    def check_user(self, uid):
        if uid is None:
            return False
        return True if self.rs.sismember(self.uids, uid) else False

    def add_memo(self, uid, memoid, memo="") :if uid is None:
            return False
        memoid = memoid
        self.rs.sadd(str(uid), memoid)
        self.rs.hset(self.unfinished+str(uid), memoid, memo)
        self.rs.zadd(self.rank+str(uid), memoid, int(memoid))


    def update_memo(self, uid, memoid, memo):
        if uid is None:
            return False
        if not self.rs.sismember(str(uid), memoid):
            return False
        self.rs.hset(self.unfinished+str(uid), memoid, memo)
        return True

    def delete_memo(self, uid, memoid):
        if uid is None:
            return False
        memo = self.rs.hget(self.unfinished+str(uid), memoid)
        self.rs.hset(self.finished+str(uid), memoid, memo)
        self.rs.zrem(self.rank+str(uid), memoid)
        return True

    def get_memo(self, uid, memoid):
        if uid is None:
            return None
        return self.rs.hget(self.unfinished+str(uid), memoid)

    def get_memos(self, uid, reverse=True):
        if uid is None:
            return None
        memoids = self.get_memoids(uid, reverse)
        print memoids
        memos = []
        for item in memoids:
            memos.append(self.rs.hget(self.unfinished+str(uid), item[0]))
        return memos

    def get_memoids(self, uid, reverse=True):
        if uid is None:
            return []
        if reverse == True:
            return [item[0] for item in self.rs.zrevrange(self.rank+str(uid), 0, -1, withscores=True)]
        else:
            return [item[0] for item in self.rs.zrange(self.rank+str(uid), 0, -1, withscores=True)]Copy the code

server.py

#! /usr/bin python
# coding: utf8
# file: .py

import sys

reload(sys)
sys.setdefaultencoding('utf8')
from flask import Flask, request
import redishelper
import datastructor
import logging
import json

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s:%(levelname)s %(message)s',
    datafmt="%Y-%m-%d %H:%i:%s",
    filename="/Users/changba164/guo/tools/remeber/server/memo-server.log",
    filemode="a"
)

app = Flask(__name__)
helper = redishelper.RedisHelper()


@app.route("/", methods=['GET'.'POST'])
def home():
    return "It works!"

@app.route("/register", methods=["POST"])
def register():
    uid = request.form["uid"]
    helper.register(uid)
    res = datastructor.ResponseCode(1000, "uid={}".format(uid))
    return res.getcode()

@app.route("/add", methods=["POST"])
def add():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    memoid = request.form['memoid']
    memo = request.form['memo']

    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "The user has not registered yet!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    logging.info("{}: {}".format(uid, securitycode))
    if isvalid == False:
        return datastructor.ResponseCode(1002, "The identity information is illegal!").getcode()
    helper.add_memo(uid, memoid, memo)
    return datastructor.ResponseCode(1000, "The memo is already saved!").getcode()

@app.route("/update", methods=["POST"])
def update():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    memoid = request.form['memoid']
    memo = request.form['memo']

    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "The user has not registered yet!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "The identity information is illegal!").getcode()
    helper.update_memo(uid, memoid, memo)
    return datastructor.ResponseCode(1000, "The memo has been updated!").getcode()

@app.route("/delete", methods=["POST"])
def deletememo():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    memoid = request.form['memoid']

    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "The user has not registered yet!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "The identity information is illegal!").getcode()
    helper.delete_memo(uid, memoid)
    return datastructor.ResponseCode(1000, "The memo has been deleted/completed!").getcode()

@app.route("/detail", methods=["POST"])
def detail():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    memoid = request.form['memoid']

    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "The user has not registered yet!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "The identity information is illegal!").getcode()
    detail = helper.get_memo(uid, memoid)
    return datastructor.ResponseCode(1000, detail).getcode()

@app.route("/lists", methods=['POST'])
def lists():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    reverse = request.form['reverse']
    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "The user has not registered yet!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "The identity information is illegal!").getcode()
    memos = helper.get_memos(uid, reverse)
    res = datastructor.ResponseCode(1000, json.dumps((memos))).getcode()
    return res

@app.route("/recent", methods=['POST'])
def recentids():
    uid = request.form['uid']
    securitycode = request.form['securitycode']
    isuserexists = helper.check_user(uid)
    if isuserexists == False:
        return datastructor.ResponseCode(1001, "The user has not registered yet!").getcode()
    isvalid = helper.check_request_valid(uid, securitycode)
    if isvalid == False:
        return datastructor.ResponseCode(1002, "The identity information is illegal!").getcode()
    memoid = helper.get_memoids(uid, False)[0]
    result = {
        "memoid": memoid,
        "memo": helper.get_memo(uid, memoid)
    }
    res = datastructor.ResponseCode(1000, json.dumps((result))).getcode()
    return res


if __name__ == '__main__':
    app.run(host='localhost', port=9999, debug=True)Copy the code

“Thin Client”

All the client needs to do is: add the memo, get the memo that needs to be processed most, and make a pop-up prompt, etc. The current client task is not complete, it is simply implemented.

➜  client ls
testutils.py utils.py
➜  clientCopy the code

utils.py

#! /usr/bin python
# coding: utf8
# file: .py

import sys

reload(sys)
sys.setdefaultencoding('utf8')

import requests
import json
import hashlib
import time
import datetime
import tkMessageBox
from Tkinter import *

def get_security_code(uid):
    return hashlib.md5(uid).hexdigest()

def time_to_stamp(seed):
    """Convert the time of the vernal point to a timestamp in the format: 00:00:00:00 day: hour: minute: second"""
    curtime = int(time.time())
    d, h, m, s = (int(item) for item in str(seed).split(":"))
    targettime = curtime + (d*86400)+ (h*3600)+(m*60)+s
    return targettime

def stamp_to_time(stamp):
    return datetime.datetime.utcfromtimestamp(float(stamp))

def make_dialog(timestr, msg):
    tkMessageBox.showinfo(title=timestr, message=msg)
    # root = Tk()
    # root.title(timestr)
    # frame = Frame(root)
    # Label(frame, text=msg).pack()
    # frame.pack(side=TOP)
    # root.mainloop()

class UserService(object):
    """Registered User"""
    def __init__(self):
        pass

    @staticmethod
    def register(uid):
        if uid is None:
            return False
        url = "http://localhost:9999/register"
        response = requests.post(url, data={"uid": uid})
        if response.status_code == 200 and response.json()['code'] = = 1000:return True
        return False




class MemoHelper(object):
    """Client Memo Tool class"""
    def __init__(self):
        self.url = "http://localhost:9999/{}"

    def post(self, uid, memoid, memo):
        posturl = self.url.format("add")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "memoid": memoid,
            "memo": memo
        }
        response = requests.post(posturl, data=payload)
        return response.text
    def getmemo(self, uid, memoid):
        url = self.url.format("detail")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "memoid": memoid
        }
        response = requests.post(url, data=payload)
        return response.text
    def getmemos(self, uid, reverse=True):
        url = self.url.format("lists")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "reverse": reverse
        }
        response = requests.post(url, data=payload)
        return response.text
    def getrecent(self, uid):
        url = self.url.format("recent")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid)
        }
        response = requests.post(url, data=payload)
        return response.text
    def updatememo(self, uid, memoid, memo):
        url = self.url.format("update")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "memoid": memoid,
            "memo": memo
        }
        response = requests.post(url, data=payload)
        return response.text
    def deletememo(self, uid, memoid):
        url = self.url.format("delete")
        payload = {
            "uid": uid,
            "securitycode": get_security_code(uid),
            "memoid": memoid
        }
        response = requests.post(url, data=payload)
        return response.text


class Emitter(object):
    """A reminder box pops up when an expired task is detected! """
    def __init__(self, uid):
        self.uid = str(uid)
        self.memohelper = MemoHelper()

    def emit_in(self, timestr, memo):
        timestamp = time_to_stamp(timestr)
        self.memohelper.post(self.uid, timestamp, memo)



    def emit_out(self):
        If the time is correct, then emit the memo and find the one farthest from the time.
        data = self.memohelper.getrecent(self.uid)
        data = json.loads(data)
        data = data['msg']
        data = json.loads(data)
        targettime = stamp_to_time(data['memoid'])
        memo = data['memo']
        make_dialog(targettime, memo)


if __name__ == '__main__':

    emitter = Emitter(201393260)
    emitter.emit_out()Copy the code

conclusion

Overall, the gadget didn’t work as well as I expected. Here are a few things:

  • The scanning service doesn’t do much.
  • The “security” here simply limits the client request mode to POST, and simply uses securityCode to make a determination (if the packet is captured and analyzed, it does not work, it is better to emulate the Flask’s PIN code to further improve security).
  • Client pop-ups are not very good, and there is no GUI support for Posting a POST. Currently, it is used in the command line like:

    Emitin XXXXXXX A: B: C: D Record the items to be done after a day b hours C minutes D seconds.

If you are interested, you can optimize it based on the current code.