All posts by Chui

Calibre E-book – Authoring Plugins with a Custom Context Menu

I’ve been hacking with the Calibre ebook reader for a few hours, and I’d like to share with you some notes of how to add a custom context menu to a book, so that we can open a secondary folder where the book has been exported to.

A good place to start is to download the interface_demo.zip from the Plugins documentation. Unzip the directory, and run Calibre in debug mode from that directory.

mkdir demo
cd demo
unzip interface_demo.zip

REM loads calibre plugin from the current directory
../calibre-customize.exe  -b .

REM launch calibre gui in debug mode
../calibre-debug.exe -g

Next, add the custom action to the context menu manually through the preference toolbar. I’ve taken some screenshots below:

That done, we can go in and hack on the plugin until it works.

In my particular case, I’ve got an alternate filepath stored in a custom metadata field, and I’d like to open the alternate containing folder.

#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai

__license__   = 'GPL v3'
__copyright__ = '2014 Chui Tey teyc@cognoware.com'
__docformat__ = 'restructuredtext en'


from calibre.gui2.actions import InterfaceAction
import os

class InterfacePlugin(InterfaceAction):

    name = 'Open Oracle Folder'
    action_spec = ('Open Oracle folder', 'images/blank.png',
                   'Open the file in the Oracle folder', 'CTRL+O')
    dont_add_to = frozenset(['context-menu-device'])
    action_type = 'current'

    def genesis(self):
        #icon = get_icons("images/blank.png")
        #self.qaction.setIcon(icon)
        #self.qaction.setEnabled(True)
        self.qaction.triggered.connect(self.open_folder)

    def open_folder(self):
        rows = self.gui.current_view().selectionModel().selectedRows()
        if not rows or len(rows) == 0:
            return
        not_found = []
        for i, row in enumerate(rows):
            if i > 5: continue
            mi = self.gui.library_view.model().db.get_metadata(row.row())
            filepath = mi.get('#filepath', None)
            if filepath:
                import os
                folderpath = os.path.split(filepath)[0]
                if os.path.isdir(folderpath):
                    os.startfile(folderpath)
                else:
                    not_found.append(folderpath)
        if not_found:
            if len(not_found) > 1:
                raise Exception("Folders %s not there!" % ", ".join(not_found))
            else:
                raise Exception("Folder %s not there!" % ", ".join(not_found))

            # actual path
            # path = self.gui.library_view.model().db.abspath(row.row())

    def location_selected(self, loc):

        enabled = loc == 'library'
        self.qaction.setEnabled(enabled)

Tip: Create an App icon for your website

If you already have a web site, and you are thinking about creating a mobile app version of this, there is a less expensive approach.

Mobile users who bookmark your website can get essentially an App experience because your website will have a proper App icon, rather than a screenshot of your website.

Here’s a flow chart of what you will need to support iOS, Android and Windows

Note to myself.

NodeJS requires VS2010 to build some binary components. If you install Visual C++ 2010 Express, you only get to build 32bit binaries. Therefore, you should install the 32-bit NodeJS msi.

Cocktail for Windows Store – Part 3

One style of Entity Framework development is known as Code-First. With Code-First style, you’d define the schema using .NET classes and properties. This style of development makes it easy to keep the data model under source control.

Entity Framework comes with a powershell script that detects the schema changes as you add/modify the classes, and generates the necessary upgrade scripts for you.

Our project involves an assignment tracker for schools. Students can be assigned one or more assignments in subjects they are enrolled to. Assignments are created by the teachers who teach the relevant subject.


