Thursday, December 31, 2015

Android soft key menu 보유 여부 확인

    public boolean hasSoftKeys() {
        boolean hasSoftwareKeys = true;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            final Display d = getWindowManager().getDefaultDisplay();

            final DisplayMetrics realDisplayMetrics = new DisplayMetrics();
            d.getRealMetrics(realDisplayMetrics);

            final int realHeight = realDisplayMetrics.heightPixels;
            final int realWidth = realDisplayMetrics.widthPixels;

            final DisplayMetrics displayMetrics = new DisplayMetrics();
            d.getMetrics(displayMetrics);

            final int displayHeight = displayMetrics.heightPixels;
            final int displayWidth = displayMetrics.widthPixels;

            hasSoftwareKeys = (realWidth - displayWidth) > 0 || (realHeight - displayHeight) > 0;
        } else {
            boolean hasMenuKey = ViewConfiguration.get(this).hasPermanentMenuKey();
            boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
            hasSoftwareKeys = !hasMenuKey && !hasBackKey;
        }
        return hasSoftwareKeys;
    }

Sunday, December 27, 2015

Android Volley와 Gson 예제

AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>

build.gradle
    compile 'com.google.code.gson:gson:2.5'
    compile 'com.github.bumptech.glide:volley-integration:1.3.1'

mt_rand.php
<?php
$ret = array('value' => mt_rand()+1000000000);
echo json_encode($ret);
?>

    public class Uid {
        private String value;
        public String getValue() {
            return value;
        }
    }
    private RequestQueue mRequestQueue;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String url ="http://192.168.29.188/mt_rand.php";

        JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        Log.d("MainActivity", response.toString());

                        Uid uid = new Gson().fromJson(response.toString(), Uid.class);

                    }
                },
                new Response.ErrorListener() {

                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d("MainActivity", error.toString());
                    }
                }
        );

        mRequestQueue = Volley.newRequestQueue(this);
        mRequestQueue.add(request);
    }

Tuesday, December 22, 2015

MySQL C API에서 /var/lib/mysql/mysql.sock으로 접속이 안될때

MySQL Server를 설치한다.
yum -y install mysql mysql-server (or apt-get install mysql mysql-server)

Enable the MySQL service:
/sbin/chkconfig mysqld on

Start the MySQL server:
/sbin/service mysqld start

vi /etc/my.cnf
아래와 같이 클라이언트를 추가한다.
이미 있다면 수정하여 준다.

 [client]
 #password       = your_password
 host            = 127.0.0.1
 port            = 3306
 socket          = /var/run/mysql/mysql.sock

socket을 /var/lib/mysql/mysql.sock로 수정해주고
host를 127.0.0.1 또는 localhost로 수정해준다.
host는 여기에 입력한 대로 mysql_connect에서 호출해야 한다.(localhost로 입력했으면 127.0.0.1로는 접속안됨)

Monday, December 21, 2015

iOS 디바이스 UUID 획득하기

//처음에 UUID를 KeyChain에서 불러오는데 nil이라면 UUID를 생성해서 KeyChain에 저장한다.
//저장 후에 다시 함수를 호출 하면 저장된 값을 리턴한다.
NSString* getUUID()
{
    // initialize keychaing item for saving UUID.
    KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"UUID" accessGroup:nil];
   
    NSString *uuid = [wrapper objectForKey:(__bridge id)(kSecAttrAccount)];
   
    if( uuid == nil || uuid.length == 0)
    {
        // if there is not UUID in keychain, make UUID and save it.
        CFUUIDRef uuidRef = CFUUIDCreate(NULL);
        CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
        CFRelease(uuidRef);
        uuid = [NSString stringWithString:(__bridge NSString *) uuidStringRef];
        CFRelease(uuidStringRef);
       
        // save UUID in keychain
        [wrapper setObject:uuid forKey:(__bridge id)(kSecAttrAccount)];
    }
   
    return uuid;
}

Android 디바이스 UUID 획득하기

public static String getDeviceUUID(final Context context) {
        // preference에서 저장된 것이 있는지 확인해봄
        final SharedPreferences prefs = context.getSharedPreferences("UUIDTEST", MODE_PRIVATE);
        final String id = prefs.getString("UUID", null);

        UUID uuid = null;
        if (id != null) {
            uuid = UUID.fromString(id);
        } else {
            final String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
            try {
                // 예전에 아래와 같은 고정값이 리턴되는 문제가 있었음
                if (!"9774d56d682e549c".equals(androidId)) {
                    uuid = UUID.nameUUIDFromBytes(androidId.getBytes("utf8"));
                } else {
                    final String deviceId = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId();
                    uuid = deviceId != null ? UUID.nameUUIDFromBytes(deviceId.getBytes("utf8")) : UUID.randomUUID();
                }
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }

            final SharedPreferences.Editor editor = prefs.edit();
            editor.putString("UUID", uuid.toString());
            editor.commit();
        }

        return uuid.toString();
    }

Friday, December 18, 2015

Android 플레이스토어 앱설치시 '콘텐츠 제공자의 권한이 중복됩니다' 및-505 에러 유저 대응 방법

플레이스토어 앱설치시 '콘텐츠 제공자의 권한이 중복됩니다' 및 -505 에러가 난 유저에게는 다음과 같이 안내하면 된다.

1. 설정 > 앱(LG) 또는 애플리케이션 관리자(삼성) > 전체로 이동
2. Google Play 스토어 > 캐쉬 삭제 > 데이터 삭제
3. Google Play 서비스 > 캐쉬 삭제 > 데이터 삭제
4. 설정 > 계정 > Google > 기존에 사용하는 구글 계정 삭제
5. 디바이스 재부팅
6. 설정 > 계정 > Google > 다시 기존에 사용하는 구글 계정 추가
7. Play 스토어에서 앱 다시 다운로드

Thursday, December 17, 2015

Tuesday, December 15, 2015

Mac 기본 웹서버 apache root 폴더

/Library/WebServer/Documents
/etc/apache2/httpd.conf
sudo /usr/sbin/apachectl restart

Monday, December 14, 2015

iOS 리뷰 유도를 위해서 앱스토어로 이동하기

#define URL_APPSTORE @"itms-apps://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=xxxxxxxx&onlyLatestVersion=true&pageNumber=0&sortOrdering=1&type=Purple+Software"

            // 앱스토어로 이동
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:URL_APPSTORE]];

Android 리뷰 유도를 위해서 플레이스토어로 이동하기

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + Config.getPackageName()));

Sunday, December 13, 2015

Ubuntu The mcrypt extension is missing. PHP

php5-fpm과 nginx환경에서 phpmyadmin을 실행과정에서 다음과 같은 에러가 발생했다.
The mcrypt extension is missing. Please check your PHP configuration.

해결방법은 다음과 같다.
sudo apt-get install php5-mcrypt
sudo php5enmod mcrypt
sudo service php5-fpm restart
sudo service nginx restart

CentOS nginx phpMyAdmin 설치


# 확장 패키지(EPEL)가 있는지 확인
rpm -qa epel-release

# 확장 패키지 설치
yum install epel-release

# phpMyAdmin 설치
yum install phpMyAdmin

# 설치폴더
/usr/share/phpMyAdmin

