2017年11月18日土曜日

Yesodでファイルアップロードサイズを調整する方法

Content Lengthで調整


Yesodで、デカいファイルをアップロードすると、エラーが発生する

POST /list/0
  Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  Status: 413 Too Large 0.000029971s


ファイルのアップロードサイズというか、ContentLengthの調整で対応


設定ファイルからサイズを取れるようにする


* config/settings.ymlに追加

# Content Length Max MB
max-byte: 50

* src/Settings.hs
data AppSettings = AppSettings
...
    , appMaxByte                :: Word64
    -- ^ Content Length Max MB
...

FromJSONのインスタンス設定も修正
instance FromJSON AppSettings where
    parseJSON = withObject "AppSettings" $ \o -> do
        let defaultDev =
...
...
  appMaxByte <- o .:  "max-byte"
..

maximumContentLength関数を実装する


設定を取得してその値を元に、ContentLengthの上限を設定する
instance Yesod App where
...
...
    -- Content Length Max
    maximumContentLength app _ = do
        let size = appMaxByte $ appSettings app
        return $ size * 1024 * 1024 -- MB
...

これでデカいのがあがるようになる


2017年11月11日土曜日

YesodのURLパラメータ

URLのパラメータ変換


URLのパラメータの扱いについて調べてみた

config/routes


/list/#Int ListR GET POST
こう書くと、URLのパラメータに数字が使える、それ以外は「404エラー」、ちょっと注意が必要で、「0087」みたいなパラメータも受け付けるので、これはプログラム側のバリデーション等で対応が必要

基本的な型は、PathPieceクラスのインスタンスなっているので、そのままURLに使えて変換してくれる

それ以外の型をURLパラメータに利用する


適当な場所に、型を定義してPathPieceのインスタンスにする

import qualified Text.Read as R

newtype DirId = DirId Int deriving(Show, Read, Eq)

instance PathPiece DirId where
  toPathPiece (DirId n) = pack $ show n
  fromPathPiece n = case R.reads $ unpack n of
    (p, ""):_
      | p < 1 -> Nothing
      | otherwise -> Just $ DirId p
    _ -> Nothing
toPathPiece関数は特定の型をTextへ変換してURLで利用できるようにし、fromPathPiece関数はその逆。

そしてhandlerで

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
module Handler.DirList where

import Import

getDirListR :: DirId -> Handler Html
getDirListR (DirId n) = defaultLayout $ do
    setTitle "Routing test"
    $(widgetFile "did")

テンプレート

<div .container>
    <div .bs-docs-section>
        <div .row>
            <div .col-lg-12>
                <div .page-header>
                    <h1 #start>DirID Test
                    <p>n = #{show n}


こんな感じで、できまする、昨晩の宴で呼吸が苦しい...

2017年11月7日火曜日

最近、うまいと思ったビール

色々飲むけどやっぱり一番搾りがうまい


ふつうにうまい、ふるーてぃー、魚で言う、アジの刺身てきな存在。


今日もやるか...


2017年11月1日水曜日

YesodのセッションストレージをRedisにする

SessionのBackendをRedisにする


Yesodでディフォルトのセッションストレージはファイルになっているので、そのBackendをRedisにする。



今回使用したライブラリは

  • lts-9.10をベースに作成
  • serversession-frontend-yesod
  • serversession-backend-redis
  • hedis
  • hedis-config

セイッ

サンプルコード


さんぷるはここへおいときます

https://github.com/calimakvo/sessredis.git

Redisへの接続設定を作成


かいつまんで...

* config/settings.yml

当な場所へ追加、環境変数も利用可能

redis:
  host:      "_env:REDIS_HOST:localhost"          # host name or address
  port:      "_env:REDIS_PORT:6379"               # you can specify either port
  # socket: /run/redis.socket                     # or unix socket path
  # service: redis                                # or service name
  password:   "_env:REDIS_PASSWORD:"              # if not specified then no password used
  database:   "_env:REDIS_DATABASE:0"             # database number to connect to
  max-connections: "_env:REDIS_MAX_CONNECTIONS:5" # max 5 connections in pool
  max-idle-time:   "_env:REDIS_MAX_IDLE_TIME:30"  # max 30 idle time in pool

* package.yaml dependenciesへ追加

dependencies:
..
..
- hedis
- hedis-config
- serversession-backend-redis
- serversession-frontend-yesod

* stack.yaml extra-depsへ追加

lts-9.10に入っていないパッケージをextra-depsへ追加

extra-deps:
  - hedis-config-0.0.3
  - serversession-backend-redis-1.0.2
...

ソースをイジる


* src/Settings.hs

import宣言と、RedisConfigの1行を追加、AppSettingsのFromJSONインスタンス宣言にfromYamlAppRedisConfを追加
..
import Database.Redis.Config (RedisConfig (..))
..
..
data AppSettings = AppSettings
    { appStaticDir              :: String
    -- ^ Directory from which to serve static files.
    , appDatabaseConf           :: MySQLConf
    -- ^ Configuration settings for accessing the database.
    , appRedisConf              :: RedisConfig
    -- ^ Configuration settings for accessing the redis.
    , appRoot                   :: Maybe Text
...
...
instance FromJSON AppSettings where
    parseJSON = withObject "AppSettings" $ \o -> do
        let defaultDev =
#ifdef DEVELOPMENT
                True
#else
                False
#endif
        appStaticDir              <- o .: "static-dir"
        fromYamlAppDatabaseConf   <- o .: "database"
        fromYamlAppRedisConf      <- o .: "redis"
        appRoot                   <- o .:? "approot"
        appHost                   <- fromString <$> o .: "host"
        appPort                   <- o .: "port"
...
...
        let appRedisConf = fromYamlAppRedisConf {                       
              getConnectInfo = getConnectInfo fromYamlAppRedisConf
            }                       

        appAuthDummyLogin         <- o .:? "auth-dummy-login"      .!= defaultDev

        return AppSettings {..}
...

* src/Foundation.hs
 
import宣言と、関数の宣言、makeSessionBackendの切り替え
...
import qualified Database.Redis as R
import Database.Redis.Config (RedisConfig (..))
import Web.ServerSession.Backend.Redis (RedisStorage(..))
import Web.ServerSession.Frontend.Yesod (setCookieName,
                                         setAbsoluteTimeout,
                                         setIdleTimeout,
                                         simpleBackend)
...
sessionCookieName :: Text
sessionCookieName = "SESSION"

redisStorage :: RedisConfig -> IO (RedisStorage sess)
redisStorage rc = do
  conn <- R.connect $ getConnectInfo rc
  return $ RedisStorage conn Nothing Nothing
..
..
instance Yesod App where
...
    makeSessionBackend app = do
        let redisConf = appRedisConf $ appSettings app
        simpleBackend opts =<< redisStorage redisConf    
            where opts = setIdleTimeout     (Just $  30 * 60)          -- 30  minutes
                       . setAbsoluteTimeout (Just $  2 * 60 * 60 * 24) -- 2 days minutes
                       . setCookieName      sessionCookieName
..

buildと実行


redisサーバーを起動しておいてね
sessredis $ sudo systemctl start redis

sessredis $ stack build && stack exec -- yesod devel

確認


ブラウザからログインして、redisの中をみてみる


karky7 ~ # redis-cli
127.0.0.1:6379> KEYS *
(empty list or set)
127.0.0.1:6379> KEYS *
1) "ssr:session:XSkbZBiMlMsw-yqCGJXNSIxF"
2) "ssr:authid:1"
127.0.0.1:6379>

