Rokiのチラ裏

学生による学習のログ

コンパイル時のシーケンス生成は時が経って簡単になった

偶然見つけた以下のエントリ。

cpplover.blogspot.jp saito.hatenablog.jp

2010年...あれから時は経ち、いとも簡単にコンパイル時のシーケンス生成は簡単になった。

#include<srook/mpl/constant_sequence/equ_sequence.hpp>
#include<srook/mpl/constant_sequence/algorithm/transfer_array.hpp>
#include<iostream>

for(auto&& value:srook::constant_sequence::transfer_array<srook::constant_sequence::make_equ_sequence_t<5>>::value)
        std::cout<<value<<std::endl;

make_equ_sequenceとかいう適当な命名の再起関数によって数値5から0,1,2,2,3,3,3,4,4,4,4,5,5,5,5,5のシーケンスを生成し、transfer_arrayによって配列(std::array)に変換している。

良き時代になったものだ。

Siriから自宅サーバー監視カメラ(motion)をオンオフするまで

今回もお家ハックネタ。ラズパイとかでも同じ事ができると思う。 motionについては過去のエントリを参照してほしい。

さて、取り敢えずまずはphpからmotionを起動したり殺したりしなければならない。そのためにはroot権限が必要になる。これには、apacheからのmotion実行に対しての操作はパスワードが不要である旨をvisudoに明記する必要がある。*1

% sudo visudo
Defaults visiblepw
www-data ALL=(ALL) NOPASSWD:/usr/bin/motion;/usr/bin/killall

で、phpファイルであるが、motionプロセスが立ち上がっていれば殺し、立ち上がっていなければ生成させるようにする。取り敢えず以下のようにしている。

<?php
$output=shell_exec('ps alx | grep motion | grep -v grep | grep -v \'php motion\'');

if(strpos($output,'motion')!==false){
    `sudo killall motion`;
}
else if(strpos($output,'motion')===false){
    `sudo motion`;
}
?>

以上で、HTTP GETリクエストを送ればmotionが付いたり消えたりするようになる。

$ curl -X GET "http://server/motion.php"

さて、次にHTTPリクエストを送るだけのアプリを作る。といっても本当にHTTPリクエストを送るだけなので簡単。レスポンスが返ってきた瞬間にアプリを即終了させる。

import UIKit

class URLSessionGetClient {
    func get(url urlString: String) {
        let url = URL(string: urlString)
        let task = URLSession.shared.dataTask(with: url!) { data, response, error in
            if let _ = data, let response = response {
                print(response)
                exit(EXIT_SUCCESS);
            } else {
                print(error ?? "Error")
                exit(EXIT_SUCCESS);
            }
        }
        task.resume()
    }
    
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let urlSessionGetClient = URLSessionGetClient()
        urlSessionGetClient.get(url: "http://192.168.12.2/motion.php")
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

適当にアイコンを設定。アイコンは一つ素材を用意すればconvertコマンドで別サイズの画像を生成できる。

% convert icon.png -resize 120x120 appiconset/iPhone-App-7-10-60pt@2x.png
% convert icon.png -resize 180x180 appiconset/iPhone-App-7-10-60pt@3x.png

後は、連絡先などでニックネームを決めてやるとSiriから任意の呼称で起動できるようになる。以下のように動く。

総括

お家ハックは楽しい。

*1:セキュリティ的に公開サーバーなどではこのような事をしない方が良い。

Line Notify APIで遊ぶ

仕事関連の作業や自作ライブラリ開発等の合間に、息抜きとして何かお家ハック的な別の事で遊びたかったのだが、少し良い遊び道具が見つかったので一つ。

Line NotifyというLine社提供のapiがある。これが何なのかについては、公式ページに詳しくあるので参照してほしい。

簡単に言えば、OAuth2 による認証を行い取得したアクセストークンからAPIを叩く事でグループ内に通知を送りつける事ができるAPIである。使い方は、開発ブログにあるように、curlを使えばとても簡単。

curl -X POST -H 'Authorization: Bearer [access_token]' -F 'message=foobar' https://  
notify-api.line.me/api/notify

APIに関するドキュメントもシンプルで整備されている。

さて、このAPIを使ってグループ内に通知を送る。用途は…まあなんというか個人的な話なのだが、執筆時でもうすぐ二年ぐらい付き合っている彼女と私は同棲をしている。付き合ってから一月に一度、互いにまあ初心を忘れぬよう、今日で何ヶ月付き合っているのだなという話をするのだが、最近になって私が忘れたりするようになった。これはいかんので、一月に一度、手元の端末に通知を送れたりできれば良い(?)。そういう用途を満たすには、適当にシェルスクリプトを記述して自宅サーバのcronに登録しておけば問題ない。ただまあ、どうせならバイナリにしておこうと思ったので、そうする。libcurlを使えば簡単だろう。

#include<iostream>
#include<fstream>
#include<iterator>
#include<boost/optional.hpp>
#include<curl/curl.h>

using namespace std::string_literals;

struct line_curler{
    explicit line_curler(const char* token,const char* str)
        :line_curler(token,str,boost::none,boost::none)
    {}