// Schema.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace AssignmentTracker
{
    using IdeaBlade.Aop;

    [ProvideEntityAspect]
    public class Assignment
    {
        [Key]
        public int Id { get; set; }

        [MaxLength]
        public string Description { get; set; }

        [ForeignKey("Student")]
        public int StudentId { get; set; }

        [ForeignKey("Subject")]
        public int SubjectId { get; set; }

        public DateTime? DateDue { get; set; }

        [Association("Assignment_StudentSystemUser", "StudentId", "Id")]
        public Student Student { get; set; }

        [Association("Assignment_Subject", "SubjectId", "Id")]
        public Subject Subject { get; set; }
    }

    [ProvideEntityAspect]
    public class Subject
    {
        public Subject()
        {
        }

        [Key]
        public int Id { get; set; }

        [Required]
        [MaxLength(255)]
        public string Description { get; set; }

        [ForeignKey("Teacher")]
        public int TeacherId { get; set; }

        [Association("Subject_Teacher", "TeacherId", "Id")]
        public Teacher Teacher { get; set; }

        public IList<Student> Students { get; set; }
    
    }

    [ProvideEntityAspect]
    public class SystemUser
    {
        [Key]
        public int Id { get; set; }

        [MaxLength(100)]
        [Required]
        public string UserName { get; set; }

        [MaxLength(100)]
        public string FullName { get; set; }

    }

    public class Student : SystemUser
    {
        public Student()
        {
            //Subjects = new RelatedEntityList<Subject>();
        }

        [Required]
        [MaxLength(15)]
        public string StudentNumber { get; set; }

        public ICollection<Subject> Subjects { get; set; }
    }

    public class Teacher : SystemUser
    {
        public virtual IList<Subject> Subjects { get; set; }
    }
}

Schema.cs is added to AssignmentTracker.Server.dll and added as a link to the AssignmentTracker.RT project. This is the main value from DevForce, the same code that is used on the server is deployed on the client.

Ideablade Cocktail for Windows Store – Part 2

A Cocktail project is usually grouped into folders called Views and ViewModels. The Views folder contains all the XAML files, and the ViewModels contain the datacontext that each view binds against.

We’ll create the missing GroupedItemsPageViewModel first, and then drag them into the Views and ViewModels folder. All Cocktail ViewModels that represent a screen should inherit from Screen, and INavigationTarget. The INavigationTarget has two purposes:

  1. It provides notification when a screen has been navigated to, or navigating away from – Similar to Silverlights Navigation events
  2. It also provides notification that the screen is about to go into suspended state, so that we can save the work and resume later.
using Caliburn.Micro;
using Cocktail;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AssignmentTracker
{
    public class GroupedItemsPageViewModel : Screen, INavigationTarget
    {
        private INavigator _navigator;

        public GroupedItemsPageViewModel(INavigator navigator)
        {
            _navigator = navigator;
        }

        #region INavigationTarget

        public void LoadState(object navigationParameter, Dictionary<string, object> pageState, Dictionary<string, object> sharedState) { }

        public void OnNavigatedFrom(NavigationArgs args) { }

        public void OnNavigatedTo(NavigationArgs args) { }

        public void OnNavigatingFrom(NavigationCancelArgs args) { }

        public string PageKey { get; set; }

        public void SaveState(Dictionary<string, object> pageState, Dictionary<string, object> sharedState) { }

        #endregion
    }
}

So far, we have not loaded any entity from the database yet. That’ll be the next topic.

Next: Querying entities

Ideablade Cocktail for Windows Store – Part 1

Ideablade is currently offering licenses to Windows Store version for free. The Dev_Tour sample shows how a basic DevForce is set up. However, there is scant documentation on how to create a Windows Store application that runs on top of the Ideablade Cocktail framework.

There are two Windows Store samples bundled with Cocktail – the Todo and the NavSample. The best place to start is the NavSample project. The Todo sample is a little too simplistic to learn much from.

Create Windows Store Application

Create a blank Windows Store Application using the Grid app as a base, and we will call it AssignmentTracker.RT and the solution is named sans the RT suffix. Assignment Tracker allows teachers to assign homework to students. It is not very different from a Todo list conceptually. The reason we add RT to the project is because, one day, we might wish to develop a desktop version.

However, we don’t want the created assemblies to have the .RT suffix. So, go into Project Properties and set accordingly.

Add packages via NuGet

After you create a blank Windows Store application in Visual Studio, add the following packages via NuGet:

  1. IdeaBlade.Cocktail
  2. IdeaBlade.Cocktail.Utils
  3. IdeaBlade.DevForce.Core
  4. Microsoft.Composition

Bootstrapper

