t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

LPI Linux Essentials 試験レビュー

本日 LPIが提供するLinux Essentials試験を受験してきた。
無事に合格したのでレビューを書こうと思う。

試験のレベル感と取得の意義

Linux認定試験の中では最もエントリーレベル。
ただし単に簡単ということではなく、Essential(本質的・根本的・必要不可欠)という名前のとおり、Linux触るなら最低限このくらいは知っておきましょうという内容が学習範囲となるため本職以外でLinux知識が必要な方には必要十分な内容だと思う。

さすがに本格的なサーバーエンジニアになるとこの出題範囲では物足りないが、オペレーターなら十分かなと思う。

特に趣味でラズパイサーバーを立てている私みたいな人間が、スキルアピールとしてLinux触ったことありますと主張するにはちょうど良い手ごろなレベル感。

逆に、Linuxをまったく触らない人が単に教養として認定取得する意義は薄い気がする。

受験料と受験方法

通常だと税込 11,000円。
ping-tという学習サイトで割引チケットを買うと税 9,900円。

試験はピアソンVueで提供されているので全国のテストセンターで受験することになる。

学習前の私のレベル感と学習方法、学習期間

学習前の私のスキルレベル

もともと10年ほど前にLinuxカルチャーに傾倒した時期があり、ファイル・ディレクトリの操作やcron設定、パッケージインストール、viでのテキスト編集など、そもそも知らないとLinuxのコマンド操作がままならないような内容に関する知識は持っていた。
ただ所詮趣味で触っている都合、ユーザー管理・パーミッション管理・アーカイバーなどは必要に迫られたことが無いため弱い。

基本情報技術者・応用情報技術者を持っているのでIT全般の基礎知識はあり。
ネットワークも以前は必要に迫られなかったのでもともと苦手分野だけどラズパイの登場以降は管理デバイスが増えすぎてCiscoスイッチを導入し、自宅ネットワークをいじり倒したので現在は苦手意識はない。

私の学習方法

以下の書籍が5編に分かれているので1編やってはping-tで対応する問題を解いた。

LPI公式認定 Linux Essentials 合格テキスト&問題集

LPI公式認定 Linux Essentials 合格テキスト&問題集

  • 作者:長原 宏治
  • 日本能率協会マネジメントセンター
Amazon

ping-tは登録無料でLinux Essentialsの学習範囲はすべて無料なので特に費用は掛かっていない。

私の学習期間

体感的には1日30分~1時間くらいで概ね1か月程度。

Redmineで学習管理していたので具体的に示すと、書籍とping-tの1周目が4/10~4/23

ping-tの2周目と全問題コンボ※までが4/24~5/8 (ソート順が逆)

※同じ問題を2回連続で正解するとコンボ

模擬試験機能もあるので計画はしていたんだけどフルコンボしたしどうにかなるだろってことで最後はダレたまま試験日を迎えた。

問題数と試験時間

60分で40問を解くのだが、所詮は知識問題なのでサクサク解けないものは考えても解けない。
従って実質所要時間は15分くらい。

総括

所詮エントリーレベル認定なので権威性は全くないが、学習期間・テスト時間ともに短い割にLinux運用の中核となる知識が習得できるので、Linuxをなんとなく使っている人がもうすこしちゃんと知識を補完したいというケースでは比較的コスパが良い資格だと思う。

普段からLinuxを触っている人なら自分の知識範囲と出題範囲が重なれば無勉強で挑んでも運よく取れるかもしれないが、コレクション価値のある認定ではないのでそういう取り方は時間と金の無駄だと思う。ちゃんと網羅的に学習を進めるのがおススメ。

日本では高難度認定ばかりがもてはやされてエントリーレベルの認定を無意味と一蹴する人も多い。
しかしエントリーレベルの認定は通常その分野で最も重要な知識をカバーしているため、学習時間に対する得られる知識という意味で最もコスパが良い。
専門分野ならともかく周辺分野の知識を固めるのに高難度認定はオーバーキルなので今後もこうしたエントリーレベルの認定を積極的に活用していきたい。

Python・数学・Bing AIでそれぞれSatisfactoryの代替レシピガチャの確率を求めてみた。

今回は私が遊んでいるゲーム Satisfactoryのなかのガチャ要素で狙ったものを引きあてる確率を求めてみた。

