Getting the hang of css.

There’s some really nice stuff you can do in CSS, one of which is getting rid of graphics for borders, backgrounds, and buttons. Transparency is a boon as well allowing good styling with minimal effort, in my case surrounding pre tags with transparent slightly grey background to help them stand out:

pre {
	background-color: rgba(150,150,150,0.4);
	border: 2px solid rgba(100,100,100,0.6);
	-moz-border-radius: 2px;
	-webkit-border-radius: 2px;
	-opera-border-radius: 2px;
	-kthml-border-radius: 2px;
	border-radius: 2px;
	margin-left: 2em;
	margin-right: 2em;
	margin-bottom: 0px;
	margin-top: 0px;
	padding: 5px;
}

What has been bugging me though after working through a custom wordpress theme to tie in with the website for my application (see here) is how to match dynamically generated css class or ID styles. Such as the ones produced by wordpress functions like the_ID() and post_class().

The answer is quite straightforward in the end. This is how you match dynamically generated class or ID styles in CSS3, it might work in other variants of CSS:

div[id^="post"] h1 {
	font-weight: normal;
	text-align: left;
}

The above will match an h1 tag inside any div that has an ID starting with “post”.

I’d scanned the w3 document quite a bit but the use of this didn’t sink in until I realised that err.. well class and id tags ARE elements of div, h1 or whatever.

Soon I’ll be able to upload this local wordpress install in all it’s glorylack of content^H^H^H^H^H^H^H^H^H^H^H. 😛

Posted in Miscellany | Tagged , , , | Comments Off on Getting the hang of css.

An interesting week

I think I’ve made around 5 abortive attempts to submit my app now, that’s no reflection on the quality of the app store or the submissions process but on where my brain has been focused. Purely on the development of the software and not the support structure that it might need. The latest hiccup came at the final stage which I didn’t realise you had to have prepared prior to approval. The optional web/support site that it asks for are probably not optional if you want people to trust the software and believe that it will be maintained. Cue scurrying off, finding a suitable domain that is free, registering it… and then going “Oh bollocks I don’t actually KNOW how to do this”. So around 5 days later I have a reasonable enough grasp of HTML and CSS to make a basic site and can get on with doing the bit I do understand, systems administration, software, configuration and maintenance.
I knew I was going to have to do that, I was just hoping to do it whilst apple do their approval thang.

So now we’re very close:

– Site content is written.
– Site is mostly configured.
– Email configured, and tested.
– Sitemap scripts sorted out.

All that is really left is:

– Gather 5 suitable iPad screenshots.
– Gather 5 suitable iPhone screenshots.
– Write a little support page, probably just formmail for now.

Ideally I also need to:

– Install blog, easy.
– Skin blog, err….
– Install forum, modified to allow .chefdoc. Probably easy.
– Write CV. Which I’m crap at.

Posted in Miscellany | Comments Off on An interesting week

Whilst deciding what to include with the App, we write a PDF generator!

Managed to implement rendering of recipes into multipage PDF documents. They look something like this:

Lasagne Recipe

One thing to note whilst writing the code was the usual inverted co-ordinates. I wanted an image in the top left, title and summary in the top right. After some internal debate I settled on a two column format for the rest. The image is easy, just use the UIImage drawInRect message once you’ve started your PDF context. As for the text the origin of your text box is the bottom left due to the inverted co-ordinates, so it is important to remember when calculating offsets that you need to set the offset.x and offset.y options to the margins of your page and adjust the size.height & size.width settings of the rect to ensure the text renders at the appropriate point. If you need to calculate the size of the rect the text occupies before rendering (not to adjust the rect for rendering but to calculate the location of the next rect) use CTFramesetterSuggestFrameSizeWithConstraints. For example when rendering the summary in the top right I use that function so I can decide whether the image or the summary is the larger and can therefore decide which value to use when calculating the available height for the columns. The following PDF has some debug code turned on that shows a red outline for the rect used to render the text and a small circle at the origin point of the rect. As you can see although the summary text is small the rect was the full height of the page.

Lasagne Recipe with Guidelines

As for rendering the core text itself I settled on a function like this:


