Saturday, 24 December 2011

Unix Domain Sockets on OSX


It's been a while since I've done anything with Unix domain sockets, and it's not the same on OSX!

If we take a look at the structure for sockaddr_un we see that the standard is:
struct sockaddr_un {
  sa_family_t sun_family; /* AF_UNIX */
  char sun_path[108]; /* pathname */
};


However, on OSX it's:
struct sockaddr_un {
  unsigned char sun_len; /* length including null */
  sa_family_t sun_family; /* AF_UNIX */
  char sun_path[104]; /* pathname */
};


Next, the standard ( or at least as far as I am aware of ) way to call bind() is as follows:

len = strlen(local.sun_path) + sizeof(local.sun_family);
if (bind(s, (struct sockaddr *)&local, len) == -1) {
  Didn't work;
}

In the above, local is the sockaddr_un struct, and the path is /var/tmp/socket.

Now as we all know ;) the normal practice is to call unlink() on the path before attempting the bind just in case the program has crashed and left the socket file lying around, however under OSX with the above code, unlink is unable to find the file.

This is because the name of the socket becomes truncated by one character, to /var/tmp/socke

It took some digging, but the correct way to call bind under OSX is as follows:

if (bind(s, (struct sockaddr *)&local, SUN_LEN(&local)) == -1) {
  perror("bind");
exit(1);


This will create the socket with the full path name, here's a complete code snippet:
#define SOCK_PATH "/var/tmp/socket"
struct sockaddr_un local;
char str[100];
int s;
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
 perror("socket");
 exit(1);
}

if (strlen(SOCK_PATH) >= sizeof(local.sun_path)) {
 perror("path to long!");
 exit(1);
}

local.sun_len = sizeof(local);

local.sun_family = AF_UNIX;

strcpy(local.sun_path, SOCK_PATH);

unlink(local.sun_path);

if (bind(s, (struct sockaddr *)&local, SUN_LEN(&local)) == -1) {
perror("bind");
exit(1);
}

//GO off an do what you want here

This caught me out with much head scratching for some time, so I hope it helps someone out there!


Sunday, 11 December 2011

Adobe XMP Panel - Improvements

After publishing the original post about creating a custom XMP panel ( see here ), I was contacted via the Resource Space forum about wether it was possible to create multiple sets of check boxes on the panel that related to different XMP fields, ie:

chk1,chk2,chk3 = XMP field 1
chk4,chk5,chk6 = XMP field 2

I posted on the forum that this would be easy to do using multiple handlers, and calling the relevant handler function when a checkbox is ticked.
Now many of you will note that this is not an elegant solution, but it would work, and was easy to explain!
I started thinking about this further, and came up with a more elegant way to achieve the task, that was also much easier to expand upon as there was only one place you needed to add extra code.
So this post will attempt to describe what I've done, and hopefully make it easier for others to reuse the code.

Step 1 - Layout the Panel
The first thing we need to do is layout the panel, as before this is done using the grid control, and using a naming convention for the id's of the checkboxes that relate to the label of the checkbox but without spaces.

<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Road Haulage"
id="_chkRoadHaulage"    click="updateCats(event,'docTitle')"/>
</mx:GridItem>

The label "Road Haulage" above has an id of "_chkRoadHaulage" so that we can reference it in code easily, as show in the previous article.
The major change to the layout of the checkboxes is the click event. The function that is called now has a second parameter that is the id of the XMP text input that the check box should modify.

<fi:XMPTextInput id="docTitle"
xmpPath="mc:MainCategory
xmpType="Localized"
name="docTitle"
visible="false"
/>

The second set of checkboxes have a different XMP Text Input.

<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="House"
id="_chkHouse"
click="updateCats(event,'docBuilding')"/>
</mx:GridItem>

<fi:XMPTextInput id="docBuilding"
xmpPath="mc:Building
xmpType="Localized"
name="docBuilding"
visible="false"
/>

I'm also setting visible to false as I don't want to give the user the ability to manually edit the field and screw everything up ;)

Step 2 - Code Changes
Next we need to make some changes to the code.
The first thing I thought about is how we handle multiple arrays and reference them easily?
I decided the easiest way to do this was to keep the cats array and store the arrays within that refernced with a key that is the id of the XMP text input.
So the new function that gets called when the panel has completed loading is now very simple.