Satisfactoryは異星に工場を発展させていく生産シミュレーション系のゲームである。
詳しくは以下の記事で紹介しているので気になる方はチェックしてみて欲しい。
thom.hateblo.jp
ただし面白すぎて生活に支障が出ても責任は負いかねるのであしからず。

さて、このゲームでは部品を組み合わせて製品を作っていくのだが、通常のレシピとは別に代替レシピというものが存在する。

代替レシピはこの世界に転がっている墜落した降下ポッドから入手できるハードドライブというアイテムを分析器にかけることで手に入る。

このように3つの代替レシピが提示され、そのうち1つを選択して入手する形である。

ハードドライブの分析結果は87通り存在し、うち85種類がレシピ、のこり2つはインベントリスロットの増加(要するにキャラ強化)であるが、プログラムを書く上では便宜上レシピ87個としておこう。

つまり87個のうち、3個がランダムで提示されるので、そのうち狙った1点を引き当てる確率がどれくらいかという話になる。
そこでまずはPythonのrandomモジュールを使ってこのガチャをひたすら回すシミュレーションを書いてみた。
ゲームの仕様上、入手済みレシピは提示されなくなるため未開放レシピ数に応じた確率も調べてみた。

from random import randint

for z in range(87,2,-5):
    lookfor = randint(0, z-1)
    number_of_trial = []
    for y in range(10000):
        n=0
        hit = False
        while not hit:
            n+=1
            recipes = list(range(z))
            hit = False
            for x in range(3):
                item = recipes.pop(randint(0,len(recipes)-1))
                if item == lookfor:
                   hit = True
        number_of_trial.append(n)
    
    print("未開放レシピ:" + str(z) + "個",end=" ")
    print("最大:" + str(max(number_of_trial))+"回",end=" ")
    print("平均:" + str(sum(number_of_trial)/len(number_of_trial))  + "回")

実行結果は次のとおり。
例えば87個の未開放レシピが残っている状態で狙った1点を入手するためにリセマラ※した場合、最大270回、平均28.7882回やり直しが必要ということ。
※リセットマラソン=思い通りの結果になるまで挑戦前のセーブデータを読み直す行為。

未開放レシピ:87個 最大:270回 平均:28.7882回
未開放レシピ:82個 最大:311回 平均:27.4874回
未開放レシピ:77個 最大:253回 平均:25.6612回
未開放レシピ:72個 最大:203回 平均:23.8749回
未開放レシピ:67個 最大:219回 平均:22.5432回
未開放レシピ:62個 最大:206回 平均:20.6362回
未開放レシピ:57個 最大:186回 平均:18.7125回
未開放レシピ:52個 最大:179回 平均:16.9635回
未開放レシピ:47個 最大:138回 平均:15.4295回
未開放レシピ:42個 最大:141回 平均:13.8436回
未開放レシピ:37個 最大:105回 平均:12.2546回
未開放レシピ:32個 最大:121回 平均:10.7985回
未開放レシピ:27個 最大:93回 平均:9.0674回
未開放レシピ:22個 最大:87回 平均:7.3508回
未開放レシピ:17個 最大:44回 平均:5.616回
未開放レシピ:12個 最大:30回 平均:3.9715回
未開放レシピ:7個 最大:18回 平均:2.3499回

ハードドライブ1個につき分析に10分かかるので平均して5時間近くかかる見込み。
で、5時間あれば世界中駆け回ってハードドライブ87個集まるので、このゲームでは特にリセマラする意味はないということが分かった。


ちなみにリセマラ回数ではなくて1発で引き当てる確率を求める場合はこのようなコードになる。

from random import randint
lookfor = randint(0, 86)
n=0
for y in range(1000000):
    recipes = list(range(87))
    for x in range(3):
        item = recipes.pop(randint(0,len(recipes)-1))
        if item == lookfor:
            n+=1
print(n/1000000)

3回実行したところ、いずれも3.4%となった。

0.034789
0.034437
0.034695

ちなみに数学的にはこういう式になる。
\displaystyle \frac{_{86}C_2 \times _1C_1}{_{87}C_3}

計算してみると、こちらも3.4%と出た。