+ (CFRange)renderTextRange:(CFRange)currentRange andFrameSetter:(CTFramesetterRef)frameSetter intoRect:(CGRect)frameRect {
	CGMutablePathRef framePath = CGPathCreateMutable();
	CGPathAddRect(framePath, NULL, frameRect);
	CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, currentRange, framePath, NULL);
	CGContextRef currentContext = UIGraphicsGetCurrentContext();
	CGContextSaveGState(currentContext);
	CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);	
	CGContextTranslateCTM(currentContext, 0, 792); 
	CGContextScaleCTM(currentContext, 1.0, -1.0);
	CTFrameDraw(frameRef, currentContext);
	/*
	CGContextSetStrokeColorWithColor(currentContext, [[UIColor redColor] CGColor]);
	CGContextAddRect(currentContext, frameRect);
	CGContextStrokePath(currentContext);
	CGContextAddEllipseInRect(currentContext, CGRectMake(frameRect.origin.x-5, frameRect.origin.y-5, 10, 10));
	CGContextStrokePath(currentContext);
	 */
	CGContextRestoreGState(currentContext);
	CGPathRelease(framePath);
	currentRange = CTFrameGetVisibleStringRange(frameRef);
	currentRange.location += currentRange.length;
	currentRange.length = 0;
	CFRelease(frameRef);
	return currentRange;
}

The debug code is the commented out section. The Save/Restore CGState calls are for my own sanity as is the CGContextSetTextMatrix so I can ensure that drawing operations outside of this routine do not affect the drawing process. Although if you remove them keep in mind that the CGContextXXXCTM calls will need undoing somehow or each time the routine is called you’ll apply new and interesting transformations to your text.

If anyone wants me to post a full example block of code comment and I’ll update or make a new post.

Posted in Programming | Tagged , , , , | Comments Off on Whilst deciding what to include with the App, we write a PDF generator!

Nearing release, implement shiny things whilst waiting.

As the project nears release my thoughts turn to what will be needed in the near future and how best to solve an intermittent bug that’s present now. The bug was resolving by doing something I should have done ages ago, be consistent with how I use data in table views. When developing the app the most complicated view did not use NSFetchedResultsController as it required 2 of them, one to drive each section.
Implementing this bought another problem to the fore, how to identify recipes uniquely in the DB? The obvious method is to use the objectID that core data uses but this changes until the recipe is finalised by saving it. The problem there is driving the sections from NSFetchResults whilst the recipe is being created. Each addition can change the objectID invalidating a simple predicate such as:

[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"recipe== %@",[recipe objectID]]];

The simplest solution is to create the recipe and immediately save it, blank, to the database then present it to the view. I have issues with that, crashes could lead to blank recipes polluting the integrity of the database and all sorts of problems. My minimum requirement is for the recipe to have a title (non unique) at least. So the solution I’ve known was needed has been implemented, UUID’s. For ease of use you want something that:

  • Adds UUIDs as part of the core db migration when the new managed object model is loaded.
  • Automatically creates and adds a UUID to a newly inserted recipe.

To help with this I created a helper class with the function:

+ (NSString*)UUIDString {
    CFUUIDRef theUUID = CFUUIDCreate(NULL);
    CFStringRef string = CFUUIDCreateString(NULL, theUUID);
    CFRelease(theUUID);
    return [NSMakeCollectable(string) autorelease];
}

To ensure the UUID is added to the core data object every time a recipe is created you can add this to the NSManagedObject subclass:

- (void)awakeFromInsert {
	[super awakeFromInsert];
	[self setValue:[KookaDIS UUIDString] forKey:@"uuid"];
}

Now for the method to update the database upon launch. Assuming you have automatic migrations turned on with the NSMigratePersistentStoresAutomaticallyOption option you need to create a NSEntityMigration subclass. I didn’t choose the best name for mine but it looks like this:

#import 

@interface AddUUIDToRecipeEMP : NSEntityMigrationPolicy {

}

@end
#import "AddUUIDToRecipeEMP.h"
#import "KookaDIS.h"

@implementation AddUUIDToRecipeEMP

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject*)src 
									  entityMapping:(NSEntityMapping*)map 
											manager:(NSMigrationManager*)mgr 
											  error:(NSError**)error
{
	NSManagedObjectContext *destMOC = [mgr destinationContext];
	NSString *destEntityName = [map destinationEntityName];
	
	NSManagedObject *dest = [NSEntityDescription insertNewObjectForEntityForName:destEntityName
														  inManagedObjectContext:destMOC];
	
	NSArray *keys = [[[src entity] attributesByName] allKeys];
	NSDictionary *values = [src dictionaryWithValuesForKeys:keys];
	
	[dest setValuesForKeysWithDictionary:values];
	[dest setValue:[KookaDIS UUIDString] forKey:@"uuid"];
	// 511 is binary 111 111 111 or chmod 777.
	[dest setValue:[NSNumber numberWithInt:511] forKey:@"permissions"];
	
	[mgr associateSourceInstance:src withDestinationInstance:dest forEntityMapping:map];
	
	return YES;
}

