- adding-a-tabbarcontroller-as-the-subview-of-a-view
- uitabbarcontroller-view-position-and-size
- uitabbarcontroller-in-ios5
- adding-tab-bar-controller-interface-builder
- Sub-Views lifecycle methods are not called when UITabBarController inside a UIViewController
- In iTunes search for "WWDC 2011 Session Videos - HD" and go 27m:15sec into "Session 102 - Implementing UIViewController Containment"
Thursday, December 29, 2011
Add Tab Bar Controller in a View instead of a Window
Links used for research while experimenting:
Labels:
Tab Bar Controller,
UITabBarController,
UiView,
UIWindow,
view,
window
Monday, December 26, 2011
Friday, December 16, 2011
Use web analytics as a mirror for your users
The question on you mind may be: What do I possibly have to gain by exposing my website analytics to my users?
Well, if used simply & sparingly, it offers you a chance to engage your users by letting them satiate their curiosity about where they belong in the overall community that frequents the given website.
The following tools are polar opposites in how they decide to track the information that will be exposed but both offer the ability to expose the data and controls for which parts to expose:
Well, if used simply & sparingly, it offers you a chance to engage your users by letting them satiate their curiosity about where they belong in the overall community that frequents the given website.
The following tools are polar opposites in how they decide to track the information that will be exposed but both offer the ability to expose the data and controls for which parts to expose:
- SeeTheStats - hooks into your Google Analytics account to collect the data and will let you expose it in a controlled manner.
- Piwik - runs locally on your webserver to collect real-time data and expose it in a controlled manner. You can check this link to find hosts that offer automated installation of Piwik on your webserver. In my experience PowWeb is another host that does this well.
Labels:
analytics,
google,
piwik,
seethestats,
tools
Thursday, December 15, 2011
Saturday, December 10, 2011
Provision & Automate free Amazon EC2 instances with Hosted Chef
- Sign-Up with Amazon via this link and they will offer one year of usage (micro instance) to help you understand and evaluate EC2. Read their terms of usage carefully and make sure to only provision the free instances if you don't want to get dinged on costs due to ignorance or carelessness on your own part.
- Now the free instance image provided by Amazon happens to run RHEL4 but the out-of-the-box Chef-Client requires a higher version of that OS! Luckily some, if not all, of the very same issues have already been tackled in a post on the opscode's help website. And here's a gist of my experience with it as well. In case it is difficult to follow, let me highlight the fact that the modified file that allows you to properly finish the chef-client installation process on the AWS image for Linux was found here: CHEF-2515
-
Some Notes:
- Either command works well:
- knife ec2 server create -x ec2-user -i /Users/xxx/.ec2/ec2.pem --template-file ~/chef-repo/.chef/bootstrap/centos5-gems-modified.erb
- knife ec2 server create --ssh-user ec2-user -i ~/.ec2/ec2.pem -d centos5-gems-modified
-
I find it crucial to configure the appropriate Amazon security group as part of the instance creation otherwise its ends up using the "default" group. To do this, add it to your "knife ec2 server create" command with "-G groupName" and remember that it can be a comma separated list of security groups "-G secGroup1,secGroup2" if you want. Example:
- knife ec2 server create --ssh-user ec2-user -i ~/.ec2/ec2.pem -d centos5-gems-modified -G elasticsearch
- Either command works well:
Monday, December 5, 2011
EC2 Bare Minimum
- install ec2 tools
cd ~/.ec2
ssh -i /Users/XXX/.ec2/ec2.pem ec2-user@ec2-XXX.compute-1.amazonaws.com
sudo yum install screen (why?) - For moving data to & from the EC2 instance, simply use scp which runs on top of ssh like so:
- A lot of images will flunk all ssh connection attempts! It seems like this has nothing to do with the permissions on the pem file that you use to connect to them or opening up port 22 by default in their security policies. They just don't work even after all that, all the attempts spit out:
- either, Permission denied (publickey,gssapi-keyex,gssapi-with-mic)
- or, Net::SSH::AuthenticationFailed
- ami-31d41658, Red Hat Enterprise Linux 6.1, x86_64
- ami-8f4083e6, RHEL-5.6-Starter-EBS-x86_64-11-Hourly2
Monday, November 14, 2011
Ideas for improving GitHub's Gist
Lets list and acknowledge some of the work that has already started happening around GitHub's Gist when it comes to using it for embedding code snippets in blogs:
-
Loading the Gist through an iFrame to shorten page load times.
- A nice side effect is the ability to get around any non-working Blogger templates like the Dynamic Template that was recently released but doesn't work with an embedded Gist (remark date: Nov. 14th 2011).
-
Tell the script to provide line numbers.
- I haven't experimented with it yet, but this looks like a promising solution: http://got-ravings.blogspot.com/2010/06/line-numbers-in-embedded-gists.html
- And another one: http://www.whyhat.com/2012/01/line-numbers-on-embedded-gists/
- Allow a line-start/offset and line-end/length attribute for the embedded script so that users can have one large file and then simply talk about the chunks in sections. This will remove the need to break up the original large Gist into multiple individual Gists.
- Tell the script the array or range of line numbers to highlight.
- The following Gist will look for a div with the id of "foo" and place the script's formatted content in there. It is an actual example of the code running on this blog page to perform basic highlighting:
Labels:
embed,
Gist,
github,
syntax highlighter
Saturday, November 12, 2011
FAIL: Installing Simon Listens on Mac
- Mac OS X 10.6.8 on MacBook Pro with an Intel Core
- Download and unzip Simon Listens, then:
- You will run into errors like kde and cmake are missing.
- Instructions for installing KDE on a mac (cmake is included) are available here.
- I chose to perform the MacPorts based installation. The following command takes a long time to run.
sudo port -v install kdesdk4
I waited an hour before I simply decided to leave the MacBook running overnight. So I don't know the total time it ended up taking.
- I was warned that macports in general is a bit out of date, so I decided to update that too.
sudo port selfupdate
- I was also warned that some of the existing Macports installs may be outdated, so I fixed that as well. Though I wonder if this is what caused the overall experiment to fail in the end?
sudo port upgrade outdated
- I decided to run some extra/redundant commands in order to make sure that the kdesdk4 really did install its counterparts:
sudo port -v install cmake sudo port -v install qt4-mac sudo port -v install kdelibs4
All of them were already installed so I was satisfied with the sanity test.
- I pasted whatever I was instructed to:
cd ~ mkdir bin vi bin/findup
- I decided to append-to/edit the .bash_profile instead of creating .profile file. And once again I pasted whatever I was told to:
chmod 777 bin/findup vi .bash_profile
- Performed all the steps leading up to the checkout as instructed but did the checkout over a non-SSL (cleartext) channel:
mkdir -p ~/Desktop/kde/build mkdir -p ~/Desktop/kde/home mkdir -p ~/Desktop/kde/inst mkdir -p ~/Desktop/kde/src mkdir -p ~/Desktop/kde/src/branches mkdir -p ~/Desktop/kde/src/trunk vi ~/kde/src/trunk/.my-setup cd ~/kde/src/trunk/ env svn co svn://svn.kde.org/home/kde/trunk/kdesupport
- Performed the actual build and created a directory that didn't exist but I had put-off creating because I thought perhaps the KDE build/installation process would create it:
cd kdesupport cmakekde sudo chown -R $USER ~/Library/Preferences/KDE mkdir ~/Library/Preferences/KDE sudo chown -R $USER ~/Library/Preferences/KDE/ sudo launchctl load -w /Library/LaunchDaemons/org.freedesktop.dbus-system.plist launchctl load -w /Library/LaunchAgents/org.freedesktop.dbus-session.plist
- This time the build for Simon Listens went much further but ultimately it failed!
cd simon-0.3.0 ./build.sh
- I lost the link but somewhere on the web it was suggested that the errors I saw in the build will happen when binary files have been built for a 32-bit on a 64-bit system! And the way to get around it was by passing some arguments to let Simon's build script skip this stuff. But I found way too many configure files and couldn't make heads or tails of which one to use and how to tell it do what I need to workaound the problem.
find ./ -name configure <6-7 results all in different nested directories>
cd simon-0.3.0 grep "enable-charconv" julius/julius/* vi julius/julius/configure ./build.sh --enable-charconv=no vi julius/julius/configure.in ./build.sh
- The build error seemed to have the word iconv mentioned in a bunch of places so I decided to figure out which MacPorts install was part of the problem and remove them:
port list | grep iconv sudo port uninstall libiconv @1.14_0 sudo port uninstall libiconv @1.14_0+universal
But then the one being used (libiconv @1.14_0+universal) couldn't actually be removed because there were dependencies present!
- I also found out that that Mac actually ships with a 64-bit flavor of libiconv by default and I was able to locate the said files on my system:
find /usr/lib -name libiconv* /usr/lib/libiconv.2.4.0.dylib /usr/lib/libiconv.2.dylib /usr/lib/libiconv.dylib
- Found some clues on how to try out the Mac's system libiconv library as a fallback without uninstalling MacPorts' version of the lib ... but that too didn't help the Simon build work because now the Mac's system libiconv couldn't agree on version #s with some of the other MacPorts based libs ... so I reverted that trial change as well:
mkdir ~/temp sudo mv /opt/local/lib/lib sudo mv /opt/local/lib/libiconv* ~/temp/ ./build.sh sudo mv ~/temp/libiconv* /opt/local/lib/
- Finally I decided to remove all the clutter that had been placed on my system during this FAIL-ed attempt to the best of my abilities:
sudo port uninstall kdesdk4 @4.7.3_0 sudo port uninstall cmake sudo port uninstall --follow-dependents qt4-mac sudo port uninstall --follow-dependents kdelibs4 cd ~ rm -rf kde/ rm -rf ~/Library/Preferences/KDE vi /Library/LaunchDaemons/org.freedesktop.dbus-system.plist launchctl list launchctl list | grep dbus launchctl remove org.freedesktop.dbus-session launchctl list | grep dbus cd ~ rm -rf bin/ vi .bash_profile
cd simon-0.3.0 ./build.sh
Labels:
libiconv,
mac,
simon listens,
voice recognition
Wednesday, November 2, 2011
Image Editing Toolset for Mac
All of the following are free and available on the Mac:
- Grab - Awesome tool for taking snapshots of on screen material.
- iPhoto - Decent~ish for cropping.
- Preview - Pretty good for cropping. You can follow the instructions given here. Hint: Select what should remain then press "cmd+k" to crop out the rest.
-
Gimp
- Great for smudging out unwanted portions of existing images.
- Converting regular icons into those that look disabled
How to build a webapp with CouchApp and CouchDB
This is a chronicle of the additional work I had to do as I followed Ed Parcell's Tutorial: Using jQuery and CouchDB to build a simple AJAX web application. It is supposed to serve as a supplement.
Ed's post covers the use of CouchApp as far as generating templates to work on is concerned. As a supplement, my post will leverage the Evently framework (already a part of the generated CouchApp 0.8.1) to do the same exact thing.
Ed's post covers the use of CouchApp as far as generating templates to work on is concerned. As a supplement, my post will leverage the Evently framework (already a part of the generated CouchApp 0.8.1) to do the same exact thing.
- When asked to edit index.html for the first time in Ed's post, here is how the content will differ if you want to do things the Evently way:
<!DOCTYPE html> <html> <head> <title>Address Book</title> <link rel="stylesheet" href="style/main.css" type="text/css"> </head> <body> <h1>Address Book</h1> <div id="add"><button type="button" id="add">Add</button></div> <div id="addressbook"></div> </body> <script src="vendor/couchapp/loader.js"></script> <script type="text/javascript" charset="utf-8"> $.couch.app(function(app) { $("#addressbook").evently("addressbook", app); }); </script> </html>
- Lets take a moment to acknowledge all the differences:
- Links to json2.js, jquery.js or jquery.couch.js are not present in the head section. This is now taken care of by loader.js (view source +/-), which loads all of the required javascript files.
function couchapp_load(scripts) { for (var i=0; i < scripts.length; i++) { document.write('<script src="'+scripts[i]+'"><\/script>') }; }; couchapp_load([ "/_utils/script/sha1.js", "/_utils/script/json2.js", "/_utils/script/jquery.js", "/_utils/script/jquery.couch.js", "vendor/couchapp/jquery.couch.app.js", "vendor/couchapp/jquery.couch.app.util.js", "vendor/couchapp/jquery.mustache.js", "vendor/couchapp/jquery.evently.js" ]);
- If you took note of the relative URLs that start with _utils then you should realize that the JS files are being referenced from another application known as Futon which serves as CouchDB's Admin Interface.
- Now this may cause some concerns about exposing Futon to your user populace!
- As a workaround you can always move such files to your own CouchApp, such as the AddressBook webapp that we are working on right now and then simply change these links.
- Based on what I read (somewhere on the web) if folks really want to then they can simply host their own Futon and point it to your CouchDB server! I admit that this is hearsay and I haven't really experimented with such a hack myself. But overall what I personally took away from all this ... is that removing Futon from your CouchDB is not really what secures it, rather setting up proper accounts so that login is required to perform non-read operations is what truly makes an instance secure.
- An additional script section:
$.couch.app(function(app) { $("#addressbook").evently("addressbook", app); });
is used to wire-up the addressbook div element with an evently widget (also named addressbook but the names don't really have to match, its not a requirement) which we will be writing later in this tutorial.
- Links to json2.js, jquery.js or jquery.couch.js are not present in the head section. This is now taken care of by loader.js (view source +/-), which loads all of the required javascript files.
- The Evently approach also mandates a rewrite of Ed's refreshAddressbook function such that the html and javascript are placed in separate files and tied together via the use of the Mustache template engine. So instead of injecting any more javascript into your index.html, follow these steps:
- Navigate to the addressbook directory, which was generated by couchapp for you when you ran, in the past:
couchapp generate app addressbook cd addressbook
- Create an evently widget named addressbook by simply creating the appropriate directory structure:
mkdir -p evently/addressbook/
- Create _query.js file to hold the view which should be loaded from CouchDB when the addressbook div element is hooked-up with the addressbook widget (refer to line # 19 in index.html source provided earlier):
function () { $.log('Inside evently/addressbook/_init/query.js'); return { "view" : "phonenumbers", }; }
- Create data.js file to process the data returned by CouchDB which will be available for the Mustache template engine's use:
function(data) { $.log('Inside evently/addressbook/_init/data.js'); $.log(data.rows); return { "addressbook" : data.rows }; }
- Create mustache.html file to substitute the data and render the html directly into the addressbook div element:
{{#addressbook}} <div class="address"> <span class="name">{{key}}</span> <span class="phonenumber">{{value}}</span> <a href="#edit" class="edit">edit</a> <a href="#delete" class="delete">delete</a> </div> {{/addressbook}}
- Uplaod the app:
couchapp push
- Before we go any further let's test what we've written so far. You'll notice that I've thrown in logs and alerts into the javascript above. If all is well then we should see an alert pop up with the data being retrieved from the server. Also if you have debugging tools like Firebug or Web Inspector at your disposal, then you should be able to see the data printed out in the console.
- Safari and Firefox on my mac work well and I hope that you too see a popup stating [object Object] and your console output looks something like:
Entering view callback in evently/addressbook/_init.js jquery...util.js (line 3) Object { total_rows=2, offset=0, rows=[2]} jquery...util.js (line 3) Exiting view callback in evently/addressbook/_init.js
- B U T ... if you head over to your iPhone's safari browser and try the same thing, you may be in for a huge disappointment! Nothing will happen. And if you enable the Debug Console, you'll see javascript errors stating that jQuery does not exist!
- If you run into this then based on the wisdom gleaned from this post on stackoverflow, here's what you need to do:
- Get all the supporting JS files listed under the _utils URL to be directly present in your own addressbook couchapp:
cd vendor/couchapp/_attachments/ wget http://fermyon.iriscouch.com/_utils/script/jquery.couch.js wget http://fermyon.iriscouch.com/_utils/script/sha1.js wget http://fermyon.iriscouch.com/_utils/script/json2.js wget http://fermyon.iriscouch.com/_utils/script/jquery.js
- Update loader.js to serve the files out of your own addressbook couchapp:
vi vendor/couchapp/_attachments/loader.js
... couchapp_load([ "vendor/couchapp/sha1.js", "vendor/couchapp/json2.js", "vendor/couchapp/jquery.js", "vendor/couchapp/jquery.couch.js", "vendor/couchapp/jquery.couch.app.js", "vendor/couchapp/jquery.couch.app.util.js", "vendor/couchapp/jquery.mustache.js", "vendor/couchapp/jquery.evently.js" ]);
- Get all the supporting JS files listed under the _utils URL to be directly present in your own addressbook couchapp:
- If things are still breaking then there is one other analysis & workaround for this dilemma presented here in a blog post by Lee Boonstra.
- If you are still having issues then use the Safari on your Mac's iOS Simulator to browse the couchapp because it gives more detailed information in its Debug Console than the actual browser on the iPhone. If you are still seeing something like the following image:
Then you still haven't fixed the problem as described in the steps above so go back and make sure you follow all the instructions to get it resolved.
- If you run into this then based on the wisdom gleaned from this post on stackoverflow, here's what you need to do:
- Safari and Firefox on my mac work well and I hope that you too see a popup stating [object Object] and your console output looks something like:
- Now lets finish off editing _init.js:
put code here...
- Navigate to the addressbook directory, which was generated by couchapp for you when you ran, in the past:
- mustache stuff
Monday, October 31, 2011
Hacking Weebly: PowWeb's Drag & Drop Builder
Background
- What led to the Weebly hack-a-thon?
I'm a big fan of PowWeb because of their unlimited bandwidth and storage plan which doesn't leave me guessing. Also the simplicity of additional software installation (powered by SimpleScripts) puts even the biggest Software-As-A-Service (SAAS) players in the market to shame.
When I was charged with hosting a website on PowWeb, I decided to give their wheel-of-fortune a big'ol whirl. In the beginning these were my top choices to try and build a website:
- Joomla
- Drupal
- ocPortal
- Xoops
- What had kept me from using Weebly in the first place?
- Popups that kept asking me to pay-up for any drag & drop component that was even remotely useful for building a meaningful website. For example: being able to embed a simple video.
- I thought that any of the free and fully-featured Content Management Systems (CMS) available on PowWeb would easily beat anything Weebly had to offer. This was not true.
- What keeps my love & "ughhh" relationship with Weebly going?
- Love: The have a lot of great looking templates to offer. Really really nice stuff in my opinion when one is stating from scratch and couldn't even color inside the lines in a picture book with crayons to save one's life.
- Ughhh: Only very few of the templates follow the basic-design principles like:
- Provide a way for the user to clearly & qucikly distinguish which page they are on.
- Most of the templates had tab bars to navigate through the website but after clicking their was no indication of what was selected. There was no change in the text font, no change in the background color of the selected/clicked item of the navigation bar and there are no breadcrumbs anywhere.
- Do not force the user to scroll vertically.
- If there might not be any content that takes up the space, then what's the point of enforcing a fixed-height or min-height template?
- Don't add banner images/placeholders that cause unnecessary scrolling.
- I could not understand why the templates wouldn't allow the banner placeholders to be removed or resize them if a thinner banner image was provided.
- Provide a way for the user to clearly & qucikly distinguish which page they are on.
- Ughhh: A prototype website cannot be built with the palm-open and finger-twiching-for-cash like-a-bellboy approach that their pop-ups have going.
- Love: Someone in the Weebly camp understood that it takes time for any developer to truly get behind a technology via trial & error PLUS convince all vested parties that it is worth forking over cash for ... so they threw in the "Custom HTML" widget in the free goodies bag. This allows hackers to put together a presentable website and then perhaps if their is value in everyone being able to edit the content easily, you take away the hacks, pay for the service, and use the simple drag & drop components that allow anyone to jump in and make edits without having the web-dev know how.
Lets Hack!
- How can images be replaced if the Weebly template does not allow it?
- Publish the Weebly website as-is and use the "View Source" option in your browser.
- Search for the image in the source and if it is directly embedded in the html, then you can jot down the "id" of the "img" element and change the "src" attribute via a javascript call placed in the "Custom HTML" element. You'll have to add it to your pages in the Weebly editor.
- If instead of the image, their is a placeholder element like a "div" in the html then jot down the "id" of that "div" element as it is probably being styled via one of the Weebly template's CSS files. Once you locate the CSS responsible, you will know which parts you want to override yourself via javascript and a "Custom HTML" widget in the Weebly editor. Here's an example:
- How can expandable/collapsible (+/-) areas be added?
- Add a "Custom HTML" widget to your page in the Weebly Editor. Add the following to load the jQuery javascript library without conflicting with other libraries like Prototype which the Weebly template may already be loading on its own:
- Place a div around the toggle-able section, give it an id, and hide it by default. Then use jQuery to show/hide the content when the user clicks on a button or text that you provide as the control.
Click me to control show/hide.
The div wrapped content will be shown/hidden at your whim.
- Add a "Custom HTML" widget to your page in the Weebly Editor. Add the following to load the jQuery javascript library without conflicting with other libraries like Prototype which the Weebly template may already be loading on its own:
Thursday, October 20, 2011
HTML5 vs. Native Mobile Apps
Technologies that are enabling HTML5 to either deploy to multiple mobile platforms or keep-up with the native look:
- PhoneGap
- Sencha Touch
- jqTouch
- jQuery Mobile
- There is a minor issue in iOS that doesn't properly set the width when changing orientations with these viewport settings, but this will hopefully be fixed a a future release.
- It's not currently possible to deep link to an anchor (index.html#foo) on a page.
Monday, October 17, 2011
ElasticSearch and CouchDB: Match made in heaven?
- In ElasticSearch (ES):
each indexed document is given a version number. This version number can be supplemented with an external value (for example, if maintained in a database). To enable this functionality, version_type should be set to external.
Sounds nice, primed for CouchDB, right? But:
The value provided must be a numeric, long value greater than 0, and less than around 9.2e+18.
The was CouchDB does versioning, it isn't numeric because it appends two numbers to create a sequence/version, for example:1-1234567890
So how is this handled in case of a CouchDB stream for ES? - How does ES facilitate the generation of an "_id" based on the data in the incoming document?
- Does it allow to take the value of a field of that document? For example, there is a way to perform "_routing" via one of the incoming document's fields for distributed indexing across shards. So what about something for picking out the id?
- Does it allow to concatenate values of multiple fields of that document?
- Same question as the one above for CouchDB.
- TBD
Sunday, October 9, 2011
Scalability Madness
CouchDB | CouchDB-Lucene | CouchIO / CouchOne / CouchBase | Solr | Elastic Search | MongoDB | BigCouch | |
---|---|---|---|---|---|---|---|
Full-Text Search | No | Yes | ? | Yes | Yes | May Be with Photovoltaic | ? |
Distribute-able | Yes | ? | ? | Yes | Yes | ? | ? |
Distribute-ed | ? | ? | ? | No | Yes | ? | ? |
Schema-less | Yes | ? | ? | ? | Yes | ? | ? |
Tools for Importing Data | ? | ? | ? | Yes | ? | ? | ? |
Comments | ? | Does it go Toe to Toe against all the features of Lucene exposed by Solr? | best for HTML5 dev? | it is distributable but not distributed. SolrCloud has very few features | Compass got punted to invent Elastic Search. | ? | ? |
Labels:
compare,
CouchDB,
ElasticSearch,
scalable,
Solr
Wednesday, October 5, 2011
Splitting up large XML data files for use with DIH in Solr
It is ridiculously beneficial to split up XML files if you will be using Solr's Data Import Handler (DIH) to process the data. I personally saw an improvement from a speed of 166 entries/minute to 30860 entries/minute after splitting up all the large XML data files into an individual file for every entity that is to become a lucene document in Solr.
It was only on a whim but the script that allowed me to experiment with this and yield the desired results was found here:
So if your file looks something like:
Then all the items from 1 to 19,999 will be divided up by this script into idividual files named row1.xml, row2.xml ... row19999.xml and look like:
It was only on a whim but the script that allowed me to experiment with this and yield the desired results was found here:
awk '/<item>/{close("row"count".xml");count++}count{f="row"count".xml";print $0 > f}' *.xml
So if your file looks something like:
- ...
Item 1 Description 1 ...Item 20000 Description 20000 ...
Then all the items from 1 to 19,999 will be divided up by this script into idividual files named row1.xml, row2.xml ... row19999.xml and look like:
But the last (20,000-th) item will have a trailing tag:Item N Description N ...
<item>
<title>Item 20000</title>
<description>Description 20000</description>
...
</item>
</items>
If you have processed 10 files, each with 20000 entries using the splitter command mentioned above ... then basically every 20000, 20000*2 ... 20000*10 numbered file will need to have the trailing tag deleted from it. To that end, the following script can be edited by providing the # of original files in the while loop's comparison statement:
#!/bin/sh if [ $# -eq 0 ] then echo "Error - Number missing form command line argument" echo "Syntax : $0 number" echo " Use to print multiplication table for given number" exit 1 fi n=$1 i=1 while [ $i -le 10 ] do echo "sed -ibak '/items>/d' row`expr $i \* $n`.xml" sed -ibak '/items>/d' row`expr $i \* $n`.xml i=`expr $i + 1` doneAnd then running the script by passing it the # of the last entry (20000-th):
./sanitize.sh 20000 sed -ibak '/items>/d' row20000.xml sed -ibak '/items>/d' row40000.xml sed -ibak '/items>/d' row60000.xml sed -ibak '/items>/d' row80000.xml sed -ibak '/items>/d' row100000.xml sed -ibak '/items>/d' row120000.xml sed -ibak '/items>/d' row140000.xml sed -ibak '/items>/d' row160000.xml sed -ibak '/items>/d' row180000.xml sed -ibak '/items>/d' row200000.xml
Import Dynamic Fields from XML into Solr via DIH
Given an XML file that needs to be imported into Solr, you may often run into some uncommon data values that would be:
Off the cuff, one may be at a loss on how exactly to accomplish this ... and in doubt if it can even be done! You've seen this done for databases with Data Import Handler (DIH) but not with the XPath handler, or URL datasource, or File datasource for XML.
Well it can be done and here's an example:
- best grouped together under the banner of some dynamic field defined in schema.xml file,
- with their mapping left up to the discretion of an admin tweaking the data-config.xml file, just before running DIH.
Off the cuff, one may be at a loss on how exactly to accomplish this ... and in doubt if it can even be done! You've seen this done for databases with Data Import Handler (DIH) but not with the XPath handler, or URL datasource, or File datasource for XML.
Well it can be done and here's an example:
- Lets say your XML file looks something like this:
hammer tough and durable heavy 2 inches nail sharp and thin hazard 1 inch - Now disposition, width, dangerLevel, height are pieces of data that you may not be able to plan ahead for, in your schema.xml file. So instead it makes sense to leave some wiggle room by defining somewhat predictable dynamic fields like so:
Keep in mind that you have some flexibility and responsibility here in terms of choosing the type of the dynamic field ahead of time.
- Now when your customers or customer-facing-admins who are handling the data-import.xml file and will be looking to kick-off DIH against an XML file that they know best ... it will be quite an easy for them to come up with something like the following on the spot:
and still have an agreed upon well-oiled working index at the end of the day.
Labels:
Data Import Handler,
DIH,
dynamic,
dynamicField,
field,
Solr,
xml
Monday, October 3, 2011
Embedding Videos in Joomla 1.7
- Log in as the administrator
- Hover over the Extensions drop-down and click on Extension Manager
- In the Install from URL section, paste the URL pointing to a zip file for the AllVideos Joomla plugin.
- http://joomlaworks.googlecode.com/files/plg_jw_allvideos-v4.0_j1.5-1.7.zip
- Once you see a notification on screen for a successful installation, click on the Manage tab
- Locate the row that lists the AllVideos plugin and click on the red status icon in order to toggle it to enabled.
- Hover over the Extensions drop-down and click on Plugin Manager
- Locate the row that lists the AllVideos plugin and click on the title of the plugin itself.
- Configure the plugin based on your needs.
Sunday, October 2, 2011
Screencast Toolset
Best toolset that I've found for working on the Mac with screencasts:
- ScreenR for recording.
- Jing for recording.
- SimpleMovieX for merging.
- The videos merged using this tool will not work as intended on Vimeo or YouTube. They will stop at the very first location that was stitched together.
-
Final Cut Pro for merging.
- The videos merged using this tool can be seamlessly uploaded to top providers like YouTube and everything in the video works as intended. But the content may show up as Public by default! So make sure to secure your content afterwards.
Labels:
mac,
screencast,
tools,
utilities
Monday, September 19, 2011
Compound primary key for Solr's Data Import Handler (DIH)
If you ever find yourself with a datasource where you need to concatenate multiple columns or values to form the primary or unique key ... but hasn't been already done for you ... then you can do so on-the-fly with Solr's DIH using the TemplateTransformer like so:
Labels:
compound,
concatenate,
Data Import Handler,
DIH,
key,
primary,
Solr,
unique
Wednesday, September 14, 2011
Import data from Amazon RSS feeds into Solr
- For this example, lets use the RSS feed for new products that have been tagged as blu-ray, here's the URL:
- http://www.amazon.com/rss/tag/blu-ray/new/ref=tag_rsh_hl_ersn
- If you are using Firefox, you may not get a chance to view the feed in its raw xml format as the browser tends to actually interpret and present most of the RSS feed in a user-friendly fashion. This is not very desirable for developers or adminstrators. In order to view the raw content of the feed, you can simply view the same URL via Feed-Proxy: http://persistent.info/cgi-bin/feed-proxy?url=http%3A%2F%2Fwww.amazon.com%2Frss%2Ftag%2Fblu-ray%2Fnew%2Fref%3Dtag_rsh_hl_ersn
- Refer to the following links for info on how to get the RSS feeds you want from Amazon:
- Before we import any data into Solr, lets take a moment to understand the format of the data from the RSS feed. Here's a sample item from the RSS feed:
- There are 5 basic fields per item: title, guid, link, pubdate and description.
- If we take a closer look at the HTML CHUNK inside description, we will find more information like:
- the image URL
- the price for a new item
Buy new: $10.99
- the price of used items starting from a lowerbound:
22 used and new from $6.35
- In fact, the HTML Chunk is like a complete webpage, it even has a separate description section inside of itself!...
- All this is rather messy and unpredictable (sometimes its there, sometimes its not, sometimes its a class, sometimes its an id, sometimes there are duplicates) but we must try to make the best of it.
- the image URL
- Having understood the complexities of our source of data, we are now ready to configure the Data Import Handler (DIH) for Solr.
- Navigate to the directory which has the out-of-the-box sample core for configuring RSS feeds. And edit the rss-data-config.xml file for importing data to look as follows:
cd /trunk/solr/example/example-DIH/solr/rss vi conf/rss-data-config.xml
- Navigate to the directory which has the out-of-the-box sample core for configuring RSS feeds. And edit the rss-data-config.xml file for importing data to look as follows:
- In order to grab and add the price from the description:
- add the price as a field to the schema.xml file
cd /trunk/solr/example/example-DIH/solr/rss vi conf/schema.xml
- add the RegexTransformer to the chain of transformers in the rss-data-config.xml file
cd /trunk/solr/example/example-DIH/solr/rss vi conf/rss-data-config.xml
... - add the regex to find and extract the price
cd /trunk/solr/example/example-DIH/solr/rss vi conf/rss-data-config.xml
Please keep in mind that this is NOT the best regex that you can use. This one simply grabs the last set of digits with a dollar sign in front of them in the HTML, so it may grab the used price instead of the new price! Feel free to come up with a better regex for yourself.... - If there are any items where the regex is incapable of pulling the price due to malformed HTML or any other reason, you can choose to either skip those items and not add them to Solr. OR you can introduce your own dummy price for them. Here's how you may do so:
- add the use of the ScriptTransformer function you'll define into the chain of transformers in the rss-data-config.xml file
cd /trunk/solr/example/example-DIH/solr/rss vi conf/rss-data-config.xml
... - add the following script to rss-data-config.xml file if you want to skip the items where a price wasn't available or couldn't be deduced
cd /trunk/solr/example/example-DIH/solr/rss vi conf/rss-data-config.xml
... - add the following script to rss-data-config.xml file if you simply want to inject a dummy price
cd /trunk/solr/example/example-DIH/solr/rss vi conf/rss-data-config.xml
...
- add the use of the ScriptTransformer function you'll define into the chain of transformers in the rss-data-config.xml file
- add the price as a field to the schema.xml file
- The following can be added to pull out the image URL:
... - The following can be added to pull out the date (SimpleDateFormat was used as a reference to devise a format string to parse the incoming dates from the feed such as Mon, 12 Sep 2011 21:14:23 GMT):
... - Start the Solr server:
cd /trunk/solr/example java -Dsolr.solr.home="./example-DIH/solr/" -jar start.jar
- Navigate to the following URL
http://localhost:8983/solr/rss/admin/dataimport.jsp?handler=/dataimport
- Click the Full Import with Cleaning button to import data from Amazon RSS feed into Solr.
Junk / Errata
url="http://www.amazon.com/rss/tag/blu-ray/new/ref=tag_rsh_hl_ersn" regex=".*Buy new.*span.class..tgProductPrice...(\d*.\d*)" stripHTML="true"
Labels:
amazon,
Data Import Handler,
DIH,
RSS,
Solr,
transformer
Saturday, September 10, 2011
Multicore master-slave replication in Solr Cloud
- If you've already done some work with Solr Cloud then you may want to start fresh by cleaning up any previous ZooKeeper configuration data in order to run this example exercise smoothly.
cd /trunk/solr/example/solr rm -rf zoo_data
- We will create the following setup:
- there will be 2 Solr instances, each with 3 cores
- 1 of the 3 cores will be a master and the other 2 will be slaves
- the slaves of one instance will be configured to use the master of the other one
- The infrastructure will look like:
- Solr-Instance-A
- master1 (indexes changes for shard1)
- slave1-master2 (replicates changes from shard2)
- slave2-master2 (replicates changes from shard2)
- Solr-Instance-B
- master2 (indexes changes for shard2)
- slave1-master1 (replicates changes from shard1)
- slave2-master1 (replicates changes from shard1)
- Solr-Instance-A
- We can reuse the multicore directory of the out-of-the-box example. It already has the core0 and core1 directories, lets create an additional core:
cd /trunk/solr/example/multicore cp -r core0 core2
- If we were NOT using Solr Cloud which has us upload an universal configuration at startup, then we would perform the following sub-steps:
Replace any mention of core0 with core2
sed -ibak 's/core0/core2/g' core2/conf/solrconfig.xml sed -ibak 's/core0/core2/g' core2/conf/schema.xml sed -ibak 's/zero/two/g' core2/conf/schema.xml
- Edit solr.xml as follows:
- Copy over the example's zoo.cfg from the single-solr setup over to the multicore setup:
cd /trunk/solr/example cp ./solr/zoo.cfg ./multicore/
- Copy example to example2 in order to create another Solr instance
cd /trunk/solr cp -r example example2
- Edit solr.xml for example2 as follows:
cd trunk/solr/example2/multicore vi solr.xml
- So where is the configuration that we will we be uploading to ZooKeeper? And what should we edit? Well, the most well formed configuration is sitting in the out-of-the-box single core example so let us simply upload it from there to ZooKeeper! And have it configured such that it can be applied conditionally to all our cores based on the java params that we specify at startup!
- Let us begin by editing the solrconfig.xml file of the single solr core example as follows:
cd /trunk/solr/example/solr/conf vi solrconfig.xml
${enable.master:false} commit startup schema.xml,stopwords.txt ${enable.slave:false} http://${masterHost:localhost}:${masterPort:8983}/solr/${masterCoreName:master1}/replication 00:00:60 - We cannot pass the true/false values via -Denable.master or -Denable.slave at startup because it will end up applying globally to all the cores (1 master & 2 slaves) and there isn't a way to start only one core at a time from the command line. So we must leverage each individual multicore's solr.xml to provide core specific values as follows:
cd /trunk/solr/example/multicore vi solr.xml
- Now let us start the 1st instance of the multicore Solr with the appropriate java params and let ZooKeeper know exactly where to get its universal-config (bootstrap_confdir) from:
cd /trunk/solr/example #java -Dbootstrap_confdir=./solr/conf \ # -Dsolr.solr.home=multicore \ # -DmasterHost=localhost -DmasterPort=7574 -DmasterCoreName=master2 \ # -DzkRun \ # -jar start.jar java -Dbootstrap_confdir=./solr/conf -Dsolr.solr.home=multicore -DmasterHost=localhost -DmasterPort=7574 -DmasterCoreName=master2 -DzkRun -jar start.jar
- Start the 2nd instance of the multicore Solr with the appropriate java params:
cd /trunk/solr/example2 #java -Djetty.port=7574 \ # -DhostPort=7574 \ # -Dsolr.solr.home=multicore \ # -DmasterHost=localhost -DmasterPort=8983 -DmasterCoreName=master1 \ # -DzkHost=localhost:9983 \ # -jar start.jar java -Djetty.port=7574 -DhostPort=7574 -Dsolr.solr.home=multicore -DmasterHost=localhost -DmasterPort=8983 -DmasterCoreName=master1 -DzkHost=localhost:9983 -jar start.jar
- Let us begin by editing the solrconfig.xml file of the single solr core example as follows:
- Now, you can check the ZooKeeper status here:
http://localhost:8983/solr/master1/admin/zookeeper.jsp
And that's all there is to it, feel free to leave any feedback as comments below.
Labels:
Cloud,
master,
multicore,
replication,
slave,
Solr,
Solr Cloud,
ZooKeeper
Friday, September 9, 2011
My Solr Cloud Wishlist
- Add code in Solr such that the admin may configure a limit on how many documents is way too many to hold in single Solr core and kick-off an automated process to:
- Either, CREATE another core (same or separate machine?) and add it to the ZooKeeper configuration with a weight that signifies that all new addition should happen to this new core's index only. Though I wonder how an update (delete+add) would work?
- Or, begin sharding the existing core. This could be done by CREATE-ing a copy of it (core_copy) and distributing the index into two shards (core_shard1,core_shard2) using a scheme/policy that does so in a best-effort manner such that the scoring wouldn't get thrown off by too much due to each individual shard's differing IDF. Then SWAP the two sharded-cores in as replacement for the overloaded core. What would happen to any changes made during this process?
- Either, CREATE another core (same or separate machine?) and add it to the ZooKeeper configuration with a weight that signifies that all new addition should happen to this new core's index only. Though I wonder how an update (delete+add) would work?
Setup Solr master-slave replication with ZooKeeper
Reading Chapter 9: Scaling Solr from the book Solr 1.4 Enterprise Search Server before jumping into the world of Solr Cloud is essential for anyone who wants to understand what the embedded ZooKeeper can or cannot do. This is because you have to know how to configure al the nuts & bolts in Solr manually before you can gain a natural understanding of what the automation does and does not take care of for you.
If you go through the basic exercises for Solr Cloud, then you will come across Example B: Simple two shard cluster with shard replicas. It is important to note that the wording here can be a bit misleading based on what you are looking to accomplish. It is not replication that is being set up there. Instead, that example uses "replicas" as "copies", to demonstrate high search availability.
Here are the tested & tried steps for replication with a master-slave setup that will fit-in with a ZooKeeper managed Solr Cloud:
Sources:
If you go through the basic exercises for Solr Cloud, then you will come across Example B: Simple two shard cluster with shard replicas. It is important to note that the wording here can be a bit misleading based on what you are looking to accomplish. It is not replication that is being set up there. Instead, that example uses "replicas" as "copies", to demonstrate high search availability.
Here are the tested & tried steps for replication with a master-slave setup that will fit-in with a ZooKeeper managed Solr Cloud:
- If you've already done some work with Solr Cloud then you may want to start fresh by cleaning up any previous ZooKeeper configuration data in order to run this example exercise smoothly.
cd /trunk/solr/example/solr rm -rf zoo_data
- Collection is a ZooKeeper oriented terminology to indicate a bunch of Solr cores that share the same schema and this has nothing to do with the name of a Solr Core itself. Lets keep this fact plain to see by editing the solr.xml file and providing an appropriate name for the core & collection:
<cores adminPath="/admin/cores" defaultCoreName="master1"> <core name="master1" instanceDir="." shard="shard1" collection="collection1"></core> </cores>
- Navigate to the configuration directory for the example in the trunk & begin editing solrconfig.xml using your preferred text-editor:
cd /trunk/solr/example/solr/conf vi solrconfig.xml
- Uncomment and edit the replication requestHandler to be as follows:
<requesthandler name="/replication" class="solr.ReplicationHandler" > <lst name="master"> <str name="enable">${enable.master:false}</str> <str name="replicateAfter">commit</str> <str name="replicateAfter">startup</str> <str name="confFiles">schema.xml,stopwords.txt</str> </lst> <lst name="slave"> <str name="enable">${enable.slave:false}</str> <str name="masterUrl">http://localhost:8983/solr/replication</str> <str name="pollInterval">00:00:60</str> </lst> </requestHandler>
- Navigate out of the examples directory and create another copy of it
cd /trunk/solr/ cp -r example example2
- Edit the solr.xml file for the example2 directory:
- change the name of the core to indicate that it is a slave
- leave the name of the shard as-is to indicate which shard it is a replica of
- leave the name of the collection as-is because this slave core should join the same collection as its master in ZooKeeper config
cd /trunk/solr/example2/solr vi solr.xml
<cores adminPath="/admin/cores" defaultCoreName="slave1"> <core name="slave1" instanceDir="." shard="shard1" collection="collection1"></core> </cores>
- Start the master core, the use of java params allows us to call this out as a master at startup:
cd /trunk/solr/example java -Dbootstrap_confdir=./solr/conf -Denable.master=true -DzkRun -jar start.jar
- Start the slave core, the use of java params allows us to call this out as a slave at startup:
cd /trunk/solr/example2 java -Djetty.port=7574 -DhostPort=7574 -Denable.slave=true -DzkHost=localhost:9983 -jar start.jar
- After starting the slave, towards the end of the logs for the slave, you should be able to spot info to affirm that replication is working:
INFO: Updating cloud state from ZooKeeper... Sep 9, 2011 6:20:00 PM org.apache.solr.handler.SnapPuller fetchLatestIndex INFO: Slave in sync with master.
Sources:
- http://lucene.472066.n3.nabble.com/Solr-Cloud-is-replication-really-a-feature-on-the-trunk-td3317695.html
- http://lucene.472066.n3.nabble.com/Replication-setup-with-SolrCloud-Zk-td2952602.html
Sunday, August 21, 2011
Adding Core Data to an already existing XCode4 project
- The mechanism to add the model is simple:
Ctrl+Click > New File > Core Data > Data Model > Next > Save
but this only works if you Ctrl+Click on the project!
- If you Ctrl+Click anywhere else, for example lets say on the Resources folder, then you will get a useless .xcdatamodeld file which simply won't open in the editor.
- So make sure to add the model file by Ctrl+Click(ing) on the project name and nothing else.
- Beware, if try to move it to an appropriate location like Resources at a later time, it will stop working again. After the move, once again, no editor will come up if your try to re-select the .xcdatamodeld file.
- You must have it directly under the project in order to be able to work with it.
Monday, August 8, 2011
Versioning iPhone App: Beta & Production Builds
A little bit of Google search yields the following great resources to help get started in the arena of versioning your iOS apps:
- easy-iphone-application-versioning-with-agvtool
- recipe-automatic-version-number-build-number-build-date-handling
- setting-ios-application-build-versions
- attempt to provide the grand total of everything I learned from these articles which are based in XCode3,
- provide instructions on how to do the same in XCode4, and
- explain some of the finer points which weren't clear to me without trial & error.
- This snapshot shows how to add apple generic versioning to your XCode4 project.
- Open YOUR_PROJECT in XCode 4 and navigate to YOUR_PROJECT > Targets > YOUR_PROJECT > Summary > iOS Application Target > Version
- Even though it may seem intuitive to think so but this is NOT the version number that the iPhone app users will see.
- Instead this is an internal build number that you can increment as many times as you like and whenever you deem appropriate. Follow the links at the top to learn more.
- You can easily comfirm this by attempting to edit this property in YOUR_PROJECT > Target > YOUR_PROJECT > Info (maps to YOUR_PROJECT-Info.plist file) > Custom iOS Target Properties > Bundle Version
and then confirming that the change took effect again in Summary > iOS Application Target > Version
- If you Ctrl+click and ask it to Show raw keys/values
- Then you'll notice that this maps to the key: CFBundleVersion
- If you would eventually like to build your "About" page in the app and format a string that shows up something like version 0.1.0 (build 42), where 42 will correspond to the value of CFBundleVersion, then instead of giving it an initial value like 1.0, I would suggest setting it to 1
- So now what corresponds to the 0.1.0 part of the version 0.1.0 (build 42) in your about page? Well, the Bundle versions string, short property with the raw key value of CFBundleShortVersionString would make up the 0.1.0 piece of that string.
- And could this 0.1.0 be the version # that is displayed as your app's version in the App Store? Yes! this is known as the marketing version and it is what shows up to the users of the App Store.
- What's up with the x.y.z version structure? As one of the links in the beginning explains, X - major revision number, Y - minor revision number, Z - maintenance or patch release. This is a very nice & simple concept to use.
- Why should the starting of an App's marketing version be from 0.1.0? Because an application would like to enter the App Store as 1.0.0, so you don't want to use 1 as your major (X) version in the very beginning when you are probably in the process of distributing Ad-Hoc builds for alpha/beta testing. 1 as your minor (Y) version makes sense 0.1.0 because your app starts with the smallest set of features and then grows to perhaps 0.2.0, 0.3.0 and so on. Also the 0 as your patch (Z) version makes sense because there is nothing for you to patch at the very start. In conclusion: minor.major.patch is conveniently 0.1.0 for starters.
- Here's what my email notification message looks like when I use TestFlightApp to publish beta builds to testers: v0.1.0 (4) of ShoppinPal is ready for a TestFlight
- To check your CFBundleVersion via Terminal, use:
agvtool what-version
- All the following commands increment CFBundleVersion by 1 but do nothing to CFBundleShortVersionString:
agvtool next-version agvtool next-version -all agvtool bump -all
- To check your CFBundleShortVersionString via Terminal, use:
agvtool what-marketing-version
- To change CFBundleShortVersionString, you will yourself have to understand if the change is major, minor or patch and then use:
agvtool new-marketing-version 0.2.0
- With XCode4 there really shouldn't be any good reason not to use Git so remember to commit to the repository every time after you change the version. Perhaps even take the time to tag the repo so that you can pull the exact source for debugging if someone ever reports an issue against a specific version/build #
Tuesday, August 2, 2011
PayPal: Why their mobile integration grinds my gears
Sometimes it is frustrating to see a company or business excel at something and then be so bad at advertising it! PayPal's mobile integration makes that list because it doesn't show up in the first page of google search results.
Why would you have something so awesome but fail to show-up when people search for you? Perhaps they are protected against search engine crawlers? Anyway, allow me to break it down for you the reader, who found this blog through a google search ... and let you know what I think is the best way to get up and running with the PayPal iPhone in-app payment awesomeness:
Happy coding!
Why would you have something so awesome but fail to show-up when people search for you? Perhaps they are protected against search engine crawlers? Anyway, allow me to break it down for you the reader, who found this blog through a google search ... and let you know what I think is the best way to get up and running with the PayPal iPhone in-app payment awesomeness:
- The 1st 4 slides are enough to put a smile on any face: Mobile Express Checkout - best practices
- Here's the landing pad for mobile devs. Let me make it even simpler, get this reading material in the following order:
- Mobile Payments Library Developer Guide and Reference – iOS Edition
- Sandbox User Guide
- PayPal Express Checkout Integration Guide
- Only importing the PayPal header files and libPayPalMEP.a is not enough. There are a few indirect dependencies too, you can figure out what they are by looking at the configuration for the Simple and Interactive demos ... so make sure to add them to your project as well:
- libz.dylib
- libxml2.dylib
- Security.framework
- You will probably have no trouble with the simple integration but as your business model and transactions become more & more complex, you'll run into error codes which will be hard to make heads or tails of. At that time sign-in to your PayPal developer account and refer to
DOC-1412 for the meaning behind the cryptic codes.- One example of such a cryptic code is 589023, which maps to: "If a fractional amount is rounded due to currency conversion, funds could be lost"
- Now this is confusing on a whole different level because there may not be any currency conversion involved in the transaction at all! So the error might seem to make no sense at all BUT it turns out that PayPal just wants your app to submit NSDecimalNumbers that do not have more than 2 decimal places. I didn't understand this simple requirement until I came across this post. Luckily, this blog makes it clearer on how to apply such a policy when you have made a decision.
Happy coding!
Sunday, July 31, 2011
IBAForms: An Engaging Experience
I recently found out about IBAForms, which is an Objective-C framework for easily building forms for the iPhone. I liked it a lot so I thought I'd make it tad easier for other beginners like myself:
- I've uploaded How To Use IBAForms Part 1 of the screencast where I cover the basics of importing IBAForms so that you may begin using it. Relying on the screencast isn't necessary as the Adding IBAForms to your project section of the README covers the same content but it may help to watch it in action.
- Here's one of the commands used in the screencast, used in the Terminal, which you may find a little bit more helpful than pausing the screencast and trying to visually copy it from there:
git submodule add https://github.com/ittybittydude/IBAForms.git IBAForms
- In case you find the submodule approach less than desirable, feel free to remove it.
- In How To Use IBAForms Part 2 screencast, you'll get more hands-on instructions on how to actually start adding the form fields.
Wednesday, July 20, 2011
Using Git, XCode4 and Unfuddle together
- With XCode4 supporting Git out-of-the-box it is highly encouraging to know that any endeavor that you "the developer" might embark on ... will benefit from a default development branch (on your local box) known as master repository (unless you explicitly de-select a checkbox in XCode4 when creating your project).
- As your single-developer project gains a coherent enough form for you to want to share it with other developers ... Unfuddle will pop into the picture because it provides: (a) Git hosting, and (b) it is free for upto 2 developers. This is the perfect setup for figuring out if Git and XCode4 are the right fit for you.
- A lot of folks who make use of GitHub hosting (your source code is open to the world) seem to be concerned about how XCode4 doesn't live up to their expectations because it doesn't provide an automated process or any clear cut instructions for configuring and pushing the local master-repo over to a remote upstream repository (lets call it origin-repo) when this phase in their development life-cycle finally arrives.
- Well the instructions over @ Unfuddle couldn't be more direct and clear-cut, they get the job done in under 10 minutes flat. They are suited exactly for a project where a local master-repo already exists and you want to push it to a remote origin-repo.
- After the push setup is completed and your team member has cloned the origin-repo onto their own dev machine as a master-repo, you are ready to roll.
- Lets say the n00b member makes a change to their local files. They can quickly find out what has changed using
git status
- Now the n00b wants to commit the changes, then they can use
git commit -m "comment"
- But wait, this only committed the changes to the local master-repo on the n00b's machine, next to get this pushed over to the origin-repo use:
git push git@subdomain.unfuddle.com:subdomain/abbreviation.git
... where subdomain and abbreviation must be replaced with values appropriate to your account and repository. - Now lets say you realize that it doesn't make much sense to have the *.xcodeproj files/folders under source control as their contents might differ based on the machine they are on, well then:
git rm --cached <file>
would remove file from version control, while keeping it in the working repository. And to avoid from ever mistakenly adding it back to source control, just add a .gitignore file and inside it put the expression to ignore the file/folder in question.
- Then use
git commit -m "comment"
for putting the change into master-repo - And
git push git@subdomain.unfuddle.com:subdomain/abbreviation.git
for pushing the change to the origin-repo - Finally use
git pull git@subdomain.unfuddle.com:subdomain/abbreviation.git
to get everything back over to your own local master-repo - Some common tasks that may crop-up once you move to a multi-dev env could be simple things like wanting to rename the project. For XCode4 this was a particularly simple yet difficult find for me. Apparently according to Konstantin Salavatov: in XCode4 clicking on the project name after selecting it, starts the process of renaming it. This is absolutely correct.
- Here's a set of links to keep close at hand for quick reference:
- http://www.arlt.eu/blog/2009/12/01/importing-iphone-keys-p12-and-pem-into-snow-leopards-keychain/
- https://git.wiki.kernel.org/index.php/GitCheatSheet
Tuesday, July 19, 2011
TestFlight Fever
I recently heard about TestFlight and I became extremely excited. Now having never done any ad-hoc distribution for iPhone apps in the past, I thought that I had hit the jackpot! I quickly signed up, sent invites to my team members and then rushed to upload a build ... so that everyone could start testing instantly :)
But oh boy was I in for a surprise. TestFlight became a victim of my false expectations:
In conclusion TestFlight rocks out loud and hopefully I've covered any novice errors that would have otherwise prevented others from thinking likewise.
But oh boy was I in for a surprise. TestFlight became a victim of my false expectations:
- False expectation # 1: I don't have to maintain the profiles for the folks that I invite for testing my app via TestFlight.
- WRONG! The 1st thing that TestFlight warned me about was the fact that it could not accept my build because I was probably using a distribution profile. Now since a dist-profile doesn't have a record for any of the devices from my team members, it wouldn't work. So TestFlight warned me about this and told me to go perform my build with an Ad-Hoc profile.
Falseexpectation # 2: TestFlight will clearly document and guide a novice like me how to setup an Ad-Hoc configuration in XCode4.
- UPDATE! They now prompt you with an informative link that will give you all the help you need.
WRONG! While they may make a welcome improvement in the future, I could not find any help on the immediate TestFlight site... but just like any good engineer of the Google-era I was able to find an excellent article on the web for it that got me past this hurdle.
- False expectation # 3: Just this one time, I can get away by faithfully following the instructions of other folks and not having to bang my head bloody w/ trial & error.
- WRONG! The XCode4 + Ad-Hoc distribution configuration article got me off to a flying start but I quickly realized that I had no idea how to create an Ad-Hoc profile.
- After mucking about I figured out the steps are:
- Goto iOS Provisioning Portal
- Select Provisioning from the left pane
- Select the Distribution tab from the right
- Click the New Profile button
- Make sure to mark the Ad Hoc bullet option as the distribution method.
- Select all the Devices that can use this profile.
- Download and install this new profile and prepare your build with it.
- False expectation # 4: Any TestFlight teammates who accept their invites and whose devices weren't explicitly added as a part of the Ad-Hoc profile at the time of its creation ... will be injected into the Ad-Hoc profile auto-magically.
- SUPER WRONG! You have to go edit you Ad-Hoc profile and add the devices yourself. TestFlight does NOT commandeer your Apple provisioning account to do this for you.
- What TestFlight DOES offer us (the users) is the ability to easily collect the UDIDs of our fellow teammates. Think about it, having your teammates get an email that they open via their iPhone/iPad ... and which results in their UDID being shuttled over to a central location in TestFlight for easy collection is already a huge boon! Perhas the only boon one should be asking for :)
- Gone are the days of running around looking for a computer so that you can connect to iTunes and painstakingly jot down an impossibly long identifier.
- Anyways, make sure to add the UDIDs for all of your team members as they accept the invites and sign up. Collect them from TestFlight and go update your Ad-Hoc profile as & when needed ... by adding and then selecting the decives for inclusion by marking the checkboxes under the Devices section of the Ad-Hoc profile.
In conclusion TestFlight rocks out loud and hopefully I've covered any novice errors that would have otherwise prevented others from thinking likewise.
Labels:
ad hoc,
distribution,
TestFlight,
xcode,
xcode4
Friday, July 15, 2011
Combining Tab Bar Controller and Navigation Controller
It may seem elusive but the best practices for combining Navigation Controller with Tab Bar Controller are very well covered by the Apple docs. Furthermore, even the instructions on "HOW TO DO IT" when using NIB files versus doing so programmatically are available too.
Having gone through this process myself, I must admit that I made various misinterpretations with the NIB file related instructions, which wasted way too much of my time. I even reached a point where I had discovered a new-ish but probably improper way of making things function. On a whim I luckily went through the whole process again ... only to this time (thankfully) discover the correct approach.
So as a supplement, I've made a screencast that aims to close this gap for any other novices. Enjoy!
While my screencast deals heavily with the NIB file based approach, the 7th iTunes University lecture by Paul Hegarty starting at 29:50, demonstrates how to do so programmatically.
Having gone through this process myself, I must admit that I made various misinterpretations with the NIB file related instructions, which wasted way too much of my time. I even reached a point where I had discovered a new-ish but probably improper way of making things function. On a whim I luckily went through the whole process again ... only to this time (thankfully) discover the correct approach.
So as a supplement, I've made a screencast that aims to close this gap for any other novices. Enjoy!
While my screencast deals heavily with the NIB file based approach, the 7th iTunes University lecture by Paul Hegarty starting at 29:50, demonstrates how to do so programmatically.
Wednesday, July 13, 2011
Integrating with ZBar SDK 1.1
It took only 35 minutes to get a barebones scanning app up & running on the iPhone with ZBar SDK. As far as I'm concerned ZBar is a pretty good rival to RedLaser. If you haven't done already, head over to this section in the ZBar SDK docs and try out the example which will have you singing its praises in no time.
I followed it word for word other than the fact that I used UILabel instead of UITextView.
I followed it word for word other than the fact that I used UILabel instead of UITextView.
Sunday, July 10, 2011
Integrating with RedLaser SDK 3.0.0
If you've had the chance to try out the RedLaser app on your iPhone, then you know its usability is excellent and it is simply the best technology out there when it comes to scanning bar codes. As such, you may be rightfully tempted to download their SDK and try it out for yourself.
Now even though you'll find their out-of-box documentation in the 6MB-ish download to be enlightening, you may find yourself struggling and without any instructions when it comes to getting any project of you own up & running other than their RLSample project. I must say that the ZBar folks do a much better job of documenting similar integration items for their barcode scanning SDK. I just hope that the newly released RedLaser SDK 3.1.1 build1 will offer better docs than the Redlaser SDK 3.0.0 build21 that was used at the time of writing this blog entry.
Also you may feel dubious about purchasing the extremely expensive license without first figuring out if you have the skills to take your idea all the way. And you can't get into their forums without getting a license. So, I've put together some highlights to help you grapple with the SDK and get to a point where you can perform trial & error on your own to see if you have the right stuff :)
Now even though you'll find their out-of-box documentation in the 6MB-ish download to be enlightening, you may find yourself struggling and without any instructions when it comes to getting any project of you own up & running other than their RLSample project. I must say that the ZBar folks do a much better job of documenting similar integration items for their barcode scanning SDK. I just hope that the newly released RedLaser SDK 3.1.1 build1 will offer better docs than the Redlaser SDK 3.0.0 build21 that was used at the time of writing this blog entry.
Also you may feel dubious about purchasing the extremely expensive license without first figuring out if you have the skills to take your idea all the way. And you can't get into their forums without getting a license. So, I've put together some highlights to help you grapple with the SDK and get to a point where you can perform trial & error on your own to see if you have the right stuff :)
- Add files to "<your project>"...
- Navigate to wherever you downloaded and extracted the RedLaserDevSDK-3.0.0build21
- The naming is not set in stone, it is spelled out here only to give you a feel for what you may be looking for on your mac.
- Drill-down into the RedLaserDevSDK-3.0.0build21/Sample/ folder and select the RedLaserSDK directory
- Make sure to select the checkbox to Copy items into destination group's folder(if needed)
- Also select the bullet option which will Create groups for any added folders
- Just to see if you can get anything simple to work on your own, you may try to run the build after pasting the barebones code from the Using the 3.0 RedLaser SDK.pdf file included with the SDK download.
- Here's the code to throw into a method of your choosing:
BarcodePickerController *picker = [[BarcodePickerController alloc] init]; // Let's keep things simple by using the default built-in overlay //[picker setOverlay:customOverlay]; [picker setDelegate:self]; picker.orientation = UIImageOrientationUp; [[UIApplication sharedApplication] setStatusBarHidden:YES]; [self presentModalViewController:picker animated:TRUE]; [picker release];
- Ofcourse you'll need to add #import "RedLaserSDK.h" at the top where you've pasted this code.
- But the build still won't go through because some frameworks need to be added for the build to succeed.
- Here's the code to throw into a method of your choosing:
- Goto <Your Project> > TARGETS > Build Phases > Link Binary With Libraries > +
- libRedLaserSDK.a - XCode would have probably automatically placed this in the list when you imported the ./RedLaserDevSDK-3.0.0build21/Sample/RedLaserSDK directory.
- CoreVideo.framework - makes sense since we need to the camera is used to perform the scans. If you started with something like 24 errors, adding this will bring them down to 18.
- Security.framework - If you started with something like 18 errors, adding this will bring them down to 3.
- libiconv.dylib - If you started with something like 3 errors, adding this will bring them down to 0.
- In addition you should add an empty implementation for what happens when a barcode is successfully read.
- Here's the code:
- (void) barcodePickerController:(BarcodePickerController*)picker returnResults:(NSSet *)results { [[UIApplication sharedApplication] setStatusBarHidden:NO]; [self dismissModalViewControllerAnimated:TRUE]; }
- Here's the code:
- With this you can at least do a test build and run in the simulator mode which will not do much other than throw up the mock scanner screen from RedLaser.
- If you actually try to plug in your iPhone and directly test your app on the device you will run into a whole new slew of build errors! I received a total of 22 errors. Once again, goto <Your Project> > TARGETS > Build Phases > Link Binary With Libraries > +
- libRedLaserSDK.a - XCode would have probably automatically placed this in the list when you imported the ./RedLaserDevSDK-3.0.0build21/Sample/RedLaserSDK directory.
- AVFoundation.framework - If you started with something like 22 errors, adding this will bring them down to 6.
- CoreMedia.framework - If you started with something like 6 errors, adding this will bring them down to 0.
- Now with a lot of luck and a little bit of elbow grease, when you connect your iPhone then build & run you app, you should see it launch and have the default RedLaser full-screen camera-view-like overlay being shown. All you will see is what you camera sees, no fancy buttons ... no nothing. But given that you never actually invoked any camera/media related functionality yourself, have faith in the fact that you've cleared the first phase in your experiments to integrate with RedLaser.
- Before beginning the 2nd phase, I had hoped to perform a simple experiment that would turn out as described below ... but that didn't happen so until I figure that out, this is it :(
- Remembering that there are an extremely limited # of scans allowed w/ RedLaser SDK ... take the iPhone over to a barcode and try to scan it ... given the barebones code that we've talked through thus far, all that should happen is that the camera overlay screen will just disappear after a successful scan and you'll be left looking at an empty plane white view that had loaded this stuff to begin with.
Tuesday, June 7, 2011
Building a step up/down notched slider for iPhone
If you implement the following delegate method with similar code then you will gain the desired effect as you move the slider with your finger on-screen.
- (IBAction) sliderValueChanged:(UISlider *)sender {
float value = [sender value];
if (value < 0.5) {
value = 0;
} else if (value > 0.5 && value <1.5) {
value = 1;
} else if (value > 1.5 && value <2.5) {
value = 2;
} else if (value > 2.5 && value <3.5) {
value = 3;
} else if (value > 3.5 && value <4.5) {
value = 4;
} else if (value > 4.5 && value <5.5) {
value = 5;
} else if (value > 5.5 && value <6.5) {
value = 6;
} else if (value > 6.5 && value <7.5) {
value = 7;
} else if (value > 7.5 && value <8.5) {
value = 8;
} else {
value = 9;
}
self.slider.value = value;
}
- (IBAction) sliderValueChanged:(UISlider *)sender {
float value = [sender value];
if (value < 0.5) {
value = 0;
} else if (value > 0.5 && value <1.5) {
value = 1;
} else if (value > 1.5 && value <2.5) {
value = 2;
} else if (value > 2.5 && value <3.5) {
value = 3;
} else if (value > 3.5 && value <4.5) {
value = 4;
} else if (value > 4.5 && value <5.5) {
value = 5;
} else if (value > 5.5 && value <6.5) {
value = 6;
} else if (value > 6.5 && value <7.5) {
value = 7;
} else if (value > 7.5 && value <8.5) {
value = 8;
} else {
value = 9;
}
self.slider.value = value;
}
SIGABRT when opening URLs in iPhone App
The code for opening URLs from an iPhone app is relatively simple:
NSURL *url = [NSURL URLWithString:@"http://pulkitsinghal.blogspot.com"];
[[UIApplication sharedApplication] openURL:url];
Yet the 1st time I tried to use it, I kept running into the SIGABRT exception.
Ofcourse I blame the code right? Yup!
But after many searches on Google yielded no clues at all, I went back to the stack trace:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[IconDetailsViewController gotoAuthorWebSite:]: unrecognized selector sent to instance 0x4e619b0'
For the life of me, I couldn't figure out why it was claiming that the method/selector gotoAuthorWebSite was not present when I could see it in my code and the webpage was launching as well! How could it do what it was supposed to do and still claim that the code that was supposed to do the work didn't exist?
Turns out that at some point of time there was a case-sensitive discrepancy between the signatures in my .h and .m files!
- (IBAction) gotoAuthorWebSite:(UIButton*)sender;
- (IBAction) gotoAuthorWebsite:(UIButton*)sender{...}
And I happened to have resolved this inconsistency but only after I had already wired up both these methods to the UIButton component in the NIB file! So the problem was that the non-existent method (with the incorrect casing) had not been auto-removed as the following picture shows:After manually deleting this incorrect mapping from the NIB file, everything was peachy!
NSURL *url = [NSURL URLWithString:@"http://pulkitsinghal.blogspot.com"];
[[UIApplication sharedApplication] openURL:url];
Yet the 1st time I tried to use it, I kept running into the SIGABRT exception.
Ofcourse I blame the code right? Yup!
But after many searches on Google yielded no clues at all, I went back to the stack trace:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[IconDetailsViewController gotoAuthorWebSite:]: unrecognized selector sent to instance 0x4e619b0'
For the life of me, I couldn't figure out why it was claiming that the method/selector gotoAuthorWebSite was not present when I could see it in my code and the webpage was launching as well! How could it do what it was supposed to do and still claim that the code that was supposed to do the work didn't exist?
Turns out that at some point of time there was a case-sensitive discrepancy between the signatures in my .h and .m files!
- (IBAction) gotoAuthorWebSite:(UIButton*)sender;
- (IBAction) gotoAuthorWebsite:(UIButton*)sender{...}
And I happened to have resolved this inconsistency but only after I had already wired up both these methods to the UIButton component in the NIB file! So the problem was that the non-existent method (with the incorrect casing) had not been auto-removed as the following picture shows:After manually deleting this incorrect mapping from the NIB file, everything was peachy!
Subscribe to:
Posts (Atom)