# 폴더 심볼릭 링크 생성
cd /usr/share/nginx/html
ln -s /usr/share/phpMyAdmin phpMyAdmin

CentOS nginx + php에서 file not found 에러

Nginx와 php연동시 File not found 에러가 나는 경우가 있다.
이때는 nginx설정에서 fastcgi_param에서 다음과 같이 html폴더를 절대경로로 명시해 주면된다.

vi /etc/nginx/conf.d/default.conf

    location ~ \.php$ {
        #root           /usr/share/nginx/html;
        #fastcgi_pass   127.0.0.1:9000;
        fastcgi_pass    unix:/var/run/php-fpm/www.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /usr/share/nginx/html$fastcgi_script_name;
        include        fastcgi_params;
    }

그리고 nginx restart하자.
service nginx restart

CentOS nginx iptables 설정


CentOS에서 nginx를 설치하고 나면 80번 포트가 막혀있다.
그래서 다음과 같이 iptables를 편집해서 80번 포트를 열어야 한다.

sudo vi /etc/sysconfig/iptables

# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

service iptables restart


Visual C++ 11자리 정수형 유니크 ID 만들기

정수형으로 유저ID로 사용할 유니크ID를 만들어 보았다.
32비트 UINT_MAX는 0xFFFFFFFF로 4,294,967,295가되며 약 42억9천만이 된다.
그리고 rand()를 사용하면 리턴값은 int지만 사실상 short인 SHRT_MAT 32767이 최대값이다.
따라서 다음과 같이 만들면 long형인 11자리 값을 만들수가 있다.

srand(time(NULL));
unsigned hirand = rand() << 16;
unsigned lorand = rand();

//앞에 1또는 다른 값을 붙여 써도 됨
printf("1%010u\n", hirand | lorand);

Saturday, December 12, 2015

Android 네이버 앱스토어에서 결제 실패로 반려되는 경우

네이버 앱스토어는 자사의 결제를 이용하지 않은 구글 플레이스토어 결재만을 탑재한 앱의 APK를 올리는 것을 허용하고 있다.

이때 유의할 것이, 플레이스토어의 앱 업데이트가 먼저 배포된 다음에 네이버 앱스토어의 심사를 진행해야 한다는 것이다. 왜냐면 아래에 언급한 문제가 있기 때문이다.

Android 앱에서 플레이스토어 결제가 되지 않는 경우

그리고 일반적으로 구글의 심사 속도보다 네이버의 심사속도가 느리다. 그러므로 항상 구글의 심사가 완벽히 끝난 다음에 네이버 앱스토어의 심사를 진행하자.

Android 앱에서 플레이스토어 결제가 되지 않는 경우

플레이스토어에 올라간 앱의 경우, 스토어의 최신 버전보다 높은 version number를 가진 앱의 경우에는 결제가 실패한다.

업데이트 대기중인 앱의 결제 테스트가 실패하는게 그 원인이다. 따라서 앱의 결제 테스트를 할때는 스토어의 최신버전으로 맞춰놓고 결제 테스트를 하고, 실제 테스트는 플레이스토어 배포를 한후에 테스트를 하자.

Ubuntu ssh 설치

우분투 desktop은 외부에서 접속하는 ssh가 기본적으로 설치되어 있지 않다.
그래서 다음과 같이 설치하면 22번으로 ssh가 설치되고 자동으로 서비스가 시작된다.

sudo apt-get install openssh-server

Thursday, December 10, 2015

Mac 패러랠즈 10에서 우분투 15.10 설치기

오랫만에 리눅스가 필요해서 패러랠즈 10에다가 우분투 15.10을 설치였더니 설치가 잘되었다.

하지만 parallels tools가 설치가 되지 않아서 14.04로 다운그레이드 했다. 가상 시디를 넣으면 한자가 깨지는 것 같은 파일만 보이게 되기 때문이다.

패러랠즈를 11로 업데이트를 해야되는 시점이 온것 같다.

Sunday, December 6, 2015

Android 플레이스토어 앱설치시 '콘텐츠 제공자의 권한이 중복됩니다'

구글 플레이스토어에서 앱을 설치시 '콘텐츠 제공자의 권한이 중복됩니다' 에러 메세지가 나타날 경우가 있다.

이 문제는 앱의 Facebook SDK를 버전업 하였을때 provider의 이름 설정과 export 설정이 잘못되어서 다른 앱과 충돌할때 나타난다.

아래의 exported를 반드시 false로 해야지 앱자체의 provider로만 동작한다. 왜냐면 exported를 true로 하면 provider name이 다른 Facebook SDK를 사용한 앱의 provider와 같기 때문이다.

    <provider android:authorities="com.facebook.app.FacebookContentProvider0123456789..."
        android:name="com.facebook.FacebookContentProvider0123456789..."
        android:exported="false" />


Android 인증 SMS 자동인증

<uses-permission android:name="android.permission.RECEIVE_SMS" />

// 폰번호를 인식하여 문자를 보내달라고 요청
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        final TelephonyManager tm = (TelephonyManager) getActivity()
                .getSystemService(Context.TELEPHONY_SERVICE);
        if(tm == null)
            return;

        // 내 폰번호를 받음
        String myNumber = tm.getLine1Number();
        if(StringUtils.isEmpty(StringUtils.trim(myNumber)))
            return;

        // KT일경우 폰번호 변환
        if(myNumber.startsWith("+82")) {
           myNumber = myNumber.replace("+82", "0");
        }
        _phone.setText(myNumber);

        _useSmsReceiver = true;
        sendVerification();
    }
});

private void sendVerification() {
    // 인증번호란에 바로 입력할수 있도록 포커스를 줌
    _code.requestFocus();


    // SMS 인증문자를 바로 수신해서 결과를 입력해줌
    if(_useSmsReceiver)
        registerReceiver();
}

private void registerReceiver() {
    unregisterReceiver();

    final IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(SMSReceiver.ACTION);

    _receiver = new SMSReceiver();
    registerReceiver(_receiver, intentFilter);
}
private void unregisterReceiver() {
    if(_receiver != null) {
        unregisterReceiver(_receiver);
        _receiver = null;
    }
}

private class SMSReceiver extends BroadcastReceiver {
    private static final String TAG="SMSReceiver";
    private static final String ACTION = "android.provider.Telephony.SMS_RECEIVED";

    @Override
    public void onReceive(Context context, Intent intent) {
        // 문자만 수신받음
        if(intent.getAction().equals(ACTION)) {
            final Bundle bundle = intent.getExtras();

            if(bundle == null)
                return;

            final Object[] pdusObj = (Object[])bundle.get("pdus");
            if(pdusObj == null)
                return;

            final SmsMessage[] smsMessages = new SmsMessage[pdusObj.length];

            for(int i=0; i<smsMessages.length; i++) {
                smsMessages[i] = SmsMessage.createFromPdu((byte[])pdusObj[i]);

                final String address = smsMessages[i].getOriginatingAddress();
                final String message = smsMessages[i].getDisplayMessageBody();

                if(address.equals(CS_NUMBER)) {
                    String adjustMessage = message.replace("[Web발신]", "");
                    int begin = adjustMessage.indexOf('[');
                    int end = adjustMessage.indexOf(']');

                    // 인증번호를 찾았다
                    final String codeMessage = adjustMessage.substring(begin+1, end);

                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            _code.setText(codeMessage);
                            confirmVerification();
                        }
                    });

                    unregisterReceiver();
                }
            }
        }
    }
}