はいってる

調査不足なところが、RedisStorageのidleTimeoutとabsoluteTimeoutが、simpleBackendのoptsで設定する、timeoutとabsoluteの設定にどう効いてくるのか調べてないとこ、今度しらべる...だれか教えて。

haskellはとにかくたのしい


2017年10月27日金曜日

stackageに無いパッケージの利用方法

ないので困った


どこかのネ申祭りではありません、神祭りがはじまりました、これからがめでたいネモウの始まりです...どうでもいいですが


普通に使ってると必要なパッケージは自動で入ってくるけど、無い奴に遭遇したときにやり方がわからなかったのでメモ。

まず、package.yamlのdependenciesに追加、バージョンは書かない、これで***.cabalファイルが自動で更新される

..
- serversession-backend-persistent
...

次に、stack.yamlのextra-depsへ以下を追加

..
extra-deps:
  - serversession-backend-persistent-1.0.4

このあとこれをやる、これ、忘れがち

empeleryoshi $ stack build

stack便利なんだけど、gentooのebuildとかぶるので、ちょっと気まずい感じになる。

なので浪越...


2017年10月23日月曜日

Dolphin Manと同級生

有名になったらお金をいただこうと思っています


2017東京国際映画祭に出展される「DOLPHIN MAN」に私の友人が出演します。

泳いでる人ではありません。



観にいきたいと思います、皆さんもぜひどうぞ。


となっております、ちなみにその友人は日本人です@どうでもいい


2017年10月22日日曜日

Empty YoshidaとYesodのForm

AFormとMForm


YesodにはAFormとMFormがある、「あぷりかてぃぶふぉーむ」「モナドふぉーむ」らしいが、名前をみても何が違うのが分からないのがYesod...簡単にいうと、AFormは「お手軽版」でMFormが「好きにやれよ版」。

だと思ってる...

Formの情報をhaskellの型へ流し込んだり、その逆をやったりするもの、Formをいじくる関数を定義しておいて、generateFormPost関数とかでフォームを作成する、なんでGetとかPostでフォームの作成関数が違うのかは、CSRFとかのタグの出力があるとかないとかでちがいがある。



サンプルはこちらに置いときます。

AForm


ほとんどの場合が、これで済みそうな感じ、普通のフォームを上から並べてくならAFormで十分。
ただ、renderDivs関数とかでくるむ必要があるので、ちょっと細工をしたい場合に、邪魔になるケースがある、多分こでが気に入らなかったらMForm使えってことかな。
その他に、 renderDivsNoLabels関数とかrenderTable関数とかある、サンプルはrenderBootstrap3をBootStrap用の3種類のフォームで出力してみた。

-- Formの情報がここにはいる
data FileForm = FileForm {
    fileInfo :: FileInfo
  , inpText :: Text
}

fileUploadAFormInline :: Form FileForm
fileUploadAFormInline = renderBootstrap3 BootstrapInlineForm $ getAform

fileUploadAFormBasic :: Form FileForm
fileUploadAFormBasic = renderBootstrap3 BootstrapBasicForm $ getAform

fileUploadAFormHorizon :: Form FileForm
fileUploadAFormHorizon = renderBootstrap3 (BootstrapHorizontalForm (ColXs 2) (ColXs 8) (ColXs 2) (ColXs 6)) $ getAform

getAform :: AForm Handler FileForm
getAform = FileForm
    <$> fileAFormReq fs <*> areq textField tfs Nothing
    where fs = FieldSettings {
            fsLabel = "ファイル"
          , fsTooltip = Nothing
          , fsId = Nothing
          , fsName = Just "media"
          , fsAttrs =
              [   ("class", "form-group")
                , ("placeholder", "File description")
              ]
          }
          tfs = FieldSettings {
            fsLabel = "なんかいれろ"
          , fsTooltip = Nothing
          , fsId = Just "inptext"
          , fsName = Just "inptext"
          , fsAttrs =
              [   ("class", "form-group form-control")
                , ("placeholder", "input text")
              ]
          }


フォームの情報を取得する変数を<$>と<*>でいれていく、Formコントロールにスタイル等を設定したい場合は、FieldSettingsで属性情報を追加する。

でこれをHandlerで走らせて、Widgetを取得する

getFormexamR :: Handler Html
getFormexamR = do
  (formAWidgetBasic, formAEnctypeBasic) <- generateFormPost fileUploadAFormBasic
  (formAWidgetInline, formAEnctypeInline) <- generateFormPost fileUploadAFormInline
  (formAWidgetHorizon, formAEnctypeHorizon) <- generateFormPost fileUploadAFormHorizon
  ((res, widget), enctype) <- runFormPost fileUploadMForm
  defaultLayout $ do
        setTitle "Form Example!!!"
        $(widgetFile "formexam")

MForm


こっちは細かくフォームを作成できる方、mreqでつなげるぐらいで、基本的にAFormと一緒、mreqでFormから値を取り出すときに、ResultとViewが別々にとれるので、手動でFileFormへつめてやるところぐらいが違う、HTMLを細かく調整できる。
fileUploadMForm :: Html -> MForm Handler (FormResult FileForm, Widget)
fileUploadMForm extra = do
    let fsMedia = FieldSettings {
            fsLabel = ""
          , fsTooltip = Nothing
          , fsId = Nothing
          , fsName = Just "media"
          , fsAttrs =
              [   ("class", "uploadFile")
                , ("style", "display:none")
                , ("placeholder", "")
              ]
        }
    let fsText = FieldSettings {
            fsLabel = "なんか"
          , fsTooltip = Nothing
          , fsId = Nothing
          , fsName = Just "inptext"
          , fsAttrs =
              [   ("class", "form-group form-control")
                , ("placeholder", "")
              ]
        }

    (fileInfoRes, fileInfoView) <- mreq fileField fsMedia Nothing
    (inpTextRes, inpTextView) <- mreq textField fsText Nothing
    let fileRes = FileForm <$> fileInfoRes <*> inpTextRes
    let widget = do
            [whamlet|
                #{extra}
                <label>
                    <div class="form-group">
                        <span class="btn btn-default">Upload
                            ^{fvInput fileInfoView}
                        ^{fvInput inpTextView}
            |]
    return (fileRes, widget)