>>> (((86*85)/(1*2))*(1/1))/((87*86*85)/(1*2*3))
0.034482758620689655

このようにプログラムによるシミュレーションでも十分な回数を繰り返せば数学的に求めた理論値に近似するので私のように数学が苦手な方はプログラミング力を磨いておくと何かと便利だと思う。

数学・プログラミングどちらも苦手という場合、これからの時代はAIによるサポートを受けることができる。
このように直接聞けば色々教えてくれる。
Powered by Bing

ただ問題を一般化して言語で表現することでこのように計算根拠まで確認することができるのでより的確な語彙を選択できる方が有利だとは思う。

Bingが出した式をPythonで計算してみるとBingが出した答えとちょっと違うのでそこらへんは鵜呑みには出来なさそうだけど。

>>> 1 - (86/87)**3
0.03408792366929225

てことで数学・ブログラミングが苦手なら国語力を鍛えるのがおススメ。

全部苦手って方は。。
うん、大丈夫。デキる友達に飯をごちそうすれば万事解決だ。

以上

電動エアダスターレビュー

ヨドバシポイントが貯まったので以前から気になっていた電動エアダスターを買ってみた。

購入したのはコンセント式のこちらの製品。

実際に使ってみるまでは、所詮エアーの勢いはガス圧にはかなわないだろうと思っていたんだけど電動の圧勝だった。
3メートル離れた位置からカーテンを窓に押し付けることができるくらいの勢いがある。

といってもピンとこないと思うのでデジタルスケールを使ってエアダスターと風圧を比較してみた。


実は手持ちのガス缶は少し使った後なので上記の画像では公平な比較は出来ていないんだけどまだまだエアダスターとして十分使える状態のものだ。

まさに圧勝。

動画に撮ったので気になる方は音も聞いてもらえればと思う。
youtube.com
音は完全に掃除機だ。

部屋の隅にたまりがちな埃もこれで巻き上げて表にだしてから掃除すれば掃除機が届かないところの埃を一掃できる。
私はこれで数か月分の埃を処理したので部屋が大惨事になったけど。。マメにやると良さげ。

あとはエアコンフィルターを水洗いした後に水滴を飛ばしたり、キーキャップ洗浄後に裏側の水滴を吹き飛ばしたりするのに使う。

注意点は消費電力が400Wあるのでコンセントの許容量を超えないように気を付けることと、すぐ温風が出るほどモーターが発熱するため説明どおり15分以上連続稼働させないことくらいかな。

結構な値段はしたけどもうガス缶を買わなくてよくなるので良い買い物だったと思う。

以上。

GUI開発が面倒ならCUIで作れば良いという気付き~Pythonリマインダー管理用コマンドツールを作成

今回は、以前作成したリマインダーシステムの管理ツールを作成した話。
thom.hateblo.jp

あれからリマインダーシステムはますます重要度を増し、周期的な作業も含めると既に40近くのTo Doが登録されている。

最初のうちは手修正で良かったが、これだけ数が増えると管理が煩雑なので別途仕組みを考えた。

考察

ドラッグ&ドロップで1日あるいは1週間延期できるような仕組みを作ったものの、これだけ項目が多いと間違って延期してしまったら埋もれてしまい気づかないリスクがある。

また、細かい調整はどうしても手動でファイル名を変えていたのだが、文字が小さいので入力ミスに気付かなかったり、存在しない日付を入力してしまったり、日を変えたつもりで月を変えてしまったりといったリスクも生じる。

そろそろもう少し安心して利用できる管理ツールが欲しいと思ってはいたのだが、画面レイアウトの設計と実装が面倒くさくて長らく放置していた。

そしてついに閃いた。

。。。CUIでいいのでは?

経緯

これは私の偏見も多分に混じっているかもしれないが、Windows文化においてはGUIが正義で今どきCUIアプリなんて見向きもされない。
例えばプログラミング入門書は大抵CUIを前提に解説しているのだが、黒画面にテキストを表示させるだけのアプリなんて作っても何も嬉しくないと感じる人が大半だと思う。

ところがしばらくUnix/Linux文化にどっぷりつかるとこの考えは180°変わる。
もう10年以上前になるけど、しばらく私はLinux文化に傾倒していた時期がある。当時はコマンドラインこそが正義という考えのものとWindowsを捨てて1年間ほどCUIのみのDebianノートをメインPCとしていた。
そのきっかけとなったのはこの書籍。