Android 채팅 EditText 자동 크기 확장 및 멀티라인

<EditText
android:id="@+id/message_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="3dp"
android:hint="@string/chat_room_message_hint"
android:paddingLeft="5dp"
android:textSize="13sp"
android:inputType="textMultiLine"
android:scrollbars="vertical"
android:singleLine="false"
android:minLines="2"
android:maxLines="5"
/>

Android Toast 폰트 변경 및 single line 만들기

// 토스트 폰트 통일
final LinearLayout toastLayout = (LinearLayout) toast.getView();
final TextView toastTV = (TextView) toastLayout.getChildAt(0);

toastTV.setTypeface(Typeface.createFromAsset(getAssetManager(), "NanumGothic.otf"));

if(singleLine) {
    toastTV.setSingleLine(true);
    toastTV.setEllipsize(TextUtils.TruncateAt.END);
}

toast.show();

Visual C++ 파일 열기 다이얼로그 API

char szFile[256];
  //API file dialog
  OPENFILENAMEA ofn;
  ZeroMemory(&ofn, sizeof(ofn));
  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = NULL;
  ofn.lpstrFile = szFile;
  ofn.lpstrFile[0] = '\0';
  ofn.nMaxFile = sizeof(szFile);
  ofn.lpstrFilter = "All\0*.*\0Map\0*.map\0";
  ofn.nFilterIndex = 1;
  ofn.lpstrFileTitle = NULL;
  ofn.nMaxFileTitle = 0;
  ofn.lpstrInitialDir = NULL;
  ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
  BOOL ret = GetOpenFileNameA(&ofn);
  if (ret) {
   // load map and enter training mode
  }

Wednesday, December 2, 2015

Linux epoll example

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>

#define MAXEVENTS 64

static int
make_socket_non_blocking(int sfd) {
    int flags, s;
    flags = fcntl(sfd, F_GETFL, 0);
    if (flags == -1) {
        perror("fcntl");
        return -1;
    }
    flags |= O_NONBLOCK;
    s = fcntl(sfd, F_SETFL, flags);
    if (s == -1) {
        perror("fcntl");
        return -1;
    }
    return 0;
}

static int
create_and_bind(char *port) {
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int s, sfd;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */
    hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
    hints.ai_flags = AI_PASSIVE;     /* All interfaces */
    s = getaddrinfo(NULL, port, &hints, &result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        return -1;
    }
    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
        if (sfd == -1)
            continue;
        s = bind(sfd, rp->ai_addr, rp->ai_addrlen);
        if (s == 0) {
            /* We managed to bind successfully! */
            break;
        }
        close(sfd);
    }
    if (rp == NULL) {
        fprintf(stderr, "Could not bind\n");
        return -1;
    }
    freeaddrinfo(result);
    return sfd;
}

