서두

System.Data.DataRow

데이터베이스 기반의 CS나 Web 관련 개발을 하면 DataTable을 많이 활용하는데, 이 때 데이터들은 DataRow를 통해서 전달 받게 된다. 그런데, 이 DataRow에 담긴 값들은 모두 Object 형식으로 담겨 있는데, 이 때 데이터를 끄집어 내는데, 그냥 ASP 시절의 웹 프로그래밍 방식을 따르게 되면 오류 발생율이 높다. 물론 의도적으로 try ~ catch 문에서 끄집어 내기 위해서 만들기도 하지만, 단순히 데이터를 끄집어 오는데, try ~ catch로 잡아야 될 정도면 개인적으로는 아니다 싶을 때가 많다.

 

본문

예를 들면, Person 이라는 테이블이 있는데, Name - varchar(50), Age – int, Address – varchar(100) 의 형태의 테이블이 있다고 하자.

Name Age Address
Goshe Mack 12 Gangnam
Park Sung-ook 23 [NULL]
Sonia Aka 22 Mapo

데이터테이블이 dtResult 라는 변수에 담겨 있다고 한다면,

   
string sName = dtResult.Rows[0]["Name"].ToSting();

하면 그 해당하는 값인 “Goshe Mack” 이라는 데이터가 나온다.

여기서 발생할 수 있는 오류의 형태는 2가지.

첫번째로는 데이터가 없을 때다. 즉 dtResult 안에 DataRow가 하나도 없으면 저기서 에러가 난다. [0] 부분은 0번째 Row를 가져오겠다는 말인데, 그 안에 앖이 없으면 Null 이고 Null에 대한 [Name]은 없으므로 에러다.

두번째로는 Name 부분이 DbNull 인 경우다. 이 경우 [0][Name] 결과가 null 이 되므로, null의 ToString 에서 오류가 발생하게 된다.

그렇다면 이제 Null을 피해보도록 하자.

첫번째의 경우.

foreach(DataRow row in dtResult.Rows)
{
       string sName = row["Name"].ToString();
}

라고 하면 일단 Data가 없는 경우에 발생되는 문제는 피할 수 있다. 왜냐면 foreach를 할 때, dtResult 안에 Row가 없으면 더 이상 안의 내용을 처리하지 않기 때문이다.

두번째의 경우는 어떻게 할까?

위의 경우 처럼 직관적으로 표현하자면 이렇게 할 수 있을 것이다.

foreach(DataRow row in dtResult.Rows)
{
    if(row["Name"] != null || row["Name"] != DBNull.Value)
    {
        string sName = row["Name"].ToString();
    }
}

조금 더 다듬어 보자.

row["Name"]
이 너무 자주 나온다!  이것을 조금 더 개선 시키는 방향은 아예 값을 밖으로 빼서 하나의 변수에 넣고, 그 값을 돌려 쓰는 방식을 취한다. 이러면 딱 한번만 row[“Name”] 을 부를 뿐 실제 값은 변수에서 직접 가져와 쓰게 된다.
foreach(DataRow row in dtResult.Rows)
{
    object value = row["Name"];
    if(value != null || value != DBNull.Value)
    {
        string sName = value.ToString();
    }
}

만일 Null 일 때 Default 값을 설정하고 싶다면? 더 간단하다. 이번에는 sName 부분을 위로 끄집어 올린다.

foreach(DataRow row in dtResult.Rows)
{
    string sName = "DEFAULT";
    object value = row["Name"];
    if(value != null || value != DBNull.Value)
    {
        sName = value.ToString();
    }
}

여기까지는 좋다고 생각된다. 그런데, 만일, 여러 개의 필드일 때는 어떻게 할까? 더욱이 String도 아닌 Int 같은 필드 인 경우 ToString 으로 해결도 안된다. 한번 위의 방식대로 코드를 짜보자