過去に記事も書いていたようだ。
thom.hateblo.jp

しばらく忘れていた感覚ではあるが、確かにコマンドにはコマンドの良さがあるのだ。
このことを再度思い返すきっかけとなったのが最近やっているLinux Essentialsの学習である。

ということでCUIアプリを作ることにした。

要件

機能としてほしいのは閲覧・登録・延期・削除である。

機能 動作イメージ
閲覧 showで10件の閲覧、nextで次の10件、prevで前の10件。
登録 create タスク名で1時間後を期限として新規タスクを作成。
延期 selectで複数選択し、extend n dでn日延期。extend n hでn時間延期。
削除 selectで複数選択し、removeで削除。

実装

取り急ぎpythonで作ったmanage.pyが以下のコード。

import os
import re
import datetime
from datetime import datetime as dt

def get_tasks():
    tasks = os.listdir(os.path.join(os.getcwd(), "tasks"))
    return [s for s in tasks if s.endswith(".txt")]

tasks = get_tasks()
selection = set()

def show_tasks(i):
    for x in tasks[i:i+10]:
        print(tasks.index(x), x)


cmd = ""
i=0
while cmd != "exit":
    cmd = input("$ ")
    if cmd == "show":
        show_tasks(i)

    if cmd == "next":
        if i + 10 < len(tasks):
            i = i + 10
            show_tasks(i)
        else:
            print("EOF")

    if cmd == "prev":
        if i - 10 >= 0:
            i = i - 10
            show_tasks(i)
        else:
            print("BOF")

    if cmd == "reload":
        i = 0
        tasks = get_tasks()
        selection = set()
        show_tasks(i)

    pattern = re.compile("^select( [0-9]+)*$")
    if pattern.match(cmd) is not None:
        for x in sorted(set(map(int,cmd.strip().split(" ")[1:]))):
            if len(tasks) >= int(x):
                selection.add(x)
        for y in sorted(selection):
            print(y, tasks[y])
    
    pattern = re.compile("^unselect( [0-9]+)*$")
    if pattern.match(cmd) is not None:
        for x in sorted(set(map(int,cmd.strip().split(" ")[1:]))):
            if len(tasks) >= int(x):
                selection.discard(x)
        for y in sorted(selection):
            print(y, tasks[y])

    pattern = re.compile("^extend -?[1-9][0-9]* (d|h)$")
    if pattern.match(cmd) is not None:
        for x in sorted(selection):
            exstr = cmd.strip().split(" ")[1:]
            if exstr[1] == "d":
                td = datetime.timedelta(days=int(exstr[0]))
            else:
                td = datetime.timedelta(hours=int(exstr[0]))

            newtime = (td + dt.strptime(tasks[x][0:16], "%Y_%m_%d_%H_%M")).strftime("%Y_%m_%d_%H_%M")
            old_path = os.path.join(os.getcwd(), "tasks", tasks[x])
            new_path = os.path.join(os.getcwd(), "tasks", newtime + tasks[x][16:])
            os.rename(old_path, new_path)
        tasks = get_tasks()
        selection = set()

    pattern = re.compile(r'^create [^ .\\/:*?"<>|]+$')
    if pattern.match(cmd) is not None:
        fname = (dt.now()+datetime.timedelta(hours=1)).strftime("%Y_%m_%d_%H_%M_") + cmd.split(" ")[1] + ".txt"
        fpath = os.path.join(os.getcwd(), "tasks", fname)
        open(fpath,"w").close()
        print(fpath)
        tasks = get_tasks()
        selection = set()

    if cmd == "remove":
        for x in sorted(selection):
            print(tasks[x])
        if input("Are you sure? ") == "sure":
            for y in sorted(selection):
                rm_path = os.path.join(os.getcwd(), "tasks", tasks[y])
                os.remove(rm_path)
                print("[Removed]" + rm_path)
        else:
            print("Cancelled.")
        tasks = get_tasks()
        selection = set()

雑な説明

python manage.pyとして実行するとプロンプトとして$マークを表示しコマンドを受け付ける。
コマンドを受け付けると処理に応じて出力し、次のコマンドループに入る。exitが入力されたらプログラムを終了する。