    explicit line_curler(
            const char* token,
            const char* str,
            boost::optional<const char*> image_url,
            boost::optional<const char*> thumbnail=
            "https://placehold.jp/150x150.png"
    )
        :mes_(str),
        post_data("message="s+mes_),
        thumbnail_(thumbnail?"imageThumbnail="s+std::string(thumbnail.value()):""s),
        image_url_(image_url?"imageFullsize="s+std::string(image_url.value()):""s),
        handler(curl_easy_init()),
        slist(nullptr)
    {
        std::string token_="Authorization: Bearer "s+std::string(token);

        slist=curl_slist_append(slist,token_.c_str());
        curl_easy_setopt(handler,CURLOPT_URL,"https://notify-api.line.me/api/notify");
        curl_easy_setopt(handler,CURLOPT_SSL_VERIFYPEER,false);
        curl_easy_setopt(handler,CURLOPT_HTTPHEADER,slist);
        curl_easy_setopt(handler,CURLOPT_POST,true);
        
        if(!image_url_.empty()){
            post_data+="&"s+thumbnail_+"&"s+image_url_;
            curl_easy_setopt(handler,CURLOPT_POSTFIELDS,post_data.c_str());
        }else{
            curl_easy_setopt(handler,CURLOPT_POSTFIELDS,post_data.c_str());
            curl_easy_setopt(handler,CURLOPT_POSTFIELDSIZE,post_data.size());
        }
    }
    
    void perform()const{curl_easy_perform(handler);}

    ~line_curler()
    {
        curl_slist_free_all(slist);
        curl_easy_cleanup(handler);
    }
private:
    const std::string mes_;
    std::string post_data;
    const std::string thumbnail_;
    const std::string image_url_;

    CURL* handler;
    curl_slist* slist;
};

std::string& trimer(std::string& str)
{
    const char* trimlist="\t\v\r\n";
    const std::string::size_type left=str.find_first_not_of(trimlist);
    if(left!=std::string::npos)
        str=str.substr(left,str.find_last_not_of(trimlist)-left+1);    
    return str;
}

struct token_reader{
    explicit token_reader(const char* filename):file_(boost::none)
    {
        std::ifstream ifs(filename);        
        if(ifs){
            std::string all_((std::istreambuf_iterator<char>(ifs)),std::istreambuf_iterator<char>());
            file_=trimer(all_);
        }
    }

    token_reader(const token_reader&)=default;
    token_reader(token_reader&&)=default;