foreach(DataRow row in dtResult.Rows)
{
    string sName = "DEFAULT";
    int nAge = 0;
    string sAddress = "DEFAULT";

    object value = row["Name"];
    if(value != null || value != DBNull.Value)
    {
        sName = value.ToString();
    }

    object value = row["Address"];
    if(value != null || value != DBNull.Value)
    {
        sAddress = value.ToString();
    }

    object value = row["Age"];
    if(value != null || value != DBNull.Value)
    {
        string sTemp = value.ToString();
        try
        {
            nAge = int.Parse(sTemp);
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

}

갑자기 길어지는 코드!!!!

지금은 값이 세 개니까, 저렇지 만일 필드수가 10개 이상된다고 하면, 값 한번 조회하는 코드만 100라인 넘게 된다. 까잇거 Copy&Paste 신공이 있는데! 라고 외치며 무식하게 짜시는 분이 계신다면, 나중에 그 코드를 유지보수해야 할 때 참여하는 자신 혹은 남을 위해 자제해주셨으면 한다. 제발 ㅋ

그래서 아예 값을 별도로 처리하기 위한 Util 형태의 Static 클래스를 만들어 사용하는 방법을 추천한다.

/// 
/// System.Data.DataRow의 데이터를 쉽게 접근하기 위해서 사용되는 함수 모음
/// 
static public class DBUtils
{

    /// 
    /// DataRow 상에서 특정 Coloumn 이름에 해당하는 문자열을 읽어온다.
    /// 별도 기본값을 제공하여 읽어오는데 실패한 경우 입력받은 기본값으로 돌려준다.
    /// 
    /// 읽어올 데이터가 담긴 DataRow
    /// 읽어올 데이터의 Column 이름
    /// 읽어오는 것을 실패한 경우 돌려줄 기본값
    /// 읽어온 문자열 값
    public static string ReadString(DataRow row, string sColumnName, string sDefaultValue)
    {
        string sResult = sDefaultValue;
        if (row.Table.Columns.Contains(sColumnName))
        {
            object value = row[sColumnName];
            if ((value is DBNull) == false && value != null)
            {
                sResult = value.ToString();
                    
            }
        }
        sResult = sResult.Trim();
        return sResult;
    }

    /// 
    /// DataRow 상에서 특정 Coloumn 이름에 해당하는 문자열을 읽어온다.
    /// 
    /// 읽어올 데이터가 담긴 DataRow
    /// 읽어올 데이터의 Column 이름    
    /// 읽어온 문자열 값
    public static string ReadString(DataRow row, string sColumnName)
    {
        return ReadString(row, sColumnName, string.Empty);
    }


    /// 
    /// DataRow 상에서 특정 Coloumn 이름에 해당하는 숫자값을 읽어온다.
    /// 별도 기본값을 제공하여 읽어오는데 실패한 경우 입력받은 기본값으로 돌려준다.
    /// 
    /// 읽어올 데이터가 담긴 DataRow
    /// 읽어올 데이터의 Column 이름
    /// 읽어오는 것을 실패한 경우 돌려줄 기본값
    /// 읽어온 숫자 값
    public static int ReadInteger(DataRow row, string sColumnName, int nDefaultValue)
    {
        int nResult = nDefaultValue;
        if (row.Table.Columns.Contains(sColumnName))
        {
            object value = row[sColumnName];
            if ((value is DBNull) == false && value != null)
            {
                string sValue = value.ToString();
                if (int.TryParse(sValue, out nResult) == false)
                    nResult = nDefaultValue;
            }
        }
        return nResult;
    }

    /// 
    /// DataRow 상에서 특정 Coloumn 이름에 해당하는 숫자값을 읽어온다.
    /// 
    /// 읽어올 데이터가 담긴 DataRow
    /// 읽어올 데이터의 Column 이름
    /// 읽어온 숫자 값
    public static int ReadInteger(DataRow row, string sColumnName)
    {
        return ReadInteger(row, sColumnName, 0);
    }

    /// 
    /// DataRow 상에서 특정 Coloumn 이름에 해당하는 DateTime값을 읽어온다.
    /// 별도 기본값을 제공하여 읽어오는데 실패한 경우 입력받은 기본값으로 돌려준다.
    /// 
    /// 읽어올 데이터가 담긴 DataRow
    /// 읽어올 데이터의 Column 이름
    /// 읽어오는 것을 실패한 경우 돌려줄 기본값
    /// 읽어온 DateTime 값
    public static DateTime ReadDateTime(DataRow row, string sColumnName, DateTime defaultTime)
    {
        DateTime result = defaultTime;
            
        if (row.Table.Columns.Contains(sColumnName))
        {
            object value = row[sColumnName];
            if ((value is DBNull) == false && value != null)
            {
                try
                {
                    result = (DateTime)value;
                }
                catch (Exception ex)
                {
                    Core.LogWriter.Current.Write(ex);
                }
                //try
                //{
                //    result = DateTime.Parse(sValue);
                //}
                //catch
                //{
                //}
            }
        }
        return result;
    }


    /// 
    /// DataRow 상에서 특정 Coloumn 이름에 해당하는 DateTime값을 읽어온다.
    /// 
    /// 읽어올 데이터가 담긴 DataRow
    /// 읽어올 데이터의 Column 이름
    /// 읽어온 DateTime 값
    public static DateTime ReadDateTime(DataRow row, string sColumnName)
    {
        return ReadDateTime(row, sColumnName, DateTime.MinValue);
    }

    public static System.Data.SqlTypes.SqlBinary ReadSQLBinary(DataRow row, string sColumnName)
    {
        System.Data.SqlTypes.SqlBinary result = System.Data.SqlTypes.SqlBinary.Null;

        if (row.Table.Columns.Contains(sColumnName))
        {
            object value = row[sColumnName];
            if ((value is DBNull) == false && value != null)
            {
                try
                {
                    result = new System.Data.SqlTypes.SqlBinary((byte[])value);                        
                }
                catch (Exception ex)
                {
                    Core.LogWriter.Current.Write(ex);
                }
                //try
                //{
                //    result = DateTime.Parse(sValue);
                //}
                //catch
                //{
                //}
            }
        }
        return result;

    }
}

저런 클래스를 만들면 위에서 제시한 예제 코드는 다음 형태로 변하게 된다.

foreach(DataRow row in dtResult.Rows)
{
    string sName = DBUtils.ReadString(row,"Name","DEFAULT");
    int nAge = DBUtils.ReadInteger(row,"Age",0);
    string sAddress = DBUtils.ReadString(row,"Address","DEFAULT");
}

 

정리

반복적인 작업을 보다 효율적으로 빠르게 처리하라고 있는 컴퓨터에서 노가다하고 있는 우리네 현실을 보면 조금 답답한 것 같다. 사실 C#에는 프로그래머에게 보다 더 편리하고 간단하게 짜라는 장치가 많다. 하지만, 100% 현실에 맞는 것은 아닌 것 같다.

하나씩 자신만의 Util을 만들어 자신의 코드 내에 있는 노가다의 요소들을 정리해보는 것은 어떨까?

728x90

10년치 생일선물을 한 큐에 받는 센스로 New IPAD를 받았다! 행복 행복..
그러다가, 갑자기 무슨생각이 들었는지 장모님께 효도폰 만들어 드린다고, 뜬금 넥서스 S 받고 IPhone 3GS를 받는 기묘한 일을 벌였다.

내 Mobile 체계는 Apple계로 돌아서게 되었다.
그 덕에 이번 iOS6 업그레이드에 눈독을 들이게 되었다.

그래서 어제 9월 19일 부터 전 장비를 백업했다. 미국 태평양 표준시 기준으로 9월 19일이라고 해서 어제 오후 6~7시까지 기다려 봤지만, 역시 출근한 뒤에 배포할 요량인지, 최소한 회사에 있는 동안에는 업그레이드를 시행하지 못했다. 그냥, 장비의 전체 백업만 한채 집으로 발길을 돌렸다.

그러다 오전에 뉴스를 하나 봤는데, 미국 태평양시 기준으로 9월 19일 오전 10시에 배포되었다고 한다. 한국 시간으로는 한밤 새벽 2시에 배포된 것이다.

iOS6, 국내서 어떻게 달라졌나

이야 하는 마음으로 Apple 홈페이지에 가보니, 예전에 달려있던 Comming Soon은 없어졌고!

드디어 ITunes를 이용해 IPAD를 연결했고, 업데이트를 눌렀다! 그러자 다운로드.

최초 10분이였지만, 역시 High Traffic으로 거의 1시간 정도의 시간이 소요되었다.

생각 보다 업그레이드 방법은 어렵지 않았고, 그냥 연결해놓고 가만히 있으니 알아서 업그레이드가 완료되었다.

완료된 IPAD는 늘 띄워놓은 잠금화면에서 멈추었다. 그래서 슬쩍 잠금 화면을 푸니까, 최초 운영체제 설치했던 것 처럼 업그레이 완료를 위한 화면이 떴다.

 

계속~ 터치!

위치 정보 공유 작업인데, 뭐 특별히 숨길것도 없어 “위치 서비스 활성화”를 터치하고 상단의 다음을 터치!

이제 iCloud, iMessage, FaceTime 등의 서비스를 이용하기 위해서는 Apple 계정이 필요한데, 이 계정에 대한 암호를 새로 입력해야 한다. 업그레이드다 보니, 이전에 설정된 정보는 그대로 남아 있고, 다만 암호만 새로 입력해야 한다. 입력하고 다음을 터치한다.

다음으로 넘어가면 자동으로 iCloud 서비스에 대한 설정화면이 잠깐 나왔다 사라진다. 워낙 순식간에 벌어져서 캡쳐 불가 ㅋ

이제 iMessage와 FaceTime 연결에 사용할 계정을 선택한다. 미리 다 선택은 되어 있으니, 그냥 기본값으로 두면 될 듯. 물론 받기 싫으면 체크를 꺼주면 된다.

두둥! 드디어 나오다. “Siri”!!!  당연 사용 사용! 물론 한국내에서는 미국에서 처럼 다양한 분야에서는 활용이 불가능하다고는 하지만, 그래도 ㅋㅋ 사용 사용!

드디어 끝! 이제 모든 설정은 완료되었고! 이제 늘 쓰던 그 IPAD로 사용하면 된다.

업그레이드 인증 샷! 버전은 6.0 / 롬 번호는 10A403.

최초 시작을 들어가면 홈 키를 오래 누르면 시리가 뜬다고 나온다.

그래서 길게 눌러봤더니 뭔 예제가 “용빈이에게 FaceTime 걸어줘” ㅋㅋ

 

이제 더 써보고 난 뒤 이런 저런 포스팅 예정이다. 조금 두근 거린다

대체 뭐가 좋아졌을라나?

 

PS. 지금 iPhone 3GS 업그레이드를 위해 업데이트 시도하려고 연결하니 77분 남았다고 뜬다 ㄷㄷㄷ ;;;;

728x90

원본 : http://msdn.microsoft.com/ko-kr/library/ms180915(VS.80).aspx

(즐겨찾기에 있는 정보로 링크 변경이나, 사이트 변경에 따라 정보가 유실될 것 같아 홈페이지에 등록합니다. 아래의 문건의 권리는 모두 Microsoft 에 귀속됩니다.)


이 항목에는 사용자 암호 관리에 대한 정보 및 코드 예가 들어 있습니다.

다음 C# 코드 예에서는 IADsUser::SetPasswordadsi.iadsuser_setpassword 메서드를 호출하여 사용자 암호를 설정하는 방법을 보여 줍니다. IADsUser::SetPassword에 대한 자세한 내용은 MSDN Library(http://msdn.microsoft.com/library)의 "IADsUser::SetPassword"를 참조하십시오.

 
usr.Invoke("SetPassword", SecurelyStoredPassword);

다음 C# 코드 예에서는 IADsUser::ChangePasswordadsi.iadsuser_changepassword 메서드를 호출하여 사용자 암호를 변경하는 방법을 보여 줍니다. IADsUser::ChangePassword에 대한 자세한 내용은 MSDN Library(http://msdn.microsoft.com/library)의 "IADsUser::ChangePassword"를 참조하십시오.

 
usr.Invoke("ChangePassword", OldSecurelyStoredPassword, NewSecurelyStoredPassword);

다음 C# 코드 예에서는 다음 로그온 시 사용자 암호를 변경할 수 있도록 설정하는 방법을 보여 줍니다. 이 경우 pwdLastSetadschema.a_pwdlastset 속성을 off(-1)로 설정합니다. adschema pwdLastSet 특성에 대한 자세한 내용은 MSDN Library(http://msdn.microsoft.com/library)의 "pwdLastSet" 또는 "Pwd-Last-Set attribute"를 참조하십시오.

 
usr.Properties["pwdLastSet"].Value = -1; // To turn on, set this value to 0.
usr.CommitChanges();

 

다음 C# 코드 예에서는 암호를 변경하는 사용자 권한을 거부하도록 ACE를 설정하는 기능을 보여 줍니다. 이 경우 ntSecurityDescriptor 속성을 가져올 수 있도록 IADsSecurityDescriptor에 액세스하는 데 ADSI 액세스에 COM 상호 운용성 사용를 사용합니다. 그런 다음 IADsAccessControlList를 사용하여 보안 설명자의 DACL을 가져오고 IADsAccessControlEntry를 사용하여 AceType, AceFlags, Trustee, Flags, ObjectType 및 AccessMask 속성을 가져옵니다. AceType 플래그는 ADS_ACETYPE_ENUM에 정의되어 있습니다. AceFlags는 ADS_FLAGTYPE_ENUM에 정의되어 있습니다. AccessMask 플래그는 ADS_RIGHTS_ENUM에 정의되어 있습니다.

 
using System;
using System.DirectoryServices;
using ActiveDs;

static void DenyChangePassword(DirectoryEntry User)
{

	const string PASSWORD_GUID = "{ab721a53-1e2f-11d0-9819-00aa0040529b}";
	const int ADS_UF_PASSWORD_EXPIRED=0x800000;
	const int ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION=0x1000000;

	string[] trustees = new string[]{@"NT AUTHORITY\SELF","EVERYONE"};
	

	ActiveDs.IADsSecurityDescriptor sd = (ActiveDs.IADsSecurityDescriptor)

	User.Properties["ntSecurityDescriptor"].Value;

	ActiveDs.IADsAccessControlList acl = (ActiveDs.IADsAccessControlList) sd.DiscretionaryAcl;

	ActiveDs.IADsAccessControlEntry ace = new ActiveDs.AccessControlEntry();

	foreach(string trustee in trustees)
	{
		ace.Trustee = trustee;
		ace.AceFlags = 0;
		ace.AceType = (int)ActiveDs.ADS_ACETYPE_ENUM.ADS_ACETYPE_ACCESS_DENIED_OBJECT;
		ace.Flags = (int)ActiveDs.ADS_FLAGTYPE_ENUM.ADS_FLAG_OBJECT_TYPE_PRESENT;
		ace.ObjectType = PASSWORD_GUID;
		ace.AccessMask = (int)ActiveDs.ADS_RIGHTS_ENUM.ADS_RIGHT_DS_CONTROL_ACCESS;
		acl.AddAce(ace);
	}

	sd.DiscretionaryAcl = acl;
	User.Properties["ntSecurityDescriptor"].Value = sd;
	User.CommitChanges();
}

다음 C# 예에서는 암호를 변경하는 사용자 권한을 거부하도록 ACE를 설정하는 기능을 보여 줍니다. 이 예에서는 .NET Framework 2.0에서 사용할 수 있는 관리되는 ACL 기능을 사용합니다.

using System;
using System.DirectoryServices;
using System.Security.Principal;
using System.Security.AccessControl;

static void DenyChangePassword(DirectoryEntry user)
{
    // Create a Guid that identifies the Change Password right.
    Guid changePasswordGuid = 
        new Guid("{AB721A53-1E2F-11D0-9819-00AA0040529B}");

    // Get the ActiveDirectorySecurity for the user.
    ActiveDirectorySecurity userSecurity = user.ObjectSecurity;

    // Create a SecurityIdentifier object for "everyone".
    SecurityIdentifier everyoneSid = 
        new SecurityIdentifier(WellKnownSidType.WorldSid, null);

    // Create a SecurityIdentifier object for "self".
    SecurityIdentifier selfSid = 
        new SecurityIdentifier(WellKnownSidType.SelfSid, null);

    // Create an access rule to allow everyone the change password 
    // right. 
    // This is used to remove any existing access rules.
    ActiveDirectoryAccessRule allowEveryone = 
        new ActiveDirectoryAccessRule(
            everyoneSid,
            ActiveDirectoryRights.ExtendedRight, 
            AccessControlType.Allow, 
            changePasswordGuid);

    // Create an access rule to deny everyone the change password right.
    ActiveDirectoryAccessRule denyEveryone =
        new ActiveDirectoryAccessRule(
            everyoneSid,
            ActiveDirectoryRights.ExtendedRight,
            AccessControlType.Deny,
            changePasswordGuid);

    // Create an access rule to allow self the change password right.
    // This is used to remove any existing access rules.
    ActiveDirectoryAccessRule allowSelf =
        new ActiveDirectoryAccessRule(
            selfSid,
            ActiveDirectoryRights.ExtendedRight,
            AccessControlType.Allow,
            changePasswordGuid);

    // Create an access rule to deny self the change password right.
    ActiveDirectoryAccessRule denySelf =
        new ActiveDirectoryAccessRule(
            selfSid,
            ActiveDirectoryRights.ExtendedRight,
            AccessControlType.Deny,
            changePasswordGuid);

    // Remove any existing rule that gives "everyone" the change 
    // password right.
    userSecurity.RemoveAccessRuleSpecific(allowEveryone);

    // Add a new access rule to deny "everyone" the change password 
    // right.
    userSecurity.AddAccessRule(denyEveryone);

    // Remove any existing rule that gives "self" the change password 
    // right.
    userSecurity.RemoveAccessRuleSpecific(allowSelf);

    // Add a new access rule to deny "self" the change password right.
    userSecurity.AddAccessRule(denySelf);

    // Commit the changes.
    user.CommitChanges();
}

다음 코드 예에서는 암호가 만료되지 않도록 설정하는 방법을 보여 줍니다. 이 경우 ADS_USER_FLAG_ENUM에 정의되어 있는 ADS_UF_DONT_EXPIRE_PASSWD 플래그를 설정할 수 있도록 userAccountControl 속성에 액세스하기 위해 Properties 메서드를 사용합니다.

using System;
using System.DirectoryServices;
using ActiveDs;

static void DontExpirePassword(DirectoryEntry User)
{
    int val;
    const int ADS_UF_DONT_EXPIRE_PASSWD =0x10000;
    val = (int) User.Properties["userAccountControl"].Value;
    User.Properties["userAccountControl"].Value = val | 
        ADS_UF_DONT_EXPIRE_PASSWD;
    User.CommitChanges();
}


728x90

UPDATE : http://www.hind.pe.kr/1166 에 보시면 자동(?) 업데이트 도구를 마련했습니다. 다른 기종은 모르겠지만, NEXUS S 라면 저 도구를 써보세요. ㅋ


UPDATE : 처음에는 Radio ROM을 이전 ICS 버전의 ROM을 사용하는 형태로 작성했습니다. 하지만, 주말 내내 실사를 해보니, 젤리빈에서 제공하는 순수한 Radio ROM 보다 그 기능이 떨어지는 것 같습니다. 첫번째로 발열이 심해지면서 밧데리 수명도 짧아지더군요. 그리고 3G 데이터 통신 속도도 의외로 떨어지고요. 그래서 이번 글에서 ICS의 Radio를 심는 부분은 모두 없앴습니다.

fastboot.zip

 

usb_driver.zip

 

젤리빈이 7월 15일경에 발표되었고, OTA로는 8월 초에 배포 된 것 같다. 예전 부터, World Wide 버전의 순정을 얹어놓고 살고 있다가 갑자기 OTA가 떨어져서 업데이트해서 잘 썼었다. 그러다가, 순정롬으로 한번 다시 밀어볼 결심이 생기고, 이번에 한번 다시 밀어보는 작업을 남겨 본다.

먼저 준비해야 할 것은 Fastboot가 실행가능하도록 구성해야 한다. 그럴려면, Andriod용 Driver가 정상적으로 설치되어 있어야 하며, fastboot 실행을 위한 파일들이 있어야 한다. Android용 Driver나 fastboot은 모두 Android SDK안에 있는데, 용량도 크고 원하는 것만 받기 어렵다고 생각이 들어, 이 페이지 안에 첨부로 올린다.

Andriod 드라이버 파일은 첨부한 파일을 다운받으시고, 설치는 http://mynexusone.tistory.com/1 사이트를 참고해 설치한다.

다음은  fastboot 도구. 이 도구도 여기 첨부 파일을 다운로드 받으면 된다.

일단 Android 용 Driver 설치가 끝나고 fastboot 준비도 다 되었으면 이제 공장초기화용 이미지를 받는다.

 

이미지 다운로드

공장 초기화용 이미지는 https://developers.google.com/android/nexus/images?hl=ko-KR 에서 다운을 받는다.

받아야 될 이미지는 Android 4.1.1(JRO03E) 라는 이미지 이다.
(그 사이 또 업데이트 되었군요. 4.1.1 이였다가, 확인해보니, 4.1.2 버전이 되었습니다. 기존에는 JRO03L 이미지기준으로 배치 파일을 만들어 업데이트 했지만, 4.1.1 이미지 자체가 아예 없더군요. 대신, 아예 구글에서 배치 파일로 만들어서 제공합니다. 즉 제가 만든 배치 파일은 더 이상 없어도 될 것 같습니다. 공장 초기화용 롬 파일의 압축을 풀어서 보이는 FLASH-ALL.BAT 파일을 직접 실행하시면 됩니다. )

순정롬들은 모두 tgz 이라는 확장자로 압축이 되어 있는데, linux/unix에서 일반적으로 사용되는 압축으로 tar + zip 이다. 그래서 압축을 두 번 풀어야 한다. 빵집이든, 알집이든, 7Zip이든, 압축을 풀면, 그 안에 다시 압축된 파일이 하나씩 생기는데, 다시 그 파일의 압축을 풀면 된다. 푸는 방법은 아래의 그림과 같이 풀면 된다. ( 아래의 예제는 7-Zip )

 

압축을 다 풀면 위와 같은 파일 목록을 볼 수 있다.

여기서 주목해야 할 파일은 3개의 파일이다.

  • bootloader-crespo-i9020xxlc2.img -> 젤리빈용 부트로더
  • image-soju-jro03e.zip -> 젤리빈 System 파일
  • radio-crespo-i9020xxki1.img -> 젤리빈 Radio ROM

즉 펌웨어에 사용될 파일은 위와 같이 총 세가지로 나머지 파일들은 삭제해도 무방하다.

 

펌웨어 업데이트 전에 백업하기

이게 제일 중요. 백업하는 방법은 수도 없이 많지만, 애석하게도 이 작업을 위한 백업 방법은 필자도 모른다.

다만, 이 순정롬 업데이트 방식은 진짜 완전 초기화를 하기 때문에, 기존에 설정되어 있거나 저장된 내용이 모두 지워진다. 그러므로, 중요한 데이터가 있거나, 급하게 전화를 쓸데가 있으면 이 작업을 더 이상 진행하지 마시고, 정식 젤리빈 ROM이 OTA를 기다리는게 낫다. ( 이전 문자메시지나, 카톡 메시지, 기타 설정데이터들 등등 Google에서 백업 서비스가 지원되지 않는 모든 항목은 다 지워진다 )

적당히 알아서 백업할 수 있거나, 중요한 내용이 없다면 이 부분을 건너 뛰겠다.

필자의 경우에는 공인인증서나 카메라로 찍은 사진, 가계부 정도 밖에 없어서, 적당히 USB로 복사해서 보관했다.

 

Fastboot 준비하기.

먼저 앞서 다운 받은 fastboot 파일들을 이미지가 있는 폴더와 함께 몰아 넣는다. 이제 작업은 도스창에서 하게 되므로, 도스창에서 접근하기 쉬운 폴더이어야 편하다.

자 이제 압축을 풀었으면, 펌웨어 업데이트를 본격적으로 시작한다.!

 

펌웨어 업데이트

먼저 nexus S를 종료한다. 완전히 전원을 끄고, USB로 컴퓨터와 Nexus S를 연결한다.

그리고 볼륨 업 버튼을 누른 상태로 전원 버튼을 눌러서 켜준다.

제대로 켜지면 아래와 같은 화면이 보인다.

자 이 상태에서 도스창을 연다.

그리고 압축 풀린 위치로 이동한다.

이미지와 fastboot 관련 파일 및 flash-all.cmd 파일이 있는 것이 모두 확인되었다면, 도스창에서 flash-all.cmd 를 실행한다.

그러면 Nexus S 폰의 화면이 나오는데, Unlock을 할 것인지 나온다. 볼륨 업 키를 눌르면 Yes로 바뀌는데, Yes 상태에서 전원 버튼을 누르면 된다.

전원을 눌러 Unlock을 했으면, 이제 도스창을 보면 pause 되어 사용자 입력을 기다리는데, 아무 키나 입력하면 펌웨어 업데이트를 시작하게 된다. 이 때, 중요한 것은 한 단계씩 실행되는데, 단계 단계 마다 도스 창에서 Pause가 되서, 사용자의 입력을 기다리게 된다. 이 때 막 누르지 말고! 반드시 부트로더 초기화면인지 확인하고 키를 입력해야 한다.(안드로이드 로봇이 표시되는 화면)

 

여튼, Pause 상태에서 아무키를 입력하면, 자동으로 Nexus S에 bootloader를 기록하는데, 기록이 완료되면 자동으로 리셋되면서 다시 처음 화면으로 돌아온다.

부트로더가 제대로 올라갔으면 다시 도스창에서 아무키나 입력한다.

그러면 Nexus S에 Radio Rom이 기록되고, 다시 리셋된다.

이제 아무키나 입력하면 System을 업로드 한다. 이 작업은 대략 1분 정도 걸린다. 컴퓨터에 부하 줄 수 있는 부분은 최대한 배제하고 그대로 두도록 한다. 도스 화면상에 업데이트 되는 항목들을 계속 표시해 주게 된다.

이제 이 작업이 완료되면, 도스창은 완전 종료되어 끝나고 Nexus는 자동 리셋되고, 젤리빈이 되었음을 확인할 수 있다. USB를 분리하신 후, 이제 부팅 완료되면, 초기 화면에서 초기 설정을 잡아주기만 하면 된다.

 

정리

공장 초기화롬을 적용하는 것은 생각보다 어렵지 않다. 다만, 왠지 그래픽 화면으로 동작하지 않아서, 원시적으로 느껴져서 그렇지 사실 펌업 조작 자체는 그렇게 어렵지는 않다. 개인적으로는 커펌 올리는 방법보다 간단하다는 생각이다.

초기화가 진짜 초기화라서 문제라면 문제랄까?

나중에, 저 배치파일을 .NET 윈도우 GUI 화면으로 만들어봐야 겠다.

 

PS.
일부 캡쳐 화면에서는 아직도 ICS의 Radio ROM 이름이 보이는데, 무시하시고, 그냥 진행하시기 바랍니다.

PS2.

업그레이드 되면 flash-all.cmd 파일을 열어 업그레이드 된 부분의 파일이름만 변경하고 사용하시면 됩니다. 일단 젤리빈 4.1.1. JRO03L 버전 배치파일은 별도로 올리긴 했습니다.


adb.zip


728x90

지금 사용하는 Phone은 Nexus S이다. Google Reference Phone 이다 보니, Google에서 발표한 신버전 안드로이드 ROM은 바로 바로 만들어져 올라간다. ( https://developers.google.com/android/nexus/images?hl=ko-KR )

7월 10일 경 즈음 OTA를 통해 젤리빈 업그레이드가 시작되었는데, 내 폰에도 떨어졌다.
오해의 소지가 있을지 모르겠는데, 현재 내 Phone의 ROM은 영문판으로 world wide 버전의 i9020t 와 i9023용이다.
한국에서 사용되는 ROM 은 m200 용이라고 하는데, 현재까지 m200용 젤리빈 롬은 올라오지 않은 상태로 봐서, 아직 한국어 ROM에 OTA는 적용되지 않을 것으로 안다.

여튼, 내 Phone의 이전 롬은 4.0.4 (IMM76D) for i9020t, i9023 으로 "soju" 라는 프로젝트 명을 달고 있는 Factory Image로 설치되어 있었다. 당연히 ICS(Ice Cream Sandwitch) 였다. 한 달 전 즈음 갑자기 새로운 업데이트가 생겼다는 화면이 뜨고, 확인을 하라고 해서 확인했더니 OTA를 통해 업그레이드를 시작했다.

업그레이드 후 최초로 뜨는 화면은 단순하게 뜨는 "X" 라는 글자에 각 끝자락이 색이 변하는 아주 단순한 애니메이션으로 바뀌었다. (ICS는 현란하긴 한데, 왠지 버벅이는 기분이 들어 별로 마음에는 안들었다. )

첫 부팅을 하면 맨 처음 나오는 화면에서 잠금 해제를 나타내는 애니메이션이 조금 달라졌다. 자물쇠 아이콘 주변으로 불꽃놀이처럼 팍 터지는 느낌. 화려하지는 않지만, 집중도는 있어 보였다.

잠금 해제를 하려고 할 때 보면 기존과는 다르게 위쪽에 "Google" 이라는 표시가 나타난다. 아마도 Google Current 같은데, Google과 바로 연결해서, Google에서 제공되는 각종 정보들을 쉽게 나타내려 하는 의도 같다. ( 하지만 개인적으로는 별 사용도가 없다는... )

        

 

그리고 로그인 해서 들어가보면, 기존 ICS에 비해 달라진 내용은 거의 없다. 전체적인 Blue 빛 Layout도 그대로 유지 되었고, 기타 각종 UI 배치나, 구성도 ICS와 동일하다. 그래서 버전도 (4.1.1) 인듯.

화면 상의 변화로 본다면, 응용 프로그램 전환 시 약간의 애니메이션이 추가되어 마치 기존에 띄워놨던 응용 프로그램이 떨어져 나가는 듯한 애니메이션 정도?

 

개인적인 관점에서 바라봤을 때 젤리빈으로 업그레이드 하고 난 뒤의 변화점은 아래와 같다.

  • 화면 전환이 ICS 때 보다 빠른 느낌이다.
    진짜 느낌이다. ICS 때 보다는 화면 전환이 빠르다고 느껴진다. 물론 극적으로 빨라졌다고 표현하기에는 무리지만, 그래도 확실히 예전 보다는 전환이 빠르다고 생각된다. 예전에는 한 타임을 쉬었다 가는 느낌이면, 지금은 바로 넘어가는 느낌이다. 아마도 화면 전환 애니메이션이 중간에 껴서 그럴 수 도 있겠다.
  • 다운 타임이 줄었다.
    ICS에서는 종종 사용하다가 가끔 앱이 알 수 없게 죽는 경우가 발생했다.
    정확히 어떤 앱이 그런 현상을 발생시키는지는 기억을 할 수 없지만, 최소한 ICS에서 젤리빈으로 업그레이드 할 때, 그대로 있는 앱들을 실행할 때, 예전보다는 그나마 나아진 것 같다.
    또, 잠긴 화면에서 전화가 왔을 때, 터치가 안되는 현상도 예전보다는 적어졌다. 하지만, 여전히 간혹가다 발생하는 것 같다.
  • USB 파일 송/수신 속도가 향상.
    확실히 느낄 수 있다. 예전에는 마치 USB 1.0으로 파일 복사하는 느낌이였다면, 지금은 2.0 으로 처리되는 것 같다. 음악 파일들을 자주 넣었다 뺐다 하는데, 예전에는 복사를 걸어놓고 화장실 갔다면 지금은 바로 처리되는 것 보고 난 뒤 갈 수 있다고나 할까? 극단적으로 빠르다고는 볼 수 없지만, 이 부분은 상당히 개선된 것 같다.
  • 그 외
    막상 쓰려고 보니 그다지 없다.
    기타 Google에서 전하는 변경된 사항은 http://www.android.com/whatsnew/ 에서 참고하면 된다.

 

뭐 강추 강추 하는 레벨은 아니지만, 잦은 다운타임과 불편함을 호소하고 있다면 한번 즈음 젤리빈으로 써보는 것이 좋을 것 같다. ( 젤리빈으로 해보았는데도 잦은 다운타임과 불편함이 쏟아진다면, 앱 문제거나, 사용 방법 혹은 사용 기대치가 너무 높아서 일 수 있다. - 이건 방법이 없고, 그냥 아이폰 같은 선진화된 폰으로 갈아타심이.... )

아직은 영문 ROM에서만 되기 때문에, 사용은 못해보겠지만, 조만간 한국어 ROM이 나온다면 굳이 거부까지는 할 필요가 없어보인다.

 

마지막으로 강조하는데, 지금 이 글은 Nexus S, Nexus Gallaxy 용으로써, 다른 메이커( 삼성, LG, 팬택 등등 )의 제품들에서는 각 메이커에서 업그레이드 할 때까지 기다려야 한다. ICS 때와 마찬가지로 Jelly bean 으로 업그레이드를 지원할지 여부도 각 메이커가 결정할 것이다.

 

덧. 넥서스7에서는 이 젤리빈이 탑재된다고 하는데, 아마도 이 젤리빈의 기능을 잘 활용한 장치가 되지 않을까 싶다.

728x90

집에는 팩스가 없고, 사무실에 있는 팩스 머신에는 전화선이 없어서 개인적으로 팩스를 보낼 때는 보통 인터넷 팩스를 자주 이용한다.

그런데, 이번에 아들녀석에 대한 보험금 처리 때문에, 많은 양의 팩스를 보내게 되었는데, 문제는 웹 기반의 인터넷 팩스에서는 여러장을 입력하기가 무척 불편한 구조로 되어 있다. 일단 한장씩 등록하게 되어 있는데, 그것도 장당 최대 용량은 5M 이하여야 되고, 한번에 모두 올릴 수 있는 양이 10M 안쪽이다.

문제는 스캔한 이미지를 가지고 올리게 되는데, 아무생각없이 스캔한 이미지는 저 사이즈는 훌쩍 뛰어 넘는다.

어쨌던 귀찮던 힘들던 간에, 하나씩 올리기는 했는데, 이번에는 팩스 전송이 실패하는 바람에 귀찮고 힘들게 올린 문서는 공중 분해. 다시 하나씩 올려야 되는 아픔이 있었다.

그래서 스캔한 이미지를 팩스로 올리기 편하도록 팩스용 Tiff 파일을 만드는 프로그램을 만들었다.

어떤 형태로 스캔을 한 이미지라도, 팩스로 보내기 위해서는 어느 정도 이미지 정리를 해야 한다.

  1. 첫번째로는 흑백 이여야 한다.
  2. 두번째는 해상도가 가로는 294 dpi, 세로는 190dpi 여야 한다. ( 최고급 팩스라도 저 이상의 크기는 지원하지도 않고, 잘 되지도 않는다. )
  3. 세번째는 압축 규칙을 CCITT 규약에 맞게 압축한 이미지 여야 한다.

이 세가지 조건만 만족하면 간단하게 팩스 보내는 이미지가 완성된다.

더욱이 TIFF 파일은 파일 내에 여러장의 페이지를 넣을 수 있는 구조이기 때문에, 여러 장으로 된 하나의 문서일 경우 여러장을 담은 하나의 파일로 만들 수 있다.

위의 조건을 충족하는 TIFF 파일을 만드는 도구를 급하게 만들어 봤다.

사용법은 간단.

Add 버튼을 눌러 이미지를 추가한다. (추가 가능한 이미지는 JPG, PNG, BMP 등이다. )

그리고 불필요한 이미지를 추가했으면 선택한 뒤에 Del 버튼을 누른다.

페이지의 순서를 변경하려면, UP/Down 버튼을 눌러 위치를 변경한다.

최종적으로 배치가 끝나면 Convert 버튼을 눌러 Tiff 파일로 저장한다.

등록한 이미지를 모두 없애고 처음부터 다시 하려면 Clear 버튼을 클릭하면 된다.

 

기타 사용상 편의 사항 따윈 일체 없다. ( 사용하다가 불편하면 첨부로 올린 소스를 직접 편집해서 필요한 대로 수정해서 쓰시면 된다. ) 단지, 필자가 편리하게 써보려고 만들어 보았다.

 

이 프로그램은 .NET FrameFramework 2.0 기반으로 구성되어 있으며, 개발은 Visual Studio 2010 에서 했다. (Visual Studio 2010 Express에서도 컴파일 가능 )

PS. 바로 전에 다니던 회사가 이미지 관련된 어플리케이션 개발하던 곳인데, 그 회사에서 개발했던 경험이 이렇게 유용하게 나올 줄은 몰랐다. – 물론 그 회사에서 사용한 기술은 사용하지 않고, 순수 .NET Framework 만들어 개발했다. – 다만, TIFF 개념이나, FAX 개념을 이해하는데 많은 도움이 된 것은 사실이다.

** UPDATE : 2012/08/08
프로그램 내 버그가 발생할 때마다 소스와 릴리즈를 압축하여 여기에 업로드하는 불편함 때문에,
네이버 개발자 센터를 이용하여 업로드를 할 예정이다. 접속하기 위한 네이버 개발자 센터 URL은 다음과 같다.

http://dev.naver.com/projects/imgconvertforfa/

빌드된 최신 버전 파일은 아래의 링크에서 다운 받을 수 있다.

http://dev.naver.com/projects/imgconvertforfa/download

코드는 SVN을 통해 다운로드가 가능하다. 필요하면 해당 프로젝트에 참여자로 등록도 가능하다.
(비밀댓글로 자신의 네이버 아이디를 알려주시면 됩니다. )

728x90
USE MYDB; 
GO 
ALTER DATABASE MYDB SET RECOVERY SIMPLE; 
GO 
DBCC SHRINKFILE (MYDB_log, 130); 
GO 
ALTER DATABASE MYDB SET RECOVERY FULL;
728x90

과연 어떤 코드가 좋을까? 이 문제는 프로그래머들의 스타일과 성격에 따라 무척 다르다.

일단 내 기준으로 보았을 때 다음과 같은 코드가 있으면 일단 뜯고 싶어진다.


        
char[] array4 = new char[]            
{             
         '<'
};
text17 = text17.TrimEnd(array4);
string[] array5 = text17.Split(array4, StringSplitOptions.RemoveEmptyEntries);
text17 = "";
for (int num24 = 0; num24 < array5.Length; num24++) 
{
       if (text17 == "")
       { 
               text17 = array5[num24];
       }
       else
      { 
              text17 = text17 + "." + array5[num24];  
       }
}

변수 명은 일단 넘어가자. Visual Studio를 쓰고 있다면 간단하게 이름을 바꿔줄 수 있으니까..

일단 저 프로그램의 목적은 ‘>’ 가 들어간 문자열 내에 문자들을 나누어서 최종적으로 “.” 로 연결된 문장을 만들고 싶은 것이다.

예를 들면 아래와 같다.

ABC1>2222>>222334>5>66666666>>>>>>>>>  

ABC1.2222.222334.5.66666666

로 바꿔주는 프로그램인 것이다.

 

위의 코드가  어떤 부분이 내 기준에 부합되지 않는지를 설명하려고 한다.

1. String “+” 연산자.

C# 중급 정도 하다보면, 문자열의 “+” 연산자의 낭비를 잘 알 수 있다. 즉 문자열을 단순히 한 두번 연결하는 레벨이라면 “+” 를 써서 연결은 하겠지만, 반복문 안에서 문자열 더하기는 확실히 좋지 않다.

만일 위의 예제로 본다면, 아래와 같은 문자열들이 나오게 된다.

ABC1  /  2222  /  222334  /  5  / 66666666

5개 정도의 문자열이 나오는데, 순서대로 더하게 되면 어떻게 될까?

메모리 상에서 더하는 과정을 순서대로 나열해 보겠다.

  1. ABC1, 2222, ABC12222   ( ABC1, 2222 은 언젠가 지워진다.. 언젠가..)
  2. ABC1(?), 2222(?), ABC12222 , 222334, ABC12222222334 ( 마찬가지로 ABC1 부터 222334까지의 문자열은 언제가 지워진다 –_-; )
  3. ABC1(?), 2222(?), ABC12222(?) , 222334(?), ABC12222222334, 5 ,ABC122222223345 ( ...... )
  4. ABC1(?), 2222(?), ABC12222(?) , 222334(?), ABC12222222334(?), 5(?) ,ABC122222223345,  66666666 , ABC12222222334566666666( ...... )

총 네번의 단계 동안 언젠가 지워져 줘야할 문자열의 갯수는 이미 상상을 초월한다.

“+” 연산자를 할 때, 특정 공간에 쭉 채워가듯 더하는 것이 아니라, 항상 문자열 공간을 새로 만들어 더하기를 하는 것이다. 저건 5 단계 정도니 저 정도 지만, 문자열의 길이가 길어지거나, 반복 횟수가 많아지기 시작하면, 언젠가 지워져야 할 문자열이 지워지기를 기다려야 될지도 모른다.

해결책은 여러가지가 있겠지만, C# 유저라면, StringBuilder를 추천한다. 사용방법은 간단하다.

StringBudiler sbReuslt = new StringBuilder();

sbResult.Append(“ABC1”);

sbResult.Append(“2222”);

sbResult.Append(“222334”);

sbResult.Append(“5”);

sbResult.Append(“66666666”);

sbResult.ToString();

매번 더해진 결과는 한 공간에 넣어서 계속 관리한다. 그 방법을 StringBuilder 가 해결해준다.

 

2. else 문과 string 비교.

저 프로그램이 정말 하고 싶어하는 것이 무엇이길래 저런 방식으로 짠걸까?
한번 생각해보았다. 그러니까, 다음 세 가지의 조건이 나온다.

1. 문자열을 더할 때 그 사이에 “.” 를 넣는다.

2. 맨 마지막 문자열을 더할 때 “.”  가 붙지 않는다.

3. 최초 문자열을 더할 때, 맨 앞에 “.” 가 붙지 않는다.

2번과 3번은 로직을 짤 때 2번을 택해도 되고, 3번을 택해도 된다. 그 이유는 문자열을 더한 뒤, 더 이상 붙여야 될 문자열이 없을 때 “.” 를 안붙이는 로직을 만들거나, 최초 문자열을 더할 때 앞쪽에 “.”를 붙이지 않고, 나머지 경우에 “.” 를 붙이는 방식이다.  처리 방법에 따라, 2번 조건 3번 조건을 쓸 수 있다.

자 저 위의 로직을 보자. 위의 조건 중 1번과 2+3 번의 형태를 만들었다.

즉 반복문 최초에는 그냥 문자열을 대입한 뒤, 다음 반복 부터는

이전 문자열 + “.” + 붙일 문자열

이런 식으로 짰다. 앞서 이야기 한대로 String 의 “+”는 안쓰는게 백번 낫다고 했다.
그렇다면 StringBuilder로 재 구성한다고 할 때, 최소한 저 방식대로 한다면, 매번 뒤에서는 Append를 두 번해야 한다.

sbResult.Append(“.”);

sbResult.Append(array[num24]);

코드가.. 아주 그냥...

더욱이 if 문을 보면, 문자열 비교를 한다. text17 == “”

개인적으로 숫자 비교는 용서하지만 문자열 비교는 가급적 피하고 싶다라는 개인적인 생각이다. 컴퓨터가 숫자에 대한 비교는 빠르지만, 문자열에 대한 비교는 문자열 갯수에 비례하도록 차이가 크게 발생된다. 그래서 꼭 필요한 경우가 아니라면 문자열 비교는 피하는게 좋다. ( 뭐 요즘 컴퓨터 사양은 무지하게 좋기 때문에, 무시되기도 하지만...)

또, for 문의 경우 index라는 개념이 있어서, 코드를 뭔가 알흠답지 않게 만든다. ( 100% 개인 취향 )

 

자 이것을 어떻게 수정할까?

먼저 최초 문자열일 때는 어떻게 판단하고, 어떻게 행동할까?

최종 무엇을 더하도록 할까?

 

결론부터 내자면 개인적으로는 위의 코드가 아래와 같이 만들어지길 원한다.

 

결론

StringBuilder 만 빼면, 나머진 개인 취향이다.
"어디를 향하든 서울만 가면 된다는” 말이 있듯이 사실 결과만 잘 나오면 된다. 어떻게 짜든 개인 취향이라는 것.
하지만, 최소한 나중에 유지보수를 해야 한다고 생각한다면, 간결하고, 단순하게 짜는게 더 낫지 않을까?

처음부터 저런 코드가 툭툭 떨어지진 않는다. 그래서 저렇게 코드가 안 나온다고 고민할 필요까진 없다. 다만, 시간이 될 때, 자신이 짠 코드를 뒤돌아 보면서 Refactoring을 하면서 정리해 보았으면 좋겠다.

728x90

+ Recent posts

728x90