2011년 12월 20일 화요일

JSONObject 전용 CustomView Adapter를 만들었다.


질보다 양이 우선하는 허접안드로이드 프로그램 의뢰를 처리해주고 있다.
어찌나 기능들이 다양한지 덕분에 스터디는 제대로 하네 ㅎㅎ
하다보니 리스트들이 엄청 많은데
SimpleAdapter 로 대충 하려고 했더니 요리조리 잘도 빠져들 나가서
커스텀 시리즈를 하나씩 종류별로 다 만들고 있다.
SimpleAdapter를 선호하는 편인데 Hash Map 에만 넣어주면 이미지건 뭐건 알아서 잘 해주니까 좋다.

myAdapter = new SimpleAdapter(this, groupData, R.layout.couponlist,

new String[] { "title", "desc" }, new int[] {

R.id.textViewListTitle, R.id.textViewListDesc });

mListView.setAdapter(myAdapter);


대략 이런식.

1.context 를 주고,

2.Map의 ArrayList 형을 받고,

3.리스트를 표현할 Layout을 정하고

4.리스트에 값을 가져올 Map의 키와

5. 그 키값에 대응하는 List안의 View들을 대응

이것만으로 오케이

물론 이것마저도 사실 지저분하고 더러운 코드로 보이지만 자바에선 이정도면 짧은 코드인편

그런데 이걸 Custom으로 구현하면 갑자기 일이 커진다.

http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List4.html

나는 짜증나서 이짓 못하겠다.

일단 서버나 array.xml 등등에서 JSON을 다루는 일이 대부분이라서

SQLite DatabaseAdapter (어라 이것도 만들었네)에서도 그냥 죄다 키발키발(key:val)해버렸다.

하나 만들고 리팩토링하면서 생각해봐야지 하고 촥 만들고 보니

Custom ListAdapter라고 해봤자. 결국 getView 만 외부에서 콜백으로 지정할 수 있으면 나머지는 매번 작성할 필요가 없다는 생각이 들었다.

package com.appsoulute.library;

import java.util.List;

import org.json.JSONObject;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;

public class CustomJSONListAdapter extends ArrayAdapter {

private onGetViewCallback getViewCustomCallBack;
public List objects;
private int listViewResourceId;
Context context;

public CustomJSONListAdapter(Context context,
int listViewResourceId, List objects) {
super(context, listViewResourceId, objects);
this.listViewResourceId = listViewResourceId;
this.objects = objects;
this.context = context;
}

/-
* Callbacks from getView
*-
public interface onGetViewCallback {
View getView(final int position, View convertView,
ViewGroup parent);
}
public void setOnGetViewCallback(onGetViewCallback onGetViewCallback) {
getViewCustomCallBack = onGetViewCallback;
}

@Override
public int getCount() {
return objects.size();
}
@Override
public JSONObject getItem(int position) {
return objects.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if(convertView==null) {
convertView=(LayoutInflater.from(context)).inflate( listViewResourceId, parent, false);
}
return getViewCustomCallBack.getView(position, convertView, parent);
}
}
이런 형태가 되겠다.

실제 사용은 해당 CallBack 만 채워주면 된다.

groupData = new ArrayList();

mListView = (ListView) findViewById(R.id.listViewSearchLocation);

myAdapter = new CustomJSONListAdapter(this, R.layout.bookmarkperformancelist, groupData);

myAdapter.setOnGetViewCallback(getViewListener);

mListView.setAdapter(myAdapter);

요정도로 OnCreate 에 선언해주고

private onGetViewCallback getViewListener = new onGetViewCallback() {

  @Override

  public View getView(int position, View convertView, ViewGroup parent) {

    try {

      ((TextView)convertView.findViewById(R.id.textViewListTitle)).setText(myAdapter.objects.get(position).getString("title"));

      ((CheckBox)convertView.findViewById(R.id.checkBox1)).setChecked(((ListView)parent).isItemChecked(position));

    } catch (JSONException e) {

      e.printStackTrace();

    }

    return convertView;

  }

};

리스너를 취향에 맞게 만들어 주시면 되겠다. setText 할때 myAdapter.objects 라고 쓴 부분이 맘에 안드는데 자바 하도 오래간만에 해서 다 까묵었다.

일단 여기까지 올리고 차차 수정해보자.

