아이폰/컴퓨터 색상 차이 해결법
아이폰/컴퓨터 색상 차이 해결법
http://www.smashingmagazine.com/2009/10/12/setting-up-photoshop-for-web-app-and-iphone-development/
-
Working RGB -> Monitor
-
Desaturate RGB -> 20-25%
-
Modify Gamma
-
Assign Profile -> Working RGB
아이폰/컴퓨터 색상 차이 해결법
http://www.smashingmagazine.com/2009/10/12/setting-up-photoshop-for-web-app-and-iphone-development/
Working RGB -> Monitor
Desaturate RGB -> 20-25%
Modify Gamma
Assign Profile -> Working RGB
스프레드시트 리스트 읽어오기
client = gdata.spreadsheets.client.SpreadsheetsClient()
feed = client.get_spreadsheets()
내부적으로 다음과 같은 URL 을 사용한다.
http://spreadsheets.google.com/feeds/spreadsheets/private/full
특정 스프레드 시트의 워크 시트 리스트 읽어오기
feed = client.get_worksheets(spreadsheet_key)
내부적으로
http://spreadsheets.google.com/feeds/worksheets/<키>/private/full
특정 워크 시트를 리스트 기반으로 읽어오기
이건 gdata.spreadsheets.client 에서 지원하지 않는다.
url = "https://spreadsheets.google.com/feeds/list/%s/%s/private/full" % (spreadsheet,worksheet)
feed = client.get_feed(url, desired_class = gdata.spreadsheets.data.ListsFeed)
이렇게 읽어온 feed.entry 에서 적당한 필드를 잘 읽어내야 한다. 내부적으로는 XML 이므로 다음과 같이 읽을 것. 잘 안읽히면 feed 를 그대로 출력해서 어떤 값이 있는지 보면 된다.
핵심은,
parent 가 있는 키 속성을 bulkloader.yaml 을 통해 export/import 할 경우, create_foreign_key + key_id_or_name_as_string 로는 부족하다.
따라서, 부모의 키값과 자식의 키 아이디 또는 이름을 별도의 컬럼으로 내보내고, 반대로 가져올 때에는 하나로 합쳐서 원래의 키값을 복구해야 한다.
property_map:
- property: __key__
external_name: Activity
import_transform: transform.create_deep_key((‘Visit’, ‘Visit’), (‘VisitActivity’, ‘Activity’))
export:
- external_name: Visit
export_transform: transformhelper.extract_deep_key(1)
- external_name: Activity
export_transform: transformhelper.extract_deep_key(2)
이때 만약 키가 아니라 id/name 을 내보내고 싶다면, transform.key_id_or_name_as_string_n(N) 을 사용할 것.
구글 앱 엔진으로 개발하면서 학습한 내용을 정리합니다. 이론적으로 혹은 실제 서비스할 때 잘못되거나 틀린 부분이 있을 수 있음을 미리 알려 드립니다.
관계형 데이터베이스의 경우, 스키마 및 그 정합성을 검증할 책임이 데이터베이스 엔진에 있는 반면, 구글 데이터스토어는 분산 및 속도 향상을 위해, 스키마와 검증의 책임이 프로그램 쪽으로 넘어와 있다. 이게 무슨 뜻이냐 하면, 같은 모델(테이블)에 서로 다른 필드로 구성된 객체(Entity,열)가 들어갈 수 있다는 뜻이다. 따라서 데이터스토어는 임의의 데이터 집합을 쿼리해서 읽어왔을 때, 그 안에 어떤 데이터가 들어가 있을지 모르기 때문에, 마이그레이션 역시 프로그램이 책임지고 구현해야 한다. 새로 속성(Property,컬럼)을 추가하거나, 삭제하려면 해당 모델에 속한 모든 객체들을 찾아서 하나씩 변경해줘야 한다.
예를 들어, 어떤 모델의 속성이 더이상 필요없어져서 삭제하려면, 무려 소스 코드에서 모델의 부모 클래스를 바꿔서 저장한 후, 서버에 터미널을 열고 수동으로 해당 속성들을 지우고 다시 돌아와서 코드를 롤백하는 등의 수고로움이 필요하다.
이런 이유로, 가급적이면 앱엔진에서 서비스중에, 모델의 특정 속성 이름을 바꾸는 일은 피해야 한다.
관계형 데이터베이스에서는 서비스 이후에도 적당히 쿼리 최적화와 마이그레이션을 통해서 새로 트랜잭션을 추가할 수 있다. 그런데, 데이터스토어는 좀 다르다. 왜냐하면 일단 만들어진 객체의 키값은 절대 변경될 수 없는데, 트랜젹션으로 묶을 객체들은 생성 시점에 미리 같은 부모 객체를 지정해서, 같은 엔티티 그룹으로 만들어 줘야 하기 때문이다. 아마도 분산 트랜잭션을 피하기 위해서, 같은 그룹에 속한 객체들을 동일한(또는, 가급적 가까운?) 물리적인 노드에 두기 위함일테다.
또한 동시에 여러 명이 같은 데이터를 변경하는 등의 충돌을 방지하려면, 주의깊게 부모 엔티티를 고르거나 아예 이런 일이 없도록 설계하는 것이 중요하다. 이런 제약으로 인해, 개발 시점에 어떤 트랜잭션이 필요할 것인지를 미리 예측하고, 코드 레벨에서 부모 객체를 미리 만들어둬야 한다. 구체적인 예를 들자면, 어떤 플레이어에 속한 모든 아이템, 도전과제, 퀘스트 같은 하위 객체들은 플레이어를 부모로 가지는 게 정신 건강에 좋다는 정도가 되겠다.
이처럼 앱엔진 개발은 유지 보수의 책임이 데이터베이스가 아니라 프로그래머의 영역이 된다는 점을 항상 염두에 둬야 한다.
모든 객체는 시스템에서 유일한 키 값을 가진다는 점은 비교적 이해하기가 쉽다. 그런데, 키 이름(문자열)과 아이디(정수) 중 하나만을 가져야 한다는 점은 처음에 좀 헷갈렸다. 과연 어떤 객체에는 키 이름을 쓰고, 어디에는 아이디를 자동 생성해야 하는지가 할까? 처음엔, 해석을 잘못해서 키 이름이 시스템 전체에서 유일한 줄 알아서 많이 헤맸었는데, 알고보니 같은 모델(정확하게는 엔티티 그룹)안에서만 유일하면 된다는 것을 늦게서야 알게 되었다.
결론부터 말하자면, 내가 맞게 쓰고 있는 건지는 모르지만, 개발 시점에 미리 생성해둬야 하는 정적인 객체들(아이템 정보, 맵 정보, 밸런싱 데이터 등 유일성을 개발 시점에 파악할 수 있는 객체들)에게는 유일한 키 이름을 할당해서, 향후 빠른 변경 및 접근을 할 수 있게 하고, 런타임에 생성하기 때문에 유일성을 미리 파악하기 힘든 다른 수많은 객체들(계정, 플레이어, 아이템 인스턴스, 도전 과제 인스턴스 등)은 자동으로 아이디를 할당받게 하는 게 좋다.
그런데, 개발을 위해서 동일한 코드를 기반으로 서로 다른 app-id를 가진 구글 앱엔진 서버(Development - Test - Production)를 운영할 경우, 자동으로 만들어져서 아이디를 가진 객체들 - 특히나 ReferenceProperty 로 관계가 맺어진 넘들-을 다른 서버로 옮기는 것이 거의 불가능하다는 점에 유의해야 한다. 왜냐하면 객체의 키값에 이미 app-id 가 들어가 있기 때문에 CSV 등으로 백업해서 복원할 경우, 만약 기존에 동일한 id 를 가진 객체가 존재하면 그냥 덮어써버리기 때문이다. :(
ReferenceProperty 를 이용하면, 1:N 의 관계를 표현할 수 있다. 아래의 코드처럼
class Player(db.Model):
pass
class Item(db.Model):
player = db.ReferenceProperty(Player,collection_name="items")
과 같이 정의된 경우, player.items 라는 역참조가 자동적으로 만들어진다. 문제는 이게 코드를 읽을 때에는 왠지 캐싱이 될 것처럼 보이지만, 내부적으로 datastore_v3.RunQuery 라는 RPC가 호출되기 때문에 매번마다 다시 데이터스토어에서 읽어온다는 점이다. 무조건 appstat 을 활성화시켜 두고, 조금이라도 반응 속도가 느린 요청이 있으면 항상 확인하는 버릇을 길러야 한다.
반대로 이미 키값을 알고 있는 item 의 소유자 이름을 읽어오려면, item.player.name 처럼만 하면 된다. 그런데 내부적으로 datastore_v3.Get 요청은 특정 필드만 읽어오는 기능을 지원하지 않기 때문에, 항상 모든 속성들이 다 읽어지게 된다. 더욱 무서운 점은 그냥 item.player 나 item.player.key() 라고만 해도 Get 요청이 이루어진다는 거다. 만약 참조하는 객체의 하위 속성이 아니라 오직 키값만 알고 있으면 되는 경우에는 model_name.reference_prop.get_value_for_datastore(entity) 를 이용하면, 코드는 더러워지지만 불필요한 요청을 피할 수 있다.
특히 루프 안에서 참조키의 하위 속성을 읽는 짓은 가급적 피하는 것을 권장한다.
for item in Item.all().filter():
if item.player.hp == 0: # bad bad
pass
see also Compare-And-Set in Memcache by Guido
Query.fetch(limit, offset)을 이용하면 간단히 페이징 네비게이션을 구현할 수 있다.
으로 데이터를 읽어올 수 있다. 문제는 offset 이라는게 실제로 데이터스토어에서 읽으면서 skip 하기 때문에 느리다는 점. 이를 위해서 cursor 가 도입되었다.
result, end_cursor, more = ndb.Query.fetch_page(limit, start_cursor, end_cursor)
를 이용하면 start_cursor ~ end_cursor 사이의 아이템 limit 개를 읽어올 수 있다. 또한 더 읽어올 데이터가 있는지, 다음 번에 읽을 커서는 무엇인지도 쉽게 리턴해준다. offset 과는 달리 커서는 한번에 해당 객체까지 찾아가므로 보다 빠르게 페이징을 구현할 수 있다.
이때 커서는 urlsafe 문자열로 바꿔서 클라이언트로 보내서 다음번 요청때 사용하게끔 한다.
문제는 특정 커서로부터 이전 몇 개 - 예를 들면 fetch_page(-N, cursor) 같은 건 아직 지원하지 않는다. 따라서 next - next 이동은 간편한 반면 특정 시점에서 prev - prev 이동은 매번마다 이전 쿼리의 시작 커서를 알고 있지 않는 한 불가능하다는 점이다.
해결책이라면
name 은 유니크해야 한다. 이미 큐잉 되어 있으면 Already Exist, 실행된 적이 있으면 TombstonedTaskError 가 발생한다. 즉, 이미 실행되고 사라져도 시스템은 그걸 기억하고 있으며, 오직 내부적으로 중복 큐잉 및 실행을 방지하기 위한 거다. (또는 나중에 취소하기 위함)
queue.purge 를 실행해도 이름 자체는 남아있다. ㅎㅎㅎ
예전엔 --passin
파라미터를 이용하면 패스워드를 커맨드라인에 지정할 수 있었는데 어떻게든 패스워드가 노출되기 때문에 보안적으로 위험이 있었다. 요즘에는 --oauth2
를 이용해서 비교적 안전하게 인증을 할 수 있게 되었다.
계속 추가할 예정입니다 :)
css sprite 는 특정 DIV 에 CSS 스프라이트 클래스를 설정해서 배경 이미지를 보여주는 방식인데, 문제는 이전에 어떤 클래스 이름이 들어가 있는지 알 수 없다는 거다. 즉 정규식으로 클래스 이름을 검색해서 스프라이트 클래스만을 찾아서 지우고 새 스프라이트 클래스를 넣어야 한다.
.removeClass(function(index,class_str){
var class_list = class_str.split(' ');
for ( var i = 0 ; i < class_list.length ; i ++ ) {
var cls = class_list[i];
if ( cls.match(/unit-.+?_(L|R)/) ) {
return cls;
}
}
}
주의할 점은, 이 콜백 함수가 $.each 처럼 기존 클래스 명 하나하나마다 호출되는게 아니라, 그냥 공백으로 구분된 클래스 이름 문자열을 파라미터로 넘겨서 한번만 실행된다는 점이다. (index 는 왜 있는 거지;;;)