これをHandlerでつかう、resにはFileFormでwidgetにはwhamletで作成したwidgetがはいる。
...
((res, widget), enctype) <- runFormPost fileUploadMForm
...

出力はこんな感じ




HTMLの出力は4種類出力してみましたが、BootstrapとHTMLの知識は皆無ですので、あまり突っ込まないでください。



2017年10月15日日曜日

ER図をテキストから作成するhaskell製ツールのerd すげぇー

こういうの探してた


綺麗に図を作ってくれるツールを使ってみました。

gentooならこれでいれて、いちようgentoo-haskellに投下しておきましたのでそのうち使えるようになると思います。

ugui7 ~ # emerge dev-haskell/erd

使い方


オフィシャルに書いてあるとおりのまんま、
ER図の元になるファイルを作る、sample.erってファイル名で保存する。

title {label: "nfldb Entity-Relationship diagram (condensed)", size: "20"}

# Entities

[player] {bgcolor: "#d0e0d0"}
  *player_id {label: "varchar, not null"}
  full_name {label: "varchar, null"}
  team {label: "varchar, not null"}
  position {label: "player_pos, not null"}
  status {label: "player_status, not null"}

[team] {bgcolor: "#d0e0d0"}
  *team_id {label: "varchar, not null"}
  city {label: "varchar, not null"}
  name {label: "varchar, not null"}

[game] {bgcolor: "#ececfc"}
  *gsis_id {label: "gameid, not null"}
  start_time {label: "utctime, not null"}
  week {label: "usmallint, not null"}
  season_year {label: "usmallint, not null"}
  season_type {label: "season_phase, not null"}
  finished {label: "boolean, not null"}
  home_team {label: "varchar, not null"}
  home_score {label: "usmallint, not null"}
  away_team {label: "varchar, not null"}
  away_score {label: "usmallint, not null"}

[drive] {bgcolor: "#ececfc"}
  *+gsis_id {label: "gameid, not null"}
  *drive_id {label: "usmallint, not null"}
  start_field {label: "field_pos, null"}
  start_time {label: "game_time, not null"}
  end_field {label: "field_pos, null"}
  end_time {label: "game_time, not null"}
  pos_team {label: "varchar, not null"}
  pos_time {label: "pos_period, null"}

[play] {bgcolor: "#ececfc"}
  *+gsis_id {label: "gameid, not null"}
  *+drive_id {label: "usmallint, not null"}
  *play_id {label: "usmallint, not null"}
  time {label: "game_time, not null"}
  pos_team {label: "varchar, not null"}
  yardline {label: "field_pos, null"}
  down {label: "smallint, null"}
  yards_to_go {label: "smallint, null"}

[play_player] {bgcolor: "#ececfc"}
  *+gsis_id {label: "gameid, not null"}
  *+drive_id {label: "usmallint, not null"}
  *+play_id {label: "usmallint, not null"}
  *+player_id {label: "varchar, not null"}
  team {label: "varchar, not null"}

[meta] {bgcolor: "#fcecec"}
  version {label: "smallint, null"}
  season_type {label: "season_phase, null"}
  season_year {label: "usmallint, null"}
  week {label: "usmallint, null"}

# Relationships

player      *--1 team
game        *--1 team {label: "home"}
game        *--1 team {label: "away"}
drive       *--1 team
play        *--1 team
play_player *--1 team

game        1--* drive
game        1--* play
game        1--* play_player

drive       1--* play
drive       1--* play_player

play        1--* play_player

player      1--* play_player


それから、erdコマンドで吐き出す、「-o」オプションで出力ファイルを指定する、ファイルの拡張子をみて出力するファイルを変換してくれる。ちょっと適当に調べて見たところ「pdf」「png」「svg」とかも出てくる、ちなみに漢字もlabelの中ならいけるっぽい。
cuomo@ugui7 ~ $ erd -i sample.er -o simple.svg

出力はこんな綺麗


すごく見やすく出力してくれる、自動でテーブル配置とかリレーション情報を出力してくれる。


しばらく使ってみようかと思う。

オフィシャル


erd
Translates a plain text description of a relational database schema to a graphical entity-relationship diagram.

2017年10月13日金曜日

スターティングgo言語買いマスタ

それが私の信念です




すたーてんぐごうげんご、買いました、オブジェクト指向はないよとか、いろいろ興味深いところもあるので、読んでみます。


2017年10月12日木曜日

三島haskell無名関数の会 復活のお知らせ

偉く無くとも正しく生きる



haskell楽しいのですが、一人でやってる感が最近加齢臭のように漂いはじめたので、そろそろ開始しようかと思います。

今までの経緯は、こちらを参照してもらうとして、やったら誰かくるかな?

まぁ、半分以上は飲み会だとは思いますが...

2017年10月9日月曜日

Yesodのsettings.ymlとセッティング

Yesodの設定


三連休も結婚式だの、2次会からただの飲み会まで、昼からやりつづけた結果、夜にはバイオレンスおじさんが登場し出すし、それを押さえつける暴力おじさんとか、もう結構ひどい有様でだいぶウケたんだけど、Yesodのsettings.ymlです

いやぁつかれましたな

Databaseの設定


settings.ymlのdatabaseセクションにかかれているのがそれ、これはDatabase.Persist.MySQL.ConnectInfoにハマる
database:
  user:     "_env:MYSQL_USER:karky7"
  password: "_env:MYSQL_PASSWORD:hongani"
  host:     "_env:MYSQL_HOST:localhost"
  port:     "_env:MYSQL_PORT:3306"
  path:     "_env:MYSQL_SOCK:/var/run/mysqld/mysqld.sock"
  # See config/test-settings.yml for an override during tests
  database: "_env:MYSQL_DATABASE:hongani"
  poolsize: "_env:MYSQL_POOLSIZE:10"

で、これがSettings.hsのAppSettingsに読み込まれるのでHandlerの中でこうやって使える。
...
    master <- getYesod
    let dbconf = myConnInfo $ appDatabaseConf $ appSettings master
...

ここで帰ってくるdbconfの型はConnectInfo型でそれからsettings.ymlに書かれているそれぞれの値を取得するには
...
    host = connectHost dbconf,
    user = connectUser dbconf,
    passwd = connectPassword dbconf,
    db = connectDatabase dbconf,
    port = (fromIntegral(connectPort dbconf) :: Int),
    socket = connectPath dbconf
...