2011년 12월 7일 수요일

왜 object에 prototype을 쓰지 않나?




이유는 이렇다.
prototype 에 method 를 정의하면
자기 자신도 해당 object 에 카운트가 된다.

무슨말이냐?
object (array포함) 의 key와 value를 보기 위해 종종 아래와 같이 해본다.
var ages = {  John: 10,  Mary: 28,  Alice: 16};
일때
(function(obj){ for (idx in obj) { console.log(idx+':'+obj[idx]) }})(ages) 
이렇게 떠 보면
John:30
Mary:30
Alice:30
뭐 이렇게 나오겠지.

그럼 아예 object 에 이걸 prototype 해서 그냥 method 로 만들지 싶어

Object.prototype.printObject=function(){ for (idx in this) { console.log(idx+':'+this[idx]) }};
요따우로 해주면

ages.printObject()
> John:10
> Mary:20
> Alice:30
> printObject:function (){ for (idx in this) { console.log(idx+':'+this[idx]) }}

망한다.
다시 이야기 하지만 자기 자신도 객체의 일부가 되니까.
그래서 쓰지말라는 것이다.

hasOwnProperty를 사용해서 피해가는 방법도 있겠지만 다른 문제가 있다.
자세한 설명은 생략한다. 왜 그런지 알고 싶으면 본문 봐라. (난 알고싶지 않아서 안봤다. 안쓸거니까 우왕ㅋ)

결론은 이거다.
defineProperty 를 사용하자.
ECMAScript 5 스펙이다. 하지만 망할 IE7 에선 안되겠지
http://ejohn.org/blog/ecmascript-5-objects-and-properties/ (꼼꼼히 읽어볼 필요가 있다)
사용법이 좀 뜨억함.

Object.defineProperty(Object.prototype, "printObject", {
  value: function() {
    for (idx in this) { console.log(idx+':'+this[idx]) }
  }
});

이런식으로 쓰면 된다.
console.log 를 찍은 삽질을 해도 이해바란다.

---------- 보너스 ---------
get 으로 합을 구해보자.
array 형인 경우 length 라는 property 가 있어서 항상 갯수를 반환한다.
length() 가 아니고 length 로 말이다.

[10,20,30].length() <- 이게 아니다.
[10,20,30].length <- 요거

그렇다. getter를 정의한 것이다. 그럼 sum이 getter 로 선언하면 되는데.

일단 sum은 어떻게 구하는가?
reduce를 사용해본다. noSQL! 하고 외친다면 당신이 생각하는 그것이 맞다고 말해주고 싶다.

var ages = [10,20,30];
ages.reduce(function(sum,val) { return sum+val; });
> 60

Object.defineProperty(Object.prototype, "sum", {
  get: function() {
    return this.reduce(function(sum,val) { return sum+val; });
  }
});

ages.sum
> 60

좋지 아니한가?

2011년 11월 29일 화요일

node.js 로 만드는 좀 더 본격적인 TCP Server


서로 에코만 하는게 부족하다면 유닉스의 wall 채팅을 구현해보자.
사실 이것만 되면
누가 접속했는지도 알고
누가 누구한테 무얼 보낼 수 있는지도 아는 셈이므로
채팅방을 하든지
온라인대전 격투게임 서버를 만들던지
바카라(어이쿠 위험한 단어) 서버를 만들던지
엠엠오알피지 서버를 만들던지
알아서 주무르면 된다.

자 그럼 이 대단한 서버의 코드를 보자

var clients = [];
require('net').createServer(function(socket) {

  socket.on("connect", function() {
    clients.push(socket);
  });

  socket.on("close", function() {
    clients.splice(clients.indexOf(socket), 1);
  });

  socket.on("data", function(data) {
    var sender = this;
    clients.forEach(function(client) {
      if (client !== sender) client.write(data);
    });
  });
}).listen(1337,"0.0.0.0");

네트워크 프로그래밍 좀 해본 사람이라면 다리에 힘이 턱 풀릴 정도로 허무한 코드라고 할 수 있겠다.
코드를 위에서부터 말로 쭉 설명하자면

1. 빈 접속자 배열을 만든다.
2. connect 할때마다 접속자 배열에 해당 소켓을 추가한다.
3. close 하면 socket 의 위치를 찾아(indexOf) 해서 떼어낸다(splice)
4. data 즉 접속자로 부터 입력을 받으면 자기 자신이 아닌 다른 모든 소켓에게 쓰기를 한다.
5. 이 모든 것을 1337번 포트에 연다.