private function catsReadHandler(event:Event):void
{
/*  This is called once the panel has completed initialising.
        */
// create an array for each xmp field
cats['docTitle'] = new Array();
cats['docBuilding'] = new Array();
//populate the arrays and tick the right boxes
chkBoxes('docTitle');
chkBoxes('docBuilding');
}


All we have to do now is create the sub array for each XMP text input.
The next part calls a new function that reads the text input, populates the array if it contains data and then ticks the boxes that were referenced in the text input. It's basically the same as the previous catsReadHandler function, but I'm using the this[controlID] nomenclature to reference the fields and the checkboxes.
This gets called for each set of checkboxes / XMP field in the panel.

Here's the function:

private function chkBoxes(xmpFieldName: String):void
{
// this takes the name of the xmp field, populates the array, and ticks the checkboxes
var outStr:String;
var catsString: String = this[xmpFieldName].text;
var chkName:String
var catStr:String;
if (catsString.length > 0)
{
// split the text by commas and populate the array
cats[xmpFieldName] = catsString.split(",");
for (var i:int = 0; i < cats[xmpFieldName].length; ++i) 
{
// remove spaces from the cat name and add to _chk to ref the box
chkName = "_chk";
catStr = cats[xmpFieldName][i];
catStr.replace(/s+/g, "");
chkName += catStr;
try {
// tick the box!
this[chkName].selected=true;
} catch (someError:Error)
{
this[xmpFieldName].text=i + " : " + " " + chkName + " " + someError.message;
}
}
}
}

As you can see, it's pretty much the same as before, but putting this in a separate function enables us to simplify the catsReadHandler function and only have to add extra code there.

The final piece of the puzzle is the function that gets called when a user checks / un-checks a box.
This now takes the id of the XMP text input to update, and so we can reference that directly without having to write a special function for each set of check boxes & XMP fields.

private function updateCats(event: Event,fieldName: String):void
{
//we use event.target to identify the control that triggered the function :)
var catName:String = event.target.label;
var outStr:String;
var cName:String;
var delItem:Boolean;
var localArray:Array;
// copy the main array so we can manipulate it.
localArray = cats[fieldName];
if (localArray.length == 0)
{
localArray.push(catName);
} else {
for (var i:int = 0; i < localArray.length; ++i) {
if (localArray[i] == catName)
{
localArray.splice(i,1);
delItem = true;
}
}
if (!delItem)
{
localArray.push(catName);
}
}
cats[fieldName]=localArray;
outStr = cats[fieldName].toString();
this[fieldName].text = outStr;
this[fieldName].dispatchEvent(new Event(Event.CHANGE));
} 

One of the major changes here apart from calling the function with the field id is that I'm copying the array before manipulating it and then copying it back to the main array after the manipulation is done.
I know in theory I shouldn't need to do this, but I was having issues manipulating the main array directly, so I chose this method because I couldn't be bothered to debug it any further than I already had ;)

The complete file can be found here, feel free to play around with it.




Thursday, 8 December 2011

Wednesday, 30 November 2011

ResourceSpace and Static Sync

Some of our clients have a workflow which uses static sync to add the assets to ResourceSpace, but does not ingest them.

The assets are kept on a read only file share, and the users link to the assets within InDesign etc.
One of the problems encountered with this kind of workflow is how do the users find an asset on the file share from within RS?

To overcome this problem, I've written a simple plugin that will open a finder window of the folder that contains the asset.



The only thing you have to configure is the afp server path to the root of the share. This might work for smb as well, but I haven't tested that yet!




To install it, unzip the file and place in the plugins folder.

You can get the file here

AD / OD Plugin - Some tweaks!

I've had feedback from people who have been testing and using this plugin, and there a number of changes that have been made which I though it would be best to document here.

stristr()
If you are a running an older version of PHP ( pre 5.3 I believe ) and you get the following error:
Wrong parameter count for stristr()

Change line 101 in ldap_class.php from:
$usercn = stristr($username,"@",true);
to:
if($pos=stripos($username,"@")) $usercn=substr($username,0,$pos);

This is because the function stristr was changed in version 5.3 and I'm using the newer features.

Changing the name displayed in RS
By default, the plugin is set to display the users name as FirstName LastName in RS, but this can be changed.
To use the displayname container in AD for example, change the following in ldap_class.php

Line 142, change from:
$retArr = array("sn", "givenname", "mail","cn");
to:
$retArr = array("sn", "givenname", "mail","displayname");