    operator bool()const{return bool(file_);}
    operator const char*(){return file_?file_.value().c_str():nullptr;}
protected:
    boost::optional<std::string> file_;
};

template<class... Args>
auto exec_line_curl(Args&&... args) -> decltype(std::enable_if_t<sizeof...(args)<4>())
{
    line_curler lc(std::forward<Args>(args)...);
    lc.perform();
}

int main(const int argc,const char* argv[])
{
    if(argc<2)return -1;

    token_reader token("line_notify_api");

    if(token and argc==2)
        exec_line_curl(std::move(token),argv[1]);
    else if(token and argc==3)
        exec_line_curl(std::move(token),argv[1],argv[2]);
}

ビルドには-lcurlが必要。バイナリと同じディレクトリのline_notify_apiというファイル名からアクセストークンを読み込んで、単にapiを叩くだけだ。 以下のように使える。

./a.out 横から失礼致します。 http://www.dotup.org/uploda/www.dotup.org1173928.jpg

後は、このプログラムをcronで設定しておけば良いだろう。

Line Notify APIは、GithubやIFTTT、Mackerelなどと連携させる事ができるようなので、お家ハック的な意味ではまだ遊びようがありそうではある。取り敢えず、IFTTTから天気情報を流す設定はしてみた。

というわけで仕事に戻る

Tuple-based for loops

あまり体調が良くないのだが、適度に頭を使わなくても書けるネタを見つけたのでアップロード。P0589R0にインスパイアされて、そういえば書いておくと楽だろうなと今頃思ったので自分でも書いた。tupleの展開には、いつも再起したりindex_sequence使ったりapplyしたりしていたが。

P0589R0には、本提案によってinitializer-listとparameter packの相互作用により、複数の型を持つパラメータパックなどを適用できるようになるとある*1。...とここでinitializer_listの話題があったので、どうせなら対応しようと思い対応してみた。また、for_each中にカウントしたい場合があるかもしれないとも思ったので、そのようなものも。まぁ、こういうのは結構前からあるとは思うのだが、一応こう使える。

std::vector<int> v{10,20,30};
const auto disp=[](const auto&){/* */}; 
const auto counter=[](const auto&,std::size_t){/* */}; // valueとカウンター値

srook::for_each(v.begin(),v.end(),disp); // 普通のfor_each
srook::for_each(v,disp); // range base for_each
srook::for_each(std::make_tuple(42,'a',42.5f),disp); // tuple
srook::for_each({10,20,30},disp); // initializer_list

srook::for_each(srook::make_counter(v.begin(),v.end()),counter); // カウント.デフォルト値は0
srook::for_each(srook::make_counter(v.begin(),v.end(),5),counter); // 5からカウントを始める
srook::for_each(srook::make_counter(v),counter); // rangeで指定
srook::for_each(srook::make_counter(std::make_tuple(42,'a',42.5f)),counter); // tuple
srook::for_each(srook::make_counter({10,20,30}),counter); // initializer_list

まあ、boost.fusionを使えば良いという気はする。

*1:P0589R0- Interaction with initializer lists and parameter packs

Srook range adaptor and Srook range pipe algorithm

※ 2018/3/11 ライブラリ整理より当エントリ内で紹介されている一部機能は削除した。
Srook Range AdaptorとSrook Range Pipe AlgorithmSTLアルゴリズムとその他諸々の機能をイテレータアダプタによる効率的なアクセスと、パイプライン記法をサポートさせたC++14自作ラッパーライブラリである。Srook Range Adaptorは基本的に、boost range adaptorsと互換性があるが、boostには搭載されていないアダプタもある。例えば:

std::vector<int> v{2,2,2,2,2,2,3,3,3,4,5,6,7,8};
std::vector<int> result = v | boost::adaptors::replaced(2,4) | srook::adaptors::remove_copied(3); // 2を4に置換し3を排除したものをコピーする
result | srook::pipealgo::print(); // 4 4 4 4 4 4 4 5 6 7 8 
auto v = boost::irange(0x41,0x47) | srook::adaptors::to_range<std::vector>(); // boost::irangeと併用可能
auto li = v | srook::adaptors::to_range<std::list>(); // リストへ
auto t1 = v | srook::adaptors::to_range<std::tuple,6>(); // タプルへ
auto str = v | srook::adaptors::to_range<std::basic_string>(); // std::stringへ
auto ar = v | srook::adaptors::to_array<std::array,6>(); // std::arrayへ
auto str1 = v | srook::adaptors::to_string<std::wstring>(); // std::wstringへ
auto str2 = v | srook::adaptors::to_string<std::u16string>(); // std::u16stringへ
auto str3 = v | srook::adaptors::to_string<std::u32string>(); // std::u32stringへ

またSrook Range Pipe AlgorithmはSTLアルゴリズムを全てパイプライン記法で記述できるようにしたもの。よってイテレータアダプタの同機能とは、処理内容が異なる。例えば:

std::vector<int> v = boost::irange(0,9) | srook::adaptors::copied;
    
v | srook::adaptors::reversed | srook::adaptors::print(); // 8 7 6 5 4 3 2 1 0 
v | boost::adaptors::reversed | srook::adaptors::print(); // 8 7 6 5 4 3 2 1 0 

v | srook::pipealgo::reverse | srook::adaptors::print(); // 8 7 6 5 4 3 2 1 0 

srook::adaptors::reversedboost::adaptors::reversedの動作は同じ。具体的に述べれば、イテレータによって逆順にアクセスしていくため、範囲そのもの(上記の場合v)の内部要素らがreverseされるわけではない。しかし、srook::pipealgo::reverseイテレータによる逆順アクセスではなく、std::reverseを範囲に適用した形となる。std::reverseアルゴリズムの戻り値は原則voidであるが、便宜的に渡された範囲の参照を返すようにしている。

また、メンバとして同名の関数を持っていた場合はその関数を呼び出すようにしている。例えば:

#include<srook/range/adaptor/find.hpp>

std::string str("That is so good C++");
std::size_t pos = str | srook::pipealgo::find("good"s); // like that: str.find("good")
std::size_t pos1 = str | srook::pipealgo::find("C++"s,pos); // like that: str.find("C++",pos)
std::size_t pos2 = str | srook::pipealgo::find("C++"s,pos,"C++"s.size()); // like that: str.find("C++",pos,"C++"s.size())

std::vector<int> v{1,2,3,4,5,6,7,8};
decltype(v)::const_iterator it = v | srook::pipealgo::find(6); // like that: std::find(v.cbegin(),v.cend(),6)
#include<srook/range/adaptor/find_first_of.hpp>

using namespace std::string_literals;

std::vector<int> v(10);
std::vector<int> hs(3);
    
std::string str="hoge foo";
std::string search="hoge";

boost::range::iota(v,0);
boost::range::iota(hs,4);

const auto f=[](decltype(v)::value_type x,decltype(v)::value_type y){return x<y;};

decltype(v)::const_iterator it1 = v | srook::pipealgo::find_first_of(hs);
decltype(v)::const_iterator it2 = v | srook::pipealgo::find_first_of(hs.cbegin(),hs.cend());
decltype(v)::const_iterator it3 = v | srook::pipealgo::find_first_of(hs,f);
decltype(v)::const_iterator it4 = v | srook::pipealgo::find_first_of(hs.cbegin(),hs.cend(),f);
std::string::size_type result1 = str | srook::pipealgo::find_first_of(search);
std::string::size_type result2 = str | srook::pipealgo::find_first_of(search.c_str(),4);
std::string::size_type result3 = str | srook::pipealgo::find_first_of(search.c_str(),4,search.size());

全てのコードは以下のテストコードによりテストされており、幾つかのci環境でテストコードを走らせている。サポート環境は