show等の引数無しコマンドはif文で比較し、extendなどの引数付きコマンドは正規表現でコマンドと引数の正しさをチェックしている。
selectコマンドは実行する度に選択アイテムが追加される仕組みなのでshow・next・prev等で別ページにあるアイテムを追加するのも簡単。
引数無しでselectを実行すると現在選択されているアイテムがずらっと表示される。
removeコマンドは確認が入るので、sureと入力すると削除、それ以外が入力されたらキャンセルされ、アイテムの選択が解除される。
※今のところunselect allは実装していないのでunselectで個別指定するかremoveをキャンセルするかexitしてやり直すことになる。

終わりに

CUIで面白いプログラムを書けないならGUIでも書けないという言葉をどこかで読んだ記憶がある。
その言葉を知った当時は少々過激な表現だなと思っていたけど、CUIでも十分に使えるプログラムを書けるということを改めて実感できたので、あながち間違いでもない気がしてきた。

プログラミング入門者が挫折するひとつの要因は、入門書で学習してもなかなか実用的なプログラムが作れるところまでたどり着かない点にあると思う。
実用的なプログラム=GUIアプリだという思い込みがこの傾向に拍車をかけているのかもしれない。
初心者はGUIアプリの作成に憧れを抱きがちだ。しかし初心者が作るとなるとなかなか難しい。

ひょっとするとその憧れの向き先をGUIではなくCUIに向けることができれば、案外すんなりとプログラミング言語を習得できるかもしれない。
確かにGUIの方が直感的に使えるけれど、慣れればCUIの方が効率的というケースはよくある。

それにマウスでポチポチ操作するよりも黒画面でカタカタやってるほうがなんとなく玄人っぽくて格好良い※。最初はそういう適当な憧れで良いんだと思う。

皆さんも自分だけのCUIツールを作ってみてはいかがだろうか。

※個人の主観です。PC音痴の知人によると黒画面で何かしてる人はみんなサイバー犯罪者に見えるようですが。。

Linux Essentialの学習で得たヒントでラズパイのフリーズ原因を究明した話

このところ自作した目覚まし時計のフリーズに悩まされて来たのだが、ついにこれだという原因にたどり着いた。
thom.hateblo.jp

ヒントになったのがLinux Essentials試験の学習で得たメモリ管理まわり。
freeコマンドでメモリの状況を確認できるのでメモリリークを疑ってみようと思ったのだ。

試しに疑わしいスクリプトのcron実行を1分に1回の起動に変えてみたうえ、同じタイミングで毎回freeコマンドでメモリ容量を~/free.txtに書き込むようにしてみたところ次のような結果になった。

Thu  4 May 20:46:01 JST 2023
               total        used        free      shared  buff/cache   available
Mem:          931432       93744      706304         964      131384      778148
Swap:         102396           0      102396

Thu  4 May 20:47:01 JST 2023
               total        used        free      shared  buff/cache   available
Mem:          931432       99220      700760         964      131452      772652
Swap:         102396           0      102396

Thu  4 May 20:48:01 JST 2023
               total        used        free      shared  buff/cache   available
Mem:          931432      104972      694472         964      131988      766880
Swap:         102396           0      102396

~中略~

Thu  4 May 21:03:01 JST 2023
               total        used        free      shared  buff/cache   available
Mem:          931432      192092      605556         964      133784      679380
Swap:         102396           0      102396

これだ!
かなりのハイペースでメモリを食い続けている。

今回の犯人はwokeup.pyというスクリプト

前提として目覚まし時計は鳴るタイミングでcron起動されるalarm.pyと、常時起動のwokeup.pyで構成されている。
以前、常時起動の方が落ちていたことがあったのでいずれデーモン化しようと思っているのだが面倒なのでとりいそぎcronで毎時10分と40分に起動させている。
wokeup.pyはすでに起動しているwokeup.pyがあればpkillで落とすような仕様なので疑って無かったんだけど、psコマンドで確認してみると53件ものwokeupが起動中。まじか。

thom@alarm:~ $ ps aux | grep -c wokeup
53


とりいそぎ手動でpkillしたところ起動直後の基準まで落ち着いた。