int
main(int argc, char *argv[]) {
    int sfd, s;
    int efd;
    struct epoll_event event;
    struct epoll_event *events;
    if (argc != 2) {
        fprintf(stderr, "Usage: %s [port]\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    sfd = create_and_bind(argv[1]);
    if (sfd == -1)
        abort();
    s = make_socket_non_blocking(sfd);
    if (s == -1)
        abort();
    s = listen(sfd, SOMAXCONN);
    if (s == -1) {
        perror("listen");
        abort();
    }
    efd = epoll_create1(0);
    if (efd == -1) {
        perror("epoll_create");
        abort();
    }
    event.data.fd = sfd;
    event.events = EPOLLIN | EPOLLET;
    s = epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
    if (s == -1) {
        perror("epoll_ctl");
        abort();
    }
    /* Buffer where events are returned */
    events = calloc(MAXEVENTS, sizeof event);
    /* The event loop */
    while (1) {
        int n, i;
        n = epoll_wait(efd, events, MAXEVENTS, -1);
        for (i = 0; i < n; i++) {
            if ((events[i].events & EPOLLERR) ||
                (events[i].events & EPOLLHUP) ||
                (!(events[i].events & EPOLLIN))) {
                /* An error has occured on this fd, or the socket is not
                   ready for reading (why were we notified then?) */
                fprintf(stderr, "epoll error\n");
                close(events[i].data.fd);
                continue;
            }
            else if (sfd == events[i].data.fd) {
                /* We have a notification on the listening socket, which
                   means one or more incoming connections. */
                while (1) {
                    struct sockaddr in_addr;
                    socklen_t in_len;
                    int infd;
                    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
                    in_len = sizeof in_addr;
                    infd = accept(sfd, &in_addr, &in_len);
                    if (infd == -1) {
                        if ((errno == EAGAIN) ||
                            (errno == EWOULDBLOCK)) {
                            /* We have processed all incoming
                               connections. */
                            break;
                        }
                        else {
                            perror("accept");
                            break;
                        }
                    }
                    s = getnameinfo(&in_addr, in_len,
                                    hbuf, sizeof hbuf,
                                    sbuf, sizeof sbuf,
                                    NI_NUMERICHOST | NI_NUMERICSERV);
                    if (s == 0) {
                        printf("Accepted connection on descriptor %d "
                                       "(host=%s, port=%s)\n", infd, hbuf, sbuf);
                    }
                    /* Make the incoming socket non-blocking and add it to the
                       list of fds to monitor. */
                    s = make_socket_non_blocking(infd);
                    if (s == -1)
                        abort();
                    event.data.fd = infd;
                    event.events = EPOLLIN | EPOLLET;
                    s = epoll_ctl(efd, EPOLL_CTL_ADD, infd, &event);
                    if (s == -1) {
                        perror("epoll_ctl");
                        abort();
                    }
                }
                continue;
            }
            else {
                /* We have data on the fd waiting to be read. Read and
                   display it. We must read whatever data is available
                   completely, as we are running in edge-triggered mode
                   and won't get a notification again for the same
                   data. */
                int done = 0;
                while (1) {
                    ssize_t count;
                    char buf[512];
                    count = read(events[i].data.fd, buf, sizeof buf);
                    if (count == -1) {
                        /* If errno == EAGAIN, that means we have read all
                           data. So go back to the main loop. */
                        if (errno != EAGAIN) {
                            perror("read");
                            done = 1;
                        }
                        break;
                    }
                    else if (count == 0) {
                        /* End of file. The remote has closed the
                           connection. */
                        done = 1;
                        break;
                    }
                    /* Write the buffer to standard output */
                    s = write(1, buf, count);
                    if (s == -1) {
                        perror("write");
                        abort();
                    }
                }
                if (done) {
                    printf("Closed connection on descriptor %d\n",
                           events[i].data.fd);
                    /* Closing the descriptor will make epoll remove it
                       from the set of descriptors which are monitored. */
                    close(events[i].data.fd);
                }
            }
        }
    }
    free(events);
    close(sfd);
    return EXIT_SUCCESS;
}

Tuesday, December 1, 2015

PHP CDKEY 쿠폰번호 생성 소스

<?php


//지정된 자릿수의 랜덤한 숫자를 반환합니다. 최대 10까지 가능합니다. 4 이면 1000 에서 9999 사이의 랜덤 숫자
function get_rand_number($len=4) {


    $len = abs((int)$len);
    if ($len < 1) $len = 1;
    else if ($len > 10) $len = 10;


    return rand(pow(10, $len - 1), (pow(10, $len) - 1));
}


//넘어온 세자리수를 36진수로 변환해서 반환합니다. preg_match_callback 을 통해서만 사용됩니다.
function get_simple_36($m){


    $str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $div = floor($m[0] / 36);
    $rest = $m[0] % 36;


    return $str[$div] . $str[$rest];
}


//지정된 자리수에 존재하는 소수 전체를 배열로 반환합니다. max len = 5
function get_simple_prime_number($len=5){

    $len = abs((int)$len);
    if ($len < 1) $len = 1;
    else if ($len > 5) $len = 5;

    $prime_1 = Array(1, 2, 3, 5, 7);

    if ($len == 1) return $prime_1;

    $start = pow(10, ($len - 1)) + 1;//101
    $end = pow(10, $len) - 1;//999
    $prime = $prime_1;

    unset($prime[0]);//1제거
    unset($prime[1]);//2제거
    $array = Array();
    for($i = 11; $i <= $end; $i+=2){//10보다 큰 소수에는 짝수가 없다.

        $max = floor(sqrt($i));
        foreach($prime as $j) {

            if ($j > $max) break;
            if ($i % $j == 0) continue 2;
        }

        $prime[] = $i;
        if ($i >= $start) $array[] = $i;
    }

    return $array;
}


//지정된 자릿수의 숫자로된 시리얼을 반환합니다. - 를 포함하고 싶지 않을때는 $cut 이 $len 보다 크거나 같으면 됩니다. max len = 36
 function get_serial($len=16, $cut=4, $hipen='-'){


    $len = abs((int)$len);
    if ($len < 1) $len = 16;
    else if ($len > 36) $len = 36;


    $cut = abs((int)$cut);
    if ($cut < 1) $cut = 4;
    else if ($cut > $len) $cut = $len;


    list($usec, $sec) = explode(' ', microtime());
    $base_number = (string)$sec . str_replace('0.', '', (string)$usec);
    $base_number .= (string)get_rand_number(10) . (string)get_rand_number(8);//36자리 유니크한 숫자 문자열

 


    $prime = get_simple_prime_number(5);//5자리 소수 배열
    shuffle($prime);
    $prime = $prime[0];//랜덤한 5자리 소수


    $serial = bcmul(substr($base_number, 0, $len), $prime);
    $serial_length = strlen($serial);
    $sub = $len - $serial_length;


    if ($sub > 0) $serial .= (string)get_rand_number($sub);
    else if ($sub < 0) $serial = substr($serial, 0, $len);


    return preg_replace("`(.{" . $cut . "})`", "$1" . $hipen, $serial, floor(($len-1) / $cut));
 }


//지정된 자릿수의 숫자와 영문으로된 시리얼을 반환합니다. - 를 포함하고 싶지 않을때는 $cut 이 $len 보다 크거나 같으면 됩니다. max len = 24
 function get_serial_mix($len=16, $cut=4, $hipen='-'){


    $len = abs((int)$len);
    if ($len < 1) $len = 16;
    else if ($len > 24) $len = 24;


    $cut = abs((int)$cut);
    if ($cut < 1) $cut = 4;
    else if ($cut > $len) $cut = $len;


    $len2 = (int)($len * 3 / 2);
    if ($len2 % 2 == 1) $len2 += 1;


    $serial = get_serial($len2, $len2, $hipen);


    $serial = substr(preg_replace_callback("`.{3}`", "get_simple_36", $serial), 0, $len);


    return preg_replace("`(.{" . $cut . "})`", "$1" . $hipen, $serial, floor(($len-1) / $cut));
 }


echo get_serial_mix(36, 4, '-');
?>

Sunday, November 29, 2015

Android Facebook SDK hash key

public void printHashKey() {
        try {
            PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
            for (Signature signature : info.signatures) {
                MessageDigest md = MessageDigest.getInstance("SHA");
                md.update(signature.toByteArray());
                String hashKey = new String(Base64.encode(md.digest(), 0));
                Log.i(TAG, "printHashKey() Hash Key: " + hashKey);
            }
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG, "printHashKey()", e);
        } catch (Exception e) {
            Log.e(TAG, "printHashKey()", e);
        }
    }

원격제어 VNC, RDP 차이

요즘 원격 접속 제어 등에 관심이 생겨서 그러는데,

VNC와 RDP 차이가 어떤 점일까요...?
기술적으로 어떤게 다른지 알면 더 좋을 것 같네요.

예를 들면, VNC는 다중 접속이 가능하고 RDP는 그렇지 않다던가...
VNC는 화면 캡쳐 기반이고 RDP는 가상 채널 기반이라던가...
(실제로 맞는지 모릅니다. 그냥 대충 적어놓은 거에요. ㅠㅠ_

확실히 서로 다른 솔루션이고 장단점이 있을 것 같은데 정확히는 잘 모르겠네요.
RDP는 윈도우용이고 VNC는 서버 프로그램을 설치하면 윈도우, 맥, 리눅스 등 다 사용가능하다던지...

혹시 잘 아시는 분 계세요..?


RDP는 마이크로소프트에서 만든 것으로 서버로는 윈도우만 가능하고 클라이언트는 윈도우, 맥, 리눅스 모두 가능합니다. 속도나 성능, 기능적인 측면에서 좋은 편입니다. 서버 버전이 아닌 윈도우에서는 다중 접속이 안 되는 단점이 있으나 약간의 패치로 수정이 가능하기도 합니다.
VNC는 멀티플랫폼이 된다는 게 가장 큰 장점입니다.


RDP를 제외한 나머지류는 화면 전체를 캡쳐해서 인코딩하여 보내는 방식인지라 다양한 운영체제에서 사용이 가능한 반면에, 역으로 좀 느립니다. MS RDP는 MS의존적인 반면에 단순 압축이 아니라 경우에 따라 간소화된 프로토콜을 이용하여 좀 더 빠릅니다. 다만 MS RDP가 항상 MS OS에서만 사용 가능한 것은 아니고, 어디까지나 단순 이미지를 전송하는 방식도 있기 때문에 Virtualbox같은 경우에는 모든 OS에 대하여 접속이 가능하도록 설정할 수 있습니다.
그리고 또 VNC는 원격 접속류이기 때문에 해당 대상 컴퓨터의 콘솔 세션에만 접근이 가능한 반면, RDP는 비 콘솔 세션에 접근할 수 있지요.

Thursday, November 26, 2015

Android immersive fullscreen toggle

    /**
     * Detects and toggles immersive mode (also known as "hidey bar" mode).
     */
    public void toggleHideyBar() {

        // BEGIN_INCLUDE (get_current_ui_flags)
        // The UI options currently enabled are represented by a bitfield.
        // getSystemUiVisibility() gives us that bitfield.
        int uiOptions = getActivity().getWindow().getDecorView().getSystemUiVisibility();
        int newUiOptions = uiOptions;
        // END_INCLUDE (get_current_ui_flags)
        // BEGIN_INCLUDE (toggle_ui_flags)
        boolean isImmersiveModeEnabled =
                ((uiOptions | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) == uiOptions);
        if (isImmersiveModeEnabled) {
            Log.i(TAG, "Turning immersive mode mode off. ");
        } else {
            Log.i(TAG, "Turning immersive mode mode on.");
        }

        // Navigation bar hiding:  Backwards compatible to ICS.
        if (Build.VERSION.SDK_INT >= 14) {
            newUiOptions ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
        }

        // Status bar hiding: Backwards compatible to Jellybean
        if (Build.VERSION.SDK_INT >= 16) {
            newUiOptions ^= View.SYSTEM_UI_FLAG_FULLSCREEN;
        }

        // Immersive mode: Backward compatible to KitKat.
        // Note that this flag doesn't do anything by itself, it only augments the behavior
        // of HIDE_NAVIGATION and FLAG_FULLSCREEN.  For the purposes of this sample
        // all three flags are being toggled together.
        // Note that there are two immersive mode UI flags, one of which is referred to as "sticky".
        // Sticky immersive mode differs in that it makes the navigation and status bars
        // semi-transparent, and the UI flag does not get cleared when the user interacts with
        // the screen.
        if (Build.VERSION.SDK_INT >= 18) {
            newUiOptions ^= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
        }

        getActivity().getWindow().getDecorView().setSystemUiVisibility(newUiOptions);
        //END_INCLUDE (set_ui_flags)
    }

CLion에서 사용하는 CMakeLists.txt 간단 강좌

리눅스에서 프로그래밍할때 IntelliJ 기반의 CLion이라는 툴을 사용해보았다. CLion은 Makefile이 아닌 범용 maketool인 CMake를 사용한다. 그래서 Makefile 대신에 CMakeLists.txt를 만들어줘야 빌드가 된다.

1. 아래는 long string에 대한 에러가 날때 사용한다. CFLAGS옵션 이다.
Makefile
FLAGS = -g -O3 -fpermissive -w

CMakeLists.txt
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive")

2. 아래는 include directory에 대해서 설정이다. I옵션이다.
Makefile
INCS = -I/usr/include -I/usr/local/mysql/include -I/usr/local/include

CMakeLists.txt
include_directories(/usr/include)
include_directories(/usr/local/mysql/include)
include_directories(/usr/local/include)

3. 아래는 AnsiC에 대한 옵션이다.
set(CMAKE_C_FLAGS "-std=c99")

4. 아래는 define 정의이다. FLAGS+= -D와 같다.
add_definitions(-DREGION_KR=1)
add_definitions(-DMODE_REAL=1)
add_definitions(-DDB_REAL=1)

5. 아래는 static library에 대한 옵션이다.
Makefile
LIBS = -lc -lssl -lpthread -L'/usr/local/mysql/lib/mysql' -lmysqlclient -lz -lcrypt -lnsl -lm -ldl -lrt

CMakeLists.txt
add_executable(dark_server ${SOURCE_FILES})

#link must be after add_executable
target_link_libraries(dark_server c)
target_link_libraries(dark_server ssl)
target_link_libraries(dark_server pthread)
target_link_libraries(dark_server mysqlclient)

Tuesday, November 17, 2015

Linux binary executable bit check

실행파일 바이너리의 비트수를 체크하는 명령은 다음과 같다.

file <filename>

root@3bd49b29-5448-41da-a3e1-581280f439e0:~/dark_server/src# file mgs_dark
mgs_dark: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x1b8c7bd92afcb4134444103f026ddb336cff19c4, not stripped
root@3bd49b29-5448-41da-a3e1-581280f439e0:~/dark_server/src#

Monday, November 9, 2015

iOS Please verify that your device’s clock is properly set, and that your signing certificate is not expired.

Please verify that your device’s clock is properly set, and that your signing certificate is not expired. (0xE8008018).

Xcode -> Preferences -> Accounts -> View Details -> Download All

Tuesday, November 3, 2015

Android 5.0 WebView image not shown

// Android 5.0에서는 그림이 안보이니 이걸 해주어야 한다.
        if(Build.VERSION.SDK_INT >= 21) {
            mWebView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }

Sunday, November 1, 2015

gem eventmachine openssl/ssl.h not found

bundle config build.eventmachine --with-cppflags=-I$(brew --prefix openssl)/include
bundle install

Mac openssl 설치

./config (32bit)
./Configure darwin64-x86_64-cc (64bit)

make
sudo make install

Thursday, October 22, 2015

iOS 개발자 인증서 내보내기

iOS 개발자 인증서를 여러 맥북에서 공유하기 위해서 내보내기를 해보았다.

1. 먼저 key파일을 내보내야 하므로 '키체인 접근'을 실행시키자.
여기서 내보낼 키를 Development, Distribution 두개를 선택하고 '2개 항목 보내기'를 선택하여 .pem 파일로 저장한다.

2. 두번째는 개발자 프로필을 내보내야 하므로 Xcode를 실행시켜서 Preference에서 Accounts를 선택하여 내보낼 Apple ID를 선택하고 하단에 있는 설정모양 아이콘을 눌러서 Export Developer Accounts를 선택하여 내보내자.


이 두가지 파일을 다른 맥북에 옮겨서 두개를 더블 클릭하면 동일하게 다른 맥북에서도 iOS 개발을 할수 있다.



iOS -ObjC 옵션에서 SDL 2.0 라이브러리가 duplicate가 날때

iOS 프로젝트에서 SDL 2.0 라이브러리를 사용할때가 있다. 그런데 여기서 SDL_main을 사용하지 않고 라이브러리로만 사용할때 -ObjC를 사용하게 되면 duplicate symbol for architecture x86_64라는 에러메세지가 뜬다.

-ObjC는 duplicate를 허용하지 않는 옵션이기 때문에 메인 프로젝트에 UIApplication을 띄우는 main함수가 있고 그리고 SDL 라이브러리 안에도 있기 때문이다.

따라서 다음과 같이 SDL_uikitappdelegate.m에서 main을 주석처리하면 해결된다.

iOS 라이브러리 추가시에는 항상 'Add Files to (ProjectName)'을 사용하자

소스단위로 되어 있는 iOS 라이브러리를 프로젝트에 폴더단위로 드래그앤 드롭을 하게 되면 안에 있는 .a 라이브러리 파일들이 Build Settings에 Linked Frameworks and Libraries에 자동으로 추가가 되지 않는다.

따라서 항상 소스단위의 iOS 라이브러리를 프로젝트에 추가할때는 프로젝트의 폴더에서 오른쪽 버튼을 누른후 'Add Files to (ProjectName)'을 사용해야 한다.

HTML 모바일웹 사이즈

일반웹으로 모바일웹으로 바꾸려면 다음과 같이 head에 viewport를 설정해 주어야 한다. 이것은 기본적으로 화면 사이즈에 맞게 표시해주고, 화면 확대를 방지하는 코드이다.

<head>
<meta name="viewport" content="user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, width=device-width">
</head>

Ruby 다중라인 주석달기

Ruby는 파이썬과 비슷해서 #는 한라인 주석달기이다.
그런데 다중라인 주석달기는 =begin ~ =end인데 특이한점이 무조건 그 라인에서 첫번째 글자부터 시작해야 된다.
=앞에 스페이스나 탭이 들어가면 안된다.

Android Studio 쓰다보니 좋은 점들

장점
1. 소스 디컴파일러가 내장되어 있어서 소스가 없는것도 소스를 쉽게 볼수 있고, 안드로이드 소스도 바로 연결이 된다.
2. gradle 빌드 시스템으로 라이브러리의 최신버전을 repository에서 받아온다.
3. 탭간 이동이 비쥬얼 스튜디오와 동일하게 ctrl + tab이다. 이게 가장 편하다.
4. dracular 테마가 있어서 좋다.
5. 프로젝트 관리가 이클립스의 workspace가 아니라서 git에 넣었을 경우에 비쥬얼 스튜디오 처럼 프로젝트를 로딩하면 모든 셋팅이 같이 로딩된다.
6. UI 에디터도 쓸만하게 프리뷰를 할수 있다.

Sunday, October 18, 2015

Android Fullscreen PopupWindow 예제

팝업 윈도우를 타이틀바(액션바)를 포함하여 풀스크린으로 띄우고 싶을 때는 다음과 같은 코드를 이용한다.
new를 할때와 showAtLocation할때 동일한 view가 들어가는 것에 유의하자.

    final View view = getLayoutInflater().inflate(R.layout.rotate_popup, null);
    private PopupWindow rotatePopup;
        rotatePopup = new PopupWindow(view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        rotatePopup.setFocusable(true);
        rotatePopup.showAtLocation(view, Gravity.CENTER, 0, 0);

Tuesday, October 13, 2015

Ruby On Rails에서 로그 출력 하는 방법

콘솔용 Ruby에서는 print와 puts를 사용하면 화면에 문자열을 찍을 수 있는데 Ruby On Rails는 웹 프레임워크라서 print/puts를 사용하면 문자열이 HTML로 출력이 된다.

그래서 다음과 같이 출력하면 로그 파일에 안전하게 로그가 출력이 된다.

Rails.logger.debug "(문자열)"

문자열은 log/development.log, production.log에 출력이 된다.

debug대신에 info, warn등을 사용할수 있는데 이것은 다음과 같은 환경설정을 따른다.
config/environments/development.rb, production.rb에서 다음과 같이 지정한다.

config.log_level = :debug

Git 윈도우10에서 SourceTree가 다운될때

윈도우10에서는 Git클라이언트인 SourceTree 최신버전이 다운된다.
그래서 아래 링크에 있는 SourceTree 1.5.2를 받아서 설치하면 해결된다.
하지만 SourceTree 1.5.2는 파일을 add/commit할때 폴더별로 정렬이 안되어서 불편하다.
아직은 윈도우8.1이 가장 편리한 윈도우인것 같다.(최신 지원 기능 및 적은 업데이트)

다운로드

Saturday, October 10, 2015

Ruby cannot load such file -- 2.2/pg_ext (LoadError) 에러 해결 방법

윈도우에 Ruby on Rails를 설치하여 빌드하여 실행 하려고 하는데(rails s) 다음과 같은 에러가 발생했다.

C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/pg-0.17.1-x64-mingw32/lib/pg.rb:10:in `require': cannot load such file -- 2.2/pg_ext (LoadError)
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/pg-0.17.1-x64-mingw32/lib/pg.rb:10:in `rescue in <top (required)>'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/pg-0.17.1-x64-mingw32/lib/pg.rb:3:in `<top (required)>'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/bundler-1.10.6/lib/bundler/runtime.rb:76:in `require'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/bundler-1.10.6/lib/bundler/runtime.rb:76:in `block (2 levels) in require'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/bundler-1.10.6/lib/bundler/runtime.rb:72:in `each'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/bundler-1.10.6/lib/bundler/runtime.rb:72:in `block in require'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/bundler-1.10.6/lib/bundler/runtime.rb:61:in `each'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/bundler-1.10.6/lib/bundler/runtime.rb:61:in `require'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/bundler-1.10.6/lib/bundler.rb:134:in `require'
        from C:/git/amanda-api/config/application.rb:7:in `<top (required)>'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/railties-4.1.13/lib/rails/commands/commands_tasks.rb:79:in `require'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/railties-4.1.13/lib/rails/commands/commands_tasks.rb:79:in `block in server'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/railties-4.1.13/lib/rails/commands/commands_tasks.rb:76:in `tap'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/railties-4.1.13/lib/rails/commands/commands_tasks.rb:76:in `server'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/railties-4.1.13/lib/rails/commands/commands_tasks.rb:40:in `run_command!'
        from C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/railties-4.1.13/lib/rails/commands.rb:17:in `<top (required)>'
        from bin/rails:4:in `require'
        from bin/rails:4:in `<main>'

이를 해결하는 방법은 다음과 같다.
gem instasll pg --pre
gem list pg

확인결과 최신버전은 0.18.3이다.
그럼 Gemfile, Gemfile.lock에서 pg의 버전을 0.18.3으로 변경하면 해결 된다.

Monday, October 5, 2015

Android keyboard show/hide event

    public static void hideKeyBoard(View v) {
        InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(v.getApplicationWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }

    public static void showKeyBoard(View v) {
        InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(INPUT_METHOD_SERVICE);
        imm.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT);
    }

    private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            int heightDiff = rootLayout.getRootView().getHeight() - rootLayout.getHeight();
            int contentViewTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();

            LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(BaseFragmentActivity.this);

            if(heightDiff <= contentViewTop){
                onHideKeyboard();

                Intent intent = new Intent("KeyboardWillHide");
                broadcastManager.sendBroadcast(intent);
            } else {
                int keyboardHeight = heightDiff - contentViewTop;
                onShowKeyboard(keyboardHeight);

                Intent intent = new Intent("KeyboardWillShow");
                intent.putExtra("KeyboardHeight", keyboardHeight);
                broadcastManager.sendBroadcast(intent);
            }
        }
    };
    private boolean keyboardListenersAttached = false;
    private ViewGroup rootLayout;

    protected void onShowKeyboard(int keyboardHeight) {}
    protected void onHideKeyboard() {}

    protected void attachKeyboardListeners(View contentView) {
        if (keyboardListenersAttached) {
            return;
        }

        rootLayout = (ViewGroup)contentView;
        rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(keyboardLayoutListener);
        keyboardListenersAttached = true;
    }

RubyMine 7 스타트업 회사에서 싸게 구매하는 법(Startup discount)

https://www.jetbrains.com/shop/eform/startup

최근에 Ruby On Rails로 된 서버를 개발하다가 개발툴인 RubyMine을 구매하려고 하다가 보니 다음과 같은 공식 사이트에서 스타트업을 위한 할인 캠페인을 발견했다.

다음과 같이 3년이하, 10인이하 사업장에서 할인을 받을수 있다. 할인 폼에 정보를 입력하면 2-3일정도의 심사 과정을 거친후에 메일로 그 결과가 발송된다.

This discount is available to any company that meets all the following requirements:
  • Is privately held,
  • Has been in business for no more than 3 years,
  • Has no more than 10 employees,
  • Is engaged in development of a software-based product or service, and
  • Is an established business with a website and/or existing public references on the Internet.
If your company meets these criteria, apply for your discount now.
After your application is approved, your startup discount will be available for one or more purchases for 12 months

Monday, September 28, 2015

Android Studio logcat 색깔 변경

이클립스와 달리 Android Studio는 디버깅시 logcat의 색깔이 하이라이팅 지정되지 않아서 여간 불편한게 아니었다.
하지만 다음과 같이 설정하면 logcat의 색깔을 지정할 수 있게 된다. 이클립스와 똑같은 환경을 만들자.


Tuesday, September 22, 2015

MySQL 초기 root 패스워드 설정하기

mysql> use mysql;
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> update user set password=password('password') where user='root';
Query OK, 4 rows affected (0.07 sec)
Rows matched: 4  Changed: 4  Warnings: 0

mysql> flush privileges;
Query OK, 0 rows affected (0.21 sec)

mysql> select host, user, password from user;
+----------------------+------------------+-------------------------------------------+
| host                 | user             | password                                  |
+----------------------+------------------+-------------------------------------------+
| localhost            | root             | *78D22DFB85972182BE20A883D8B5021FD8B5FCCA |
| yegam400.vps.phps.kr | root             | *78D22DFB85972182BE20A883D8B5021FD8B5FCCA |
| 127.0.0.1            | root             | *78D22DFB85972182BE20A883D8B5021FD8B5FCCA |
| ::1                  | root             | *78D22DFB85972182BE20A883D8B5021FD8B5FCCA |
| localhost            | debian-sys-maint | *E54187CB3AF30C421C5063781FEE02F787E6842F |
+----------------------+------------------+-------------------------------------------+

Wednesday, September 16, 2015

Android Studio auto import 설정하기

Eclipse에서는 Cmd+Shift+M을 하면 auto import가 되었는데 Android Studio에서는 찾기가 쉽지 않았다. 그런데 드디어 찾아내서 방법을 공유한다.

Preferences - Editor - Auto Import에 가서 가운데에 있는 3가지 항목을 전부 checked한다.


Wednesday, September 9, 2015

iOS Failed to locate or generate maching signing assets


https://developer.apple.com/library/ios/recipes/xcode_help-accounts_preferences/articles/import_signing_assets.html#//apple_ref/doc/uid/TP40013306-CH9-SW1

Sunday, September 6, 2015

Android Studio ViewPager PageTabStrip titles are initialy not shown problem

Eclipse에서 Android Studio로 마이그레이션 했는데, ViewPager에서 PageTabStrip의 타이틀이 초기로딩시에 렌더링이 되지 않았다. ViewPager가 다른 페이지로 한번 페이징 된 후에는 PageTabStrip의 타이틀이 정상적으로 나왔다.

이 문제에 대해서 하루종일 고민하다가 결론을 얻었다. Android Studio에서는 appcompat v4라이브러리를 직접적으로 링크하면 안되고, 대신에 design library를 링크해야한다는 것이다.

dependencies {
...
compile 'com.android.support:design:22.2.0'
...
}

Thursday, September 3, 2015

iOS 개발시 스토리보드 제거하기

iOS 프로젝트에서 스토리보드를 제거하는 방법을 알아보자. 스토리보드가 있으면, 대형 프로젝트에서 여러사람이 협업시에 불편한점이 있고 수동으로 뷰를 컨트롤 하는게 명확하므로 제거하는 것이 좋다.

1. main.plist에서 "Main storyboard base file name"을 삭제해준다.

2. 기존 뷰 컨트롤러를 삭제해 준다. 예제에서는 ViewController.h/m이다.

3. 뷰 컨트롤러를 새로 생성한다. 생성하는 방법은 New file - iOS - Source - Cocoa Touch Class이다.

그리고 반드시 XIB를 생성하는 옵션을 선택하여야 한다.

4. AppDelegate.m에 다음과 같은 코드를 추가 한다.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    // 스토리보드 없이 사용하는 방법
    self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
    self.window.backgroundColor = [UIColor whiteColor];
    
    ViewController *viewController = [[ViewController alloc]initWithNibName:@"ViewController" bundle:nil];
    self.window.rootViewController = viewController;    
    [self.window makeKeyAndVisible];

    return YES;

}