@end

Ignore the permissions setValue for now, that’s for when we’ve covered the new UUID updates. So we now have a custom entity migration class. To use it create a new file in your project choosing “iOS->Resource->Mapping Model”. Within the mapping model click on the pre-made entity mapping that is relevant for you. In the far right hand pain you should see a Custom Policy field, simply type the name of the class you made in there. That’s it. Afaict this turns off any other automatic migration for that record so if you have other alterations you will need to code those changes manually too.

The above changes mean that the predicate for getting the data for the sections is consistent whether the recipe is new or not. It becomes:

[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@”recipe.uuid == %@”,recipe.uuid]];

The other thing I have considered is the potential inclusion of other recipes with permission and permissions for user uploads to the server. So I implemented a quick bitwise permissions system based on linux simple user/group/world permissions for read/write/execute. I’ve modified it to creater/owner/world. Owner would be someone with explicit permission or (heaven forbid) in app purchase and world would be any other download/import.

I felt that storing the data as an int is the simplest solution but to present that to the rest of the program in such a way would be anachronistic in such a modern language so I needed two routines to convert the info from an int to an NSDictionary and vice versa. Thus these two routines were born and added to the afore mentioned class:

NSString * const kKDISShareEnabled = @"KookaDocumentShareable";
NSString * const kKDISEditEnabled = @"KookaDocumentEditable";
NSString * const kKDISPrintEnabled = @"KookaDocumentPrintable";

NSString * const kKDISCreator = @"KookaDocumentCreator";
NSString * const kKDISOwner = @"KookaDocumentOwner";
NSString * const kKDISWorld = @"KookaDocumentWorld";


+ (NSDictionary *)permissionsDictionaryFromNumber:(NSNumber *)permissions {
	NSMutableDictionary *dictionary = [[[NSMutableDictionary alloc] init] autorelease];
	int value = [permissions intValue];
	int creator = value & 0x7;
	int owner = value >> 3 & 0x7;
	int world = value >> 6 & 0x7;
	
	NSNumber *cs = [NSNumber numberWithInt:creator & 0x1];
	NSNumber *ce = [NSNumber numberWithInt:creator >> 1 & 0x1];
	NSNumber *cp = [NSNumber numberWithInt:creator >> 2 & 0x1];
	NSNumber *os = [NSNumber numberWithInt:owner & 0x1 ];
	NSNumber *oe = [NSNumber numberWithInt:owner >> 1 & 0x1];
	NSNumber *op = [NSNumber numberWithInt:owner >> 2 & 0x1];
	NSNumber *ws = [NSNumber numberWithInt:world & 0x1];
	NSNumber *we = [NSNumber numberWithInt:world >> 1 & 0x1];
	NSNumber *wp = [NSNumber numberWithInt:world >> 2 & 0x1];
	
	[dictionary setObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:cs,ce,cp,nil]
													  forKeys:[NSArray arrayWithObjects:kKDISShareEnabled,kKDISEditEnabled,kKDISPrintEnabled,nil]]
				   forKey:kKDISCreator];
	[dictionary setObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:os,oe,op,nil]
													  forKeys:[NSArray arrayWithObjects:kKDISShareEnabled,kKDISEditEnabled,kKDISPrintEnabled,nil]]
				   forKey:kKDISOwner];
	[dictionary setObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:ws,we,wp,nil]
													  forKeys:[NSArray arrayWithObjects:kKDISShareEnabled,kKDISEditEnabled,kKDISPrintEnabled,nil]] 
				   forKey:kKDISWorld];
	
	return dictionary;
}

+ (NSNumber *)permissionsNumberFromDictionary:(NSDictionary *)dictionary {
	int value = 0;
	int world = [[[dictionary valueForKey:kKDISWorld] valueForKey:kKDISPrintEnabled] intValue] << 2;
	world |= [[[dictionary valueForKey:kKDISWorld] valueForKey:kKDISEditEnabled] intValue] << 1;
	world |= [[[dictionary valueForKey:kKDISWorld] valueForKey:kKDISShareEnabled] intValue];
	int owner = [[[dictionary valueForKey:kKDISOwner] valueForKey:kKDISPrintEnabled] intValue] << 2;
	owner |= [[[dictionary valueForKey:kKDISOwner] valueForKey:kKDISEditEnabled] intValue] << 1;
	owner |= [[[dictionary valueForKey:kKDISOwner] valueForKey:kKDISShareEnabled] intValue];
	int creator = [[[dictionary valueForKey:kKDISCreator] valueForKey:kKDISPrintEnabled] intValue] << 2;
	creator |= [[[dictionary valueForKey:kKDISCreator] valueForKey:kKDISEditEnabled] intValue] << 1;
	creator |= [[[dictionary valueForKey:kKDISCreator] valueForKey:kKDISShareEnabled] intValue];
	
	value |= world << 6 & 0x1c0;
	value |= owner << 3 & 0x38;
	value |= creator & 0x7;
	
	return [NSNumber numberWithInt:value];
}