こうやって取れる、ちなみにMySQLのConnectInfo型は
data ConnectInfo
  = ConnectInfo {
      connectHost :: String,
      connectPort :: GHC.Word.Word16,
      connectUser :: String,
      connectPassword :: String,
      connectDatabase :: String,
      connectOptions :: [Database.MySQL.Base.Types.Option],
      connectPath :: FilePath,
      connectSSL :: Maybe SSLInfo
    }
で定義されてる。

_envってなんだよ


settings.ymlの中の「_env:XXX:」見たいな書き方
database:
  ...
  path:     "_env:MYSQL_SOCK:/var/run/mysqld/mysqld.sock"
  ...

これは環境変数を利用する書き方、要するに上の例だと環境変数MYSQL_SOCKが定義してあったら、それを使ってそうじゃないなら/var/run/mysqld/mysqld.sockを使うよって意味。
~/Code/hongani $ export MYSQL_SOCK=/var/tmp/mysqld.sock
~/Code/hongani $ stack exec -- yesod devel
Yesod devel server. Enter 'quit' or hit Ctrl-C to quit.
Application can be accessed at:

http://localhost:3000
https://localhost:3443
If you wish to test https capabilities, you should set the following variable:
  export APPROOT=https://localhost:3443
...

こんな感じ

settings.ymlに設定を追加する方法


settings.ymlへ変数の追加
...
# Upload files store directory
image-dir: "/home/cuomo/files/"
...

Settings.hsのAppSettingへ登録

最初にAppSettingsへ変数を追加する

data AppSettings = AppSettings
    { appStaticDir              :: String
    -- ^ Directory from which to serve static files.
    , appDatabaseConf           :: MySQLConf
    -- ^ Configuration settings for accessing the database.
    , appRoot                   :: Maybe Text
    -- ^ Base for all generated URLs. If @Nothing@, determined
    -- from the request headers.
    , appHost                   :: HostPreference
    -- ^ Host/interface the server should bind to.
    , appPort                   :: Int
    ...
    ...
    , appImageDir               :: Text
    -- ^ Image Stored Dir <==== ここら
    , appAnalytics              :: Maybe Text
    -- ^ Google Analytics code
    , appAuthDummyLogin         :: Bool
    -- ^ Indicate if auth dummy login should be enabled.
}

次にjsonのparseに追加する、「.:」は必ず値がある場合で、「.:?」はオプション値の場合の書き方。
...
instance FromJSON AppSettings where
    parseJSON = withObject "AppSettings" $ \o -> do
    ...
    appImageDir               <- o .:  "image-dir"
    ...
...

これでHandlerで使えるようになる
...
master <- getYesod
let imgDir = appImageDir $ appSettings master
...

以上、二日酔いから復旧中です...

2017年10月7日土曜日

PersistentでUNIONって無いよね?

PersistentでUNIONしたかった


そもそも違うEntityをがっちゃんこって無理でしょ。
「rawSql使えば出きるんじゃねぇ」 
って思ってたけど、よく見てみたら、それも綺麗にEntityを返してくるから駄目ね。

うまくやればというか、他にエレガントな方法があるかもしれませんが、そんな時間無いし、多分面倒になりそうなので、普通にHDBC使って生SQL、何だかんだこれはこれですごくわかりやすい。



コード

Yesodのデータベース接続設定を直接読み込みmariadbへ接続してます、あとは普通のやつ、
普通のユーザーと 
「凄いテッカテカ」
のユーザーをUNIONして出力してます。

余談ですが、適当に初期データをuserとtekateka_userへinsertして実行してください。

全部のコードはgithubに置いてありますので興味のある人はどうぞ
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
module Handler.Home where

import Import
import qualified Data.Map as M
import qualified Data.Maybe as MB
import Database.Persist.MySQL (ConnectInfo(..), myConnInfo)
import Database.HDBC (execute, prepare, fetchAllRowsMap, SqlValue, fromSql)
import Database.HDBC.MySQL (Connection, MySQLConnectInfo(..),
                            defaultMySQLConnectInfo, connectMySQL)
import Yesod.Form.Bootstrap3 (BootstrapFormLayout (..), renderBootstrap3)
import Text.Julius (RawJS (..))

-- Define our data that will be used for creating the form.
data FileForm = FileForm
    { fileInfo :: FileInfo
    , fileDescription :: Text
    }

data TekaUser = TekaUser {
    tid :: Int64
  , ident :: Text
  , password :: Text
} deriving(Show, Eq)

-- This is a handler function for the GET request method on the HomeR
-- resource pattern. All of your resource patterns are defined in
-- config/routes
--
-- The majority of the code you will write in Yesod lives in these handler
-- functions. You can spread them across multiple files if you are so
-- inclined, or create a single monolithic file.
getHomeR :: Handler Html
getHomeR = do
    master <- getYesod
    (formWidget, formEnctype) <- generateFormPost sampleForm
    let submission = Nothing :: Maybe FileForm
        handlerName = "getHomeR" :: Text
        dbconf = myConnInfo $ appDatabaseConf $ appSettings master
    tekkaTeka <- liftIO $ getDBConn dbconf >>= getTekkatekaUsers
    $(logInfo) $ pack $ "=====> テカテカログ: " ++ show tekkaTeka
    defaultLayout $ do
        let (commentFormId, commentTextareaId, commentListId) = commentIds
        aDomId <- newIdent
        setTitle "Welcome To Yesod!"
        $(widgetFile "homepage")

getTekkatekaUsers :: Connection -> IO ([TekaUser])
getTekkatekaUsers con = do
    stm <- prepare con $ "SELECT u.* FROM user AS u UNION SELECT tu.* FROM tekateka_user AS tu"
    _ <- execute stm []
    rows <- fetchAllRowsMap stm
    return $ MB.catMaybes $ toTeka rows

toTeka :: [M.Map String SqlValue] -> [Maybe TekaUser]
toTeka rows = map (\m -> TekaUser
                    `liftM` (fromSql <$> M.lookup "id" m)
                    `ap` (decodeUtf8 . fromSql <$> M.lookup "ident" m)
                    `ap` (decodeUtf8 . fromSql <$> M.lookup "password" m)) rows

getDBConn :: ConnectInfo -> IO Connection
getDBConn dbconf = connectMySQL defaultMySQLConnectInfo {
                mysqlHost = connectHost dbconf,
                mysqlUser = connectUser dbconf,
                mysqlPassword = connectPassword dbconf,
                mysqlDatabase = connectDatabase dbconf,
                mysqlPort = (fromIntegral(connectPort dbconf) :: Int),
                mysqlUnixSocket = "/var/run/mysqld/mysqld.sock"
            }

sampleForm :: Form FileForm
sampleForm = renderBootstrap3 BootstrapBasicForm $ FileForm
    <$> fileAFormReq "Choose a file"
    <*> areq textField textSettings Nothing
    -- Add attributes like the placeholder and CSS classes.
    where textSettings = FieldSettings
            { fsLabel = "What's on the file?"
            , fsTooltip = Nothing
            , fsId = Nothing
            , fsName = Nothing
            , fsAttrs =
                [ ("class", "form-control")
                , ("placeholder", "File description")
                ]
            }