iOS ARC 프로젝트에서 non-ARC 소스 컴파일하기(-fno-objc-arc)

iOS 프로그램을 개발하다보면 ARC(Auto Reference Counting) 프로젝트에서 non-ARC 소스를 컴파일 해야 할 때가 있다.

그럴때는 다음과 같이 Build Phases에서 해당하는 .m(ObjC) 파일의 옵션에다가 '-fno-objc-arc'를 하게 되면 해당 .m 파일만 non-ARC 방식으로 컴파일할 수 있다.

최신 프로젝트에 구형 SDK를 연동할때 많이 쓰는 방식이다. 나는 이걸 몰라서 C#에 Native C++을 연동할때 Managed C++ 프로젝트를 만드는 것처럼 프로젝트를 ARC/non-ARC 분리를 했는데 그렇게 하면 더 많은 문제가 생긴다. iOS에서는 이 방식을 추천한다.


Wednesday, September 2, 2015

HTML5 아이폰 채팅 말풍선



<style>
body {
  font-family: "Helvetica Neue";
  font-size: 20px;
  font-weight: normal;
}

section {
  max-width: 450px;
  margin: 50px auto;
}
section div {
  max-width: 255px;
  word-wrap: break-word;
  margin-bottom: 20px;
  line-height: 24px;
}