Line 152, from:
if (isset($info[0]['cn'][0])) { $retVar['cn'] = $info[0]['cn'][0]; }
to:
if (isset($info[0]['displayname'][0])) { $retVar['cn'] = $info[0]['displayname'][0]; }

This only works for AD, for OD other changes would have to be made. I intend to make this a drop down selection in the next version of the plugin.

Domain Login
When using Active Directory, the user is required to login with the format user@domain.
The reason for this is that the plugin is designed to work in an environment with cross domain trusts.
If you don't have multiple domains, then it's possible to change this behaviour by hardcoding the domain name into ldap_class.php

Change line 89 in ldap_class.php from:
$this->ldaprdn = $username;// ."@".$userContainer;
to:
$this->ldaprdn = $username ."@". "domainName";

Change line 103 from:
$filter="(samaccountname=".$usercn.")";
to
$filter="(samaccountname=".$username.")";

Again, this is a config feature I'm going to add to the next version.

I'd like to say thanks to everyone that has helped test and improve this, I'll add more tweaks as they become known.  

Monday, 28 November 2011

Irving Thalberg

Someone we could all learn from!

http://en.wikipedia.org/wiki/Irving_Thalberg

UK Government backs call for classroom coding

There's an interesting debate going on at the moment about teaching coding in schools.

See:
http://www.bbc.co.uk/news/technology-15923113

What I find more interesting is what I perceive as the situation outside of the school room.

When I was at school, we had computers that didn't do much, unless you programmed them. I was fortunate enough that my parents bought me a computer, which I also had to program to do things, and so subsequently I learnt to program.

Today, computers at home are generally used for surfing the internet, emails etc, and very rarely used for programming.

The necessity is no longer there, and the complexities of learning to program a windows or mac computer when compared to the simplicity of something like a ZX81 are huge.

When I was young, getting a computer to say "I love you" to my girlfriend at the time in a for next loop was exciting, but to todays social media aware teenager I can image that the expression would be "whatever, have you seen the new version of Halo?".

The need to learn to program is no longer there. Where's the primitive excitement in this world of the gooey and the games?

The article complains about over emphasis on how to use software rather than how to write it.

What they don't seem to take into account is that in order to write code one has to be a hacker, a geek, interested in the fundamental design concepts and engineering challenges that have traditionally been associated with traditional engineering disciplines.

It's not without reason that we have titles like "Software Engineer" and "Software Architect. These titles are representative of the other disciplines that they reference, such as "Electrical Engineer" and "Architect".

Not everyone is cut out to be an engineer, just as not everyone is cut out to be an atomic researcher.

When I was younger computer science, programming and engineering was taught at university, and surprise surprise, it still is.

The kids today need to know how to use a computer because they are liable to experience them in most jobs. Art students need a foundation in digital photography and Photoshop, In Design, Illustrator, because they need to know not only how to draw but how to work in the digital world that any job in that field will require knowledge of. Someone working in Maccy D's needs to know how to use the till.

I learnt technical drawing amongst other things, which has now been replaced by Google Sketchup and Autocad, yet the foundations of that discipline enable me to understand how to use the software ( and my geekiness of curiosity ;) 0

So what am I saying? We don't and shouldn't need to teach kids to write software in general school, but we should teach curiosity. My partner is a school teacher and it amazes me the crap they have to teach and the way it has to be taught.

I say, teach the basics, but above all teach curiosity and the ability to learn, and then let the kids find out what it is they love and want to excel in, and then encourage them. There is nothing better for a human being than to love what they do and feel good about the impact they have on those around them, and especially society in general if they are lucky enough to have that impact.

I don't understand why think tanks who have no experience in teaching, or even understanding beyond a closed view of the world, should have the right to decide that teaching this weeks fashionable ideology is more important than exciting the kids to be curios and encouraging them to explore that curiosity?

I feel that what we need to do is encourage kids to have the right to be individual, and celebrate that, rather than try and get conformity to a uniform baseline for stats and school league tables.

Some kids will be curious, and become great software engineers. Some won't but will become amazing artists and challenge our perspectives in both the digital and analogue worlds. Others will become customer service experts and help to develop customer interaction portals that will be a joy to use.