commentIds :: (Text, Text, Text)
commentIds = ("js-commentForm", "js-createCommentTextarea", "js-commentList")

データベースから取ってきたまんまだとMap String SqlValueで帰ってくるからそれをEntityもどきに入れ直してるだけ。

なんかうまい方法があったらだれかおしえて..



 

2017年10月5日木曜日

焼きあご塩らー麺たかはし

顎ってトビ魚


うまかったので書きました、色コユイけど塩です、結構濃厚で醤油のような味、ちょっと甘め。


汁がうまいので、痛風の人は、「全汁」禁止です...


行ってみて。




2017年9月26日火曜日

PersistentのEsqueleto

PersistentのEsqueleto


夏が終わってさらに体が焼酎臭いのですが、Esqueletoなるものを発見したのちょっと触って見ました。

読み方がわからないので「えすきゅーぅうれぇっちゅ」とでもいうのか、まぁどうでもいいとして



こんなんを「えすきゅーぅうれぇっちゅ」を使って

mysql> select * from author as a left join blog as b on a.id=b.author;
+----+----------------------------------------------+------+------+--------------------------------------------------------------------------------------+--------+
| id | name                                         | age  | id   | title                                                                                | author |
+----+----------------------------------------------+------+------+--------------------------------------------------------------------------------------+--------+
|  1 | メンチョida(実在しない人物です)              |   40 |    1 | 休みあけ飲みすぎで体調不良です                                                       |      1 |
|  1 | メンチョida(実在しない人物です)              |   40 |    2 | DriverManagerDataSourceがテストのためだって誰が俺に教えてくれる?                     |      1 |
+----+----------------------------------------------+------+------+--------------------------------------------------------------------------------------+--------+
ってやってみたい

コード


{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

import Database.Persist.MySQL
import Database.Persist.TH
import Control.Monad (guard)
import Control.Monad.Trans.Resource (runResourceT, ResourceT)
import Control.Monad.Logger (runNoLoggingT, NoLoggingT)
import Control.Monad.IO.Class (liftIO)
import Data.Text
import qualified Database.Esqueleto as E
import Database.Esqueleto ((^.))

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
Author
    name Text sqltype=varchar(32)
    age Int Maybe
    deriving(Show)
Blog
    title Text sqltype=varchar(128)
    author AuthorId
    deriving(Show)
|]

main :: IO ()
main = do
  dbInit
  blogs <- esqueletoSay
  mapM_ (\(E.Value k, E.Value v1, E.Value v2) -> do
            putStrLn $ "BlogKey  => " ++ show(unSqlBackendKey $ unBlogKey k)
            putStrLn $ "    Val1 => " ++ unpack v1
            putStrLn $ "    Val2 => " ++ unpack v2) blogs

esqueletoSay :: IO [(E.Value (Key Blog), E.Value Text, E.Value Text)]
esqueletoSay = runSQLAction $ do
    E.select $ E.from $ \(blog `E.InnerJoin` author) -> do
        E.on $ blog ^. BlogAuthor E.==. author ^. AuthorId
        return ( blog   ^. BlogId
               , blog   ^. BlogTitle
               , author ^. AuthorName )
  
dbInit :: IO ()
dbInit = runSQLAction $ do
          runMigration migrateAll
          p <- selectFirst ([] :: [Filter Author]) []
          case p of
            Nothing -> do
              iid <- insert $ Author "メンチョida(実在しない人物です)" $ Just 40
              insert $ Blog "休みあけ飲みすぎで体調不良です" iid
              insert $ Blog "DriverManagerDataSourceがテストのためだって誰が俺に教えてくれる?" iid
              liftIO $ putStrLn "しょきーか完了..."
            _ -> return ()

runSQLAction :: SqlPersistT (ResourceT (NoLoggingT IO)) a -> IO a
runSQLAction = runNoLoggingT . runResourceT . withMySQLConn getConnection . runSqlConn

getConnection :: ConnectInfo
getConnection = ConnectInfo {
    connectHost = "localhost",
    connectPort = 3306,
    connectUser = "root",
    connectPassword = "",
    connectDatabase = "sampledb",
    connectOptions = [],
    connectPath = "",
    connectSSL = Nothing
}

「えすきゅーぅうれぇっちゅ」を使って複数のテーブル情報を持ってくる方法、実際の値はValueにくるまれてやってくる。
rawなSQLの場合とどう違うのかもうちょっと調べてみようかとおもう。
とりあえず「えすきゅーぅうれぇっちゅ」でした。

だれか読み方教えて...

「えすきゅーぅうれぇっちゅ」セイッ!
 

2017年8月12日土曜日

Yesod Middleware とか

YesodのMiddleware


DjangoのMiddlewareとか、SpringFrameworkのfilterみたいなもんの、Yesodのやつ、前処理をフックするための機能、コードはgithubに置いときます。

Yesod Middleware sample

認証されたユーザーのログを出力するという簡単なサンプルです。

動かしてみる


手順どおりに初期化できたら、ユーザーを作成する


作成すればログインされるので、適当なリンクをアクセスしてみる


ログを確認


mysql> select * from acc_log;
+----+---------+---------------------+
| id | user_id | acc_time            |
+----+---------+---------------------+
|  1 |       1 | 2017-08-11 20:55:32 |
|  2 |       1 | 2017-08-11 20:56:00 |
+----+---------+---------------------+
2 rows in set (0.00 sec)

簡単な説明


Yesod Middlewareの設置方法は、まず、middlewareを書く
-- src/Foundation.hs 抜粋
dbAccessMiddleware ::
         (YesodAuth site,
          YesodPersist site,
          YesodAuthPersist site,
          AuthId site ~ Key (AuthEntity site),
          PersistEntity (AuthEntity site),
          Typeable (AuthEntity site),
          AuthEntity site ~ User,
          YesodPersistBackend site ~ SqlBackend) =>
          HandlerT site IO res -> HandlerT site IO res
dbAccessMiddleware handler = do
     mauth <- maybeAuth
     case mauth of
         Nothing -> handler
         Just (Entity uid u) -> do
             runDB $ do
                 si <- get ((toSqlKey 1) :: Key SystemInfo)
                 case si of
                     Just entity -> do
                         case systemInfoInit entity of
                             True  -> liftIO getCurrentTime >>= \t ->
                                              (insert $ AccLog { accLogUserId = uid
                                                               , accLogAccTime = t }) >> return ()
                             False -> return ()
                     _ -> $(logInfo) "system_info no initialized.." >> return ()
             handler