  • boost lib version 1.63.0 or above later
  • GCC 6.2.0 or above later
  • Clang 3.7.0 or above later

であるが、OSはLinux環境が最も好ましい。 大体の使い方も、以下のテストコードで示されている。

Samba + DAAPとnetdataの導入

何度かサーバーを弄っているエントリーをアップロードしてきたが、自宅には外付けの3TBHDDに動画や音楽などのメディアファイルを全て詰め込んでおり、それを今まではメイン機のデスクトップに接続して使ってきた。しかし、どうせならサーバーに接続してそこから再生できるようにするのが良いだろうと思い、Samba & DAAPサーバーを既存のサーバー上に構築する事にした。 今回ファイル共有をしたいのは外付けのメディアデータであり、また今までメインマシン(Windows機)で使用していた事もあって、ファイル形式はexFatである。

また、netdataというブラウザからサーバーマシンの負荷度などを参照する事のできるツールを発見したので、導入する事とした。本エントリは、その作業ログである。

尚、自宅サーバー構築の今までの投稿は以下の通りである。

roki.hateblo.jp roki.hateblo.jp roki.hateblo.jp


exFatをsambaで共有する

必要なツールの導入

% sudo apt -y install samba samba-common exfat-fuse
% service smbd status

ターゲットメディアを挿しこみマウントする。

% sudo mkdir /home/roki/ShareData
% sudo fdisk -l
% sudo mount -t exfat /dev/sdc3 /home/roki/ShareData -o umask=000
% df -k

マウント時、exFatファイルシステムを考慮して、上記のようにumask=000オプションを設定する必要がある。 サーバーなのであまり再起動させることもないが、一応fstabを記述しておく。

% sudo echo "/dev/sdc3¥t/home/roki/ShareData¥texfat¥tdefaults,umask=000¥t0¥t0" >> /etc/fstab

sambaの設定。諸々の設定があるが主な内容は以下のとおり。

% sudo vim /etc/samba/smb.conf
[ServerData]
comment=server_data
path=/home/roki/ShareData
public = Yes
read only = No
writable = Yes
guest ok = Yes
force user = roki
% sudo service smbd restart

DAAP

導入

% sudo apt -y install forked-daapd

ライブラリとするディレクトリを設定する。/etc/forked-daapd.confのdirectoriesを編集することで変更できる。

# Directories to index
directories = { "/home/roki/BigData/music" }

netdata

導入

$ sudo apt -y install zlib1g-dev uuid-dev libmnl-dev gcc make git autoconf autogen automake pkg-config
$ git clone https://github.com/firehol/netdata.git --depth=1
$ cd netdata/
$ sudo ./netdata-installer.sh
$ sudo cp system/netdata.service /etc/systemd/system/
$ sudo systemctl enable netdata.service

デフォルトでは以下のポートで参照することができる。

http://localhost:19999