「kivyLauncher + zxing でバーコードスキャン、事始」を完遂。
「kivyLauncher + zxing でバーコードスキャン、事始」で書いた動機の通りで、店頭で「あれれこれ持ってたっけ持ってなかったっけ」のための。特にアレだ、「初回限定版」なんぞを偶然中古ショップで見つけてしまった日によく困るわけだ。
いきなり完成品コードをば:
1 title=Have you ever been purchased this?
2 author=hhsprings
3 orientation=all
ean_query.py: インチキ EAN lookup
1 import os
2 import sqlite3
3 import ean_query
4
5 _DBFILE = "./local_store.dat"
6 _created = os.path.exists(_DBFILE)
7
8 class LocalStore(object):
9 def __init__(self):
10 self._conn = sqlite3.connect(_DBFILE)
11
12 if not _created:
13 cur = self._conn.cursor()
14 cur.executescript('''\
15 CREATE TABLE t_local_store (
16 ean TEXT NOT NULL,
17 url TEXT,
18 title TEXT);
19 ''')
20 self._conn.commit()
21 cur.close()
22
23 def insert_new(self, ean):
24 if not self.query(ean):
25 qr = ean_query.query(ean)
26 self._insert_new(ean, qr)
27 return self.query(ean)
28
29 def _insert_new(self, ean, qr):
30 cur = self._conn.cursor()
31 for r in qr:
32 cur.execute(
33 """INSERT INTO t_local_store(ean, url, title)
34 VALUES (?, ?, ?)""", (ean, r['href'], r['data']))
35 self._conn.commit()
36 cur.close()
37
38 def query(self, ean):
39 result = []
40
41 cur = self._conn.cursor()
42 cur.execute("""SELECT url, title FROM t_local_store
43 WHERE ean = ?""", (ean,))
44 for row in cur:
45 url, title = row
46 result.append({'url': url, 'title': title})
47 cur.close()
48
49 return result
50
51 def delete_ean(self, ean):
52 cur = self._conn.cursor()
53 cur.execute("""DELETE FROM t_local_store
54 WHERE ean = ?""", (ean,))
55 cur.close()
56
57 if __name__ == '__main__':
58 ean = "4988031146163" #"4988013655645"
59 store = LocalStore()
60 qr = list(store.query(ean))
61 if not qr:
62 qr = store.insert_new(ean)
63 print(qr)
1 # -*- coding: utf-8 -*-
2 from kivy.lang import Builder
3 from kivy.app import App
4 from jnius import autoclass, cast
5 from kivy.clock import mainthread
6 from android import activity
7 from kivy.logger import Logger
8 from kivy.properties import StringProperty
9 from local_store import LocalStore
10
11 kv = u'''
12 BoxLayout:
13 orientation: 'vertical'
14
15 Button:
16 id: button_1
17 text: 'これ持ってたっけ?'
18 on_release: app.on_btn_query_purchased()
19 size_hint: (1, None)
20 height: self.texture_size[1] * 3
21
22 RstDocument:
23 id: info
24 text: app.info
25 size_hint: (1, None)
26 height: root.height - button_1.height - button_2.height * 2
27
28 BoxLayout:
29 id: bottom
30 orientation: 'vertical'
31
32 BoxLayout:
33 orientation: 'horizontal'
34 Label:
35 text: app.last_ean
36 size_hint: (1, None)
37 height: button_2.height
38 Button:
39 id: button_3
40 text: 'クリア'
41 size_hint: (1, None)
42 height: self.texture_size[1] * 3
43 on_release: app.last_ean = ''
44 BoxLayout:
45 orientation: 'horizontal'
46 Button:
47 id: button_2
48 text: '持ってるものとして記録'
49 on_release: app.on_btn_record_purchased()
50 size_hint: (1, None)
51 height: self.texture_size[1] * 3
52 Button:
53 id: button_3
54 text: '記録から削除'
55 on_release: app.on_btn_delete_purchased()
56 size_hint: (1, None)
57 height: self.texture_size[1] * 3
58
59 '''
60 from kivy.core.text import LabelBase, DEFAULT_FONT
61 # please download font from:
62 # http://www.google.com/get/noto/
63 # and locate it to proper directory.
64 LabelBase.register(DEFAULT_FONT, '/sdcard/site-local-fonts/NotoSansCJKjp-Regular.otf')
65
66
67 Intent = autoclass('android.content.Intent')
68 PythonActivity = autoclass('org.renpy.android.PythonActivity')
69
70 class DoYouHaveThisApp(App):
71 _store = LocalStore()
72 info = StringProperty()
73 last_ean = StringProperty()
74
75 def build(self):
76 activity.bind(on_activity_result=self.on_activity_result)
77 #
78 return Builder.load_string(kv)
79
80 def on_btn_query_purchased(self):
81 intent = Intent("com.google.zxing.client.android.SCAN")
82 intent.setPackage("com.google.zxing.client.android")
83 intent.putExtra("SCAN_MODE", "PRODUCT_MODE")
84 PythonActivity.mActivity.startActivityForResult(intent, 0)
85
86 def on_btn_record_purchased(self):
87 if not self.last_ean:
88 intent = Intent("com.google.zxing.client.android.SCAN")
89 intent.setPackage("com.google.zxing.client.android")
90 intent.putExtra("SCAN_MODE", "PRODUCT_MODE")
91 PythonActivity.mActivity.startActivityForResult(intent, 1)
92 else:
93 self.on_activity_result_record_purchased(self.last_ean)
94
95 def on_btn_delete_purchased(self):
96 if self.last_ean:
97 self._store.delete_ean(self.last_ean)
98 self.info = u"""
99 記録から削除しました
100 ====================
101 {}
102 """.format(self.last_ean)
103
104 @mainthread
105 def on_activity_result_query_purchased(self, ean):
106 self.last_ean = ean
107 qr = self._store.query(ean)
108 if qr:
109 s = u"""
110 既に持ってるものとして記録済み
111 ==============================
112
113 """
114 s += u"\n".join([u"* " + _['title'] for _ in qr])
115 self.info = s
116 else:
117 self.info = "あなたこれ持ってない: {}".format(ean)
118
119 @mainthread
120 def on_activity_result_record_purchased(self, ean):
121 qr = self._store.insert_new(ean)
122 if qr:
123 s = u"""
124 持ってるものとして記録
125 ======================
126
127 """
128 s += u"\n".join([u"* " + _['title'] for _ in qr])
129 self.info = s
130
131 def on_activity_result(self, requestCode, resultCode, intent):
132 Logger.info('requestCode={}, resultCode={}'.format(
133 requestCode, resultCode))
134 if requestCode not in (0, 1):
135 return
136 contents = intent.getStringExtra("SCAN_RESULT")
137 format = intent.getStringExtra("SCAN_RESULT_FORMAT")
138 Logger.info('contents={}, format={}'.format(contents, format))
139
140 if requestCode == 0:
141 self.on_activity_result_query_purchased(contents)
142 else:
143 self.on_activity_result_record_purchased(contents)
144
145 def on_pause(self):
146 return True
147
148
149 if __name__ == '__main__':
150 DoYouHaveThisApp().run()
こんな画面:
「これ持ってたっけ?」ボタンを押すとバーコードスキャナ起動。これでバーコードを読み取って戻ってくると、持ってたか持ってなかったかが真ん中の RstDocument に。
キャプチャで 「4988031146163」になってる部分が読み取れた製品コードで、この状態で「持ってるものとして記録」ボタンで Sqlite に記録。また「これ持ってたっけ?」ボタン経由しないか、クリアボタンでこのラベルを空にした状態で「持ってるものとして記録」ボタンだと、同じくバーコードスキャナが起動して、この場合はそのまま Sqlite に記録。「記録から削除」ボタンは文字通り。
みての通りで、あきらかにいらないデータがヒットしているけれど、これは「インチキ EAN ルックアップ」の宿命。まぁ「あんたナニモノ?」が EAN コードだけでおよそわかればいい、ってノリなんで、まぁいいか、て感じ。
作るのに困った点がいくつか。
一つ目。まずは on_pause
の件。「kivyLauncher + zxing でバーコードスキャン、事始」ではログとしてはちゃんと on_activity_result
に戻ってきてたのでいらないと思ってたが、ないとダメだった。
二つ目。sqlite の制限について。これ:
1 Traceback (most recent call last):
2 File "jnius/jnius_proxy.pxi", line 47, in jnius.jnius.PythonJavaClass.invoke (jnius/jnius.c:29223)
3 File "jnius/jnius_proxy.pxi", line 73, in jnius.jnius.PythonJavaClass._invoke (jnius/jnius.c:29924)
4 File "/home/tito/code/python-for-android-upstream/build/python-install/lib/python2.7/site-packages/android/activity.py", line 32, in onActivityResult
5 File "main.py", line 63, in on_activity_result
6 Logger.info(self._store.insert_new(contents))
7 File "/storage/emulated/0/kivy/zxing_barcode_0/local_store.py", line 25, in insert_new
8 self._insert_new(ean, qr)
9 File "/storage/emulated/0/kivy/zxing_barcode_0/local_store.py", line 29, in _insert_new
10 cur = self._conn.cursor()
11 ProgrammingError: SQLite objects created in a thread can only be used in that same thread.The object was created in thread id -517703376 and this is thread id -150914252
同一スレッドに閉じないとダメ。なので main.py に「@mainthread
」入れてる。
三つ目。zxing 以外が反応しうる、という点。なんだか QPython が反応しちゃって、「どっち使う?」と聞かれるようになってた。ので:
1 def on_btn_query_purchased(self):
2 intent = Intent("com.google.zxing.client.android.SCAN")
3 intent.setPackage("com.google.zxing.client.android")
4 intent.putExtra("SCAN_MODE", "PRODUCT_MODE")
5 PythonActivity.mActivity.startActivityForResult(intent, 0)
四つ目。日本語フォント問題。これは気持ち悪い対処だった…。
1 from kivy.core.text import LabelBase, DEFAULT_FONT
2 # please download font from:
3 # http://www.google.com/get/noto/
4 # and locate it to proper directory.
5 LabelBase.register(DEFAULT_FONT, '/sdcard/site-local-fonts/NotoSansCJKjp-Regular.otf')
としてる部分なんだけれども、これ、Google Noto Fontsからわざわざダウンロードして置いてる。けどなんで? アタシのスマホは android 6.0 で、 /system/fonts/NotoSansJP-Regular.otf
なんてのは元から入ってたのね。なんだけどこれ、日本語部分以外が全部化ける。んなアホな…。(日本語が化けないならまだわかるんだけど、日本語もところどころ化ける。化ける、というのは、四角にバッテン。) うーん、日本製(SHARP Aquos)なのにもとから標準で使えるものってないもんなの? ほかのアプリってフォントも自分でまかなってんのかしらん?
以上4点だけ苦労した…、けどまぁほぼ一日で出来た。まぁチョロいちゃぁチョロい。
「EAN」言うておるけれど、実際 ISBN を扱えるようにするのも別に難しくないし、今は「バーコード」だけ扱ってるけど、zxing は普通に QR コードも扱えるので…、まぁもっと拡張できますよん。もし欲しかったら自分で追加してみたら?