で、Yesod Middlewareにくっつける
-- src/Foundation.hs 抜粋
yesodMiddleware = defaultYesodMiddleware . dbAccessMiddleware
こんな感じで実装できる、ちなみにmaybeAuthで認証済みユーザーのEntityが取得出きる。


2017年7月8日土曜日

gentooで無線LANにつなぐ

wpa_supplicantで無線に繋ぐ


ノートPCの無線スイッチだけ入れれば無線につながるって思ってるひと、違うんです、人知れず無線スタックがガンバっているんです...

無線デバイスが使えるようにカーネルとfirmwareを整える


使ってるPCによりますが、大体こんなとこ弄れば無線使える的な設定項目だけ

無線デバイス


ここは、利用している無線を確認してね、私のPCはこれ
-*- Network device support  --->
    [*]   Wireless LAN  ---> 
        [*]   Atheros/Qualcomm devices

IEEE 802.11系モジュール

*- Networking support  --->
     -*-   Wireless  --->
         <M>   cfg80211 - wireless configuration API

firmwareをいれる

karky7 ~ # emerge sys-kernel/linux-firmware 
必要に応じてカーネルをrebuildして再起動

無線デバイスが利用可能か確認

karky7 ~ # ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s25: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether dd:ee:aa:dd:bb:ee brd ff:ff:ff:ff:ff:ff
3: wlp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN mode DEFAULT group default qlen 1000
    link/ether dd:ee:aa:dd:bb:ee brd ff:ff:ff:ff:ff:ff
4: enp0s29u1u3: <NOARP> mtu 1400 qdisc noop state DOWN mode DEFAULT group default qlen 20
    link/ether dd:ee:aa:dd:bb:ee brd ff:ff:ff:ff:ff:ff
karky7 ~ #
「wlp3s0」が無線、でてればOK

パッケージをインストール

karky7 ~ # emerge net-wireless/wpa_supplicant net-misc/dhcpcd

wpa_supplicantの設定


WEPはやめておけ、WPA2にしよう

/etc/wpa_supplicant/wpa_supplicant.conf

ファイルを設置
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=wheel
update_config=1

network={
        ssid="Xperia Z7_go87"
        psk="kusomamire"
        key_mgmt=WPA-PSK
        proto=WPA2
        pairwise=CCMP
}

network={
        ssid="1123A5B6BFE7"
        psk="gdqwergtp0ss8a"
        key_mgmt=WPA-PSK
        proto=WPA2
        pairwise=CCMP
}
試しに繋げてみる、上の設定はXperiaのandroid用
karky7 ~ # wpa_supplicant -B -u -iwlp3s0 -c/etc/wpa_supplicant/wpa_supplicant.conf
Successfully initialized wpa_supplicant
wlp3s0: SME: Trying to authenticate with dd:ee:aa:dd:bb:ee (SSID='1123A5B6BFE7' freq=2427 MHz)
wlp3s0: Trying to associate with dd:ee:aa:dd:bb:ee (SSID='1123A5B6BFE7' freq=2427 MHz)
wlp3s0: Associated with dd:ee:aa:dd:bb:ee
wlp3s0: CTRL-EVENT-SUBNET-STATUS-UPDATE status=0
wlp3s0: WPA: Key negotiation completed with dd:ee:aa:dd:bb:ee [PTK=CCMP GTK=TKIP]
wlp3s0: CTRL-EVENT-CONNECTED - Connection to dd:ee:aa:dd:bb:ee completed [id=1 id_str=]
karky7 ~ #

wpa_cliで確かめる

karky7 ~ # wpa_cli
wpa_cli v2.6
Copyright (c) 2004-2016, Jouni Malinen <j@w1.fi> and contributors

This software may be distributed under the terms of the BSD license.
See README for more details.


Selected interface 'wlp3s0'

Interactive mode

> Selected interface 'wlp3s0'

Interactive mode

> scan_result
bssid / frequency / signal level / flags / ssid
dd:ee:aa:dd:bb:ee       2427    -48     [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS]    1123A5B6BFE7
dd:ee:aa:dd:bb:ee       2462    -62     [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS]    VOD720J-D3A4A-G
dd:ee:aa:dd:bb:ee       5260    -79     [WPA-PSK-CCMP+TKIP][WPA2-PSK-CCMP+TKIP][ESS]    VOF760J-G5YHY-A
dd:ee:aa:dd:bb:ee       2417    -73     [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS]      Buffalu-G-F453
dd:ee:aa:dd:bb:ee       2437    -85     [WPA-PSK-CCMP][WPA2-PSK-CCMP][ESS]      Buffalu-G-1777
>
シグナルレベルとかまでわかるから目安になる、WEPとか見つけてもイタズラしないように...笑

dhcpでIPもらおう

karky7 ~ # dhcpcd wlp3s0
wlp3s0: adding address ffff::2222:afaf:5f5f:4444
DUID dd:ee:aa:dd:bb:ee:ee:ff:de:bb:ee:ee:rr:00
wlp3s0: IAID dd:ee:aa:dd
wlp3s0: soliciting an IPv6 router
wlp3s0: soliciting a DHCP lease
wlp3s0: offered 192.168.0.118 from 192.168.0.1
wlp3s0: probing address 192.168.0.118/24
wlp3s0: leased 192.168.0.118 for 86400 seconds
wlp3s0: adding route to 192.168.0.0/24
wlp3s0: adding default route via 192.168.0.1
forked to background, child pid 3861
karky7 ~ #
karky7 ~ # ip a
..
..
3: wlp3s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether dd:ee:aa:dd:bb:ee brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.118/24 brd 192.168.0.255 scope global wlp3s0
       valid_lft forever preferred_lft forever
    inet6 ffff::2222:aaaa:dddd:eeee/64 scope link
       valid_lft forever preferred_lft forever
...

systemdへ登録


/usr/lib/systemd/system/wpa_supplicant.service
[Unit]
Description=WPA supplicant
Before=network.target
Wants=network.target

[Service]
EnvironmentFile=/etc/conf.d/wpa_supplicant
Type=dbus
BusName=fi.w1.wpa_supplicant1
ExecStart=/usr/sbin/wpa_supplicant -u -i${wlan} -c${conf}

[Install]
WantedBy=multi-user.target
Alias=dbus-fi.w1.wpa_supplicant1.service
/etc/conf.d/wpa_supplicant
wlan=wlp3s0
conf=/etc/wpa_supplicant/wpa_supplicant.conf
systemdでenableする
karky7 ~ # systemctl enable wpa_supplicant
Created symlink /etc/systemd/system/dbus-fi.w1.wpa_supplicant1.service → /usr/lib/systemd/system/wpa_supplicant.service.
Created symlink /etc/systemd/system/multi-user.target.wants/wpa_supplicant.service → /usr/lib/systemd/system/wpa_supplicant.service.

