2014년 12월 26일 금요일

Q.Promise(function(resolve,reject,notify) {...})에 대한 메모

개요
Q.Promise의 사용에 있어서 역할이 혼란스러워 정리를 해두고자 한다.

수순

 var promise = Q.Promise(function(resolve,reject,notify) {  
   ???  
 });  

상기 ???에 대해, resolve, reject, notify를 대입해 promise체인에 대한 동작을 확인한다.

1. return reject();

 promise.catch(  
     function(ex) {  
       console.log("catch");    
     })  
     .fail(function(ex) {  
       console.log("fail");    
     })  
     .then(function(data) {  
       console.log(data);  
     });  
결과 :
catch
undefined

 promise  
   .fail(function(ex) {  
     console.log("fail");      
   })  
   .then(function(data) {  
     console.log(data);    
   });  
결과 :
fail
undefined.

 promise  
   .then(function(data) {  
     console.log(data);
   })  
   .fail(function(ex) {  
     console.log("fail");
   })  
   .catch(function(ex) {  
     console.log("catch");
   });

결과 :
fail

 promise  
   .then(function(data) {  
     console.log(data);  
   })  
   .catch(function(ex) {  
     console.log("catch");  
   });  
결과:
catch

-> 에러를 분리해서 체이닝 할경우, 실패가 뒤로 가야하는것을 알 수 있다. 에러보다 뒤에있는 then은, 실행이 완료된 후 다음 동작을 의미한다. catch, fail을 then과 병용할때는 catch,fail이 then의 뒤에 붙을 필요가 있다. 그리고 이런 수순이 혼란스러울 정도라면 체이닝을 할게 아니라 then의 2,3번째 callback에서 호출해야 맞지 않을까?

 promise  
    .then(function(data) {  
      console.log(data);  
    },function(err) {  
      console.log("err");  
    })  
    .catch(function(ex) { // or fail  
      console.log("catch");  
    });  
결과
err
-> catch/fail가 불려지지 않는다.

2. throw new Error("msg");
var promise = Q.Promise(function(resolve,reject,notify) {  
   throw new Error("test throw");  
});  

 promise  
    .then(function(data) {  
      console.log(data);
      res.json(data);  
    })  
    .fail(function(ex) {  
      console.log("fail");  
    })  
    .catch(function(ex) {  
      console.log("catch");  
    });  
결과
fail

 promise  
    .then(function(data) {  
      console.log(data);
      res.json(data);  
    })  
    .catch(function(ex) {  
      console.log("catch");  
    });  
결과
catch
-> throw역시 fail로 catch된다.

3. 그 외, notify
 var promise = Q.Promise(function(resolve,reject,notify) {  
   console.log("call");  
   notify(1); // some progressive  
   notify(2); // some progressive  
   return resolve("Apple");
});  

promise  
    .then(function(data) {  
      console.log(data);  
    },function(err) {  
      console.log("err");  
    },function(notification) {  
      console.log(notification);  
    })  
    .fail(function(ex) {  
      console.log("catch");  
    });  
결과
Apple
-> 1,2 notify가 호출되지 않았다...?

var promise = Q.Promise(function(resolve,reject,notify) {  
   
    Q.delay(1000)
     .then(function() {  
       notify(1);  
     })
     .delay(1000)
     .then(function() {  
       notify(2);
     })
     .then(function() {  
       resolve("apple");  
     });  
 });  

promise
    .then(function(data) {  
      console.log(data);
      res.json(data);
    },function(err) {  
      console.log("err");
      res.json(err);
    },function(notification) {  
      console.log(notification);
    });  
결과
1
2
apple
-> 각 결과가 1초 간격을 두고 불러졌다. 비동기에서만 notify가 반응하는듯 하다.

var promise = Q.Promise(function(resolve,reject,notify) {  
    Q.fcall(function() {  
      throw new Error("throw in another call stack");  });  
      return resolve("data");  
    });  

promise  
    .then(function(data) {  
      console.log(data);
      res.json(data);
    })  
    .catch(function(ex) {  
      console.log("catch");
    });  
결과
data

당연히 다른 callstack에서 throw 한 내용에 대해서는 catch 하지 않는다.

var promise = Q.Promise(function(resolve,reject,notify) {  
     Q.fcall(function() {  
       return reject(new Error("throw in another call stack"));    
       resolve("data");  
     });  
   });  

promise
    .then(function(data) {  
       console.log(data);
       res.json(data);
    })  
    .catch(function(ex) {  
      console.log("catch");
    });  

다른 call stack에서의 에러 호출은 상기와 같은 느낌으로 실시해야하고, 이후의 코드가 실행되지 않게 하기위해 (throw가 그렇듯) return하여 흐름을 끊어줄 필요가 있다.

var promise = Q.Promise(function(resolve,reject,notify) {  
    Q.fcall(function() {  
      return reject(new Error("throw in another call stack"));
      resolve("data");  
    })
    .then(function() {  
      console.log("is this code called?");  
    });  
 });  
 promise
    .then(function(data) {  
      console.log(data);    res.json(data);  
     })  
    .catch(function(ex) {  
      console.log("catch");  
     });  
결과
is this code called?
catch

-> 아... 위에서 reject를 리턴했다고 다음 then이 실행되지 않는건 아닌가보다.

var promise = Q.Promise(function(resolve,reject,notify) {  
    Q.fcall(function() {  
      return reject(new Error("throw in another call stack"));
      resolve("data");
    }).then(function(info) {  
      console.log("is this code called?");
      console.log(info);
      return "dummy";  
    }).then(function(info) {  
      console.log(info);  
    });  
});  

promise
    .then(function(data) {  
      console.log(data);
      res.json(data);  
    })  
    .catch(function(ex) {  
      console.log("catch");
    });  
결과
is this code called?
undefined
dummy
catch