thom@alarm:~ $ pkill wokeup
thom@alarm:~ $ ps aux | grep -c wokeup
1
thom@alarm:~ $ free
               total        used        free      shared  buff/cache   available
Mem:          931432       82632      714468         972      134332      788768
Swap:         102396           0      102396


wokeupスクリプト内で原因となっているのはこの部分。
要するにpkillに失敗しているらしい。

cmd = "sudo pkill wokeup"
proc = Popen(cmd.strip().split(" "))
proc.wait()
sp.setproctitle("wokeup")

そういえばalarm.pyも二重に鳴ったことがあってsudoを外したら上手く行ったんだった。
ひとつ思い当たる節はセキュリティ強化の為にsudo実行にパスワードを要求するように設定したこと。

スクリプト内のsudoならcronがうまく捌いてくれるかなと勝手に思い込んでいたんだけどダメだったみたいだ。
また、プロセスを終了させるpkillコマンドはroot権限じゃないと動かないという勘違いもあった。
自分で起動したプロセスなんだから、当然自分でpkillできるんだ。

コマンドの場所もsbinじゃなくてbinになっている。

thom@alarm:~ $ which pkill
/usr/bin/pkill


修正後の記録がこちら。

Thu  4 May 21:14:01 JST 2023
               total        used        free      shared  buff/cache   available
Mem:          931432       90912      706048         972      134472      780488
Swap:         102396           0      102396

Thu  4 May 21:15:02 JST 2023
               total        used        free      shared  buff/cache   available
Mem:          931432       85292      711736         964      134404      786152
Swap:         102396           0      102396

Thu  4 May 21:16:01 JST 2023
               total        used        free      shared  buff/cache   available
Mem:          931432       85196      711728         964      134508      786268
Swap:         102396           0      102396

Thu  4 May 21:17:01 JST 2023
               total        used        free      shared  buff/cache   available
Mem:          931432       88988      707948         964      134496      782508
Swap:         102396           0      102396

~中略~

Thu  4 May 21:23:01 JST 2023
               total        used        free      shared  buff/cache   available
Mem:          931432       87892      708996         964      134544      783620
Swap:         102396           0      102396

増減は見られるが修正前のように徐々に確実に減っていくということはない。

プロセスも確認してみた。周辺コマンドが表示されてしまっているもののwokeup本体は1つのみ。

thom@alarm:~ $ ps aux | grep wokeup
thom        1369  0.0  0.0   2064   468 ?        Ss   21:26   0:00 /bin/sh -c /home/thom/wokeup.py
thom        1371  2.0  1.0  24284  9680 ?        Sl   21:26   0:00 wokeup
thom        1377  0.0  0.0   5908   752 pts/0    S+   21:26   0:00 grep --color=auto wokeup

ということで恐らく解決。

これまでは馬鹿の一つ覚えとして落ちた後にひたすらsyslogを確認するということをしていたのだが知識不足で何が起きているのか分からず。

また、メモリの枯渇という概念を知識としては持っていたものの確認コマンドを知らなかったがためにメモリが枯渇しているという状況を想定すらできていなかった。

今回はLinux Essential試験に向けた網羅的な学習がヒントとなって解決できた形。

手段がなければ目的すら見えない。
これまでもいろんなところで実感してきたことだが今回も改めてそう認識した次第である。

LPI公式認定 Linux Essentials合格テキスト & 問題集 演習問題に文句がある 第1編

今回はこちらの書籍から引用。

LPI公式認定 Linux Essentials 合格テキスト&問題集

LPI公式認定 Linux Essentials 合格テキスト&問題集

  • 作者:長原 宏治
  • 日本能率協会マネジメントセンター
Amazon

第1編の問題12問中、3問納得いかないのがあったので愚痴記事である。
実際には試験対策としてさすがにブログで文句書けば記憶に残るだろうっていう魂胆もある。


問題2:クラウド上でLinuxがよく使われる理由として、正しいものを3つ選択せよ。
外した選択肢:無料で利用できるから

これは読解力が足りなかったけど設問も不親切。そもそもクラウドが有料だし無料は違うだろうと思ったけどクラウドサーバーのOSとしてLinuxが選択される理由って意味か。OSライセンス料がかからないからってことね。