.clear {
  clear: both;
}

.from-me {
  position: relative;
  padding: 10px 20px;
  color: white;
  background: #0B93F6;
  border-radius: 25px;
  float: right;
}
.from-me:before {
  content: "";
  position: absolute;
  z-index: -1;
  bottom: -2px;
  right: -7px;
  height: 20px;
  border-right: 20px solid #0B93F6;
  border-bottom-left-radius: 16px 14px;
  -webkit-transform: translate(0, -2px);
}
.from-me:after {
  content: "";
  position: absolute;
  z-index: 1;
  bottom: -2px;
  right: -56px;
  width: 26px;
  height: 20px;
  background: white;
  border-bottom-left-radius: 10px;
  -webkit-transform: translate(-30px, -2px);
}

.from-them {
  position: relative;
  padding: 10px 20px;
  background: #E5E5EA;
  border-radius: 25px;
  color: black;
  float: left;
}
.from-them:before {
  content: "";
  position: absolute;
  z-index: 2;
  bottom: -2px;
  left: -7px;
  height: 20px;
  border-left: 20px solid #E5E5EA;
  border-bottom-right-radius: 16px 14px;
  -webkit-transform: translate(0, -2px);
}
.from-them:after {
  content: "";
  position: absolute;
  z-index: 3;
  bottom: -2px;
  left: 4px;
  width: 26px;
  height: 20px;
  background: white;
  border-bottom-right-radius: 10px;
  -webkit-transform: translate(-30px, -2px);
}
</style>
<section>
<div class="from-me">
 <p>Hey there! What's up?</p>
