$theTitle=wp_title(" - ", false); if($theTitle != "") { ?> } else { ?> } ?>
by Andrew Johnstone
recently read “Binaries Belong in the Database Too” on sitepoint.com, and thought I would shed some light with regard to my experience of storing files in databases. I’m sure many of you have known this to be a taboo practice, and I would certainly agree depending on the database. A project I worked on for MTV Networks Europe/International required a completely shared nothing architecture. This meant that MTV’s hosting & operations imposed that I stored files in the database, and expressed my hesitation.
* Linux
* Apache
* MySQL
* PHP5
You typically get the common file upload problems with upload_max_filesize, max_input_time, execution time however you also have issues with mysql connections and max packet sizes, mysql chunked streams. Uploads via JUpload allows for large file uploads, however you still encounter TCP/IP connection interuptions and errors. Some of the more major issues I encountered were with the actual management of the data. Currently MySQL has no real support for handling Binary Large Objects, for example if you try to load data in from a file you generally encounter max packet size errors. Although the most fundamental issue is that the MySQL protocol does not send chunked streams for blobs and the client has to load the entire blob into memory. Admittedly memory limits on the server were not too much of an issue, as I was using a 8 CPU 16GB of ram server, however you may not have some of the infrastructure that I had available.
Whilst there were a number of limitations I had to resolve as described above, some of the expectations that I had not anticipated for were some user errors, such as trying to upload 4mb BMP files to be streamed as images for a website. Other factors were that hosting & operations had not expected their adsales department to attempt to upload 120+ mb video files.
Firstly lets look at some of the limitations on the BLOB datatype in MySQL, as you can see there are length limitations on blobs.
TINYBLOB
A BLOB column with a maximum length of 255 (2^8 - 1) bytes.
BLOB[(M)]
A BLOB column with a maximum length of 65,535 (2^16 – 1) bytes.
Beginning with MySQL 4.1, an optional length M can be given. MySQL will create the column as the smallest BLOB type largest enough to hold values M bytes long.
MEDIUMBLOB
A BLOB column with a maximum length of 16,777,215 (2^24 – 1) bytes.
LONGBLOB
A BLOB column with a maximum length of 4,294,967,295 or 4GB (2^32 – 1) bytes. Up to MySQL 3.23, the client/server protocol and MyISAM tables had a limit of 16MB per communication packet / table row. From MySQL 4.0, the maximum allowed length of LONGBLOB columns depends on the configured maximum packet size in the client/server protocol and available memory.
Alternatives for storing >4Gb BLOBs are:
* Compressing the BLOB so that it fits in 4Gb
* Splitting up the BLOB into 4Gb chunks as separate rows columns.
Get Blob length
To find the length in bytes of a stored BLOB. Simply use: SELECT LENGTH(blobcolumn) FROM table.
Get Blob fragment
To retrieve large BLOBs by using repeatedly retrieving only fragments of a BLOB
using substring, ie:
SELECT SUBSTRING(document, 1, 10240) FROM documents WHERE did=3;
and then
SELECT SUBSTRING(document, 10241, 10240) FROM documents WHERE did=3;
etc.
Inserting Blobs
Inserting data into BLOBs. It has to be inserted in hex ie: ‘A’ = 0×41 and ‘AB’ = 0×4142 and so on. The prefix is a zero not a cap o.
If you want to insert binary data into a string column (such as a BLOB), the following characters must be represented by escape sequences:
NUL NUL byte (ASCII 0). Represent this character by '�' (a backslash followed by an ASCII '0' character). Backslash (ASCII 92). Represent this character by '\'. ' Single quote (ASCII 39). Represent this character by '''. " Double quote (ASCII 34). Represent this character by '"'.
When writing applications, any string that might contain any of these special characters must be properly escaped before the string is used as a data value in an SQL statement that is sent to the MySQL server, base64 encoding is a good option.
Indexing Blobs
Blobs can sometimes can be indexed, depending on the storage engine you’re using:
MyISAM, InnoDB, and BDB tables support BLOB and TEXT indexing. However, you must specify a prefix size to be used for the index.This avoids creating index entries that might be huge and thereby defeat any benefits to be gained by that index.The exception is that prefixes are not used for FULLTEXT indexes on TEXT columns. FULLTEXT searches are based on the entire content of the indexed columns, so any prefix you specify is ignored.
MEMORY tables do not support BLOB and TEXT indexes.This is because the MEMORY engine does not support BLOB or TEXT columns at all.
BLOB or TEXT columns may require special care:
Due to the typical large variation in the size of BLOB and TEXT values, tables containing them are subject to high rates of fragmentation if many deletes and updates are done. If you’re using a MyISAM table to store BLOB or TEXT values, you can run OPTIMIZE TABLE periodically to reduce fragmentation and maintain good performance.
The max_sort_length system variable influences BLOB and TEXT comparison and sorting operations. Only the first max_sort_length bytes of each value are used. (For TEXT columns that use a multi-byte character set, this means that comparisons might involve fewer than max_sort_length characters.) If this causes a problem with the default max_sort_length value of 1024, you might want to increase the value before performing comparisons. If you’re using very large values, you might need to configure the server to increase the value of the max_allowed_packet parameter. See Chapter 11,“General MySQL Administration,” for more information.You will also need to increase the packet size for any client that wants to use very large values.The mysql and mysqldump clients support setting this value directly using a startup option.
The solution ended up utilizing 2 memcached servers that cached blobs and objects between the MySQL server, this saved streaming the content directly from MySQL upon each request. Then selecting chunks of data from a binary large object and concatenating the results alleviates maximum packet errors from MySQL. The only other aspects to alleviate are the initial upload, this is entirely upto you, how this is implemented whether it is via JUpload, SCP, FTP, or some other means. Finally increase the above settings. To import / export binary files I wrote a script that queried the database writing out the files, by chunking the data, this script did take a while to execute.
I have heard that Oracle has very good support for handling Binary Large Objects… Maybe thats something to look into..
Pointers.
http://jeremy.zawodny.com/blog/archives/000078.html
http://jeremy.zawodny.com/blog/archives/000840.html
http://www.lentus.se/warehouse/SlidesDW.ppt
http://sunsite.mff.cuni.cz/MIRRORS/ftp.mysql.com/doc/en/BLOB.html
In: PHP
1 Aug 2006It’s been some time since I last posted, due a rather hectic schedule and today I fly off to Loret De Mar, Spain for a week with the lads and two weeks after that to Miami. I’m sure this will help to take my mind of things.
As with anything you constantly learn from your mistakes and encounter various challenges and I have found that there are some problems with PHP itself from a recent and ongoing project.
Serialized Objects, Heredoc, and strings. (PHP Version 4.3.2)
Writing a serialized object to a a flat published file can hold its benefits, however this can become exceptionally large. One of the fundamental issues I found was that a fairly large serialized object inside the Heredoc syntax, would halt execution of php. Apache would log memory limits in the apache logs, however using base64 encoding and decoding on a serialized object and placing it within string literals, would execute fine. From observation, the fault would incur directly during the closing chevron’s.
URL’s with double slashe at the beginning
A single slash at the root directory will point to the sites root directory. e.g.
<a href="/root_script.php">/root</a>
I’m quite surprised i’ve never encountered this issue before, however you can manage to get URL’s to point to http://directory_name/script_name.php when the target is actually pointing to http://www.thehistorychannel.co.uk//directory_name/script_name.php for example, (note the double slash at the root directory).
When the script’s action simply contains:
< ?php
print "<form action="{$_SERVER['PHP_SELF']}" method="post">";
?>
I’m not sure if anyone else has encountered this problem?
I wrote this a while ago, whilst playing with SQL Injection, however a little unfinished, the idea was to try to write out entire files, through an SQL Injection attack.
I thought I would expand, on my previous post Exceptions, Exceptions, Exceptions, and see what is possible with a simple a SQL Injection attack. I will base this on the assumption, that if you’ve managed to overlook an arbitry SQL Injection attack, I will assume that there will be vulnerable output somewhere.
$id = ($_REQUEST['id'])? (int) $_REQUEST['id'] : 0; $SearchTerm = (isset($_REQUEST['q']))? $_REQUEST['q'] : null; if ( $id!=0 && !empty($SearchTerm) ) { $SQL = "SELECT id, StartDate, EndDate, Title FROM Table_One WHERE id={$id} AND Title='{$SearchTerm}';"; $Query = mysql_query($SQL) or die('Query Error: '.mysql_error()); $Row = mysql_fetch_array($Query, MYSQL_ASSOC); if(!empty($Row['Title'])) { print $Row['Title']; } } A couple experiments with sql injection. LOAD%20DATA%20INFILE%20'/home/httpd/vhosts/ajohnstone.com/httpdocs/index.php'%20INTO%20TABLE%20test.OUTRUN; LOAD DATA INFILE '/root/.bash_profile' INTO TABLE test.OUTRUN; string(96) "LOAD DATA INFILE '/home/httpd/vhosts/ajohnstone.com/httpdocs/index.php' INTO TABLE test(Output);" Query Error: Access denied for user 'user@'localhost' (using password: YES)
In: General
6 Jul 2006After toying with some XPath’s and a fair amount of XSL lately (& getting lost in XPath), I feel it would be wise to place something here to look back on…
Marcus Tucker, introduced me to Stylus Studio of which I am very thankful as XmlSpy didn’t help me to deduce the problem with one of the XPaths, (infact ‘Select Previous Link’)
So heres some snippets.
Select Previous Link – Select First Preceding Node @id with with specified PathID
//project/article[@id=$PathID]/preceding-sibling::article[1]/@id
Select Next Link – Select Following Sibling @id with with specified PathID
//project/article[@id=$PathID]/following-sibling::article/@id
Also using the following are very handy for helping you know where abouts you are:
<xsl:copy-of select=”parent::*”>
<xsl:copy-of select=”position()”>
<xsl:template match="*" mode="namespace"> <xsl:for-each select="namespace::*"> <xsl:if test="not(../ancestor::*[namespace::*[name() = name(current()) and . = current()]][last()])"> <xsl:value-of select="name(..)" /> defines <xsl:text /> <xsl:choose> <xsl:when test="name()">xmlns:<xsl:value-of select="name()" /></xsl:when> <xsl:otherwise>xmlns</xsl:otherwise> </xsl:choose> <xsl:text />="<xsl:value-of select="." />"<xsl:text /> </xsl:if> </xsl:for-each> </xsl:template>
<!-- generate namespace declarations as needed for current element node and its attributes. --> <xsl:template name="findNamespace"> <xsl:variable name="curnode" select="." /> <xsl:for-each select=".|@*"> <xsl:variable name="vName" select="substring-before(name(), ':')"/> <xsl:variable name="vUri" select="namespace-uri(.)"/> <xsl:variable name="vAncestNamespace"> <xsl:call-template name="findAncNamespace"> <xsl:with-param name="pName" select="$vName"/> <xsl:with-param name="pUri" select="$vUri"/> <xsl:with-param name="pNode" select="$curnode" /> </xsl:call-template> </xsl:variable> <xsl:if test="not(number($vAncestNamespace))"> <!-- not sure if "parent::*" should be "$curnode/parent::*" --> <xsl:if test="parent::* or namespace-uri() or contains(name(), ':')"> <xsl:text> </xsl:text> <span> <xsl:value-of select="'xmlns'"/> <xsl:if test="contains(name(), ':')"> <xsl:value-of select="concat (':', $vName)"/> </xsl:if> </span> <span>="</span> <span> <xsl:value-of select="namespace-uri()"/> </span> <span>"</span> </xsl:if> </xsl:if> </xsl:for-each> </xsl:template> <!-- Return 1 if an ancestor of pNode (or an attribute thereof) has the same prefix (pName) and namespace-uri (pUri) as the current node, and there is no closer ancestor (or attribute thereof) that has the same prefix but a different namespace-uri. Return 0 otherwise. --> <xsl:template name="findAncNamespace"> <xsl:param name="pNode" select="."/> <xsl:param name="pName" select="substring-before(name(), ':')"/> <xsl:param name="pUri" select="namespace-uri(.)"/> <xsl:choose> <xsl:when test="not($pNode/parent::*)">0</xsl:when> <xsl:otherwise> <xsl:choose> <xsl:when test="($pNode/.. | $pNode/../@*) [$pName = substring-before(name(), ':') and $pUri = namespace-uri()]"> 1 </xsl:when> <xsl:when test="($pNode/.. | $pNode/../@*) [$pName = substring-before(name), ':')]"> <!-- and $pUri !=namespace-uri() --> 0 </xsl:when> <xsl:otherwise> <xsl:call-template name="findAncNamespace"> <xsl:with-param name="pNode" select="$pNode/.."/> <xsl:with-param name="pName" select="$pName"/> <xsl:with-param name="pUri" select="$pUri"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:template>
I also wrote up some XSL to retreive parameters from XSL being parsed on the client, now I haven’t tested this although it may be beneficial to someone trying to extract parameters on the client-side.
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:eg="http://schemas.example.com/" > <xsl:output omit-xml-declaration="yes" method = "html" indent = "no" encoding = "UTF-8" /> <msxsl:script language="JScript" implements-prefix="eg"> function GetParam(name) { var start=location.search.indexOf("?"+name+"="); if (start<0) start=location.search.indexOf("&"+name+"="); if (start<0) return ''; start += name.length+2; var end=location.search.indexOf("&",start)-1; if (end<0) end=location.search.length; var result=location.search.substring(start,end); var result=''; for(var i=start;i<=end;i++) { var c=location.search.charAt(i); result=result+(c=='+'?' ':c); } return unescape(result); } </msxsl:script> <xsl:template match="/"> <xsl:value-of select="eg:GetParam('Query')" /> </xsl:template> <xsl:stylesheet>
In: General
25 Apr 2006Well, I purchased a Mac Book Pro roughly a month ago and considering I have never used a Mac, i’m quite surprised as to how quickly I have adapted. Within a week I had a good grasp of the platform and now I have everything sync’d to or through my laptop, whether that be incoming phone calls on my mobile or syncronising data between multiple servers.
The fundamental aspects I like about Mac is the degree of control, you have over the OS with applescript or shell. As mentioned above, I am now making my Mac work for me with proximity detection via bluetooth, and/or trigger applescripts when motion is detected via the iSight. I find this extremely useful to actually log phone calls in iCal, or pause iTunes when on the phone. I’m also tempted to write some applescript to automatically log me onto the the intranet at work, and track my time keeping for me, with aspects such as this it just makes my life that little bit easier.
With that said, i’m not a complete Mac convert, as I have got my windows machine at work & at home constantly shelled into the Mac, I’m finding that I am only using windows to test against IE 6 or as an extra keyboard. Despite some of its gains I am still yet to figure out how to get my Exchange account at work to work completely remotely with it using a reverse proxy, I guess thats simply time and patience I need to get that setup. I guess the key things now are finding out what else is possible, and making my development experience better.
Anyway, I found the following links useful.
www.kernelthread.com
www.opensourcemac.org
www.petermaurer.de
In: PHP
27 Mar 2006Well, i’ve been rather busy at work, which is nothing short of typical now, however I thought I would post up a script that I wrote to automate the installation of PHP on Windows. In general I’m a very lazy person, and found it irritating going to the PHP site, downloading and manually installing PHP in the same mundane fashion that I have described in the past. Oh, and that even means monitoring the PHP.net site for new releases.
The script I wrote uses a batch file, JScript on the client using WSH,WMI, and a few applications I have bundled with this quick distribution. Which copies, SVN, CVS, and an unzip package (located in the directory bin). It will then download a number of packages that I like to keep tabs on such as:
Simply execute “CVS.PHP.bat”
Whilst, I have only set this to download CVS phpweb, and extract each. Anyway, I realise that it does struggle with PHP4, however that simply requires some DLL’s into the SAPI dir for example, anyway I haven’t got time to do anymore on this, considering i’m now using a Mac Book Pro.
There are a couple things, I haven’t automated such as virtualhosts, and setting the hosts file, however if someone has some spare time, would be nice to get that working too…
Note: requires Apache 2 and I have this scheduled to run every day at 6.30pm.
Anyway for those lazy people enjoy
In: PHP
14 Mar 2006After upgrading to the very latest build of Plesk the API error seems to have been resolved, and no longer causes an API error of “Domain adding was failed. Error: Can`t resolve ID for IP ()“.
Previously:
[Server 3]# rpm -qa |grep -i psa-api | sort psa-api-cli-7.5.2-rhel3.build75050130.17 psa-api-common-7.5.2-rhel3.build75050130.17 psa-api-rpc-7.5.2-rhel3.build75050130.17 psa-api-rpc-doc-7.5.2-rhel3.build75050130.17 psa-api-rpc-protocol-7.5.2-rhel3.build75050130.17
Now:
[Server 3]# rpm -qa |grep -i psa-api | sort psa-api-cli-7.5.4-rhel3.build75051014.16 psa-api-common-7.5.4-rhel3.build75051014.16 psa-api-rpc-7.5.4-rhel3.build75050930.11 psa-api-rpc-doc-7.5.4-rhel3.build75050930.11 psa-api-rpc-protocol-7.5.4-rhel3.build75050930.11
Perhaps upgrading on windows too will also resolve this issue.
In: PHP
12 Mar 2006I found this a little amazing, but SWSoft don’t actually fully support their own software.
“Unfortunately, SWSoft does not fully support the API that is built in to Plesk and more than likely, they will request that we first perform an upgrade to the latest version of Plesk before they provide any further support.”, Rackspace.
The Fault:
“Domain adding was failed. Error: Can`t resolve ID for IP ()”
NOT Working Correctly
* Server 3 (Plesk 7.5.2)
Working Correctly
* Server 2 (Plesk 7.1.7)
* Server 1 (Plesk 7.5.4)
The IP Address does exist in the domains table, for all servers.
Working Servers:
* Server Name: Server 2
mysql> SELECT * FROM IP_Addresses; +----+-----------------+-----------------+-------+-----------+--------------------+-------------------+ | id | ip_address | mask | iface | type | ssl_certificate_id | default_domain_id | +----+-----------------+-----------------+-------+-----------+--------------------+-------------------+ | 2 | 212.100.254.166 | 255.255.255.0 | eth0 | shared | 1 | 47 | | 3 | 10.230.166.4 | 255.255.255.192 | eth1 | exclusive | 1 | NULL | | 4 | 192.168.1.20 | 255.255.255.0 | eth0 | exclusive | 1 | 22 | | 5 | 10.230.166.8 | 255.255.255.192 | eth1 | exclusive | 1 | NULL | +----+-----------------+-----------------+-------+-----------+--------------------+-------------------+ 4 rows in set (0.00 sec)
—————-
Server Name: Server 1
mysql> SELECT * FROM IP_Addresses; +----+---------------+-----------------+-------+-----------+--------------------+-------------------+ | id | ip_address | mask | iface | type | ssl_certificate_id | default_domain_id | +----+---------------+-----------------+-------+-----------+--------------------+-------------------+ | 1 | 70.85.198.202 | 255.255.255.248 | eth0 | shared | 1 | NULL | | 2 | 70.85.198.203 | 255.255.255.248 | eth0 | shared | 1 | NULL | | 3 | 70.85.198.204 | 255.255.255.248 | eth0 | exclusive | 1 | 65 | | 4 | 70.85.198.205 | 255.255.255.248 | eth0 | shared | 1 | NULL | | 5 | 70.85.198.206 | 255.255.255.248 | eth0 | exclusive | 7 | 15 | +----+---------------+-----------------+-------+-----------+--------------------+-------------------+ 5 rows in set (0.00 sec)
—————
Failed Server
Server name: Server 3
mysql> SELECT * FROM IP_Addresses; +----+---------------+-----------------+-------+-----------+--------------------+-------------------+ | id | ip_address | mask | iface | type | ssl_certificate_id | default_domain_id | +----+---------------+-----------------+-------+-----------+--------------------+-------------------+ | 4 | 10.230.166.7 | 255.255.255.192 | eth1 | exclusive | 1 | 0 | | 5 | 192.168.1.22 | 255.255.255.0 | eth0 | shared | 1 | 0 | | 6 | 192.168.1.223 | 255.255.255.0 | eth0 | shared | 1 | 0 | +----+---------------+-----------------+-------+-----------+--------------------+-------------------+ 3 rows in set (0.00 sec)
——-
One fundamental aspect that I noticed from observation is that Server3 had the columns default_domain_id default value set to 0 where as on all other installations had the value set to NULL. The assumption was that the record 0 for the default IP ID against the domain. Although ALTER’ing the table and testing against each resulted without sucess.
Testing against all IP’s in the waidev6 table results in the following.
* 1. [10.230.166.7] – Domain adding was failed. Error: IP address ‘10.230.166.7′ is not present in client ip pool.
* 2. [192.168.1.22] – Domain adding was failed. Error: Can`t resolve ID for IP ()
* 3. [192.168.1.223] – Domain adding was failed. Error: Can`t resolve ID for IP ()
The XML response for this is…
<?xml version="1.0" encoding="UTF-8"?> <packet version="1.3.1.0"> <domain> <add> <result> <status>error</status> <errcode>2307</errcode> <errtext>Domain adding was failed. Error: Can`t resolve ID for IP ()</errtext> </result> </add> </domain> </packet>
The payload of the XML document sent across via the XMLRPC request.
<packet version="1.3.1.0"> <domain> <add> <gen_setup> <name>testgen8.com</name> <client_id>1</client_id> <status>0</status> <ip_address>192.168.1.223</ip_address> <vrt_hst> <ftp_login>catalina0</ftp_login> <ftp_password>057177a</ftp_password> <ftp_quota>0</ftp_quota> <fp>false</fp> <fp_ssl>false</fp_ssl> <fp_auth>false</fp_auth> <fp_admin_login>miaumiaul </fp_admin_login> <fp_admin_password>lalalalallaa<fp_admin_password> <ssl>false</ssl> <shell>/bin/false</shell> <php>true</php> <ssi>false</ssi> <cgi>false</cgi> <mod_perl>false</mod_perl> <mod_python>false</mod_python> <asp>false</asp> <asp_dot_net>false</asp_dot_net> <coldfusion>false</coldfusion> <webstat>awstats</webstat> <errdocs>false</errdocs> <at_domains>true</at_domains> </vrt_hst> <htype>vrt_hst</htype> </gen_setup> <hosting> <vrt_hst> <ftp_login>catalina0</ftp_login> <ftp_password>057177a</ftp_password> <ftp_quota>0</ftp_quota> <fp>false</fp> <fp_ssl>false</fp_ssl> <fp_auth>false</fp_auth> <fp_admin_login>miaumiaul</fp_admin_login> <fp_admin_password>lalalalallaa </fp_admin_password> <ssl>false</ssl> <shell>/bin/false</shell> <php>true</php> <ssi>false</ssi> <cgi>false</cgi> <mod_perl>false</mod_perl> <mod_python>false</mod_python> <asp>false</asp> <asp_dot_net>false</asp_dot_net> <coldfusion>false</coldfusion> <webstat>awstats</webstat> <errdocs>false</errdocs> <at_domains>true</at_domains> </vrt_hst> <htype>vrt_hst</htype> </hosting> <prefs> <www>true</www> </prefs> </add> </domain> </packet>
I have also been told that this error also occurs on Windows.
In: General
1 Feb 2006Here are a few snapshots of Internet Explorer 7 Beta 2. Interestingly enough ASP.NET does not display correctly in Internet Explorer 7 Beta 2. I can’t say i’m keen on the new design or see any feature that particularly appeals to me, however it may grow on me. Also memory usage whilst using a single tab seems very low, however opening multiple tabs soon crept upto 120k.
Admittedly I don’t know too much about XAML, however its interesting to see some permissions mapped into Internet Explorer, already. As mentioned in my previous post, I now have “Microsoft Expression Interactive Designer” installed, so, I can dabble with XAML.
Firstly, with the RSS integration you cannot distinguish whether you have read an individual post, as it marks the entire feed as read when selecting a feed, you also cannot mark the entire feed as un/read, and it does not allow you to customise the RSS feed with XSL or CSS. I’m sure some of these short comings will be resolved in new’er versions.
In: General
25 Jan 2006Lately i’ve been playing with a number of beta’s from Microsoft.
Firstly was Microsoft MAX, after watching a fairly recent cast on channel9 and installing WinFX Runtime Components September CTP, I received the following error in Outlook 2003 “Could not initialize CLR MANAGED MAPI SERVICE CATASTROPIC FAILURE, Unknown error”. After finding out that The WinFX Runtime Components September CTP relies on a pre-release version of .NET Framework 2.0 and if you install it after you have installed the release version of .NET 2.0, it will completely mess up the CLR (This is easily fixed by uninstalling/installing the .NET Framework). So it looks like I will have to wait for the next release from the MAX team.
<script language="JavaScript" src="http://favorites.live.com:80/js/omniture.js?exp=11.0.1123.2"></script>
I must admit I am fairly fond of MSN Messenger 8.0 Beta, from what i’ve seen so far, its not differed very much besides its user interface. Some of the key changes are Shared Folders, and storing Contact details for users. Although one thing that is missing is allowing you to move your favourite groups in a custom order.
I haven’t played with Sparkle yet, and i’ve also noticed “Microsoft Prerelease Software WinFX Runtime Components – January 2006 Community Technology Preview (CTP)“, so I will give it a try shortly. Hopefully this will fix, Max, so I can get that working too.
I have been a developer for roughly 10 years and have worked with an extensive range of technologies. Whilst working for relatively small companies, I have worked with all aspects of the development life cycle, which has given me a broad and in-depth experience.