I am not used to coding bitwise operations so my use of odd masks may be a very poor way of doing it but it works.

Posted in Programming | Tagged , , , , , , | Comments Off on Nearing release, implement shiny things whilst waiting.

Want to backup files in windows the linux way?

Download this cwRsync.

Which is rsync packaged up for windows, and create a small script like this to run as administrator:


@ECHO OFF

SETLOCAL

REM ** CUSTOMIZE ** Specify where to find rsync and related files (C:\CWRSYNC)
SET CWRSYNCHOME="c:\Program Files (x86)\cwRsync"

REM Set CYGWIN variable to 'nontsec'. That makes sure that permissions
REM on your windows machine are not updated as a side effect of cygwin
REM operations.
SET CYGWIN=nontsec

REM Set HOME variable to your windows home directory. That makes sure 
REM that ssh command creates known_hosts in a directory you have access.
SET HOME=%HOMEDRIVE%%HOMEPATH%

REM Make cwRsync home as a part of system PATH to find required DLLs
SET CWOLDPATH=%PATH%
SET PATH=%CWRSYNCHOME%\BIN;%PATH%

sc stop MsMpSvc
taskkill /f /im msmpeng.exe
rsync --delete -rvP /cygdrive/d/games/steam /cygdrive/f/games
sc start MsMpSvc

Adjust your rsync line as you need to, also note that you don’t need to stop microsoft security essentials but it will slow everything down whilst you’re creating/writing so many files. Of course, if you don’t use it that’s cool but you might want to temporarily shut down whatever tool you do use.

Posted in Programming | Tagged , , , , | Comments Off on Want to backup files in windows the linux way?

Updated.

WordPress/Akismet & Slickr Flickr updated to latest versions.

Posted in Miscellany | Comments Off on Updated.

Exporting your digital comics from Comics.app on iPhone

*Post originally contained information on how to backup your comics, redacted at the request of Comixology’s CEO.*

Digital comics are a great thing, and I’m slowly amassing a reasonable library of them through the fantastic Comics.app (Comixology) on the iPhone/iPad. One thing concerns me though and almost holds me back from buying all the issues of Witchblade I want so I have the full collection. Seeing as I’m currently selling all my dead tree copies of the same I want to know that my digital comics are safe & not going to go anywhere. There’s no option on Comixology to view what you’ve purchased in the past and certainly no options to download CBR’s or similar of your purchases. As has just been pointed out to me by David Steinberger (Comixology CEO) they are indeed viewable on the web, here:

Comixology Online Comic Viewer – Account login required.

I swear that was white paging for me last night, which is why I decided to look into scripting an extraction process in the first place.

So the problem with digital distribution is that in a lot of cases you do not really own what you buy. What happens when digital distribution providers vanish? You are normally left with a purchase encumbered by DRM that you cannot read. Your investment wasted. This is a worrying development in consumers rights. If I purchase a comic/book/cd/game in a bricks and mortar store nobody can really take that away from me. I can scan it/copy it and keep it if I so wish and nobody is really going to know unless I start waving it around on the web. I can also loan the item itself to friends. They can enjoy that and go on to collect/purchase the item or recommend it to their friends leading to increased sales. Admittedly Kindle are doing this now and it’s a great thing, bringing some of the functionality of real books into the digital world.

What if I choose/amforced to abandon the provider… Say I no longer can use an iPhone/Android or have some argument with Amazon and refuse to use their services? I have made those purchases, not rented them. It’s not like deciding to avoid Waterstones or Sainsbury’s.