정말 간단하다.

2011년 11월 25일 금요일

node.js server pattern


에코 서버 3줄이면 된다는 걸 증명하기 위해선

require('net').createServer(function(socket) {
  socket.pipe(socket);
}).listen(1337,"127.0.0.1");

요정도면 충분.
접속/서버메시지/접속종료 구현은

require('net').createServer(function(socket) {
  socket.on("connect", function() {
    socket.write("hello\n");
  });
  socket.on("close", function() {
    console.log("closed");
  });
  socket.on("data", function(data) {
    socket.write("usay:"+data);
  });
}).listen(1337,"127.0.0.1");

이정도면 충분하겠다.

2011년 10월 30일 일요일

DotCloud 에서 Redmine 수정 후 Push 하였을때 발생하는 오류에 대한 대처법


http://www.redmine.org/boards/2/topics/11085 구글링해보았다.

a key is required to write a cookie containg the session data

오류인데 당황하지 말고

$ dotcloud ssh redmine.redmine (redmine.redmine 은 나만 이렇게 쓰고 있으니 알아서 변경하시길)
로 들어가서

$ cd current
$ rake config/initializers/session_store.rb

해주면 끝.

2011년 9월 28일 수요일

redmine project 식별자(identifier) 변경


보통은 식별자를 Admin모드에서 바꿀 수 없다.
하지만 누구나 실수는 하는 법.
mysql 을 뒤져보니 간단하게 바꿀 수 있더라.

$ dotcloud ssh redmine.db
$ mysql -uroot -p<암호>
mysql> use redmine;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+-------------------------------------+
| Tables_in_redmine                   |
+-------------------------------------+
| attachments                         |
| auth_sources                        |
| boards                              |
| changes                             |
| changesets                          |
| changesets_issues                   |
| comments                            |
| custom_fields                       |
| custom_fields_projects              |
| custom_fields_trackers              |
| custom_values                       |
| documents                           |
| enabled_modules                     |
| enumerations                        |
| groups_users                        |
| issue_categories                    |
| issue_relations                     |
| issue_statuses                      |
| issues                              |
| journal_details                     |
| journals                            |
| member_roles                        |
| members                             |
| messages                            |
| news                                |
| open_id_authentication_associations |
| open_id_authentication_nonces       |
| projects                            |
| projects_trackers                   |
| queries                             |
| repositories                        |
| roles                               |
| schema_migrations                   |
| settings                            |
| time_entries                        |
| tokens                              |
| trackers                            |
| user_preferences                    |
| users                               |
| versions                            |
| watchers                            |
| wiki_content_versions               |
| wiki_contents                       |
| wiki_pages                          |
| wiki_redirects                      |
| wikis                               |
| workflows                           |
+-------------------------------------+
47 rows in set (0.00 sec)

슬쩍보니까 projects 라는 테이블이 있다.

mysql> desc projects;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| name        | varchar(255) | NO   |     |         |                |
| description | text         | YES  |     | NULL    |                |
| homepage    | varchar(255) | YES  |     |         |                |
| is_public   | tinyint(1)   | NO   |     | 1       |                |
| parent_id   | int(11)      | YES  |     | NULL    |                |
| created_on  | datetime     | YES  |     | NULL    |                |
| updated_on  | datetime     | YES  |     | NULL    |                |
| identifier  | varchar(255) | YES  |     | NULL    |                |
| status      | int(11)      | NO   |     | 1       |                |
| lft         | int(11)      | YES  | MUL | NULL    |                |
| rgt         | int(11)      | YES  | MUL | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

음 이놈맞네. identifier 를 바꿔주자.

mysql> update projects set identifier='<변경할ID>' where identifier ='<이전ID>';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

영향도가 있을까봐 걱정했는데 현재 버전(redmine 1.2.1)에선 별 영향없단다.
그래도 처음에 긴장하고 identifier 는 신중하게 잘 짓자.

2011년 9월 22일 목요일

dotcloud 에 redmine 등록 후 Domain 도 연결해보자

redmine 을 dotcloud.com 에 올렸습니다. 무료니까요.
이후 할것들이 몇가지가 있다.