</div>
<div class="clear"></div>
<div class="from-them">
 <p>Checking out iOS7 you know..</p>
</div>
<div class="clear"></div>
<div class="from-me">
 <p>Check out this bubble!</p>
</div>
<div class="clear"></div>
<div class="from-them">
 <p>It's pretty cool!</p>
</div>
<div class="clear"></div>
<div class="from-me">
 <p>Yeah it's pure CSS &amp; HTML</p>
</div>
<div class="clear"></div>
<div class="from-them">
 <p>Wow that's impressive. But what's even more impressive is that this bubble is really high.</p>
</div>
</section>

Friday, August 28, 2015

Android SQLite 예제

package com.example.xxx;

import java.util.ArrayList;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class BookmarkDB extends SQLiteOpenHelper {
 private final static String TAG="BookmarkDB";

 public BookmarkDB(Context context, String name, CursorFactory factory,
   int version) {
  super(context, name, factory, version);
  // TODO Auto-generated constructor stub
 }

 @Override
 public void onCreate(SQLiteDatabase db) {
  // TODO Auto-generated method stub
  db.execSQL("CREATE TABLE bookmark (id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT, page INTEGER);");
 }

 @Override
 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  // TODO Auto-generated method stub

 }

 public void querySQL(String sql) {
  Log.d(TAG,"querySQL sql="+sql);
  SQLiteDatabase db = getWritableDatabase();
  db.execSQL(sql);
  db.close();
 }

 ArrayList<Integer> getPages(String path) {
  SQLiteDatabase db = getReadableDatabase();
  Cursor cursor = db.rawQuery("SELECT page FROM bookmark WHERE path='"
    + path + "'", null);

  ArrayList<Integer> pageList = new ArrayList<Integer>();
  while (cursor.moveToNext()) {
   int page = cursor.getInt(0);
   Log.d(TAG, "getPages page="+page);
   pageList.add(page);
  }
  db.close();
  return pageList;
 }

}