The first thing that strikes any seasoned Caliburn.Micro user is that there is no Bootstrapper class in Caliburn.Micro.RT. This is because the Windows Store application provides a base Application class that you must override to get events. I was somewhat dismayed at Microsoft for doing this. WPF, Silverlight and Windows Phone all had one startup model and WinRT comes along and insists on another.

On the other hand, the people at Caliburn.Micro had done a pretty decent job of abstracting over this. There are two base classes provided – CocktailMefWindowsStoreApplication and CocktailWindowsStoreApplication, and they behave in the same way as the bootstrapper, all the normal overrides like Configure, SelectAssemblies, StartRuntime et c are still there, so you retain instant familiarity. (Don’t worry if you don’t know much about Caliburn.Micro, under the hood Cocktail uses a lot of Caliburn.Micro, but Cocktail has a smaller API surface area).

Once NuGet packages are installed, go to App.xaml.cs delete the entire application class (and enjoy the instant weight reduction!) and replace it with this. (Replace NavSample with your own project name)

<cocktail:CocktailMefWindowsStoreApplication
    x:Class="AssignmentTracker.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:cocktail="using:Cocktail"
    xmlns:local="using:AssignmentTracker"
    xmlns:localData="using:AssignmentTracker.RT.Data">

    <cocktail:CocktailMefWindowsStoreApplication.Resources>

        <!-- Application-specific resources -->

        <x:String x:Key="AppName">AssignmentTracker</x:String>

    </cocktail:CocktailMefWindowsStoreApplication.Resources>
</cocktail:CocktailMefWindowsStoreApplication>
using Cocktail;
using IdeaBlade.Core;

namespace AssignmentTracker
{
    /// <summary>
    /// Provides application-specific behavior to supplement the default Application class.
    /// </summary>
    sealed partial class App : CocktailMefWindowsStoreApplication
    {
        /// <summary>
        /// Initializes the singleton Application object.  This is the first line of authored code
        /// executed, and as such is the logical equivalent of main() or WinMain().
        /// </summary>
        public App(): base(typeof(GroupedItemsPageViewModel))
        {
            this.InitializeComponent();
        }

        protected override void StartRuntime()
        {
            base.StartRuntime();

            IdeaBladeConfig.Instance.ObjectServer.RemoteBaseUrl = "http://localhost";
            IdeaBladeConfig.Instance.ObjectServer.ServerPort = 57209;
            IdeaBladeConfig.Instance.ObjectServer.ServiceName = "EntityService.svc";
        }

    }
}

The GroupedItemsPageViewModel is the first ViewModel that will be loaded by Cocktail. Cocktail then automatically finds GroupedItemsPage.xaml or GroupedItemsPageView.xaml, and displays it on the screen. (This is known as ViewModel-first style. If you are careful, you can develop and test the ViewModels without any dependencies on the actual visual controls, and makes it easier to port to other platforms.)

Next: Defining GroupedItemsPageViewModel

Model Driven Development – The MetaModel

A good friend showed me a code generator that their company had whipped up over a couple of days, and I was intrigued by the approach. It is reasonably straightforward, and I thought there must be more to this and there was!

Kathleen Dollard’s Pluralsight course

Kathleen has a very good course on Understanding Metaprogramming. This podcast on Visual Studio retooling from TextTemplatingToolkit and Custom Code Generator to Roslyn Code Generation.

Hundreds of million of dollars have been sunk into code generation.

Tailored Code Generators

Presentation

Tool: Essential Key take aways:

  • Generation of Sample Data
  • The templating should be clean and easy to grasp
  • Model checker
  • Composable – partial model definition, extensibility
  • Naming policy
  • Consistent UI Patterns – Master-Detail, Filter, Collection of objects, Navigation, Command, Selection, Task
  • Conceptual UI Patterns – Login, Instance, Population, Wizard, Filter http://pjmolina.com/cuip
    • Filter
    • Order criteria
    • Display set
    • Actions
    • Navigation
    • Rendering – Grid
    • Table
    • List

The real advantage:

  • Technology Forward Escape

The guy who wrote this presentation also runs radarc which is an impressive suite.

Strongly-typed metadata