하나는 Domain 연결이고
나머지 하나는 mail notification 인데 차근차근 보자

잘 등록했다면 list 를 보았을때

$ dotcloud list
redmine:
  - db      (type: mysql; instances: 1)
  - redmine (type: ruby; instances: 1)

라고 나올 것이다.

http://docs.dotcloud.com/guides/domains/ 가이드대로

$ dotcloud alias add ramen.www www.example.com 형식으로 써보자

어플리케이션명이 redmine 이고 모듈도 redmine 이니까

$ dotcloud alias add redmine.redmine www.<니가 쓰는 도메인> 형식이 되겠다.

아래와 같이 나오면 정상.

Ok. Now please add the following DNS record:
www.<니가 쓰는 도메인>. IN CNAME gateway.dotcloud.com.

내 경우엔 서브도메인으로 쓰고 싶어서

$ dotcloud alias add redmine.redmine redmine.<내 도메인> 형식으로 썼다.

브라우저에서  http://redmine.<내 도메인> 으로 접근했을때 CNAME Alias 를 줘서 호스팅 서비스쪽에서 gateway.dotcloud.com 을 바라보게 하고 gateway.dotcloud.com 에서 해당 어플리케이션으로 연결되게 하는 구조다.
netfirms.com 이란 곳에서 도메인 호스팅을 받고 있는데 관련 설정을 보면 이런 식이다.


CNAME (Alias)

A CNAME record (also known as an alias record) is a subdomain that points to a domain name (sales.yourdomain.com >> yourdomain.com)

HostPoints To:Action
redmine
gateway.dotcloud.com
       

CNAME 설정을 이렇게 했다. 쓰고 있는 도메인 호스팅 서비스마다 차이가 있겠지만 알아서 잘 하면 된다.

2011년 9월 20일 화요일

redmine 을 dotcloud.com 에 올렸습니다. 무료니까요.


PaaS(Platform as a Service)의 시대입니다.
지금 배타고 물들어올때 잽싸게 올라타자 하는 마음으로
http://www.dotcloud.com 에 계정을 하나 만들어놓았습니다.
소프트웨어(ex.ASP) 뿐만이 아니라 플랫폼마저도 빌려쓰는 세상이라니 좋은 세상입니다.

자 바로 본론입니다.
먼저 http://dotcloud.com 에 가입하시고 http://docs.dotcloud.com/firststeps/install 에서 cli 도 설치하고 기본 준비를 하세요.
이런건 패스합니다. 읽어보면 멸치라도 할 수 있어요. 봐도 모르시겠다고요. 그럼 Backspace : )
현재는 Free Plan 뿐입니다만 너무도 훌륭한 서비스입니다.

http://yanitime4u.fluxflex.com/56 를 보고 해보니까 쉽더군요.
근데 보실 필요는 없어요. 왜냐면 이 아래에 쓸 내용도 같은 내용이니까요 :)

https://www.dotcloud.com/settings/ 에서 유저명을 바꿔주세요.
API Key, Username,First name, Last name, E-mail address 순으로 있죠?
두번째 Username을 바꿔줍니다. 최종 우리가 사용할 URL은 http://redmine-<Username>.dotcloud.com 이 될테니까요.

일단 지금 홈디렉토리라고 가정하고 복사하고 붙여봅니다.
현재 최신버전인 redmine 1.2.1 을 사용합니다.

$ mkdir redmine
$ cd redmine
$ wget http://rubyforge.org/frs/download.php/75097/redmine-1.2.1.tar.gz
$ tar zxvf redmine-1.2.1.tar.gz

일단 압축을 풀으셨으면 디플로이할때 걸리적거리니까 redmine-1.2.1.tar.gz 일단 지워주고 dotcloud.yml 파일을 만듭니다.
우리는 redmine 을 쓰고 mysql을 db로 쓸테니 다음과 같은 파일을 만듭니다.

$ vi dotcloud.yml
---- 아래 복사하세요 -----
redmine:
  type: ruby
  approot: redmine-1.2.1
  config:
    ruby-version: ree

db:
  type: mysql
---- 여기까지 ----

이제 Application 을 생성해봅니다.

$ dotcloud create redmine
$ dotcloud push redmine