次回起動時から、無線で通信可能、IPは好きなように設定してください


2017年5月6日土曜日

iotopが意外といいんじゃないか

IOのread、writeがプロセスごとに取れる


ゴールデンウィーーークもそろそろ終了、アルチューハイマーになったこの体を元にもどすのに、リハビリが必要な時期に...どうでもいいですが...

いきなり話、違う話
iostatとかでも取れるんだけど、デバイスごとのIOの統計が上がってくるので、もうちょい細かいレイヤーでそれが見たいときがある。なんか無いかとportageツリーをさまよっていたら、iotopなるものをを発見。

カーネルのオプションが多分必要なので、確認してね
CONFIG_TASK_IO_ACCOUNTING
CONFIG_TASK_DELAY_ACCT
CONFIG_TASKSTATS
CONFIG_VM_EVENT_COUNTERS

で、iotopをインストール

cuomo@ugui7 ~ $ emerge sys-process/iotop

暇なので、kernelの方も覗いてみた


触りだけですが、興味本位に覗いてみました。
やっぱりプロセスごとに取れるのでtask_struct構造体に「struct task_io_accounting」がぶら下がっていて
struct task_struct {
..
..
        struct task_io_accounting ioac;
..
..
};

struct task_io_accounting {
#ifdef CONFIG_TASK_XACCT
        /* bytes read */
        u64 rchar;
        /*  bytes written */
        u64 wchar;
        /* # of read syscalls */
        u64 syscr;
        /* # of write syscalls */
        u64 syscw;
#endif /* CONFIG_TASK_XACCT */

#ifdef CONFIG_TASK_IO_ACCOUNTING
        /*
         * The number of bytes which this task has caused to be read from
         * storage.
         */
        u64 read_bytes;

        /*
         * The number of bytes which this task has caused, or shall cause to be
         * written to disk.
         */
        u64 write_bytes;

        /*
         * A task can cause "negative" IO too.  If this task truncates some
         * dirty pagecache, some IO which another task has been accounted for
         * (in its write_bytes) will not be happening.  We _could_ just
         * subtract that from the truncating task's write_bytes, but there is
         * information loss in doing that.
         */
        u64 cancelled_write_bytes;
#endif /* CONFIG_TASK_IO_ACCOUNTING */
};

これが、プロセスごとに集計されるIOの情報を管理する構造体らしい、「read_bytes」と「write_bytes」がそれ

これが特定の処理を通ったときに加算されていくらしい
*** linux-4.10.11-gentoo/fs/block_dev.c:
__blkdev_direct_IO_simple[242] task_io_account_write(ret);
__blkdev_direct_IO[377]        task_io_account_write(bio->bi_iter.bi_size);

*** linux-4.10.11-gentoo/fs/direct-io.c:
submit_page_section[800]       task_io_account_write(len);

*** linux-4.10.11-gentoo/fs/iomap.c:
iomap_dio_actor[812]           task_io_account_write(bio->bi_iter.bi_size);

*** linux-4.10.11-gentoo/fs/nfs/direct.c:
nfs_file_direct_write[1003]    task_io_account_write(count);

*** linux-4.10.11-gentoo/mm/page-writeback.c:
account_page_dirtied[2436]     task_io_account_write(PAGE_SIZE);

ディスクの書込みだけ抜粋してみた、該当するプロセスがページを書いたり、nfsへ書いたりした場合に、加算されているようです。

nfsの場合って、ネットワーク越しのディスクに書き込んだ場合は、書込みバイト数として加算しない?
などと、思ったりしてます、試してませんので分かりません。

/proc で確認する


* /fs/proc/base.c:
...
def CONFIG_TASK_IO_ACCOUNTING
        ONE("io",       S_IRUSR, proc_tid_io_accounting)

#ifdef CONFIG_TASK_IO_ACCOUNTING
        ONE("io",       S_IRUSR, proc_tid_io_accounting),
...

で定義されてて、seq_printf経由で確認できる
static int do_io_accounting(struct task_struct *task, struct seq_file *m, int whole)
{
        struct task_io_accounting acct = task->ioac;
        unsigned long flags;
        int result;

        result = mutex_lock_killable(&task->signal->cred_guard_mutex);
        if (result)
                return result;

        if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
                result = -EACCES;
                goto out_unlock;
        }

        if (whole && lock_task_sighand(task, &flags)) {
                struct task_struct *t = task;

                task_io_accounting_add(&acct, &task->signal->ioac);
                while_each_thread(task, t)
                        task_io_accounting_add(&acct, &t->ioac);

                unlock_task_sighand(task, &flags);
        }
        seq_printf(m,
                   "rchar: %llu\n"
                   "wchar: %llu\n"
                   "syscr: %llu\n"
                   "syscw: %llu\n"
                   "read_bytes: %llu\n"
                   "write_bytes: %llu\n"
                   "cancelled_write_bytes: %llu\n",
                   (unsigned long long)acct.rchar,
                   (unsigned long long)acct.wchar,
                   (unsigned long long)acct.syscr,
                   (unsigned long long)acct.syscw,
                   (unsigned long long)acct.read_bytes,
                   (unsigned long long)acct.write_bytes,
                   (unsigned long long)acct.cancelled_write_bytes);
        result = 0;

out_unlock:
        mutex_unlock(&task->signal->cred_guard_mutex);
        return result;
}

なので、「/proc/PID/io」で確認できる、whole変数の値で、この値をプロセス(thread)のまとまりで合計とってるっぽいのですが、forkとかで子プロセスとか持った場合、親のプロセスを見ると、下のプロセスの統計も加算されてくるのでしょうか?
こんな感じ、PID=16907の情報
ugui7 ~ # cat /proc/16907/io
rchar: 9651817
wchar: 10643512
syscr: 1781138
syscw: 1321495
read_bytes: 1109835776
write_bytes: 2892156928
cancelled_write_bytes: 0

使い方


余計なことばかりで、アレなんですが

Total DISK READ :       0.00 B/s | Total DISK WRITE :     686.88 K/s
Actual DISK READ:       0.00 B/s | Actual DISK WRITE:       0.00 B/s
  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO>    COMMAND
17060 be/4 root        0.00 B/s    0.00 B/s  0.00 %  0.27 % [kworker/2:0]
31094 be/4 root        0.00 B/s  400.35 K/s  0.00 %  0.00 % python2.7 -b /usr/lib/python-exec/python2.7/emerge dev-texlive/texlive-latex dev-texlive/texlive-fontutils app-text/texlive
こんな感じで、読み書きが多いプロセスを一番上に出力してくれる、他にもユーザーや、PIDで絞れるオプションがあるので確認して見てください。

どうでもいいですね...

2017年4月23日日曜日

Spring Batchの戻り値をshellで取る