Kathleen Dollard has a post and some sample code like Strongly Typed Metadata Classes. There’s a finite amount of metadata. By drawing a line between metadata and software, it works around having bad architecture.

Learning Cobol Round #2

COBOL programs are very similar to FORTRAN ones. Arguments are passed via a DATA DIVISION block. Local variables are declared in a WORKING STORAGE section in the DATA DIVISION.

Interestingly, the variables can have limited range as defined by a string format. For example:

01 THE-NUMBER    PICTURE IS 99.

means the variable THE-NUMBER can hold any numbers from 0-99. (maximum length is 18 digits)

Length of alphanumeric variables are declared using letter X. (maximum length dependent on implementation)

01 THE-MESSAGE PICTURE IS XXXXXXXXXX.
02 NEW-MESSAGE PIC X(10).

One can also declare variables using a shorthand that is reminiscent of SQL (see NEW-MESSAGE)

Formulas are declared using COMPUTE.

COMPUTE THE-RESULT = FIRST-NUMBER + SECOND-NUMBER.

Assignment is performed using the MOVE verb. For example MOVE "Samuel" TO FIRST-NAME.. Interestingly, mathematical operations are done in-place. For example:

 MOVE 5 TO THE-NUMBER.
 MULTIPLY 10 BY THE-NUMBER.
 ADD 1 TO THE-NUMBER.
*GIVES 51

Alternatively, MULTIPLY 10 BY THE-NUMBER GIVING THE-NUMBER. is probably the clearest.

GOSUBs

COBOL has a basic idea of jumping to subroutines. Labels are simply declared without any keywords. The STOP-RUN is used to prevent the program from flowing into the subroutine area.

       PROGRAM-BEGIN.      
       
           PERFORM SAY-HELLO.
           PERFORM SAY-HELLO.
       
       PROGRAM-DONE.
       
           STOP RUN.
       
       SAY-HELLO.
       
           DISPLAY "Hello World"
       

Learning Cobol Round #1

My mum learnt COBOL and FORTRAN when she was in university back in the 60s. Although I have used FORTRAN77 in a professional capacity, I still haven’t had the opportunity to try COBOL.

Since being retro is the harbinger of cool, I’ll spend the next few articles blogging about my experience working with COBOL.

Visual Cobol

There is a free non-commercial use license granted by Visual Cobol. This is sufficient for my learning purposes.

In terms of resources, Teach yourself COBOL is 21 days seems to be a good starting point.

The download is a little tricky. After receiving the confirmation email, I was presented with a screen but the download link wasn’t apparent. It’s tucked under the Software/Licenses tab (see red arrow). The download was 417MB, and I was a little concerned I’ll not have much space left on my SSD, which is burgeoning with several virtual machines. However, the promise of the familiarity of Visual Studio and having access to the .NET framework beckons me to try it out.

In comparison, the OPENCOBOL 1.1 source download is 1 Mb. That’s very inviting, although I don’t really like the idea of building GNU autoconf project on Windows.  There’s never a nice ending.  I found prebuilt-binaries for OPENCOBOL on Windows, and given the download size of 7.3 Mb,  I downloaded it as well.

Upon creating the first COBOL project with Visual Studio, I was prompted for a license key. It wasn’t immediately apparent, but all I had to do was to provide the email address I had used to register with Microfocus, and that activated my free copy automatically.

Running Hello World from a Visual Cobol Console project was simple enough.

       identification division.
       program-id. Program1.

       environment division.
       configuration section.

       data division.
       working-storage section.

       procedure division.

       PROGRAM-BEGIN.      
       DISPLAY &amp;amp;quot;Hello World&amp;amp;quot;.
       PROGRAM-DONE.
       
       end program Program1.

OpenCobol

OpenCobol was straightforward. Unzip the 64bit binaries to C:\OpenCobol, and run vcvars64.bat to set up the path to Visual C (OpenCobol translates COBOL to C and then uses the platform compiler to build executables). The command below builds hello.exe

cobc -x hello.cob

Project scope

In terms of project, I’d like to try my standard project, involving authorization, data validation and persistence (both NoSQL and SQL). I’m mindful that my projects will be un-COBOL-like, but that can be refined with time.

Next article – Round #2