Others won't be any of the above, but will provide the most essential parts of a modern society that are looked down upon. They will be the bin men, the recycling agents, the street cleaners. These people are essential to our society, and should be paid as much if not more than the bankers who keep ruining the economy for everyone else! ( oooh, a bit of controversy there  ;) )
Give your bin men, post women* etc a cash gift at Christmas. That is the real tradition of boxing day, to give a gift to those who serve you. I'm not saying that they are servants to us, but they are civil servants, servants to society who keep it working for the rest of us.
If you see them in the morning, say thank you, because without these people modern society would crumble.

So, my new algorithm is:

var kids = new curiosity();
kids.explore();
kids.develop();

set kids.future = bright;




* I'm not being sexist, just drawing on traditional writing... maybe I should have said person?

Resource Space AD & LDAP Plugin

I've had a lot of enquiries about this, so I'd thought I'd post it here to make it easier to distribute.

I can't take complete credit for this as it was based on a previous plugin written by Brian Adams and Guenter Bartsch.

I've abstracted the ldap communication to a separate class, and created the group based mapping.. see my previous post on integration.

This is a work in progress, as I want to make the configuration a bit more fluid and add some further options.

It currently requires PHP 5.3 or higher, but can be modified to work with earlier versions.

I'll add a post about tweaks that can be done to it later.

To install:
Unzip the file and copy the folder to the plugins directory.

To use the plugin:

Step 1 - Configure the server:
First enter the ldap server address and port.
Select the Directory type.
For AD, enter admin name, password and domain.
For all directories, enter the base dn, User container and login field.
Click create users, and select group based.
Enable the plugin and then click Save.
( See setup screen shot )



Step 2: Configure groups
Go back into the plugin options, and if it has managed to contact the ldap server you will see a list of groups in the directory that can now be mapped to groups in resource space.
Select the group mapping as required, and don't forget to enable each group for login!



The users for AD need to login using the format user@domain, users in LDAP / OD should login with the shortname.


NOTE : The download link has been removed as the plugin is now included in Resource Space as standard.

Custom Adobe Bridge Panel

A client recently requested a layout for a custom panel in Bridge which could not be done within the simple generic panel supplied by Adobe, and so had to be coded in Flash Builder.

After installing Flash Builder and the XMP Panel toolkit, it was time to start work!
Having not used flash / flex before, it was a bit of a learning curve, so this post attempts to describe what I have done, and how it works!

So the requirement was to have a grid of checkboxes on the panel, and as they were checked / unchecked they would update a text field which would be saved as part of the XMP metadata.

This field would then be read by ResourceSpace, which would associate those keywords in the field with categories and auto generate themes.

The keywords in the XMP field needed to be separated by a comma for this to happen.

So, the problem is twofold.
First, how to update the text field when the checkbox is ticked?
Second, how to tick the relevant checkboxes when the panel is loaded if there is text in the field?

So, I've laid out a grid in the panel, with checkboxes on it, which looks like this.
The text box underneath is the field that will contain the keywords.

The code for the grid is:
<mx:FormItem label="Categories" direction="horizontal" name="categories">
<mx:Grid name="mgrid" id="mygrid">
<mx:GridRow width="100%" height="100%">
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Dockers"
id="_chkDockers"
name="_chkDockers"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Trains"
id="_chkTrains"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Aviation"
id="_chkAviation"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Boat"
id="_chkBoat"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Education"
id="_chkEducation"
name="_chkEducation"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox width="51
label="Road
click="updateCats(event)" 
id="_chkRoad"/>
</mx:GridItem>
</mx:GridRow>
</mx:Grid>
</mx:FormItem>


Each checkbox is contained within a grid item, and has an ID associated with it. It also calls a function updateCats() when the box is clicked, more about this later.

The code for the text field is thus:
<fi:XMPFormItem 
id="ftitle"
name="ftitle"
label="$$$/xmp/sdk/custompanels/Dave3/Title=Main Category:
labelTooltip="$$$/xmp/sdk/custompanels/Dave3/TitleToolTip=The title of the document, or the name given to the resource. Typically, it will be a name by which the resource is formally known.">
<fi:XMPTextInput id="docTitle"
xmpPath="mc:MainCategory
xmpType="Localized"
name="docTitle"
/>
</fi:XMPFormItem>
 The things of note here are the XMPTextInput which is mapped to a custom namespace mc:MainCategory.

So how does it work?

First we create a global array that will hold the list of categories that have been ticked.

private var cats:Array = new Array();

Then when each checkbox is ticked / unticked it calls the function updateCats(Event).
Because it passes an event, we can use the event.target to determine which box was ticked, and retrieve it's label and id.
Once we have these, we can add or remove it from the array as necessary.