問題3:セキュリティの観点から、不適切なアクションを2つ選択せよ。
外した選択肢:特に重要なファイルではないが暗号化して送信する。

これは何がダメなのか意味不明。効率化の観点を聞いてるんじゃなくて、わざわざセキュリティの観点って書いといてなんでやねん。。何か問題が?



問題5:GPLv3で公開されているアプリケーションを基に、Androidタブレットで動作するバージョンのアプリケーションを開発した。そのソースコードはインターネットで公開する。アプリケーションの適切な扱い方として、正しいものを2つ選択せよ。
外した選択肢:改良に参加してくれたプログラマをヘッドハンティングする。

は?「正しい」とは?問題のない選択肢はどれかという設問なら分かるけど。


ということで愚痴は以上。

Pythonリマインダーに「今やる」通知と「そろそろやる」通知をアイコンで区別する仕組みを導入

今回はこちらの2記事の続き。
thom.hateblo.jp
thom.hateblo.jp

Pythonリマインダーを実際に運用し始めてからすこぶる便利に使っているんだけど、そのうちやる案件だけじゃなくて、単純に時間になったら取り組まないといけない案件もコレで管理を始めた。

どんどん増えてくる。。

そのうちやる案件は多少無視しても大丈夫だけど時間が決まっている案件は正確に知らせて欲しい。

ということでアイコンをもう一つ用意した。

そんでファイル名の日付の後ろにエクスクラメーョンマークが入ってた場合は赤いアイコンが使われる仕組み。

こんな感じ。

例では2個と表示されているけどこれは期限到来ファイルの総数で、うち1個でも!付きのものがあるとアイコンが赤になる。

変更後のコードは次のとおり。

import os
import glob
from datetime import datetime as dt
from plyer import notification


base_path = os.path.dirname(os.path.abspath(__file__))
files = glob.glob(os.path.join(base_path,"tasks\*.txt"))
cnt = 0
punctual_cnt = 0
err = 0
for file in files:
    str_task_time = os.path.basename(file)[0:16]
    try:
        task_time = dt.strptime(str_task_time, "%Y_%m_%d_%H_%M")
        if dt.now() > task_time:
            cnt += 1
            if (os.path.basename(file)[16:17]) == "!":
                punctual_cnt += 1
    except:
        err += 1


notify_title = "ERROR"
notify_message = "不明なエラー"

if not os.path.exists(os.path.join(base_path,"tasks")):
    err += 1
    notify_message = "tasksフォルダが存在しません。\n"+base_path+"を確認してください。"
else:
    if err > 0:
        notify_message = "タスクファイル名の日付書式を確認してください。\n"+base_path+r"\tasks"
    else:
        if cnt > 0:
            notify_message = str(cnt)+" 個の期限切れタスクを処理してください。\n"+base_path+r"\tasks"

icon_path = os.path.join(base_path, "notify.ico")
if punctual_cnt > 0:
    icon_path = os.path.join(base_path, "notify!.ico")

if not os.path.exists(icon_path):
    err += 1
    icon_path = None
    notify_message = "アイコンが見つかりません。\n"+base_path

if err == 0:
    notify_title = "Reminder"

if err+cnt > 0:
    notification.notify(
        title=notify_title,
        message=notify_message,
        app_name="Py Notify",
        app_icon=icon_path,
        timeout=10
    )


ついでに1日リスケするスクリプトも書いたのでご紹介。later.pywという名前で保存し、そこにドラッグ&ドロップすると引数扱いになってファイル名の日付が1日後ろ倒しになる。

コードは次のとおり。

import os
import tkinter as tk
from tkinter import messagebox
import datetime
from datetime import datetime as dt
import sys

fp = sys.argv[1]

root = tk.Tk()
root.withdraw()

str_task_time = os.path.basename(fp)[0:16]
task_time = dt.strptime(str_task_time, "%Y_%m_%d_%H_%M")
new_time = (task_time + datetime.timedelta(days=1)).strftime("%Y_%m_%d_%H_%M")

messagebox.showinfo("Postpond", "[Old]\t" + os.path.basename(fp) + "\n\n" + "[New]\t" + new_time + os.path.basename(fp)[16:])
os.rename(fp, new_time + os.path.basename(fp)[16:])


以上

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。