-> reject의 return값은 void인듯하다. 처음 info는 아무 정보도 가지고 있지 않다. 다음 info는 위에서 return한 값 "dummy"를 가지고 있다. 이들은 일종의 event chain으로 sync동작하게된다. 따라서 catch가 가장 마지막에 나오고 있다. 중간에 delay를 끼워 yield에 상당하는 동작을 하게되면 상황은 바뀌게 될것이다.

var promise = Q.Promise(function(resolve,reject,notify) {  
     Q.fcall(function() {  
       return reject(new Error("throw in another call stack"));
       resolve("data");
     }).then(function(info) {  
       console.log("is this code called?");
       console.log(info);
       return "dummy";
     }).delay(1)
     .then(function(info) {  
       console.log(info);
     });  
 });  

promise
    .then(function(data) {  
      console.log(data);
      res.json(data);  
    })  
   .catch(function(ex) {  
       console.log("catch");
    });  
결과
is this code called?
undefined
catch
dummy

-> 예를 들자면 이런느낌이다.

[Sails.js 0.10.5] sailsjs에서 transaction

ORM layer transaction

이 기능은 현재 개발중인 기능으로. 현재 버전인 0.10.5버전에서는 아직 구현이 되지 않은 부분이다. 0.11.x버전 즈음에서 한차례 기능 테스트를 하고, 0.12.x에서 인터페이스를 다듬어서 릴리스 할 예정이라는거 같다.

처음 아래 투고를 보고 당연히 지금 기능을 하는 내용인줄 알았는데 0.10.5기준에서는 그런 기능이 없다고 에러를 뱉어왔다.

https://github.com/balderdashy/waterline/issues/62

그리고 아래에서 다음 작업내용들이 정리되어있다.

https://github.com/balderdashy/sails-docs/blob/master/contributing/roadmap.md

ORM layer transaction이 구현되고 나면, transaction을 통상 지원하지 않는 다른 데이터베이스에서도 동일한 효과를 얻을 수 있지 않을까 하고 기대해본다.

MySQL transaction.

현재 ORM layer에서 transaction을 지원하지 않는다 하여 손을 놓고 있을 순 없는 일이다.
한정적이나마, MySQL에서의 transaction사용법을 체크하고 넘어가볼까 한다.

조금 조사해봤지만, ORM자체에 의존한채로 query를 날리는 방법으로는 transaction이 동작하지 않았다. 커뮤니티에 떠도는 소스중에

Model.query("BEGIN");
...
Model.query([some sql to insert, update, etc...]) ...
...
Model.query("ROLLBACK" or "COMMIT");

과 같은 형태로 써보라는 말이 있긴했는데, 위와 같은 구성으로 query를 날렸을시, mysql측에서 받기로는 다음과 같은 느낌이었다.

  52 Query BEGIN
  53 Query INSERT INTO `dummy` (`name`, `createdAt`, `updatedAt`) values ('0.0907773447688669', '2014-12-26 15:15:12', '2014-12-26 15:15:12')

  52 Query ROLLBACK

가장 좌측에 보이는 52, 53은 query시의 connection id를 나타내는 수치인데 BEGIN과 INSERT의 id가 다른것을 알 수 있다. ORM딴에선 별도 커넥션으로 접속해버리는듯 하다.
문서화가 진행중인 부분에다가, 공식적으론 아직 서포트 하지 않는 상태라, 다른 기술자들의 의견교환 정도밖에 문서가 보이지 않고 있었는데, 공식 위키에는 다음과 같은 내용이 있었다.

https://github.com/balderdashy/sails-mysql/wiki/Transactions

2012년의 좀 오래된 기록이긴 하지만, 결국에 0.10.5의 현 시점에서는 직접적으로 mysql모듈을 통해서 쿼리를 날리란 내용이었다.

0.11.x(현재 git의 unstable)에서는 ORM을 통해서도 transaction을 이용하게 된다고 하니, 그때까지는 상기의 문서에서 이야기하는 내용으로 transaction을 이용할 수 밖에 없는듯 하다.

상기 코드를 참고로 구성한 코드에서는 이하와 같이 동작확인이 가능했다.

  68 Connect sails@localhost on sails
  68 Query BEGIN;
  68 Query INSERT INTO `dummy` (`name`, `createdAt`, `updatedAt`) values ('0.1618976395111531', '2014-12-26 15:15:12', '2014-12-26 15:15:12')
  68 Query COMMIT
  68 Quit

이하에 테스트 코드를 첨부한다.

var math = require("mathjs");
var mysql = require('mysql');  

// ...  

var connectionConfig = sails.config.connections.mysql;  
var connection = mysql.createConnection({  
   host : connectionConfig.host,
   user : connectionConfig.user,
   password : connectionConfig.password,
   database : connectionConfig.database,
   multipleStatements: true,
});  


 connection.connect(function(err) {  
   connection.query(  
     "BEGIN;"+
     "INSERT INTO `dummy` (`name`, `createdAt`, `updatedAt`) values ('"+math.random().toString()+"', '2014-12-26 15:15:12', '2014-12-26 15:15:12')",  
     function(err,rows,fields) {  
     if(err)  
     {  
       sails.log.error(err);
       connection.query("ROLLBACK;");
     }  
     else
     {  
       connection.query("COMMIT;");
     }  
     connection.end();
     res.json("finish with : "+err);  
   });  
 });  

2014년 12월 25일 목요일

[PostgresSQL] OSX에 postgresSQL을 도입

개요
OSX에 PostgresSQL을 도입.

수순
http://www.moncefbelyamani.com/how-to-install-postgresql-on-a-mac-with-homebrew-and-lunchy/

GUI Administration
http://www.valentina-db.com/en/valentina-studio-overview
http://www.postgresql.org/ftp/pgadmin3/release/v1.20.0/osx/

2014년 12월 23일 화요일

sailsjs에서 localDiskDb에서 다른 데이터베이스로 전환시 트러블 슈팅