Here's the code for the updateCats() function:

private function updateCats(event: Event):void

{

//we can use the event.target to identify the control that triggered the function :)

var catName:String = event.target.label;

var outStr:String;

var cName:String;
var delItem:Boolean;
if (cats.length == 0)
{
cats.push(catName);
} else {
for (var i:int = 0; i < cats.length; ++i) {
if (cats[i] == catName)
{
cats.splice(i,1);
delItem = true;
}
}
if (!delItem)
{
cats.push(catName);
}
}
outStr = cats.toString();
docTitle.text = outStr;
docTitle.dispatchEvent(new Event(Event.CHANGE));
}

Once the array has been update, we then convert it to a string and set the text field to display that string. Finally we tell the text field that it's been updated by sending it a change event.

It's not the most elegant code, but it works!

So, onto the second problem, how do we tick the boxes when the panel loads?
Because we have given each checkbox an id, we can reference them in code with this as:
this[elementID];

As we know that we have given the checkboxes id's that follow _chkFieldName, we can easily reference these.

When the panel has completed loading, we call another function called catsReadHandler() that reads the text field, and if it is not empty creates the cats array and then cycles through the array and ticks the boxes.

Here's the function:

private function catsReadHandler(event:Event):void

{

/*  This is called once the panel has completed initialising.

This will update the checkboxes to indicate which ones are ticked, 

and will populate the cats array

*/
var outStr:String;
var catsString: String = docTitle.text;
var chkName:String
var catStr:String;
if (catsString.length > 0)
{
cats = catsString.split(",");
for (var i:int = 0; i < cats.length; ++i) 
{
chkName = "_chk";
catStr = cats[i];
catStr.replace(/s+/g, "");
chkName += catStr;
try {
// this["control id"] allows us to reference the checkboxes
this[chkName].selected=true;
} catch (someError:Error)
{
docTitle.text=i + " : " + " " + chkName + " " + someError.message;
}
}
}
}

One important part is:


catStr.replace(/s+/g, "");

Which removes any spaces in the name of the check box, ie turning Road Haulage into RoadHaulage.
We then append this to _chk to get the name of the checkbox!

Here's the complete file, feel free to play around with it! categories.mxml

I'll be happy to answer any questions, but please bear in mind this is my first attempt at programming in flash / flex / actionscript. Of course, any suggestions for improvement will be welcomed !!





Thursday, 24 November 2011

Directory Integration

Integration between disparate and not so disparate systems is becoming more and more commonplace in my current role, and I find myself having to write more code than ever to integrate systems.

It's strange, because in this connected age one would have thought that integration would have become simpler, but it seems that is not the case. Even simple things like integration with directory systems are either woefully lacking or badly implemented in my experience.

If you have a directory server, why do you need to maintain a copy of the user data locally as well as in the directory? There is NO reason to synchronise the data between the two, that's what replica servers are for.
I have been asked so many times "what happens if the directory goes down? That's why we synchronise the data"
Well I'm sorry folks, but if the directory goes down, and all the replicas fail, there's a much bigger problem than the users being unable to get email for example.

It also seems that the implementation of directory integration seems to be random. I was talking to a developer recently about their software, and how best to implement directory integration. One of the things that is so important in this type of integration is group mapping.

This software has a concurrent user licensing model. If every user in the directory is able to log in, then the chances are that the user licenses will be used up quickly. Then there's the aspect of roles, it's all very well being able to say only users in this group can log in, but how do you define roles? I want to be able to say "users in group a are admins, users in group b are normal users" If the user is in both groups I want to be able to configure whether the higher or lesser role gets mapped, and I want in documented!

Is this too much to ask?

And on top of all this, the user interface for the mapping has to be easy to use. Here's a screen shot of a plugin I wrote recently to enable this functionality. The Directory Groups are listed, select what role you wish that group to have, enable login for that group... simples!

Now I must confess that this was not as easy to write as I first thought. The problem is of course that Active Directory and Open Directory each handle groups in different ways, and querying the directories to find out which groups a user is a member of is not as straightforward as it first seems.

For example, in AD there is a member of container, which I found out doesn't always work!
In OD, one has to query the group to see if the user is in the members container.
I ended up using the the second method for both, as it seems more reliable.

It now works, and has been installed on a few systems, and each time it's installed I end up refining the code more and more.

So developers, can we please have directory integration that is functional and easy to use?