How do I take them with me? Well in many ways as it happens but with laws such as the DMCA and others it makes it potentially illegal to do so. Like so many strict interpretations of copyright law when handled by corporations they criminalise and therefore marginalise the very people who are the biggest fans. A recent example of this for me was the BBC’s showing of some of Bob Monkhouse archive. He was a huge huge fan of TV/Radio comedy and did everything he could to archive these for his personal use. It got him arrested, although charges were eventually dropped. Some TV shows now recognise this dichotomy, I believe Tru Blood is very lenient towards it’s fans use of it’s imagery. Other’s not so (*cough*Harry Potter*cough*). Another recent example I saw on 4chan’s /co/ board. Somebody had ripped and posted scans of an entire comic for others to read, effectively saying this is ace look look! The author of the comic noticed and joined in the thread, not to condemn but to encourage. Get out there, read my stuff, please buy my books. That’s to be applauded, it clearly worked for him and drove sales. Perhaps that only works for the smaller publishers… would it have worked to promote say, AP Comics Darkham Vale or Com.X’s Bazooka Jules, the first two issues said x/6 the 3rd said no such thing and turned out to be the last. Similar stories exist with Young Liars by David Lapham and other big names as well and that series was beguiling, confusing and excellent. It didn’t work for the piloted and then leaked TV version of Warren Ellis’ Global Frequency, mores the shame.

I for one am happy to get digital comic books to read for less than a 1/3 of the price UK stores tend to charge. It’ the price point digital distribution should be, significantly less than the corporeal version not some 10% tip of the hat. iBooks especially but Kindle and Waterstones etc. take note… your prices are a fucking joke, in some cases 50% plus (amazon) higher than you yourself sell the dead tree version. There is NO way a digital copy of a book should ever cost more than the dead tree version. Are they trying to protect their paper manufacturing arms? Perhaps publishers, in a vertical investment, have shares in managed forests, tree felling, pulping and paper producing industries and they think this protects their alternate revenue streams.

Posted in Literature, Miscellany, Programming | Tagged , , , , , , | Comments Off on Exporting your digital comics from Comics.app on iPhone

Taking advantage of the ebay free listings weekend.

So… free listings on ebay. It’s an experiment. I’ve listed some of these items in the past and they haven’t sold. I was kind of glad they didn’t as I listed them all at £0.99 and I’d probably have lost money on the auction just on postage costs. Now I don’t have the space and the models I want to keep and display have been kept and displayed. So here’s what’s currently up on ebay from me:

DizietEmbless Ebay Items

The swimsuit leela model really is rare.  The eva model is the only one of it’s kind I’ve ever seen so I believe they are rare.  It was found in a comic/anime store in the middle of Amsterdams red light district.  Somewhere near a church by a canal if memory serves.  It was found along with another Eva model that was huge, which I’m keeping.  In part because I like it and also because it wouldn’t resell as some of the guns have gone AWOL over the last 10 years.

The experiment here is that it makes sense that lower start prices encourage people to bid, so if you start at 0.99 by the time it gets to the price you wanted to list it at (say 10.00) you’ll have a few people competing to win it and the price would be higher than if you listed it at 10.00 to start with.  This is true and it works.  So setting reserves can help, but everyone sees reserve not yet met so might be put off challenging just to find the seller won’t sell.  Also reserves have to be set over £50 so it’s not practical for any of these items bar the PSP.  In the promotion though, to get a free auction, you couldn’t use a reserve.  So I’ve listed them at reasonable starting prices from the off, which is still free.  If they sell great.  If they don’t I might relist at 0.99, I might find a local store to take them, or I might just open the boxes and play with the toys like my inner 14 yr old wants me to.

Posted in Miscellany | Tagged , , , , | Comments Off on Taking advantage of the ebay free listings weekend.

Providing a consistent UI experience between UITextField and UITextView

There are a few major differences between the two but the most glaring is that UITextView does not support styles so you cannot create them stylistically the same as a UITextField. You cannot readily apply background images depending on state and you cannot apply styles such as UITextBorderStyleRoundedRect. To get around this in the most simple fashion it appears to me that the best method, and one of the simplest, is to create your own UITextView subclass and use that. For this you need a few images in @2x and normal formats containing the background colours you want. In my case I use these, created from a simple PSD that lets me easily alter the background colour:

For the UITextField components I can then apply the setting like so:

	[name setBackground:textfieldEditBG];
	[name setDisabledBackground:textfieldPlainBG];

Having first loaded the images into those variables with methods like:

[[UIImage imageNamed:@"UITextFieldRoundedRectD"]  stretchableImageWithLeftCapWidth:12 topCapHeight:12];

This means that when I just want to view the text it appears unadorned and when I enable it for editing with:

[desc setEnabled:YES]

I get the rounded rect effect. For the UITextView subclass I use a simple hack as my Plain variants are the same as the background of the view in which they are present:

//
//  UIRoundedTextView.h
//  Cooking Companion
//
//  Created by Paul Downs on 10/12/2010.
//  Copyright 2010 The Lost Souls. All rights reserved.
//

#import 


@interface UIRoundedTextView : UITextView {
	UIImage *borderImage;
}

@property (nonatomic, retain) UIImage *borderImage;

- (void)setBorderImage:(UIImage *)background;

@end
//
//  UIRoundedTextView.m
//  Cooking Companion
//
//  Created by Paul Downs on 10/12/2010.
//  Copyright 2010 The Lost Souls. All rights reserved.
//

#import "UIRoundedTextView diovan hct.h"


@implementation UIRoundedTextView

@synthesize borderImage;

- (void)setBorderImage:(UIImage *)background {
	if (borderImage != background) {
		borderImage = nil;
		[borderImage release];
		borderImage = [[background stretchableImageWithLeftCapWidth:12 topCapHeight:12] retain];
		[self setClipsToBounds:NO];
	}
}

- (void)drawRect:(CGRect)rect {
	[super drawRect:rect];
	if (borderImage && [self isEditable]) {
		UIGraphicsGetCurrentContext();
		[borderImage drawInRect:rect];
		UIGraphicsEndImageContext();
	}
}

- (void)dealloc {
	[borderImage release];
	[super dealloc];
}

@end

I can then initialise such a view to display the adorned version in edit mode with:

	[desc setBorderImage:[UIImage imageNamed:@"UITextFieldRoundedRectD"]];
	[desc setClipsToBounds:NO];

Seeing as the drawRect mode has been overridden it does not interfere with the rendering of the text above the image. You could also use a transparent image here if you do not wish to change the background colour away from that of the main view behind it.

It’s important to note that any UITextView components in IB (xib/nib) files will need to have their class set to UIRoundedTextView. If anyone reads this, feel free to use the code above in your own projects.

Posted in Programming | Tagged , , , , | 1 Comment

Book Review : Hidden Empire – Saga of the Seven Suns by Kevin J. Anderson

I’m not new to Kevin J. Anderson’s writing by any stretch but this is certainly the first novel of his that has left me in such an undecided state. I’m not a fan of his style but I did enjoy several of the Dune universe extensions he wrote with Frank Herbert’s son. What differs here is that this is something entirely of his own creation and something he obviously has a very clear vision of. The book itself has believable and likeable characters occupying a well thought out universe with a pretty damn good plot to tie it all together. It ends a bit abruptly but I find that entirely forgivable seeing as all seven books are out. It may feel derivative to someone who has read a lot of space opera with the usual diplomacy, suppressed peoples, mystical/religious cultures and of course impending doom but the book never felt too derivative in that respect.
I enjoyed the book and indeed want to know what happens both to some species and individual characters/relationships. I will without doubt read the next one in the series, although I probably won’t devote the same amount of time to absorbing each word like I normally would diovan medication. So what has spurred me to write a review? Something has always niggled about Anderson’s writing and I’ve never been able to characterise why in the past, reading this novel solidified why. The main problem is two fold, repetition and repetition. Err.. sorry I meant repetition. Did I write that again? Maybe I thought you didn’t take it in properly in the preceding sentence. I understood very quickly that several things were true, the proud Ildiran Solar Navy was undefeatable for example. Or the Klikiss robots made some uneasy. Or that the Theron Green Priests could communicate instantly via Treelings and that they represented a huge knowledge base etc. etc. That’s fine but telling me the same thing over and over again every time they are mentioned or perform an action is unnecessary at least and at worst insulting. Each time the solar navy performs a manoeuvre or a general is mentioned you do not need to remind me how proud they are, normally using the same sentence structure/adjectives. Another issue is that the book rarely surprises as the author’s intentions are telegraphed too far in advance, this isn’t always related to the repetition but is certainly a bedfellow of it. Consequently you know many of the stories key moments chapters ahead of schedule and in some cases at the beginning of the book.
It’s a shame in a way as I can’t shake the feeling that with a better editor the story being told here would truly shine and deliver some genuine moments along the way. That’s not to say it doesn’t or cannot surprise but I get the feeling it could be so much more. I’m also not saying it wasn’t an enjoyable enough read. Perhaps that’s the highest compliment I can give it. I enjoyed the book enough that the problems it does have inspired me to complain about them. If the story and characters were not compelling I would simply dismiss it and move on.

Posted in Art, Literature | Tagged , , | 1 Comment