개요

sails 0.10.4, 0.10.5의 버전에 있어서 아래와 같은 에러가 발생하는 경우가 있다.

/[myProject]/node_modules/sails-disk/lib/database.js:209
      self.collections[collectionName]._cast.run(values);
                                      ^
TypeError: Cannot read property '_cast' of undefined
    at /[myProject]/node_modules/sails-disk/lib/database.js:209:39
    at Array.map (native)
    at Database.read (/[myProject]/node_modules/sails-disk/lib/database.js:208:32)
    at Array.async.auto.checkData [as 0] (/[myProject]/node_modules/sails-disk/lib/database.js:59:12)
    at /[myProject]/node_modules/sails-disk/node_modules/async/lib/async.js:459:38
    at Array.forEach (native)
    at _each (/[myProject]/node_modules/sails-disk/node_modules/async/lib/async.js:32:24)
    at Object.async.auto (/[myProject]/node_modules/sails-disk/node_modules/async/lib/async.js:430:9)
    at Database.initialize (/[myProject]/node_modules/sails-disk/lib/database.js:55:9)
    at Object.module.exports.adapter.registerConnection (/[myProject]/node_modules/sails-disk/lib/adapter.js:51:40)

위 증상은 sails-disk의 내부구조에 있어서 sails-disk가 기존의 캐시 정보를 토대로 한번이라도 sails-disk였던 적이 있는 테이블에 대해 {database.js}.collections의 key에 대해 collectionName으로 접근하려 한 경우 발생하는 에러로, 데이터의 atomicity가 유지되지 않아 발생하는 문제이다.

대책

극단적인 방법으로, sailsjs프로젝트 폴더의 .tmp폴더 이하의 로컬db파일을 삭제해버리면 증상은 해결된다. 엄밀히는 내부에 캐시된 자료중 데이터베이스로 전환된 부분에 대한 캐시내역을 지우면 될듯 하지만, 애초에 localDiskDb는 개발과정에서만 사용하게끔 전제가 되어있는 물건이고 production에서 사용하는 일은 없을거라 생각되니 실상 그냥 .tmp폴더의 삭제제 혹은 .tmp내부의 localDiskDb.db파일을 삭제하는 거로 충분할거라 생각된다.


2014년 12월 22일 월요일

[Sails.js] sailsjs에서 passport를 이용한 사용자 인증

개요
sailsjs에서 passport를 이용한 사용자 인증이 가능케 하고자 한다.
passport의 strategy라는 각 인증 구현을 이용하면, 대개의 인증은 간단하게 이용할 수 있을거 같아서다.

참고자료
http://passportjs.org/guide/authorize/
http://www.bearfruit.org/2014/07/21/tutorial-easy-authentication-for-sails-js-apps/
https://github.com/kasperisager/sails-generate-auth

전제
passport binder에 해당하는 기능 구현은 직접 하지 않고, 상기 sails-generate-auth라는 구현체를 이용하고자 한다. 문서화가 아직 미약한 면이 있지만 소스가 그리 방대한것도 아니고 살짝 훑어보면 어떤 구조를 하고있는지는 간단하게 알 수 있다.

기본적으로 template을 통해 sails의 router, services, policy, controller, model등에 인증에 필요한 기능을 부분적으로 구현해 이용하는 형태를 취하고있다.

passport의 strategy는 config/passport.js에서 연결할 수 있으며, 기본적으로 local, google, twitter등의 인증은 기본으로 들어가 있는 상태이다.

기본적으로 auth에 필요한 파일들 및 디렉토리 구조를 작성해주는 형태이니 가급적이면 빈 프로젝트에서 진행하자.

git을 통해 확인해본 추가되는 파일들의 목록은 아래와 같다

        api/controllers/AuthController.js
        api/models/Passport.js
        api/models/User.js
        api/policies/passport.js
        api/services/passport.js
        api/services/protocols/
        config/passport.js
        node_modules/sails-generate-auth/
        views/auth/

적어도 상기 파일들과 충돌이 일어나지 않게끔 준비가 되어있을 필요가 있다.

수순

sudo npm install sails-generate-auth passport bcryptjs validator

# on the sails project root...
sails generate auth

이후 각 파일별의 설정

필수사항

1. config/routes.json
'get /login': 'AuthController.login',
'get /logout': 'AuthController.logout',
'get /register': 'AuthController.register',

'post /auth/local': 'AuthController.callback',
'post /auth/local/:action': 'AuthController.callback',

'get /auth/:provider': 'AuthController.provider',
'get /auth/:provider/callback': 'AuthController.callback',
'get /auth/:provider/:action': 'AuthController.callback',

2. config/bootstrap.json
sails.services.passport.loadStrategies();

3. config/policies.json
'*': [ 'passport' ]

더불어 사용하고 싶은 인증형태의 관련 passport-strategy를 인스톨 할 필요가 있다.

sudo npm install passport-local
sudo npm install passport-twitter
sudo npm install passport-github
sudo npm install passport-facebook
sudo npm install passport-google-oauth

상기는 기본적으로 설정되어있는 strategy를 도입하는 수순인데, 필요 없는 부분에 관해선 config에서 제거후, 도입하지 않으면 될거라 생각된다.

선택사항

1. 에러메세지를 지정하고 싶다면 아래와 같은 내용을 설정
{
  "Error.Passport.Password.Invalid": "The provided password is invalid!",
  "Error.Passport.Password.Wrong": "Whoa, that password wasn't quite right!",
  "Error.Passport.Password.NotSet": "Oh no, you haven't set a password yet!",
  "Error.Passport.Username.NotFound": "Uhm, what's your name again?",
  "Error.Passport.User.Exists": "This username is already taken.",
  "Error.Passport.Email.NotFound": "That email doesn't seem right",
  "Error.Passport.Email.Missing": "You need to supply an email-address for verification",
  "Error.Passport.Email.Exists": "This email already exists. So try logging in.",
  "Error.Passport.Username.Missing": "You need to supply a username",
  "Error.Passport.Password.Missing": "Oh no, you haven't set a password yet!",
  "Error.Passport.Generic": "Snap. Something went wrong with authorization."
}

