# // http://banshee-itunes-import-plugin.googlecode.com/svn/trunk/
# using System;
# using System.Collections.Generic;
# using System.Text;
# using Banshee.SmartPlaylist;
#
# namespace Banshee.Plugins.iTunesImporter
# {
# internal struct SmartPlaylist
# {
# public string Query, Ignore, OrderBy, Name, Output;
# public uint LimitNumber;
# public byte LimitMethod;
# }
#
# internal static partial class SmartPlaylistParser
# {
# private delegate bool KindEvalDel(Kind kind, string query);
#
# // INFO OFFSETS
# //
# // Offsets for bytes which...
# const int MATCHBOOLOFFSET = 1; // determin whether logical matching is to be performed - Absolute offset
# const int LIMITBOOLOFFSET = 2; // determin whether results are limited - Absolute offset
# const int LIMITMETHODOFFSET = 3; // determin by what criteria the results are limited - Absolute offset
# const int SELECTIONMETHODOFFSET = 7; // determin by what criteria limited playlists are populated - Absolute offset
# const int LIMITINTOFFSET = 11; // determin the limited - Absolute offset
# const int SELECTIONMETHODSIGNOFFSET = 13;// determin whether certain selection methods are "most" or "least" - Absolute offset
#
# // CRITERIA OFFSETS
# //
# // Offsets for bytes which...
# const int LOGICTYPEOFFSET = 15; // determin whether all or any criteria must match - Absolute offset
# const int FIELDOFFSET = 139; // determin what is being matched (Artist, Album, &c) - Absolute offset
# const int LOGICSIGNOFFSET = 1; // determin whether the matching rule is positive or negative (e.g., is vs. is not) - Relative offset from FIELDOFFSET
# const int LOGICRULEOFFSET = 4; // determin the kind of logic used (is, contains, begins, &c) - Relative offset from FIELDOFFSET
# const int STRINGOFFSET = 54; // begin string data - Relative offset from FIELDOFFSET
# const int INTAOFFSET = 60; // begin the first int - Relative offset from FIELDOFFSET
# const int INTBOFFSET = 24; // begin the second int - Relative offset from INTAOFFSET
# const int TIMEMULTIPLEOFFSET = 76;// begin the int with the multiple of time - Relative offset from FIELDOFFSET
# const int TIMEVALUEOFFSET = 68; // begin the inverse int with the value of time - Relative offset from FIELDOFFSET
#
# const int INTLENGTH = 64; // The length on a int criteria starting at the first int
# static DateTime STARTOFTIME = new DateTime(1904, 1, 1); // Dates are recorded as seconds since Jan 1, 1904
#
# static bool or, again;
# static string conjunctionOutput, conjunctionQuery, output, query, ignore;
# static int offset, logicSignOffset,logicRulesOffset, stringOffset, intAOffset, intBOffset,
# timeMultipleOffset, timeValueOffset;
# static byte[] info, criteria;
#
# static KindEvalDel KindEval;
#
# public static SmartPlaylist Parse(byte[] i, byte[] c)
# {
# info = i;
# criteria = c;
# SmartPlaylist result = new SmartPlaylist();
# offset = FIELDOFFSET;
# output = "";
# query = "";
# ignore = "";
#
# if(info[MATCHBOOLOFFSET] == 1) {
# or = (criteria[LOGICTYPEOFFSET] == 1) ? true : false;
# if(or) {
# conjunctionQuery = " OR ";
# conjunctionOutput = " or\n";
# } else {
# conjunctionQuery = " AND ";
# conjunctionOutput = " and\n";
# }
# do {
# again = false;
# logicSignOffset = offset + LOGICSIGNOFFSET;
# logicRulesOffset = offset + LOGICRULEOFFSET;
# stringOffset = offset + STRINGOFFSET;
# intAOffset = offset + INTAOFFSET;
# intBOffset = intAOffset + INTBOFFSET;
# timeMultipleOffset = offset + TIMEMULTIPLEOFFSET;
# timeValueOffset = offset + TIMEVALUEOFFSET;
#
# if(Enum.IsDefined(typeof(StringFields), (int)criteria[offset])) {
# ProcessStringField();
# } else if(Enum.IsDefined(typeof(IntFields), (int)criteria[offset])) {
# ProcessIntField();
# } else if(Enum.IsDefined(typeof(DateFields), (int)criteria[offset])) {
# ProcessDateField();
# } else {
# ignore += "Not processed";
# }
# }
# while(again);
# }
# result.Output = output;
# result.Query = query;
# result.Ignore = ignore;
# if(info[LIMITBOOLOFFSET] == 1) {
# uint limit = BytesToUInt(info, LIMITINTOFFSET);
# result.LimitNumber = (info[LIMITMETHODOFFSET] == (byte)LimitMethods.GB) ? limit * 1024 : limit;
# if(output.Length > 0) {
# output += "\n";
# }
# output += "Limited to " + limit.ToString() + " " +
# Enum.GetName(typeof(LimitMethods), (int)info[LIMITMETHODOFFSET]) + " selected by ";
# switch(info[LIMITMETHODOFFSET]) {
# case (byte)LimitMethods.Items:
# result.LimitMethod = 0;
# break;
# case (byte)LimitMethods.Minutes:
# result.LimitMethod = 1;
# break;
# case (byte)LimitMethods.Hours:
# result.LimitMethod = 2;
# break;
# case (byte)LimitMethods.MB:
# result.LimitMethod = 3;
# break;
# case (byte)LimitMethods.GB:
# goto case (byte)LimitMethods.MB;
# }
# switch(info[SELECTIONMETHODOFFSET]) {
# case (byte)SelectionMethods.Random:
# output += "random";
# result.OrderBy = "RANDOM()";
# break;
# case (byte)SelectionMethods.HighestRating:
# output += "highest rated";
# result.OrderBy = "Rating DESC";
# break;
# case (byte)SelectionMethods.LowestRating:
# output += "lowest rated";
# result.OrderBy = "Rating ASC";
# break;
# case (byte)SelectionMethods.RecentlyPlayed:
# output += (info[SELECTIONMETHODSIGNOFFSET] == 0)
# ? "most recently played" : "least recently played";
# result.OrderBy = (info[SELECTIONMETHODSIGNOFFSET] == 0)
# ? "LastPlayedStamp DESC" : "LastPlayedStamp ASC";
# break;
# case (byte)SelectionMethods.OftenPlayed:
# output += (info[SELECTIONMETHODSIGNOFFSET] == 0)
# ? "most often played" : "least often played";
# result.OrderBy = (info[SELECTIONMETHODSIGNOFFSET] == 0)
# ? "NumberOfPlays DESC" : "NumberOfPlays ASC";
# break;
# case (byte)SelectionMethods.RecentlyAdded:
# output += (info[SELECTIONMETHODSIGNOFFSET] == 0)
# ? "most recently added" : "least recently added";
# result.OrderBy = (info[SELECTIONMETHODSIGNOFFSET] == 0)
# ? "DateAddedStamp DESC" : "DateAddedStamp ASC";
# break;
# default:
# result.OrderBy = Enum.GetName(typeof(SelectionMethods), (int)info[SELECTIONMETHODOFFSET]);
# break;
# }
# }
# if(ignore.Length > 0) {
# output += "\n\nIGNORING:\n" + ignore;
# }
#
# if(query.Length > 0) {
# output += "\n\nQUERY:\n" + query;
# }
# return result;
# }
#
# private static void ProcessStringField()
# {
# bool end = false;
# string workingOutput = Enum.GetName(typeof(StringFields), criteria[offset]);
# string workingQuery = "(lower(" + Enum.GetName(typeof(StringFields), criteria[offset]) + ")";
# switch(criteria[logicRulesOffset]) {
# case (byte)LogicRule.Contains:
# if((criteria[logicSignOffset] == (byte)LogicSign.StringPositive)) {
# workingOutput += " contains ";
# workingQuery += " LIKE '%";
# } else {
# workingOutput += " does not contain ";
# workingQuery += " NOT LIKE '%";
# }
# if(criteria[offset] == (byte)StringFields.Kind) {
# KindEval = delegate(Kind kind, string query) {
# return (kind.Name.IndexOf(query) != -1);
# };
# }
# end = true;
# break;
# case (byte)LogicRule.Is:
# if((criteria[logicSignOffset] == (byte)LogicSign.StringPositive)) {
# workingOutput += " is ";
# workingQuery += " = '";
# } else {
# workingOutput += " is not ";
# workingQuery += " != '";
# }
# if(criteria[offset] == (byte)StringFields.Kind) {
# KindEval = delegate(Kind kind, string query) {
# return (kind.Name == query);
# };
# }
# break;
# case (byte)LogicRule.Starts:
# workingOutput += " starts with ";
# workingQuery += " LIKE '";
# if(criteria[offset] == (byte)StringFields.Kind) {
# KindEval = delegate (Kind kind, string query) {
# return (kind.Name.IndexOf(query) == 0);
# };
# }
# end = true;
# break;
# case (byte)LogicRule.Ends:
# workingOutput += " ends with ";
# workingQuery += " LIKE '%";
# if(criteria[offset] == (byte)StringFields.Kind) {
# KindEval = delegate (Kind kind, string query) {
# return (kind.Name.IndexOf(query) == (kind.Name.Length - query.Length));
# };
# }
# break;
# }
# workingOutput += "\"";
# byte[] character = new byte[1];
# string content = "";
# bool onByte = true;
# for(int i = (stringOffset); i < criteria.Length; i++) {
# // Off bytes are 0
# if(onByte) {
# // If the byte is 0 and it's not the last byte,
# // we have another condition
# if(criteria[i] == 0 && i != (criteria.Length - 1)) {
# again = true;
# FinishStringField(content, workingOutput, workingQuery, end);
# offset = i + 2;
# return;
# }
# character[0] = criteria[i];
# content += Encoding.UTF8.GetString(character);
# }
# onByte = !onByte;
# }
# FinishStringField(content, workingOutput, workingQuery, end);
# }
#
# private static void FinishStringField(string content, string workingOutput, string workingQuery, bool end)
# {
# workingOutput += content;
# workingOutput += "\" ";
# bool failed = false;
# if(criteria[offset] == (byte)StringFields.Kind) {
# workingQuery = "";
# foreach(Kind kind in Kinds) {
# if(KindEval(kind, content)) {
# if(workingQuery.Length > 0) {
# if((query.Length == 0 && !again) || or) {
# workingQuery += " OR ";
# } else {
# failed = true;
# break;
# }
# }
# workingQuery += "(lower(Uri)";
# workingQuery += ((criteria[logicSignOffset] == (byte)LogicSign.StringPositive))
# ? " LIKE '%" + kind.Extension + "')" : " NOT LIKE '%" + kind.Extension + "%')";
# }
# }
# } else {
# workingQuery += content.ToLower();
# workingQuery += (end) ? "%')" : "')";
# }
# if(Enum.IsDefined(typeof(IgnoreStringFields),
# (int)criteria[offset]) || failed) {
# if(ignore.Length > 0) {
# ignore += conjunctionOutput;
# }
# ignore += workingOutput;
# } else {
# if(output.Length > 0) {
# output += conjunctionOutput;
# }
# if(query.Length > 0) {
# query += conjunctionQuery;
# }
# output += workingOutput;
# query += workingQuery;
# }
# }
#
# private static void ProcessIntField()
# {
# string workingOutput = Enum.GetName(typeof(IntFields), criteria[offset]);
# string workingQuery = "(" + Enum.GetName(typeof(IntFields), criteria[offset]);
#
# switch(criteria[logicRulesOffset]) {
# case (byte)LogicRule.Is:
# if(criteria[logicSignOffset] == (byte)LogicSign.IntPositive) {
# workingOutput += " is ";
# workingQuery += " = ";
# } else {
# workingOutput += " is not ";
# workingQuery += " != ";
# }
# goto case 255;
# case (byte)LogicRule.Greater:
# workingOutput += " is greater than ";
# workingQuery += " > ";
# goto case 255;
# case (byte)LogicRule.Less:
# workingOutput += " is less than ";
# workingQuery += " > ";
# goto case 255;
# case 255:
# uint number = (criteria[offset] == (byte)IntFields.Rating)
# ? (BytesToUInt(criteria, intAOffset) / 20) : BytesToUInt(criteria, intAOffset);
# workingOutput += number.ToString();
# workingQuery += number.ToString();
# break;
# case (byte)LogicRule.Other:
# if(criteria[logicSignOffset + 2] == 1) {
# workingOutput += " is in the range of ";
# workingQuery += " BETWEEN ";
# uint num = (criteria[offset] == (byte)IntFields.Rating)
# ? (BytesToUInt(criteria, intAOffset) / 20) : BytesToUInt(criteria, intAOffset);
# workingOutput += num.ToString();
# workingQuery += num.ToString();
# workingOutput += " to ";
# workingQuery += " AND ";
# num = (criteria[offset] == (byte)IntFields.Rating)
# ? ((BytesToUInt(criteria, intBOffset) - 19) / 20) : BytesToUInt(criteria, intBOffset);
# workingOutput += num.ToString();
# workingQuery += num.ToString();
# }
# break;
# }
# workingQuery += ")";
# if(Enum.IsDefined(typeof(IgnoreIntFields),
# (int)criteria[offset])) {
# if(ignore.Length > 0) {
# ignore += conjunctionOutput;
# }
# ignore += workingOutput;
# } else {
# if(output.Length > 0) {
# output += conjunctionOutput;
# }
# if(query.Length > 0) {
# query += conjunctionQuery;
# }
# output += workingOutput;
# query += workingQuery;
# }
# offset = intAOffset + INTLENGTH;
# if(criteria.Length > offset) {
# again = true;
# }
# }
#
# private static void ProcessDateField()
# {
# bool isIgnore = false;
# string workingOutput = Enum.GetName(typeof(DateFields), criteria[offset]);
# string workingQuery = "((strftime(\"%s\", current_timestamp) - DateAddedStamp + 3600)";
# switch(criteria[logicRulesOffset]) {
# case (byte)LogicRule.Greater:
# workingOutput += " is after ";
# workingQuery += " > ";
# goto case 255;
# case (byte)LogicRule.Less:
# workingOutput += " is before ";
# workingQuery += " > ";
# goto case 255;
# case 255:
# isIgnore = true;
# DateTime time = BytesToDateTime(criteria, intAOffset);
# workingOutput += time.ToString();
# workingQuery += ((int)DateTime.Now.Subtract(time).TotalSeconds).ToString();
# break;
# case (byte)LogicRule.Other:
# if(criteria[logicSignOffset + 2] == 1) {
# isIgnore = true;
# DateTime t2 = BytesToDateTime(criteria, intAOffset);
# DateTime t1 = BytesToDateTime(criteria, intBOffset);
# if(criteria[logicSignOffset] == (byte)LogicSign.IntPositive) {
# workingOutput += " is in the range of ";
# workingQuery += " BETWEEN " +
# ((int)DateTime.Now.Subtract(t1).TotalSeconds).ToString() +
# " AND " +
# ((int)DateTime.Now.Subtract(t2).TotalSeconds).ToString();
# } else {
# workingOutput += " is not in the range of ";
# }
# workingOutput += t1.ToString();
# workingOutput += " to ";
# workingOutput += t2.ToString();
# } else if(criteria[logicSignOffset + 2] == 2) {
# if(criteria[logicSignOffset] == (byte)LogicSign.IntPositive) {
# workingOutput += " is in the last ";
# workingQuery += " < ";
# } else {
# workingOutput += " is not in the last ";
# workingQuery += " > ";
# }
# uint t = InverseBytesToUInt(criteria, timeValueOffset);
# uint multiple = BytesToUInt(criteria, timeMultipleOffset);
# workingQuery += (t * multiple).ToString();
# workingOutput += t.ToString() + " ";
# switch(multiple) {
# case 86400:
# workingOutput += "days";
# break;
# case 604800:
# workingOutput += "weeks";
# break;
# case 2628000:
# workingOutput += "months";
# break;
# }
# }
# break;
# }
# workingQuery += ")";
# if(isIgnore || Enum.IsDefined(typeof(IgnoreDateFields), (int)criteria[offset])) {
# if(ignore.Length > 0) {
# ignore += conjunctionOutput;
# }
# ignore += workingOutput;
# } else {
# if(output.Length > 0) {
# output += conjunctionOutput;
# }
# output += workingOutput;
# if(query.Length > 0) {
# query += conjunctionQuery;
# }
# query += workingQuery;
# }
# offset = intAOffset + INTLENGTH;
# if(criteria.Length > offset) {
# again = true;
# }
# }
#
# ///
# /// Converts 4 bytes to a uint
# ///
# /// A byte array
# /// Should be the byte of the uint with the 0th-power position
# ///
# private static uint BytesToUInt(byte[] byteArray, int offset)
# {
# uint output = 0;
# for (byte i = 0; i <= 4; i++) {
# output += (uint)(byteArray[offset - i] * Math.Pow(2, (8 * i)));
# }
# return output;
# }
#
# private static uint InverseBytesToUInt(byte[] byteArray, int offset)
# {
# uint output = 0;
# for (byte i = 0; i <= 4; i++) {
# output += (uint)((255 - (uint)(byteArray[offset - i])) * Math.Pow(2, (8 * i)));
# }
# return ++output;
# }
#
# private static DateTime BytesToDateTime (byte[] byteArray, int offset)
# {
# uint number = BytesToUInt(byteArray, offset);
# return STARTOFTIME.AddSeconds(number);
# }
# }
# }
# namespace Banshee.Plugins.iTunesImporter
# {
# internal struct Kind
# {
# public string Name, Extension;
# public Kind(string name, string extension)
# {
# Name = name;
# Extension = extension;
# }
# }
#
# internal partial class SmartPlaylistParser
# {
# private static Kind[] Kinds = {
# new Kind("Protected AAC audio file", ".m4p"),
# new Kind("MPEG audio file", ".mp3"),
# new Kind("AIFF audio file", ".aiff"),
# new Kind("WAV audio file", ".wav"),
# new Kind("QuickTime movie file", ".mov"),
# new Kind("MPEG-4 video file", ".mp4"),
# new Kind("AAC audio file", ".m4a")
# };
#
# ///
# /// The methods by which the number of songs in a playlist are limited
# ///
# private enum LimitMethods
# {
# Minutes = 0x01,
# MB = 0x02,
# Items = 0x03,
# Hours = 0x04,
# GB = 0x05,
# }
# ///
# /// The methods by which songs are selected for inclusion in a limited playlist
# ///
# private enum SelectionMethods
# {
# Random = 0x02,
# Title = 0x05,
# AlbumTitle = 0x06,
# Artist = 0x07,
# Genre = 0x09,
# HighestRating = 0x1c,
# LowestRating = 0x01,
# RecentlyPlayed = 0x1a,
# OftenPlayed = 0x19,
# RecentlyAdded = 0x15
# }
# ///
# /// The matching criteria which take string data
# ///
# private enum StringFields
# {
# AlbumTitle = 0x03,
# AlbumArtist = 0x47,
# Artist = 0x04,
# Category = 0x37,
# Comments = 0x0e,
# Composer = 0x12,
# Description = 0x36,
# Genre = 0x08,
# Grouping = 0x27,
# Kind = 0x09,
# Title = 0x02,
# Show = 0x3e
# }
# ///
# /// The matching criteria which take integer data
# ///
# private enum IntFields
# {
# BPM = 0x23,
# BitRate = 0x05,
# Compilation = 0x1f,
# DiskNumber = 0x18,
# NumberOfPlays = 0x16,
# Rating = 0x19,
# Playlist = 0x28, // FIXME Move this?
# Podcast = 0x39,
# SampleRate = 0x06,
# Season = 0x3f,
# Size = 0x0c,
# SkipCount = 0x44,
# Duration = 0x0d,
# TrackNumber = 0x0b,
# VideoKind = 0x3c,
# Year = 0x07
# }
# ///
# /// The matching criteria which take date data
# ///
# private enum DateFields
# {
# DateAdded = 0x10,
# DateModified = 0x0a,
# LastPlayed = 0x17,
# LastSkipped = 0x45
# }
# ///
# /// The matching criteria which we do no handle
# ///
# private enum IgnoreStringFields
# {
# AlbumArtist = 0x47,
# Category = 0x37,
# Comments = 0x0e,
# Composer = 0x12,
# Description = 0x36,
# Grouping = 0x27,
# Show = 0x3e
# }
# ///
# /// The matching criteria which we do no handle
# ///
# private enum IgnoreIntFields
# {
# BPM = 0x23,
# BitRate = 0x05,
# Compilation = 0x1f,
# DiskNumber = 0x18,
# Playlist = 0x28,
# Podcast = 0x39,
# SampleRate = 0x06,
# Season = 0x3f,
# Size = 0x0c,
# SkipCount = 0x44,
# TrackNumber = 0x0b,
# VideoKind = 0x3c
# }
# private enum IgnoreDateFields
# {
# DateModified = 0x0a,
# LastSkipped = 0x45
# }
# ///
# /// The signs which apply to different kinds of logic (is vs. is not, contains vs. doesn't contain, etc.)
# ///
# private enum LogicSign
# {
# IntPositive = 0x00,
# StringPositive = 0x01,
# IntNegative = 0x02,
# StringNegative = 0x03
# }
# ///
# /// The logical rules
# ///
# private enum LogicRule
# {
# Other = 0x00,
# Is = 0x01,
# Contains = 0x02,
# Starts = 0x04,
# Ends = 0x08,
# Greater = 0x10,
# Less = 0x40
# }
# }
# }