tag:blogger.com,1999:blog-33974918870170834152024-03-13T23:54:01.961-07:00Programming thoughts and other randomitiesAnonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.comBlogger11125tag:blogger.com,1999:blog-3397491887017083415.post-15982328296557058782013-08-29T21:39:00.000-07:002013-08-29T21:39:56.426-07:00Magento shortcuts<h2>
Various magento shortcuts</h2>
<h3>
Get a url with the correct protocol (http/https)</h3>
To get a url from a template with the correct protocol use:<br />
<pre class="brush: php">
$url = $this->getUrl('some url path', array('_secure' => Mage::app()->getFrontController()->getRequest()->isSecure()));
</pre>
<br/>
Alternatively you can use:
<pre class="brush: php">
$this->getUrl('some url path', array('_secure' => Mage::app()->getStore()->isCurrentlySecure()));
</pre>
<br/>
If you are outside a template you can replace $this-> with Mage::Anonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-63268868389338943432013-03-16T12:09:00.001-07:002013-03-16T12:09:58.965-07:00Polishing up the magento mini search<h2>
</h2>
Magento comes with a mini search form complete with search suggestions. To fetch and show the search suggestions magento uses the prototype <a href="http://madrobby.github.com/scriptaculous/ajax-autocompleter/">Ajax.Autocompleter</a>. This works well but it's not fully configured in the default magento setup. The aim of this post is to complement the magento search suggestions with the following:<br />
<ul>
<li>limit the number of returned results - magento returns all results by default and this can generate a long list as well as adding some overhead</li>
<li>add a search indicator so the user knows that a search is in progress</li>
<li>highlight the search term in the returned results</li>
<li>remove the number of results that magento adds to each returned result</li>
</ul>
<h3>
Preparing files for editing</h3>
To achieve the points above we'll need to edit some of the magento files. As it's not good to change the magento core files we'll copy these files to the local code pool. We'll need to copy the following files:<br />
<ul>
<li>app/code/core/Mage/CatalogSearch/Model/Query.php to app/code/local/Mage/CatalogSearch/Model/</li>
<li>app/code/core/Mage/CatalogSearch/Block/Autocomplete.php to app/code/local/Mage/CatalogSearch/Block/</li>
<li>app/design/frontend/base/default/template/catalogsearch/form.mini.phtml to app/design/frontend/default/default/template/catalogsearch/</li>
</ul>
In addition to the above files we will need to edit js/varien/js.js and add some css styling. You can add the css either to your theme's css files or directly in the updated form.mini.phtml file<br />
<h3>
<br />Limit the number of results returned by magento</h3>
Limiting the number of results is quite simple. All we need to do is edit app/code/local/Mage/CatalogSearch/Model/Query.php and add a line to the getSuggestCollection function so it looks like the following:<br />
<pre class="brush: php"> /**
* Retrieve collection of suggest queries
*
* @return Mage_CatalogSearch_Model_Resource_Query_Collection
*/
public function getSuggestCollection()
{
$collection = $this->getData('suggest_collection');
if (is_null($collection)) {
$collection = Mage::getResourceModel('catalogsearch/query_collection')
->setStoreId($this->getStoreId())
->setQueryFilter($this->getQueryText());
//limit the number of returned results
$collection->getSelect()->limit(10);
$this->setData('suggest_collection', $collection);
}
return $collection;
}
</pre>
The code that limits the result numbers is on line 14.<br />
<h3>
Removing results numbers and adding highlights to results</h3>
Ajax.Autocompleter needs a ul returned containing the search suggestions. Magento creates the list in Mage/CatalogSearch/Block/Autocomplete.php in the _toHtml() function. Edit app/code/local/Mage/CatalogSearch/Block/Autocomplete.php and change the _toHtml function as below. You can also add other changes you want applied to the results list.<br />
<pre class="brush: php"> protected function _toHtml()
{
//change html to return a list on error otherwise the loader image doesn't go away
$html = '<ul></ul>
';
if (!$this->_beforeToHtml()) {
return $html;
}
$suggestData = $this->getSuggestData();
if (!($count = count($suggestData))) {
return $html;
}
$count--;
//get the query text
$query = $this->helper('catalogsearch')->getQueryText();
$html = '<ul>
<li style="display: none;"></li>
';
foreach ($suggestData as $index => $item) {
if ($index == 0) {
$item['row_class'] .= ' first';
}
if ($index == $count) {
$item['row_class'] .= ' last';
}
$text = $this->htmlEscape($item['title']);
//get the first case insensitive query occurence in the text
$pos = stripos($text,$query);
$length = strlen($query);
if ($pos !== false) {
//get the actual case sensitive piece of text that matches the query and highlight it
$replacement="<b>".substr($text, $pos, $length)."</b>";
//replace the query in the text with the highlighted version
$text = substr_replace($text,$replacement,$pos, $length);
}
$html .= '
<li class="'.$item['row_class'].'" title="'.$this->htmlEscape($item['title']).'">'. $text.'</li>
';
}
$html.= '</ul>
';
return $html;
}
</pre>
<h3>
Adding the search indicator element</h3>
The autocompleter plugin can display a visual indicator while the ajax request is running. You can generate a loading indicator at <a href="http://preloaders.net/">preloaders.net</a>. We'll need to add a div that will contain our progress indicator. To do this edit app/design/frontend/default/default/template/catalogsearch/form.mini.phtml and add the following somewhere inside the search form div (or in another place where you want it displayed):<br />
<pre class="brush: php"><div id="search-running" style="display: none; position: absolute; right: 25px; top: 7px; z-index: 100;">
<img alt="" src="<?php echo $this->getSkinUrl('images/search-loader.gif')?>" /></div>
</pre>
I've uploaded my indicator to the skin/frontend/default/default/images/ folder so I can reference it using the magento getSkinUrl function.<br />
I want my loading image to show over the search input so I added the css styles to the element to make sure it's positioned correctly and in front of the input.<br />
<h3>
Updating the autocomplete javascript</h3>
The last thing we need to do is update the javascript used to initialize the Ajax.Autocompleter. Open js/varien/js.js and search for the following lines of code:<br />
<pre class="brush: js"> new Ajax.Autocompleter(
this.field,
destinationElement,
url,
{
paramName: this.field.name,
method: 'get',
minChars: 2,
</pre>
<br />
Add the following lines after the miChars line:
<br />
<pre class="brush: js"> frequency: 0.5,
indicator: 'search-running',
</pre>
The frequency tells autocompleter how often it should poll the input for changes and the indicator is the id of the element that should be shown while the ajax request is running.<br />
<br />
That's it. Comments are always welcome.Anonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-59240272059766303152013-03-08T23:32:00.000-08:002013-03-08T23:32:58.548-08:00Random vi shortcuts<h3>
Toggling syntax highlighting</h3>
<pre class="brush: bash">:syntax [on/off]
</pre>
<br />
<h3>
Changing the colorscheme</h3>
<pre class="brush: bash">:colorscheme [colorscheme]
</pre>
<br />
Short form (actually can be anything from colo to colorscheme):
<br />
<pre class="brush: bash;">:colo [colorscheme]
</pre>
<br />
Some default colorschemes:<b>torte, morning, evening, desert, pablo</b> etc. To cycle through the available colorchemes press tab after the command<br />
To set a colorscheme by default for a profile edit ~/.vimrc and add the following lines:<br />
<br />
<pre class="brush: bash">
syntax on
colorscheme [colorscheme]
</pre>Anonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-86429027810479260342013-03-02T05:08:00.001-08:002013-03-02T05:21:44.472-08:00Getting magento product custom optionsIn one case custom options for products were supposed to be required but they were not set to required in the admin.As there were a lot of products it was impractical to set those manually so the only solution was a script. The easy way is to go into the database and set the is_require field to 1 in the catalog_product_option table. The following query can be used:<br />
<pre class="brush:sql">UPDATE `catalog_product_option` SET `is_require`=1
</pre>
<br />
Getting the values for those options is difficult using sql queries so here's a bit of code to achieve that.
<br />
<pre class="brush:php"><?php
require_once (str_replace('//','/',dirname(__FILE__).'/') .'../app/Mage.php');
umask(0);
Mage::app('admin');
$collection = Mage::getModel('catalog/product')->getCollection()->load();
echo $collection->count(),"<b r/>";
foreach ($collection as $product) {
echo "<b>",$product->getId(), " - ",$product->getSku(),"</b><b r/>";
$product->load();
$i = 1;
echo "<pre>";
foreach ($product->getOptions() as $o) {
echo "<strong>Custom Option:" . $i . "</strong><b r/>";
echo "Custom Option TYPE: " . $o->getType() . "<b r/>";
echo "Custom Option TITLE: " . $o->getTitle() . "<b r/>";
echo "Custom Option Required: " . $o->getIsRequire() . "<b r/>";
echo "Custom Option Values: <b r/>";
$values = $o->getValues();
foreach ($values as $v) {
print_r($v->getData());
}
$i++;
echo "----------------------------------<b r/>";
}
}
</pre>
<br />
Thanks to <a href="http://subesh.com.np/2009/12/custom-options-product-magento/">Subesh Pokhrel</a><br />
<span style="color: red;">Due to some unknown black magic the br tags in the code snippets get interpreted and having no time to deal with it now I've just put a space between the b and the r</span>
Anonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-51342230616452931682013-03-01T04:21:00.000-08:002013-03-04T06:52:35.251-08:00Linux admin shortcutsList of sometimes useful commands<br />
<br />
<h3>
System information</h3>
<h4>
Getting hardware information </h4>
Hardware information can be obtained using the <b>dmidecode</b> tool that tries to decode the dmi table. It can also filter the information provided using the -t flag.<br />
Getting information the physical memory:<br />
<br />
<pre class="brush: bash">dmidecode -t memory
</pre>
<h4>
</h4>
<h4>
Getting information about a domain</h4>
Domain information cat be obtained using the DNS lookup utility <b>dig</b>. The simplest usage would be:<br />
<br />
<pre class="brush: bash">dig domainname
</pre>
<br />
and it returns information on the A record of the host.
<br />
The utility can be used to get other types of information by using the <b>-t </b>switch and specifying the record type. For nameservers the query is:
<br />
<pre class="brush: bash">dig domainname -t NS
</pre>
<br />
For mail records:
<br />
<pre class="brush: bash">dig domainname -t MX
</pre>
<br />
Reverse lookups can be prformed using the<b> -x </b>flag:<br />
<pre class="brush: bash">dig -x ip_address
</pre>
Anonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-87997988972348861402013-02-28T07:49:00.000-08:002013-02-28T07:49:12.966-08:00Random postgresql tasks<h3>
Getting the foreign key references to a table</h3>
<br />
<pre class="brush: sql">SELECT
tc.constraint_name, tc.table_name, kcu.column_name,
ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
WHERE constraint_type = 'FOREIGN KEY' AND ccu.table_name='table_name';
</pre>
<h3>
Getting the numbers of connections to the server</h3>
<br/>
<pre class="brush: sql">
select datname, client_addr, usename, Count(1) As connections
from pg_stat_activity
group by datname, client_addr, usename
order by datname, client_addr
</pre>Anonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-15076441036022718252013-02-23T08:52:00.000-08:002013-03-15T01:17:17.518-07:00Magento debugging thoughts<h2>
Random thoughts on debugging in magento</h2>
<h3>
Logging magento queries</h3>
Sometimes you need to take a look at the queries generated by magento while loading different objects or collections. To log queries edit lib/Varien/Db/Adapter/Pdo/Mysql.php and change the following line:<br />
<pre class="brush: php">protected $_debug = false;
</pre>
to <br />
<pre class="brush: php">protected $_debug = true;
</pre>
The log file is specified in:<br />
<pre class="brush: php">protected $_debugFile = 'var/debug/pdo_mysql.log';
</pre>
<h4>
Logging ALL magento queries</h4>
Even with logging enabled not all queries are logged. To enable logging all queries change:<br />
<br />
<pre class="brush: php">protected $_logAllQueries = false;
</pre>
to
<br />
<pre class="brush: php">protected $_logAllQueries = true;
</pre>
<h3>
Logging all magento events</h3>
To log all the events magento throws edit app/Mage.php and add the following line to the dispatchEvent function:<br />
<pre class="brush: php">Mage::log($name, null, 'events.log');
</pre>
<br />
After the change the function should look something like:<br />
<pre class="brush: php">public static function dispatchEvent($name, array $data = array())
{
Mage::log($name, null, 'events.log');
Varien_Profiler::start('DISPATCH EVENT:'.$name);
$result = self::app()->dispatchEvent($name, $data);
#$result = self::registry('events')->dispatch($name, $data);
Varien_Profiler::stop('DISPATCH EVENT:'.$name);
return $result;
}
</pre>
Events will be logged to var/log/events.logAnonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-76425410405992406032013-02-23T08:11:00.000-08:002013-03-30T00:07:05.124-07:00Magento queries list<h2>
Quick reference list of Magento queries</h2>
<br />
<h3>
Getting store information</h3>
Load store object ($identifier can be store id or code):
<br />
<pre class="brush: php">$store = Mage::getModel('core/store')->load($identifier);
</pre>
Get (admin) Store Id using store code:
<br />
<pre class="brush: php">$adminStoreId = Mage::getModel('core/store')->load('admin')->getId();
</pre>
<br />
<h3>
Filtering by null fields</h3>
Get items with a NULL field:
<br />
<pre class="brush: php">$collection = Mage::getModel('your/model')->getCollection();
$collection->addFieldToFilter('field_code',array('null' => true));
$collection->load();
</pre>
Get items with non NULL field:
<br />
<pre class="brush: php">$collection = Mage::getModel('your/model')->getCollection();
$collection->addFieldToFilter('field_code',array('neq' => 'NULL'));
$collection->load();
</pre>
Anonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-28126072680101364432013-01-15T21:22:00.000-08:002013-01-15T21:22:34.471-08:00XSLT sorting by child attributesI'm new to xslt but I had to tackle a problem involving sorting an xml structure based on attributes of generic children. Something like the xml below.
<br />
<pre class="brush: xml"><data>
<node name="Node1">
<attribute name="sortValue" value="value1"/>
<attribute name="color" value="red"/>
<attribute name="size" value="XL"/>
</node>
<node name="Node2">
<attribute name="sortValue" value="value3"/>
<attribute name="color" value="pink"/>
<attribute name="size" value="L"/>
</node>
<node name="Node3">
<attribute name="sortValue" value="value2"/>
<attribute name="color" value="blue"/>
<attribute name="size" value="M"/>
</node>
<node name="Node4">
<attribute name="sortValue" value="value3"/>
<attribute name="color" value="black"/>
<attribute name="size" value="S"/>
</node>
</data>
</pre>
After searching a little I found some solutions and documented them here for future reference.<br />
<br />
<h4>
Simple Sorting </h4>
The simple case would involve using the sortValue attributes. To perform a simple sort for the xml on the sort field you can use the following xslt:
<br />
<pre class="brush: xslt"><xsl:template match="/">
<html>
<body>
<table border="1">
<tr bgcolor="pink">
<th>Node Name</th>
<th>Sort Value</th>
<th>Color</th>
<th>Size</th>
</tr>
<xsl:for-each select="data/node">
<xsl:sort select="attribute/@value[../@name = 'sortValue']"/>
<tr>
<td><xsl:value-of select="@name"/></td>
<xsl:for-each select="attribute">
<td><xsl:value-of select="@value"/></td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template></pre>
<pre class="brush: xslt"> </pre>
This should give you the following result:<br />
<br />
<table border="1"><tbody>
<tr bgcolor="pink"><th><span style="color: #444444;">Node Name</span></th><th><span style="color: #444444;">Sort Value</span></th><th><span style="color: #444444;">Color</span></th><th><span style="color: #444444;">Size</span></th></tr>
<tr><td>Node1</td><td>value1</td><td>red</td><td>XL</td></tr>
<tr><td>Node3</td><td>value2</td><td>blue</td><td>M</td></tr>
<tr><td>Node2</td><td>value3</td><td>pink</td><td>L</td></tr>
<tr><td>Node4</td><td>value3</td><td>black</td><td>S</td></tr>
</tbody></table>
<br />
<h4>
Sorting by multiple attributes</h4>
XSLT also supports multiple sorting so if you want to sort by multiple attributes (for example the last two lines have the same sort value but the color are not sorted) you can add a second sort. For this exmpla just change<br />
<br />
<pre class="brush: xslt"><xsl:sort select="attribute/@value[../@name = 'sortValue']"/>
</pre>
with
<br />
<pre class="brush: xslt"><xsl:sort select="attribute/@value[../@name = 'sortValue']"/>
<xsl:sort select="attribute/@value[../@name = 'color']"/>
</pre>
and you should get:
<br />
<table border="1"><tbody>
<tr bgcolor="pink"><th><span style="color: #444444;">Node Name</span></th><th><span style="color: #444444;">Sort Value</span></th><th><span style="color: #444444;">Color</span></th><th><span style="color: #444444;">Size</span></th></tr>
<tr><td>Node1</td><td>value1</td><td>red</td><td>XL</td></tr>
<tr><td>Node3</td><td>value2</td><td>blue</td><td>M</td></tr>
<tr><td>Node4</td><td>value3</td><td>black</td><td>S</td></tr>
<tr><td>Node2</td><td>value3</td><td>pink</td><td>L</td></tr>
</tbody></table>
<h4>
</h4>
<h4>
Sorting finite options by priority</h4>
If you have a list of possibilities with a finite number of priorities (like the clothes sizes in the example) you can use a trick to prioritize the list. This involves converting the options to numbers and giving each a weight for the purpose of sorting and performing the sort on numeric values.<br />
To sort by size in this example you could use the following:<br />
<pre class="brush: xslt"><xsl:sort data-type='number' order='ascending'
select="(number(attribute/@value[../@name = 'size'] = 'S') * 1)
+ (number(attribute/@value[../@name = 'size'] = 'M') * 2)
+ (number(attribute/@value[../@name = 'size'] = 'L') * 3)
+ (number(attribute/@value[../@name = 'size'] = 'XL') * 4)"/>
</pre>
This should give the following result:
<br />
<table border="1"><tbody>
<tr bgcolor="pink"><th><span style="color: #444444;">Node Name</span></th><th><span style="color: #444444;">Sort Value</span></th><th><span style="color: #444444;">Color</span></th><th><span style="color: #444444;">Size</span></th></tr>
<tr><td>Node4</td><td>value3</td><td>black</td><td>S</td></tr>
<tr><td>Node3</td><td>value2</td><td>blue</td><td>M</td></tr>
<tr><td>Node2</td><td>value3</td><td>pink</td><td>L</td></tr>
<tr><td>Node1</td><td>value1</td><td>red</td><td>XL</td></tr>
</tbody></table>
<br />
Happy sortingAnonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-61764643247143665872012-08-14T00:31:00.001-07:002012-08-14T00:31:46.835-07:00Reviving the kobo touchA couple of weeks ago my wife's kobo touch decided to die. Actually I think this happened because she didn't use it for a while. I took it to load some books, connected it to the PC, loaded the books and when I pulled it out it froze on the "Connected and charging" screen. I started roaming the forums to try and find a solution for this: first I left it to charge on the usb port, then I tried with a wall charger (bought for the kobo from fnac). I left it for over 12 hours with no result. When trying to start it the led flickered blue briefly (under a second if not plugged in) and did nothing after that. When connected to the computer or to the wall charger the led was green (charging) for about 10 seconds, went blue (assume trying to start) for about 5 seconds but didn't start. It went on doing this indefinitely.
I did try to perform a factory reset using both methods: keep menu button pressed while powering on or keeping the back reset button pressed while powering on. This achieved nothing. The led just briefly flickered red twice.
Following some advice on the forums I took off the back cover and disconnected the battery. Same story after connecting again so I thought there might be something wrong with the device. I opened up my own (working) device and connected the battery to the dead one. It started up immediately.
I gave it up for a dead battery and thought I'd talk to a fried about finding a replacement and went on holiday. If you are interested in how the kobo came back from the dead please read on.
As luck has it I didn't take my phone charger with me (it has the same type of connecter as the kobo) as I had the kobo wall charger and thought I'd use that. On the first day my phone battery died so I plugged it into the kobo charger but, surprise, it just stood there. I tried all the plugs in the room but with no success. Finally I just took the cable from the charger and asked the lady at the reception desk to plug the phone into their PC which worked and my phone returned to life.
This got me thinking so when I got home I plugged the dead kobo touch into my phone charger, left it for about an hour. This time the green led stayed on all the time (no blue light). After an hour I tried to switch it on: the led wen from green to blue for about 5 seconds and then back to green. I was about to throw it at the wall in frustration when the screen turned white and the device powered up.
My assumption is that the battery was fully depleted and, for some reason, the device tries to switch on even when connected to the wall charger (either this or the charger doesn't provide enough power) which drains the battery again so it never has enough time to store the power needed for startup. This is probably an engineering flaw as I don't see the point of trying to start the device when there isn't enough power. It should probably only try to connect to the PC when the device is actually turned on.
So, before sending your touch back to kobo try a different charger. I used a samsung phone charger.
Hope this helps someone. I'll keep it for future reference just in case...Anonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com0tag:blogger.com,1999:blog-3397491887017083415.post-54323874999860912222012-07-19T22:52:00.002-07:002012-07-28T08:53:32.500-07:00Koboish ebooksI really like the kobo touch but I noticed that there is a difference in how side-loaded epubs and epubs downloaded from the kobo store are treated by the device. The thing that I liked most about the ones from kobo was the page numbering that corresponds to actual screens required to reach the end of a chapter. After noticing this I started combing the net and discovered the infamous kepub format. On finding this I also found that changing the extension of the files from .epub to to .kepub.epub should give me access to some of those features so I did just that.<br />
Results were mixed:<br />
<ol>
<li>Hooray, we've got page numbering</li>
<li>In addition to page numbering we can also control the text appearance using the fonts menu</li>
<li>hmmm, where did the covers go? (actually I did read about this happening)</li>
<li> the third thing is more subtle and I only noticed that after reading about 30 pages of a chapter, closing the book and then trying to pick up where I had left off: while the chapter was correct, it always loaded the first page. The problem here is that I could no longer add annotations to the book.</li>
</ol>
Since I really, really wanted that page numbering but I don't have the time to read a book in one go I started to poke around a little.<br />
A warning first:<br />
<ol>
<li>this work on the kobo touch with the 2.0 firmware</li>
<li>While I've managed to get covers and annotations working I don't know if anything else broke</li>
<li>FOLLOW THIS GUIDE AT YOUR OWN RISK </li>
</ol>
The first thing you need to do is to change the extension of your epub to .kepub.epub and upload it to your kobo (disconnect the device to trigger it to detect the book). The rest I'll split into three parts:<br />
<a href="#addingcovers">Give me my cover back</a><br />
<a href="#addingannotations">Recover annotations</a><br />
<a href="#lazymode">Get lazy with scripting</a><br />
<br />
<br />
<h3>
<span style="font-size: large;"><a href="" name="addingcovers">Give me my cover back</a></span></h3>
<span style="font-size: large;"><span style="font-size: small;"> The first thing I wanted to do was get rid of the ugly kobo generated covers. Even if the epub has a cover set after changing the extension to .kepub.epub the cover is no longer used and is replaced with some generated covers. </span></span><br />
<span style="font-size: small;">In order to fix this you need to do the following:</span><br />
<ol>
<li><span style="font-size: small;">Assuming you cover is a jpeg file named mywonderfullcover.jpg</span></li>
<li><span style="font-size: small;">you will need your cover in four sizes (you can also convert it to grayscale to reduce the size) </span><span style="font-size: large;"><span style="font-size: small;"> and each one of this files needs to have a special name and the extension changed from .jpg to .parsed:</span></span></li>
<ol>
<li><span style="font-size: large;"><span style="font-size: small;"> "</span></span><span style="font-size: small;">mywonderfullcover</span><span style="font-size: large;"><span style="font-size: small;"> - N3_LIBRARY_FULL.parsed" - size: 355 x 530 pixels</span></span></li>
<li><span style="font-size: large;"><span style="font-size: small;"> "</span></span><span style="font-size: small;">mywonderfullcover</span><span style="font-size: large;"><span style="font-size: small;"> - N3_LIBRARY_GRID.parsed" - size: 149 x 223 pixels</span></span></li>
<li><span style="font-size: large;"><span style="font-size: small;"> "</span></span><span style="font-size: small;">mywonderfullcover</span><span style="font-size: large;"><span style="font-size: small;"> - N3_LIBRARY_LIST.parsed" - size: 60 x 90 pixels</span></span></li>
<li><span style="font-size: large;"><span style="font-size: small;"> "</span></span><span style="font-size: small;">mywonderfullcover</span><span style="font-size: large;"><span style="font-size: small;"> - N3_LIBRARY_SHELF.parsed" - size: 40 x 60 pixels</span></span></li>
</ol>
<li><span style="font-size: large;"><span style="font-size: small;">Connect you kobo and copy the files you created to the .kobo/images folder </span></span></li>
<li><span style="font-size: large;"><span style="font-size: small;">Next you'll need to update the sqlite database. The file can be found at .kobo/KoboReader.sqlite. <span style="color: red;">I suggest making a back of this first</span>. Open the database. I use SQLiteSpy but you can use any sqlite browser you like. Open up the content table and look for the row that contains your book name in the ContentID column and has the ContentType column set to 6. The ImageId column for this row should be empty. Set it to "</span></span><span style="font-size: small;">mywonderfullcover</span><span style="font-size: large;"><span style="font-size: small;">".</span></span></li>
<li><span style="font-size: large;"><span style="font-size: small;">Save the changes and disconnect the reader</span></span></li>
<li><span style="font-size: large;"><span style="font-size: small;">In order for the cover to be detected you have to restart the reader (if anyone knows of another way of refreshing the data from the database please let me know) </span></span></li>
</ol>
<span style="font-size: large;"><span style="font-size: small;">Now you should have your cover back, plus the additional benefits already listed but you still don't have any annotations</span></span><br />
<br />
<br />
<h3>
<span style="font-size: large;"><a href="" name="addingannotations">Recover annotations</a></span></h3>
At first I thought that the annotations are kept somewhere in the database...I was wrong.<br />
I also tried putting the file in the .kobo/kepub folder and adding the required entried manually in the database but still there were no annotations although I did learn some stuff about the database structure while doing this<br />
After doing a little more reading on the forums I found some opinions that there are some special javascripts involved so I opened a free book that I got from the kobo site. This also proved wrong but it did lead me in the correct direction.<br />
There doesn't seem to be any javascript involved but, in looking at the html files it look like the html files inside the kepubs have some special tags inserted that allow annotations to be retrieved. <br />
So what I did was open each html file and wrap the content of each top level h and p tags in a span tag with the id set to "kobo.[incremental_count]".1 where incrementa_count starts at 1 and goes as high as you need it to.<br />
To illustrate this let's suppose you have the following insisde the body tag of your html:<br />
<pre class="brush: html"><h1>Chapter</h1>
<h2>SubChapter</h2>
<p>some paragraph</p>
<p>some other paragraph</p>
<p>the last paragraph</p>
</pre>
<br />
This would be changed to:<br />
<pre class="brush: html"><h1><span id="kobo.1.1">Chapter</span></h1>
<h2><span id="kobo.2.1">SubChapter</span></h2>
<p><span id="kobo.3.1">some paragraph</span></p>
<p><span id="kobo.4.1">some other paragraph</span></p>
<p><span id="kobo.5.1">the last paragraph</span></p>
</pre>
<br />
Upload your .kepub.epub back on the device and enjoy the freshly recovered annotations.<br />
<br />
<br />
<h3>
<span style="font-size: large;"><a href="" name="lazymode">Get lazy with scripting</a></span></h3>
<h3>
<span style="font-size: large;"> </span></h3>
<h4>
<span style="font-size: large;"><span style="font-size: small;">Automatic renaming using calibre</span></span></h4>
<span style="font-size: large;"><span style="font-size: small;">If you use calibre you can configure it to rename your file when uploading it to the reader by going to "Preferences->Sending books to device" and and adding ".kepub" at the end of the title template.</span></span><br />
<span style="font-size: large;"><span style="font-size: small;"><br /></span></span><br />
<h4>
<span style="font-size: large;"><span style="font-size: small;">Automatic photo shop script for updating images</span></span></h4>
<span style="font-size: large;"><span style="font-size: small;">If you use photoshop you can use the script below to automatically create and rename your cover files (save the code in a file with the extension set to .jsx). Just open your jpeg file in photoshop and go to File->Scripts->Browse and select the file where you saved the script code. Your original file will be left unchanged.</span></span><br />
<pre class="brush: js"> #target photoshop
main();
function main(){
if(!documents.length) return;
var startRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var quality = 10
var doc = app.activeDocument;
var Name = doc.name.replace(/\.[^\.]+$/, '');
var Path = decodeURI(doc.path);
var outFolder = Folder(Path);// +"/"+Name);
if(!outFolder.exists) outFolder.create();
doc.changeMode(ChangeMode.GRAYSCALE);
doc.bitsPerChannel = BitsPerChannelType.EIGHT
createNamedSnapshot("Snap 1");
doc.resizeImage(355, 530, 96, ResampleMethod.BICUBIC);
var saveFile = File(outFolder +"/"+Name+" - N3_LIBRARY_FULL.parsed");
SaveJPEG(saveFile,quality);
var saveFile = File(outFolder +"/"+Name+" - N3_LIBRARY_FULL.jpeg");
SaveJPEG(saveFile,quality);
revertNamedSnapshot("Snap 1");
doc.resizeImage(149, 223, 96, ResampleMethod.BICUBIC);
var saveFile = File(outFolder +"/"+Name+" - N3_LIBRARY_GRID.parsed");
SaveJPEG(saveFile,quality);
revertNamedSnapshot("Snap 1");
doc.resizeImage(60, 90, 96, ResampleMethod.BICUBIC);
var saveFile = File(outFolder +"/"+Name+" - N3_LIBRARY_LIST.parsed");
SaveJPEG(saveFile,quality);
revertNamedSnapshot("Snap 1");
doc.resizeImage(40, 60, 96, ResampleMethod.BICUBIC);
var saveFile = File(outFolder +"/"+Name+" - N3_LIBRARY_SHELF.parsed");
SaveJPEG(saveFile,quality);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.preferences.rulerUnits = startRulerUnits;
}
function SaveJPEG(saveFile, jpegQuality){
jpgSaveOptions = new JPEGSaveOptions();
jpgSaveOptions.embedColorProfile = true;
jpgSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
jpgSaveOptions.matte = MatteType.NONE;
jpgSaveOptions.quality = jpegQuality; //1-12
activeDocument.saveAs(saveFile, jpgSaveOptions, true,Extension.LOWERCASE);
}
function createNamedSnapshot(name) {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putClass( charIDToTypeID('SnpS') );
desc.putReference( charIDToTypeID('null'), ref );
var ref1 = new ActionReference();
ref1.putProperty( charIDToTypeID('HstS'), charIDToTypeID('CrnH') );
desc.putReference( charIDToTypeID('From'), ref1 );
desc.putString( charIDToTypeID('Nm '), name );
desc.putEnumerated( charIDToTypeID('Usng'), charIDToTypeID('HstS'), charIDToTypeID('FllD') );
executeAction( charIDToTypeID('Mk '), desc, DialogModes.NO );
}
function revertNamedSnapshot(name) {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putName( charIDToTypeID('SnpS'), name );
desc.putReference( charIDToTypeID('null'), ref );
executeAction( charIDToTypeID('slct'), desc, DialogModes.NO );
}
</pre>
<br />
<h4>
<span style="font-size: large;"><span style="font-size: small;"> </span></span></h4>
<h4>
<span style="font-size: large;"><span style="font-size: small;">Automatic kobofying</span></span></h4>
<span style="font-size: large;"><span style="font-size: small;">Now</span> <span style="font-size: small;">all that is fine but what if you don't want to spend twice as much time as is requred to read a book just to kobofy it.</span></span><br />
<span style="font-size: large;"><span style="font-size: small;">No worries, since I'm a little lazy myself I create the small python script bellow. In order to run it you need python 2.7 and BeautifulSoup installed. </span></span><br />
<span style="font-size: small;">In order to use it unpack your files to a folder, create file called something.py inside that folder containing the code bellow and run it by calling "python something.py". </span><br />
<span style="font-size: small;">Warning: this script was hastily put toghether so it has a few restrictions:</span><br />
<ol>
<li><span style="font-size: small;">It always adds the spans to h and p tags so <span style="color: red;">don't run it multple times on the same file</span> </span></li>
<li><span style="font-size: small;">It only works on top level h and p tags so, for example, if your h and p tags are wrapped in a div or another tag it won't work.</span></li>
</ol>
<br />
<pre class="brush: py">import sys
from bs4 import BeautifulSoup
import re
import os, os.path
def altertags(soup):
counter = 1
for tag in soup.body.find_all(re.compile("^(p|h)"), recursive=False):
new_tag = soup.new_tag("span", id="kobo."+str(counter)+".1")
counter = counter + 1
tag.wrap(new_tag)
tag.unwrap()
new_tag.wrap(tag)
def parsefile(infile, outfile):
soup = BeautifulSoup(open(infile))
altertags(soup)
output = open(outfile,'w')
output.write(str(soup))
output.close()
if (len(sys.argv) > 2):
infile = sys.argv[1]
outfile = sys.argv[2]
parsefile(infile, outfile)
if (len(sys.argv) == 2):
infile = sys.argv[1]
parsefile(infile, infile)
if (len(sys.argv) < 2):
for file in os.listdir("."):
if file.endswith("html"):
parsefile(file, file) </pre>
If someone can improve this please share.<br />
I don't actually use this as it requires you to unpack the book, repack and so on and that's to much work for me so I just modified kiwidude's <a href="http://www.mobileread.com/forums/showthread.php?t=154371&page=16">Modify Epub</a> plugin for calibre to do this for me.<br />
<br />
Well...that's about it. If anyone reads this and finds it useful feedback is always appreciated.Anonymoushttp://www.blogger.com/profile/12660293310913786395noreply@blogger.com33