2. 기본은 비인증도 접근할 수 있는 상태로 설정되어 있으나, 인증한정으로 하고싶다면 아래와 같은 내용을 config/policies에 설정
'*': ['passport', 'sessionAuth'],

 'auth': {
    '*': ['passport']
  }


특정 WaterlineORM(Database)을 통한 database-migration

sails lift를 실시할때 매번 database migration에 관해 물어오는데, scheme가 존재하는 database의 경우, 이 시점에서 alter를 선택하면 데이터베이스에 필요한 테이블들이 자동적으로 생성된다. (여기서 한 며칠 mysql에서 association이 아직 미지원인줄 알고 잘못된 테이블을 가지고 삽질을 했다(...))

sails-generate-auth는 내부적으로 2개의 모델간에 model-association기능을 이용하고 있다. 수동으로 이를 정의하게 되면 이 부분에서 문제가 발생할 수 있으니, 가급적 기동 초기에 alter선택지를 활용해서 데이터베이스에 테이블을 생성하도록 하자.

http://stackoverflow.com/questions/25255567/sails-js-production-env-sails-mysql-database-tables-not-created-upon-lift


Google oAuth2.0연계하기

1. ClientID & ClientSecret 발급

https://console.developers.google.com
상기 url에 접근한후, 인증용 프로젝트를 작성, 프로젝트 하위의 인증정보 페이지에서 ClientID를 작성한다.

2. ClientID & ClientSecret & Scope 설정

config/passport.js
상기 파일 내의 google에 대한 설정을 아래와 같이 설정한다. options하위에 scope란 항목을 추가한다. 기본적으로 google plus로 맞추어두었으나, 다른 서비스에 스코프를 추가한다면 해당 서비스의 스코프를 입력할 필요가 있다.
google: {
  name: 'Google',  
  protocol: 'oauth2',  
  strategy: require('passport-google-oauth').OAuth2Strategy,  
  options: {
    clientID: '*****************', 
    clientSecret: '******************',
    scope: 'https://www.googleapis.com/auth/plus.login',
   scope: 'http://yourhost-url/auth/google/callback',
  }}

https://github.com/kasperisager/sails-generate-auth/issues/136
개발자측에 문의해보니, scope에 ['email']이라 설정할 필요가 있다고 한다.

google: {
  name: 'Google',  
  protocol: 'oauth2',  
  strategy: require('passport-google-oauth').OAuth2Strategy,  
  options: {
    clientID: '*****************', 
    clientSecret: '******************',
    scope: ['email'],
   scope: 'http://yourhost-url/auth/google/callback',
  }}

위와 같이 설정을 한 후에, 구글측에 접근하면 email만 요구하게 되고, 이를 통해 사용자 인증을 하게된다.

인증정보의 확인

sails-generate-auth는 조금 불친절하게도 로그인 상태를 확인할 수 있는 장소를 따로 설명해두지 않았다. 일단 인증상태정보를 확인하기 위해, 아래와 같은 route와 AuthController.js이하에 다음 코드들을 추가하고

http://your-host/whoami

로 접근해보자.

AuthController.js
whoami: function(req,res) {
  res.json(req.session);},

config/routes.js
'get /whoami': 'AuthController.whoami',

이후에 http://your-host/login 에서 로그인후 내용을 확인하면 다음과 같이 표시되는걸 알수가 있다.

logged-out
{
  • cookie:
    {
    • expiresnull,
    • httpOnlytrue,
    • path"/"
    },
  • passport: { },
  • flash: { }
}

local-strategy
{
  • cookie:
    {
    • expiresnull,
    • httpOnlytrue,
    • path"/"
    },
  • passport:
    {
    • user2
    },
  • flash: { }
}

google-oauth-strategy
{
  • cookie:
    {
    • originalMaxAgenull,
    • expiresnull,
    • httpOnlytrue,
    • path"/"
    },
  • passport:
    {
    • user1
    },
  • flash: { }
}

google의 경우, sails-generate-oauth에서 제공해주는 설정만으로는 로그인할수가 없다.
services/passport.js에서 다음과 같은 수정이 필요하다.


// If the profile object contains a username, add it to the user.if (profile.hasOwnProperty('username'))
{
  user.username = profile.username;}
else if(profile.hasOwnProperty('displayName'))
{
  user.username = profile.displayName;
}

profile
은 획득이 가능하나, property-name이 다르기때문에 이 부분에서 user-name을 수정해서 맞추어 주어야한다. google+를 기준으로 할때 이름은 displayName혹은 name(object)형태로 온다. 후자의 경우는 first, lastname으로 나뉘어져있다. 기본적으로 displayName을 사용하면 되지 않나 싶긴 하다.

이후의 플로우에서는 이름 혹은 메일이 만족되면 진행되기때문에, 상기 조정으로 충분하지 않나 하는 생각이 든다.

일단 passportjs자체의 스펙도 확인해봤으나, 아무래도 수정해서 쓴다는거 자체가 좀 의아한 느낌이 들었기때문에 본가에 문의를 해본상태

https://github.com/kasperisager/sails-generate-auth/issues/136

빠뜨린게 있으면 조정해서 사용을 하던지 해야할듯 싶다.

위 부분에 관해서는 상기 google oAuth2.0연계하기 부분의 하단을 참고해줬으면 한다.

2014년 12월 21일 일요일

[Unity3D] Unity Batchmode build에서 reimport시간 단축하기

개요
Unity에서 각 플랫폼별의 빌드를 실시하는 경우, 플랫폼별 전환간에 Reimport all동작이 발생함으로 인해서 속도의 저하를 야기하는 현상을 해결하고자 함.