오케이 redmine 이라고 꼭 할 필욘없습니다. 이게 Application 명이니까요 다른 이름을 써도 무방. 설명은 이걸 redmine으로 합니다.

$ dotcloud list
로 확인하거나
https://www.dotcloud.com/dashboard/ 에서 확인해볼 수 있습니다.

여기까지 했다면 준비는 끝났습니다.
이제 남은 건 세세한 설정 뿐이죠.

$ dotcloud info redmine
$ dotcloud info redmine.redmine
$ dotcloud info redmine.db

설정을 보고 어딘가에 복사해놓습니다. 다 쓸데가 있어요
대충 이런식으로 나올겁니다.

$ dotcloud info redmine
db:
    config:
        mysql_password: <mysql 암호>
    instances: 1
    type: mysql
redmine:
    config:
        rack-env: production
        ruby-version: ree
    instances: 1
    type: ruby
    url: http://redmine-<Username>.dotcloud.com/
$ dotcloud info redmine.redmine
aliases:
- redmine-<Username>.dotcloud.com
build_revision: rsync-1316499221.6
cluster: wolverine
config:
    rack-env: production
    ruby-version: ree
created_at: 1316499226.2593739
image_version: aec61048b63c (latest)
ports:
-   name: ssh
    url: ssh://dotcloud@redmine-<Username>.dotcloud.com:13631
-   name: http
    url: http://redmine-<Username>.dotcloud.com/
state: running
type: ruby
$ dotcloud info redmine.db
cluster: wolverine
config:
    mysql_password: <mysql 암호>
created_at: 1316499226.5895569
image_version: 57db01da2848 (latest)
ports:
-   name: ssh
    url: ssh://mysql@redmine-<Username>.dotcloud.com:13632
-   name: mysql
    url: mysql://root:<mysql 암호>@redmine-<Username>.dotcloud.com:13633
state: running
type: mysql

대인배입니다. ssh 까지 제공하다니요. 그저 고맙습니다.
mysql 설정 먼저 합니다.
redmine DB 생성하고 redmine 유저를 만듭니다. 암호는 <mysql 암호> 를 사용합니다.

$ dotcloud ssh redmine.db
$ mysql -u root -p<mysql 암호>
create database redmine character set utf8;
grant all on redmine.* to redmine@"%" identified by "<mysql 암호>";
FLUSH PRIVILEGES;
quit
$ exit

바로 어플리케이션 설정도 들어갑니다.
필요한 ruby gem 들을 설치합니다.

$ dotcloud ssh redmine.redmine
$ gem install mysql
$ gem install rack -v=1.1.1
$ gem install i18n -v=0.4.2
$ exit

거의 다왔습니다.
이제 redmine 압축 푼 경로에 config.database 를 만듭시다.
아까 복사한 것들 잘 붙여넣습니다.

$ cd redmine-1.2.1/
$ vi config/database.yml

production:
  adapter: mysql
  database: redmine
  host: redmine-<Username>.dotcloud.com
  port: <mysql Port>
  username: redmine
  password: <mysql 암호>
  encoding: utf8

이제 내 서버 셋팅만 하면 끝입니다.

$ dotcloud push redmine

이제까지 한 작업을 push 합니다.
서버에 올라가고 나면

$ dotcloud ssh redmine.redmine

ssh 로 다시 어플리케이션 접근해서

$ cd current

현재 버전으로 들어가서
rake 합니다.

$ rake config/initializers/session_store.rb
$ rake db:migrate RAILS_ENV="production"

마지막으로 디폴트데이터를 적재하는데 언어를 묻습니다. 한글을 쓸거니까 ko 라고 입력해줍니다.

$ rake redmine:load_default_data
(in /home/dotcloud/rsync-1316495751.45/redmine-1.2.1)

Select language: bg, bs, ca, cs, da, de, el, en, en-GB, es, eu, fa, fi, fr, gl, he, hr, hu, id, it, ja, ko, lt, lv, mk, mn, nl, no, pl, pt, pt-BR, ro, ru, sk, sl, sr, sr-YU, sv, th, tr, uk, vi, zh, zh-TW [en] ko
====================================
Default configuration data loaded.

자 끝났습니다!

http://redmine-<Username>.dotcloud.com 해서 확인해봅니다.

초기 어드민 계정은 admin/admin 이므로 최초 로그인 후 암호 변경합니다.