Spring Batchの戻り値を取れずにクソレベルにハマった


単純に、Shellからjavaコマンドでバッチを起動して、$?で取りたかっただけ
ExitStatusをグリグリ弄ったところでうまくいかない、結局、中のコード見たら、BatchStatusと何かの関連で決まるようなコードを見かけたので
module-context.xmlあたりにSimpleJvmExitCodeMapperのbeanを突っ込んで
 
   <!-- exitCodeMapper -->
   <bean class="org.springframework.batch.core.launch.support.SimpleJvmExitCodeMapper" id="exitCodeMapper"/>

@Autowiredした
public class KusoTasklet implements Tasklet, InitializingBean {
 @Autowired
 private SimpleJvmExitCodeMapper sm;
...

で、@Autowiredしたクラスあたりで、ExitStatus.COMPLETEDの値を書き換える
...
    protected void setResultCode(Integer result) {
        Map<String, Integer> exitCodeMap = new HashMap<String, Integer>();
        exitCodeMap.put(ExitStatus.COMPLETED.getExitCode(), result);
        sm.setMapping(exitCodeMap);
    }
...

やってみた


cuomo@ugui7 ~/workspace/batchkoi/target $ java -cp ./dependency/*:./spring-batch-kuso-2.0.0.CI-SNAPSHOT.jar org.springframework.batch.core.launch.support.CommandLineJobRunner launch-context.xml kusoGoodJob
2017-04-23 20:28:31,123 INFO [org.springframework.context.support.ClassPathXmlApplicationContext] - <Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@2a33fae0: startup date [Sun Apr 23 20:28:31 JST 2017]; root of context hierarchy>
2017-04-23 20:28:31,147 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [launch-context.xml]>
2017-04-23 20:28:31,275 INFO [org.springframework.beans.factory.xml.XmlBeanDefinitionReader] - <Loading XML bean definitions from class path resource [META-INF/spring/module-context.xml]>
...
...
...
2017-04-23 20:28:32,778 INFO [org.springframework.beans.factory.support.DefaultListableBeanFactory] - <Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@42eca56e: defining beans [org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#0,eroUserServiceImpl,reader,writer,exampleConfiguration,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.batch.core.scope.internalStepScope,org.springframework.beans.factory.config.CustomEditorConfigurer,org.springframework.batch.core.configuration.xml.CoreNamespacePostProcessor,jobRepository,exitCodeMapper,step_kuso,kuso,kuso_Tasklet,step2,job1,fileDeletingTasklet,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#1,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,eroUserMapper,dataSource,transactionManager,sqlSessionFactory,org.dozer.spring.DozerBeanMapperFactoryBean#0,jobLauncher]; root of factory hierarchy>
2017-04-23 20:28:32,780 INFO [org.dozer.jmx.JMXPlatformImpl] - <Unregistering existing Dozer JMX MBean [org.dozer.jmx:type=DozerAdminController].>
2017-04-23 20:28:32,780 INFO [org.dozer.jmx.JMXPlatformImpl] - <Unregistering existing Dozer JMX MBean [org.dozer.jmx:type=DozerStatisticsController].>
cuomo@ugui7 ~/workspace/batchkoi/target $ echo $?
99
cuomo@ugui7 ~/workspace/batchkoi/target $ 

resultへ任意の数字を仕込めばshellの戻り値になる


ググったら、ここに 書いてあるじゃないか。。。

なかのコード見てる暇あったら、みんなググロー、1日無駄になった...




2017年4月16日日曜日

gentoo+DjangoでOracle Database 11g につなぐ

DjangoからOracle DB 11gへつないでみる


べつにgentooじゃなくても繋げます、ただgentooでやりたかっただけです

DBコネクタcx_Oracleをいれる


mysql-pythonてきなDBコネクタcx_Oracleを入れておく、ebuild作っておきましたのどうぞ
ugui7 ~ # layman -a karky7
ugui7 ~ # emerge -pv dev-python/cx_oracle
lculating dependencies... done!
[ebuild  N f   ] dev-db/oracle-instantclient-basic-11.2.0.4::gentoo  ABI_X86="32 (64) (-x32)" 0 KiB
[ebuild  N    ~] dev-python/cx_oracle-5.3::karky7  USE="doc {-test}" PYTHON_TARGETS="python2_7 python3_4" 0 KiB
ugui7 ~ # emerge -pv dev-python/cx_oracle

最近の私のlaymanリポジトリは腐りつつあるのでご注意ください。ウケル

Djangoをセットアップ


Djangoは入れてあるものとします
settings.pyを書き換え
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.oracle',
        'NAME': 'orcl', # SIDに指定するやつ
        'USER': 'ALCHU',
        'PASSWORD': 'IICHIKO20',
        'HOST': '192.168.253.13',
        'PORT': '1521'
    },
}

modelを作成


model.py
from __future__ import unicode_literals
from django.db import models

class Blogpost(models.Model):
    id = models.BigIntegerField(primary_key=True)
    title = models.TextField()
    authorid = models.ForeignKey('Person', db_column='authorId')  # Field name made lowercase.

    class Meta:
        db_table = 'BlogPost'


class Person(models.Model):
    id = models.BigIntegerField(primary_key=True)
    name = models.TextField()
    age = models.BigIntegerField(blank=True, null=True)
    regdate = models.DateTimeField(blank=True, null=True)

    class Meta:
        db_table = 'Person'

DBマイグレーションする


modelからテーブルを作成する
~/Code/django_skel $ ./manage.py makemigrations
~/Code/django_skel $ ./manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: staticfiles, messages
  Apply all migrations: admin, contenttypes, auth, app1, sessions
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying app1.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying sessions.0001_initial... OK
このあとPersonテーブルに適当にデータを突っ込む

開発サーバーで動かしてみる


~/Code/django_skel $ ./manage.py runserver


cx_Oracle入れるのに、oracle提供のバイナリが必要なのがちょっと面倒だけどとりあえずdjangoもOracle Databaseでもいけることが分かった今日この頃です。


2017年3月20日月曜日

pythonのzenhanをhaskellで作ってみた

pythonのzenhanをhaskellでこしらえてみた


まじ、久しぶりのブログで、朝から酒が抜けずに苦しみました...どうでもいいですが

個人的にhaskellで半角カタカナを弄りたい事案が発生したのですが、簡単に使えるようなものが見つからなかったので、とりあえず自分で作ってみました、まんまpythonのzenhanライブラリのパクリです。

hackageにも登録しておきましたので、stackかcabalでinstallしてください。

もちろん、gentoo-haskellにもebuild投げておきました。

よかったら使ってみてください、bugレポートお待ちしております、テヘペロ


Pythonオリジナル

  •  https://pypi.python.org/pypi/zenhan

Hackage

  •  https://hackage.haskell.org/package/hzenhan