참고자료
http://sassembla.github.io/Public/2014:12:15%2000-00-00/2014:12:15%2000-00-00.html

기법
우선 reimport all의 동작 자체를 막는 방법은 없는듯 하다.
다만 이 과정에서 불필요한 파일들 마저 reimport되는 현상이 있기 때문에,
unity에서 터치하지 않는 파일들로 일시적으로 파일명을 바꾼후에, 리빌드를 실시.

상위 투고나,
http://jemast.com/unity/fast-platform-switch
와 같은 툴에서도 같은 기법을 쓰고있는데, 어느쪽이든 유니티 내부에서 해결하고 있다.

이번에 구축하는 포트폴리오에서는 shell script를 통한 배치모드에서의 reimport all에서의 처리량 경감을 목적으로 하기에, 단순하게 일시적인 rename도 쉘스크립트에서 해결하고자 한다.

# ------------------------------------------------------------
# for avoiding reimport all : temprally change extensions
# ------------------------------------------------------------
LIST=$(find ..)
TARGET="^../MasterCSV"
for FILE in $LIST; do
if [ -f $FILE ]; then # check is file
if [ ! $(echo $FILE | grep -e "[¥.dummy|¥.cs|¥.cs¥.meta|¥.dll|¥.dll¥.meta|¥.sh|¥.sh¥.meta|¥.prefab|¥.prefab¥.meta]$") ]; then
if [ ! $(echo $FILE | grep -e $TARGET) ]; then
echo $FILE
mv $FILE $FILE".dummy"
fi
fi
fi
done


# 유니티의 배치모드 러닝 코드.