Tuesday, August 25, 2015

Android 키보드 입력시 액티비티 윈도우 리사이즈 방지하기

안드로이드에서 키보드 입력시 액티비티의 전체윈도우가 리사이즈 되는 경우가 있다.
그럴 경우 다음과 같으 AndroidManifest.xml에서 다음과 같이 정의하면 키보드가 나오더라도 리사이즈 되지 않는다.

    <application
...
        android:windowSoftInputMode="adjustPan" >

Monday, August 24, 2015

Android ViewPager + PagerTabStrip 예제

안드로이드에서 메인화면에 탭을 구현하는 방법은 여러가지가 있다.

첫번째는 ActionBar에 NAVIGATION_TABS로 모드를 설정하는 방법이고,
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

두번째는 컨텐츠뷰에 PageTabStrip이라는 UI를 사용하는 방법인데, 이 방법은 무조건 ViewPager가 있어야 한다.

일단 이전글의 ViewPager 예제를 완성해놓고, 여기다가 조금만 추가하면 PageTabStrip을 만들수 있다.
http://duongame.blogspot.kr/2015/08/android-viewpager.html

activity_main.xml
<android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >
        <android.support.v4.view.PagerTabStrip
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:textColor="#000000" />
    </android.support.v4.view.ViewPager>

 private class PagerAdapter extends FragmentStatePagerAdapter {
  // Tab Titles
  private String tabtitles[] = new String[] { "Tab1", "Tab2", "Tab3", "Tab4", "Tab5" };

...
  // PageTabStrip
  @Override
  public CharSequence getPageTitle(int position) {
   return tabtitles[position];
  }

 }

Android ViewPager 예제 (FragmentStatePagerAdapter 사용)

Android에서 많이 사용하는 ViewPager의 예제이다. ViewPager를 사용하려면 필수적으로 FragmentActivity를 사용해야하는데, ActionBarActivity를 사용하고 있으면 무시하고 ActionBarActivity를 그대로 사용해도 된다.(FragmentActivity를 상속받고 있기 때문이다.)

public class MainActivity extends ActionBarActivity {
 private ViewPager mViewPager;
 private PagerAdapter mPagerAdapter;
 private class PagerAdapter extends FragmentStatePagerAdapter {
  public PagerAdapter(FragmentManager fm) {
   super(fm);
  }
  @Override
  public Fragment getItem(int position) {
                        // 해당하는 page의 Fragment를 생성합니다.
   return PageFragment.create(position);
  }
  @Override
  public int getCount() {
   return 5;  // 총 5개의 page를 보여줍니다.
  }

 }

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  mViewPager = (ViewPager) findViewById(R.id.pager);
  mPagerAdapter = new PagerAdapter(getSupportFragmentManager());
  mViewPager.setAdapter(mPagerAdapter);
 }
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }
 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  // Handle action bar item clicks here. The action bar will
  // automatically handle clicks on the Home/Up button, so long
  // as you specify a parent activity in AndroidManifest.xml.
  int id = item.getItemId();
  if (id == R.id.action_settings) {
   return true;
  }
  return super.onOptionsItemSelected(item);
 }
}

public class PageFragment extends Fragment {
 private int mPageNumber;
 public static PageFragment create(int pageNumber) {
  PageFragment fragment = new PageFragment();
  Bundle args = new Bundle();
  args.putInt("page", pageNumber);
  fragment.setArguments(args);
  return fragment;
 }
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  mPageNumber = getArguments().getInt("page");
 }
 @Override
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
   Bundle savedInstanceState) {
  ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_page, container, false);
  ((TextView) rootView.findViewById(R.id.number)).setText(mPageNumber + "");
  return rootView;
 }
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.viewpagerexample.MainActivity" >
    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >
    </android.support.v4.view.ViewPager>
</RelativeLayout>

fragment_page.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:id="@+id/number"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/hello_world" />
</RelativeLayout>

https://github.com/duongame/ViewPagerExample1

Friday, August 21, 2015

SVN에서 Git으로 마이그레이션하기

기존에 사용하던 SVN이 네이버 개발자센터의 SVN이었다. 그런데 네이버 개발자센터의 관리자 페이지의 지원이 너무 형편없고, SVN은 다운이 잦았으며, Mac에서 SVN 클라이언트인 Versions는 유료 프로그램임에도 불구하고 기능이 너무 없어서 이번기회에 Git으로 이전하기로 하였다.

그래서 기존에 사용하던 SVN을 Git으로 안전하게 이전하는 방법에 대해서 정리해 보았다. Git 저장소는 5인이하 무료인 www.bitbucket.org를 사용하고, 클라이언트는 SourceTree를 사용하기로 결정했다.

일단 윈도우를 기준으로 GitHub 클라이언트와 SourceTree 윈도우 버전을 설치한다. 그런다음에 GitShell을  연다.

다음과 같이 GitShell을 열고 난 다음, cd "c:\git"과 같이 git 프로젝트를 clone할 폴더를 만들어서 이동하자.

그리고 다음과 같이 SVN 저장소 주소를 입력한다.
git svn clone https://dev.naver.com/svn/capture capture -s
이때 권한을 요구하게 되는데 p를 누르고 아이디, 암호를 입력을 입력하자.

그러면 git이 svn을 clone할 것이다. 그런 다음에 다음과 같이 작업공간 열기를 통해서 clone한 svn 저장소를 오픈한다.

추가된 작업 공간에 git을 연결하자. 이때는 git에서 clone하는 저장소 주소를 복사해서 입력하면 push가 된다.


다음에는 git 저장소로 push를 해야 되는데 앞에서 저장했던 git 저장소 주소로 push가 된다.

git으로 push가 끝나면 svn의 버전과 태그가 다 살아있는 것을 볼 수 있다.