echo "--------------------------------------------------------"
echo "일시적으로 이름을 변경한 파일들을 복원하고 있습니다.";
echo "--------------------------------------------------------"
# ------------------------------------------------------------
# for avoiding reimport all : restore filenames
# ------------------------------------------------------------
RESTORE_LIST=$(find .. -name "*.dummy")
for FILE in $RESTORE_LIST; do
if [ ! $(echo $FILE | grep -e $TARGET) ]; then
mv $FILE $(echo $FILE | sed s/.dummy$// )
#echo $FILE" restoring"
fi
done
# ------------------------------------------------------------

echo "--------------------------------------------------------"

위는 일부 반드시 유지되어야 할 파일들은 제외하고 .dummy라는 확장자를 잠시 붙였다가 빌드 실행후, MasterCSV라는 폴더만은 예외로 두어 유지한 채 이름 변경을 일괄적으로 실시한 예제이다.

보존해야 할 폴더의 이름이 다르다면 위를 참고로 하여 특정 패스만 보호된채 이름변경후 빌드 되게끔 수정하면 된다.

실제 빌드

이하에 현재 작업중인 포트폴리오를 실제 빌드처리해보았을때의 결과다.
아직 1개 게임의 리소스도 다 안들어간 상태에서의 빌드임에도 불구하고, 150초와 60초정도의 차이를 보인다.

플랫폼은 4개를 타겟으로 에셋번들을 빌드했을때의 결과이다.

적어도 총 5~6번의 스위칭이 일어난다 (배치모드 스타트업에서 standalone for PC -> project configured platform if not standalone for PC -> 1st targeted -> 2nd targeted -> 3rd targeted -> 4th targeted)

미 적용시
--------------------------------------------------------
유니티가 마스터 테이블의 에셋번들을 빌드하고 있습니다... 잠시 기다려주십시오
--------------------------------------------------------

Cleanup mono
debugger-agent: Unable to listen on 6
Exiting batchmode successfully now!
Material 0: Orc
Texture 0: Map #0
Material 0: Orc
Texture 0: Map #0
Material 0: Orc
Texture 0: Map #0
--------------------------------------------------------
일시적으로 이름을 변경한 파일들을 복원하고 있습니다.
--------------------------------------------------------
Buildup time : 150 sec
--------------------------------------------------------
Build trying done

--------------------------------------------------------

적용시
--------------------------------------------------------
Reimport할 필요 없는 파일들의 이름을 일시적으로 변경하고 있습니다.
--------------------------------------------------------
...
--------------------------------------------------------
유니티가 마스터 테이블의 에셋번들을 빌드하고 있습니다... 잠시 기다려주십시오
--------------------------------------------------------

...

----- Total AssetImport time: 0.089951s, AssetImport time: 0.065578s, Asset hashing: 0.000000s [0 B, 0.000000 mb/s]

Cleanup mono
debugger-agent: Unable to listen on 6
Exiting batchmode successfully now!
--------------------------------------------------------
일시적으로 이름을 변경한 파일들을 복원하고 있습니다.
--------------------------------------------------------
Buildup time : 60 sec
--------------------------------------------------------
Build trying done

--------------------------------------------------------

2014년 12월 19일 금요일

[Bash] OSX에서 md5sum、sha1sum등을 이용하기

개요

기본적으로 osx에는 md5sum,sha1sum과 같은 기능을 이용할 수가 없는 상태이다.

도입수순

brew install coredmodule
echo 'PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"' >> ~/.bash_profile

2014년 12월 18일 목요일

[OSX] OSX에서 sshfs/s3fs등 fs으로 마운트 하려했을때 에러에 대한 대처

증상

fuse4x kernel extension was not loaded. Please check /var/log/{system|kernel|debug}.log for more information.

대처

# 현재 포커싱이 맞춰진 fuse4x를 찾아봄 (없다면 패스문제)
which fuse4x 

# 퓨즈를 찾아봄
sudo find / -name "fuse4x.kext" 

#개인적인 환경에서는 아래가 검색됨(없다면 brew등을 통해 도입할 것)
/System/Library/Extensions/fuse4x.kext

#단순하게 심볼릭 링크를 연결해줌.
ln -s /System/Library/Extensions/fuse4x.kext /Library/Extensions

#이후에 다시 동작확인.

2014년 12월 16일 화요일

[Node.js] WebStorm에서 nodejs디버깅 환경 구축

개요

WebStorm을 이용해 node.js앱(여기선 sails.js) 디버깅

제약

https://www.jetbrains.com/webstorm/help/running-and-debugging-node-js.html
로컬에 있는 nodejs소스거나, 원격지에 있는 경우는 이미 구동중인 앱에 대한 어태칭을 실시할 수 있는 것을 전제로 한다고 한다.

전제

우선 원격지에 관한 앱 구동 자체가 불가능한것은 아니다. 다만 공식적으로 서포트가 안되고있는 점과, 원격에 소스를 두고 작업을 하기에는 Webstorm의 원격 파일 조작부분은 미약한 부분이 많다. sshfs등을 이용해 원격 디스크를 마운트해 작업을 하기에도 가능은 하나 역시 물리적으로 느려지는 부분은 어쩔수 없는듯 하다.

개인적으로 작업환경을 구축함에 있어서. 인프라를 따로 세우지 않고 작업을 진행하는 현 시점에서는 물리적으로 로컬 머신에 모든 데이터베이스 서버등을 준비해, 로컬에서 작업을 하다, 실제 인프라를 전제로 하는 환경에서는 작업환경 자체를 AWS의 VPC에 하나의 app-server로서 VPN을 통해 접속해 소스 개발은 로컬에서 해결을 보는 구성을 전제로 생각하고자 한다.

개인 개발 단계에서는 VPN의 이용은 비용이 발생하는 부분이라 나중 작업이 될듯 하지만, 어느쪽이든 로컬에 파일을 두고 작업을 진행하는 환경을 전제하고자 한다.

따라서 여기서의 웹스톰 디버깅은 본래 제공되고 있는 로컬앱 디버깅까지만 체크하고자 한다.

수순

1. version control된 소스를 전개한 프로젝트를 작성

2. 하기 위치에서 실행 구성을 설정


3. 앱 실행 설정의 예시
    처음엔 디폴트 설정뿐이므로, 
    왼쪽위 + 버튼을 눌러 Node.js타입의 실행구성을 추가한다.



--silly는 sailsjs의 디버깅레벨의 최하단에 위치하는 디버깅 모드이다.
필요에 따라서만 지정. 지정하지 않은 경우는 config에 설정된 내용대로 동작한다.

4. 브라우저 런처 추가


5. 작성한 실행구성을 선택후


아래 버튼을 통해서 



일반 실행이나, 디버깅모드로 실행

6. 소스상의 실행가능한 구역에 breakpoint를 설정한 후, 브라우저에서 breakpoint구간을 통과하는 endpoint에 접근. 아래처럼 디버거뷰에서 break가 걸리는 것이 확인 가능하다.

2014년 12월 14일 일요일

AWS EC2에 mongodb를 인스톨

개요

AWS EC2인스턴스의 yum을 통한 mongodb의 인스톨
기본 yum-repository에는 mongodb가 존재하지 않는다.
추가적인 mongodb를 위한 repository를 추가한 후, mongodb를 인스톨.

참고자료

http://docs.mongodb.org/manual/tutorial/install-mongodb-on-red-hat-centos-or-fedora-linux/

수순

1. mongodb repos의 추가


/etc/yum.repos.d/mongodb.repo
[mongodb]
name=MongoDB Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
gpgcheck=0
enabled=1
상기 내용을 추가함. 64bit 시스템 기준이므로 이와 다를경우 상기 참고자료에서 32비트용 설정값을 가져올 것

2. yum installation

이후에

sudo yum install mongodb-org

로 최신버전을 인스톨 가능.

service mongod start
chkconfig mongod on


3. GUI Administration

http://robomongo.org/

비인증 상태로 접근시 Authentication skipped by you라고 뜨며 x마크가 쳐지는데,
인증을 따로 설정을 안했다면 x라 떠도 정상상태로 접근이 가능하다.

4. CLI Administration

mongo

5. 처음 기동시에 접근권한

인증없는 상태로 기동하려면 일시적으로 mongod --noauth로 서버를 기동한후 클라이언트로 접근하면 비인증 상태에서 접근이 가능하다. 이 상태에서 유저를 추가하는 작업을 실시

trusted환경하에 인증없이 데이터를 주고받는게 좋을거라 생각되나, 인증이 필요한 경우에는 --auth모드로 서버를 기동하여 접근권한을 제어할것.

sails.js 도입 수순

개요

rails-like인 sails.js개발과정을 간략하게 정리

전제
nodejs가 인스톨 된 환경

수순

sails.js의 인스톨
npm install sails -g

sails.js로 새 프로젝트 만들기
쉘에서 특정 폴더 이하에

sails new {project name}

새로운 컨트롤러 추가 (모델도 추가됨)

sails generate api {controller name}

DB 커넥션 설정

connections.js에서 DB 접속등의 설정을 실시함.
sails는 waterline이라고 하는 ORM환경에서의 데이터베이스 접근환경을 제공하고 있으며
DB별로 구현된 ORM이 존재한다.

우선적으로 mysql, mongodb, redis를 이용해보고자 하므로로 아래와 같이 설치를 진행

app에 대해 한정적으로 인스톨을 진행하고자 하므로 -g옵션을 붙이지 않음
sails project folder에서 아래를 실행

npm install sails-mysql
npm install sails-mongo
npm install sails-redis

이후에 connections.js에서 나온 설정등을 참고로하여 접속정보를 설정.

Redis의 설정

Load balancer를 이용하는 환경 등에서는 세션정보나 Socket.IO정보 등을 Redis등을 통해 필요 메모리를 공유할 필요가 있음.

./config/sockets.js에서 socket.io에 대한 redis를 설정.

adapter: 'redis',
host: '127.0.0.1',
port: 6379,
db: 'sails',
pass: '<redis auth password>'

./config/session.js에서 세션에 대한 redis를 설정

adapter: 'redis',
host: '127.0.0.1',
port: 6379,
db: 'sails',
pass: '<redis auth password>'

Sails app의 기동

sails lift

추기

설치 초기에 RESTful API가 설정된 상태임
기본 설정 DB에 대한 CRUD이용이 가능해져버리는 상태이니
필요에 따라

./config/blueprints.js

에서 RESTful API에 대한 설정을 off로 하는 설정이 있음을 기억해두자.

Amazon Linux에서 nodejs / sails.js / meteor.js 도입

개요

한동안 맥에서 서버개발을 진행하다 막상 Amazon Linux에서 작업하려니 yum패키지에 포함되어있지 않은 것을 확인. 소스빌드로 설치하는 수순을 메모

참고자료

http://iconof.com/blog/how-to-install-setup-node-js-on-amazon-aws-ec2-complete-guide/#installNode


수순

cd /usr/local/src
git clone git://github.com/joyent/node.git

git checkout v0.10.33 #현 시점에서 최신 stable release

cd node
./configure
make && make install


#--------

npm install forever -g
npm install sails -g
npm install meteor -g

2014년 12월 12일 금요일

Jenkins + Unity3D

개요

Jenkins의 플러그인중, 유니티빌드를 지원하는 플러그인이 존재한다.
OSX Local에 Jenkins를 도입한후, Unity3DBuilder plugin을 이용해 빌드를 자동화한다.

전제
OSX를 이용하는 Unity Pro(Basic은 무리. 하기 기록을 참고)개발환경

참고자료

https://wiki.jenkins-ci.org/display/JENKINS/Unity3dBuilder+Plugin

도입수순

Jenkins OSX Installer를 통한 인스톨

인스톨이 완료된후 http://localhost:8080/ 로 접근하여 대쉬보드를 확인

필요한 플러그인을 인스톨

플러그인의 인스톨은 Jenkins 대쉬보드에서

Jenkins > UpdateCenter > Manage Plugins > Available

에서 선택후 인스톨 할 수 있음(GUI상만으로 가능)

Jenkins > Configure System > Unity3D

에서 유니티 앱을 추가.
(이름(임의)와 유니티 에디터 앱의 로컬 패스를 지정해야함. 이 부분 때문에 Linux상에서의 구성은 힘든가...?)

사용수순

유니티 프로젝트를 Jenkins로 빌드함에 앞서, 아래의 사전준비가 필요하다.

https://wiki.jenkins-ci.org/display/JENKINS/Unity3dBuilder+Plugin#Unity3dBuilderPlugin-Prerequisites

아래의 스크립트가 프로젝트상에 존재할 필요가 있다.
같은 패스에 스크립트를 작성해 입력.


Assets/Editor/MyEditorScript.cs
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;

class MyEditorScript {
        static string[] SCENES = FindEnabledEditorScenes();

        static string APP_NAME = "YourProject";
        static string TARGET_DIR = "target";

        [MenuItem ("Custom/CI/Build Mac OS X")]
        static void PerformMacOSXBuild ()
        {
                 string target_dir = APP_NAME + ".app";
                 GenericBuild(SCENES, TARGET_DIR + "/" + target_dir, BuildTarget.StandaloneOSXIntel,BuildOptions.None);
        }

 private static string[] FindEnabledEditorScenes() {
  List<string> EditorScenes = new List<string>();
  foreach(EditorBuildSettingsScene scene in EditorBuildSettings.scenes) {
   if (!scene.enabled) continue;
   EditorScenes.Add(scene.path);
  }
  return EditorScenes.ToArray();
 }

        static void GenericBuild(string[] scenes, string target_dir, BuildTarget build_target, BuildOptions build_options)
        {
                EditorUserBuildSettings.SwitchActiveBuildTarget(build_target);
                string res = BuildPipeline.BuildPlayer(scenes,target_dir,build_target,build_options);
                if (res.Length > 0) {
                        throw new Exception("BuildPlayer failure: " + res);
                }
        }
}
---

[Jenkins상의 특정 프로젝트] > Configure > Build
에서 위에서 정의한 유니티앱의 종류(이름으로 선택)를 지정후, 빌드옵션을 지정. 빌드 옵션에는 하기 옵션을 추가.

-quit -batchmode -executeMethod MyEditorScript.PerformMacOSXBuild 

상기옵션은 unity3dbuilder에서 기본적으로 제공하는 옵션이다.
구체적으론 아래의 공식문서를 참고하는게 좋을듯.

http://docs.unity3d.com/Manual/CommandLineArguments.html

-executeMethod가 내부에 작성된 코드를 호출할 수 있는 부분인듯 하다.
이 기능을 응용하면 이전부터 불편하게 느꼈던 AssetBundle의 일괄작성과 같은 부분의 기능도 커맨드라인 상으로만 구축이 가능할거 같이도 보인다.

또한 OSX를 기준으로 했을때는 권한에 관련된 문제가 발생함.

_RegisterApplication(), FAILED TO establish the default connection to the WindowServer, _CGSDefaultConnection() is NULL. 2014-12-12 00:01:53.560 Unity[88456:d07] NSDocumentController Info.plist warning: The values of CFBundleTypeRole entries must be 'Editor', 'Viewer', 'None', or 'Shell'.

위 문제에 관해서는

 http://la-stranger.tistory.com/entry/unity-jenkins-autobuild2

상기 투고의 Jenkins Slave Agent작성부분을 참고.
Jenkins Slave Agent로 빌드를 실시하는 경우 Java Slave가 빌드를 수행하게끔 해줄 필요가 있음. master node agent로 빌드를 지속할 경우에는 여전히 문제가 발생.

http://serverfault.com/questions/359793/tell-jenkins-to-run-a-specific-project-on-a-particular-slave-node 

따라서 위의 답변내용을 참고로 하여 NodeLabel Parameter 플러그인을 인스톨한후, 특정 슬레이브 노드가 접속중일때만 빌드를 실시하게끔 할 필요가 있음.

또한...
대부분의 현장의 개발환경이라면 UnityPro가 당연한 환경일거라 생각되지만
현재 basic으로 테스트하고 있는 환경에서는 아래와 같은 문제가 확인되었다.

Exception: BuildPlayer failure: Building Player from editor scripts requires Unity PRO 

적어도 Jenkins를 이용하려면 프로 라이센스가 요구되는듯.

http://docs.unity3d.com/ScriptReference/BuildPipeline.BuildPlayer.html

위 url에서 문제가 되는 기능을 확인할 수 있다.

-----------

우선 상기 상태에서 유니티를 일시적으로 30일 체험판모드로 구동후, 빌드를 시험해보았고 NGUI 3.6버전이 들어간 샘플을 하나 빌드씬에 추가한 상태에서 빌드해본 결과, 빌드가 가능했다.

단, 아래의 메세지로부터 동일 프로젝트를 열어둔 상태에서의 CI빌드는 불가능해보인다.

Multiple Unity instances cannot open the same project.

빌드 노드 머신을 따로 분리하던지 할 필요성이 있는듯.

-----------

추가적으로 코드품질 관리를 위한 코드복잡도계산 관련의 플러그인을 살펴보았다.

우선 될거같아 보였던 CCM Analysis Plugin

https://wiki.jenkins-ci.org/display/JENKINS/CCM+Plugin

결론부터 말하자면 Unity에선 못쓸거같다.

.NET을 위한 코드복잡도 리포트 기능이고,
using UnityEngine;
이라는곳에서부터 걸리는듯

Cleanup mono
debugger-agent: Unable to listen on 6
Exiting batchmode successfully now!
[CCM] Collecting CCM analysis files...
[CCM] Finding all files that match the pattern **/*.cs
[CCM] Parsing 219 files in /Users/ruel/Jenkins/slave/workspace/ngui_samples
[CCM] Parsing of file /Users/ruel/Jenkins/slave/workspace/ngui_samples/Assets/Editor/MyEditorScript.cs failed due to an exception:

org.xml.sax.SAXParseException; lineNumber: 2; columnNumber: 1; Content is not allowed in prolog.
 at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
 at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source)
 at org.apache.commons.digester.Digester.parse(Digester.java:1916)
 at hudson.plugins.ccm.parser.CcmParser.parse(CcmParser.java:90)
 at hudson.plugins.analysis.core.AbstractAnnotationParser.parse(AbstractAnnotationParser.java:53)
 at hudson.plugins.analysis.core.FilesParser.parseFile(FilesParser.java:323)
 at hudson.plugins.analysis.core.FilesParser.parseFiles(FilesParser.java:281)
 at hudson.plugins.analysis.core.FilesParser.parserCollectionOfFiles(FilesParser.java:232)
 at hudson.plugins.analysis.core.FilesParser.invoke(FilesParser.java:201)
 at hudson.plugins.analysis.core.FilesParser.invoke(FilesParser.java:31)
 at hudson.FilePath$FileCallableWrapper.call(FilePath.java:2677)
 at hudson.remoting.UserRequest.perform(UserRequest.java:121)
 at hudson.remoting.UserRequest.perform(UserRequest.java:49)
 at hudson.remoting.Request$2.run(Request.java:324)
 at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:68)
 at java.util.concurrent.FutureTask.run(FutureTask.java:266)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
 at hudson.remoting.Engine$1$1.run(Engine.java:63)
 at java.lang.Thread.run(Thread.java:745)

-----

2014년 12월 11일 목요일

Apache 2.4 + Gitweb

개요
http/https protocol넘어로 git환경을 제공하기 위해 gitweb을 설정함.
기본적으로 push에 관해서는 http에서는 실시하지 않는 것으로, 어디까지나 외부의 공개나 부분적으로 key를 배포하고 싶지 않은 환경하에서 ip제약적으로 clone시키고 싶을 경우를 위한 http도입.

전제
apache 2.4

참고자료

https://wiki.archlinux.org/index.php/gitweb#Apache_2.4
http://git-scm.com/book/ko/v1/Git-%EC%84%9C%EB%B2%84-GitWeb

각 패스에 대한 설명

/gitweb : symbolic link collections to make public repository
/git : all the git repositories

수순

yum install gitweb

설정 파일 구성


/etc/gitweb.conf 
our $projectroot="/gitweb";

/etc/httpd/conf.d/git.conf 
<VirtualHost *:80>

ServerName git.???????.com

SetENV GIT_PROJECT_ROOT /gitweb
SetENV GIT_HTTP_EXPORT_ALL
SetEnv REMOTE_USER=$REDIRECT_REMOTE_USER

ScriptAliasMatch \
        "(?x)^/git/(.*/(HEAD | \
                        info/refs | \
                        objects/(info/[^/]+ | \
                                 [0-9a-f]{2}/[0-9a-f]{38} | \
                                 pack/pack-[0-9a-f]{40}\.(pack|idx)) | \
                        git-(upload|receive)-pack))$" \
        /usr/libexec/git-core/git-http-backend/$1

Alias /git /var/www/git

#grant pull
<LocationMatch "^/git/.*$">
  AllowOverride All
  Require all granted
</LocationMatch>

#block push
<LocationMatch "^/.*/git-receive-pack$">
  AllowOverride All
  Require all denied
</LocationMatch>

<Directory /var/www/git>

  Options +ExecCGI +FollowSymLinks +SymLinksIfOwnerMatch

  AllowOverride All
  Require all granted

  AddHandler cgi-script .cgi
  DirectoryIndex gitweb.cgi

</Directory>



</VirtualHost>




----