Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Feature complete version of Atom2Blogger.

darcs-hash:20070925141116-07487-6c10476ed302f1a2099628732eb5ad3f93eab992.gz
  • Loading branch information...
commit 3b132615514c5523f9a0f203a441f62d0ae81f59 0 parents
@AArnott authored
8 App.xaml
@@ -0,0 +1,8 @@
+<Application x:Class="Atom2Blogger.App"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ StartupUri="Window1.xaml">
+ <Application.Resources>
+
+ </Application.Resources>
+</Application>
14 App.xaml.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Windows;
+using System.Xml;
+
+namespace Atom2Blogger {
+ /// <summary>
+ /// Interaction logic for App.xaml
+ /// </summary>
+ public partial class App : Application {
+ }
+}
114 Atom2Blogger.csproj
@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>9.0.20920</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{12199B59-C098-44D7-94A7-503D7200245D}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Atom2Blogger</RootNamespace>
+ <AssemblyName>Atom2Blogger</AssemblyName>
+ <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Google.GData.Client, Version=1.0.9.9, Culture=neutral, PublicKeyToken=04a59ca9b0273830">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\..\..\..\..\..\Program Files\Google\Google Data API SDK\Redist\Google.GData.Client.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Xml.Linq">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data.DataSetExtensions">
+ <RequiredTargetFramework>3.5</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ <Reference Include="UIAutomationProvider">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="WindowsBase">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="PresentationCore">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="PresentationFramework">
+ <RequiredTargetFramework>3.0</RequiredTargetFramework>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup>
+ <ApplicationDefinition Include="App.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </ApplicationDefinition>
+ <Page Include="Window1.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Compile Include="App.xaml.cs">
+ <DependentUpon>App.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Window1.xaml.cs">
+ <DependentUpon>Window1.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ <SubType>Designer</SubType>
+ </EmbeddedResource>
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ <AppDesigner Include="Properties\" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
17 Atom2Blogger.ps1
@@ -0,0 +1,17 @@
+param ($username, $password, $uri)
+
+function Get-Service($username, $password) {
+ [void][reflection.assembly]::LoadFile("$env:ProgramFiles\Google\Google Data API SDK\Redist\Google.GData.Client.dll")
+ $svc = new-object Google.GData.Client.Service("blogger","atom2blogger")
+ $svc.credentials = new-object net.networkcredential($username, $password)
+ $svc
+}
+
+function Get-AtomFeed($service, $uri, $numberToRetrieve=100) {
+ $query = New-Object Google.GData.Client.FeedQuery($uri)
+ $query.NumberToRetrieve = $numberToRetrieve
+ $service.Query($query)
+}
+
+$svc = Get-Service $username $password
+$feed = Get-AtomFeed $svc $uri
20 Atom2Blogger.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atom2Blogger", "Atom2Blogger.csproj", "{12199B59-C098-44D7-94A7-503D7200245D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {12199B59-C098-44D7-94A7-503D7200245D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {12199B59-C098-44D7-94A7-503D7200245D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {12199B59-C098-44D7-94A7-503D7200245D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {12199B59-C098-44D7-94A7-503D7200245D}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
33 Properties/AssemblyInfo.cs
@@ -0,0 +1,33 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Atom2Blogger")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Atom2Blogger")]
+[assembly: AssemblyCopyright("Copyright © 2007")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("fdaaabed-a349-4914-9649-0bd29c946dbf")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
63 Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.1416
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Atom2Blogger.Properties {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Atom2Blogger.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
117 Properties/Resources.resx
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root>
26 Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.1416
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Atom2Blogger.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
7 Properties/Settings.settings
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile>
102 Window1.xaml
@@ -0,0 +1,102 @@
+<Window x:Class="Atom2Blogger.Window1"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ Title="Atom feed to Blogger uploader" Height="403" Width="681">
+ <Window.Resources>
+ <Style TargetType="{x:Type TextBox}">
+ <Setter Property="Margin" Value="0,0,0,2"/>
+ </Style>
+ <Style TargetType="{x:Type PasswordBox}">
+ <Setter Property="Margin" Value="0,0,0,2"/>
+ </Style>
+ </Window.Resources>
+ <Grid Margin="5">
+ <Grid.RowDefinitions>
+ <RowDefinition Height="auto" />
+ <RowDefinition Height="auto" />
+ <RowDefinition Height="auto" />
+ <RowDefinition Height="auto" />
+ <RowDefinition Height="auto" />
+ <RowDefinition Height="auto" />
+ <RowDefinition Height="*" />
+ <RowDefinition Height="auto" />
+ <RowDefinition Height="auto" />
+ <RowDefinition Height="auto" />
+ <RowDefinition Height="auto" />
+ </Grid.RowDefinitions>
+ <Grid.ColumnDefinitions>
+ <ColumnDefinition Width="auto"/>
+ <ColumnDefinition Width="*" />
+ <ColumnDefinition Width="10"/>
+ <ColumnDefinition Width="*" />
+ </Grid.ColumnDefinitions>
+
+ <Label HorizontalAlignment="Center" Grid.Column="1">Source Blog</Label>
+ <Label HorizontalAlignment="Center" Grid.Column="3">Destination Blog</Label>
+
+ <Label Grid.Row="1">Feed</Label>
+ <TextBox Grid.Row="1" Grid.Column="1" Name="sourceFeed"
+ Text="http://csold.nerdbank.net/blogs/darnit/atom.aspx" />
+ <TextBox Grid.Row="1" Grid.Column="3" Name="destinationFeed"
+ Text="http://arnottdarnit.blogspot.com/feeds/posts/default" />
+
+ <Label Grid.Row="2">Format</Label>
+ <ComboBox Grid.Row="2" Grid.Column="1" SelectedIndex="0">
+ <ComboBox.Items>
+ <ComboBoxItem>Atom feed</ComboBoxItem>
+ </ComboBox.Items>
+ </ComboBox>
+ <ComboBox Grid.Row="2" Grid.Column="3" SelectedIndex="0">
+ <ComboBox.Items>
+ <ComboBoxItem>Blogger</ComboBoxItem>
+ </ComboBox.Items>
+ </ComboBox>
+
+ <Label Grid.Row="3">Username</Label>
+ <TextBox Grid.Row="3" Grid.Column="1" Name="sourceUsernameBox" />
+ <TextBox Grid.Row="3" Grid.Column="3" Name="destinationUsernameBox" Text="andrewarnott@gmail.com"/>
+
+ <Label Grid.Row="4">Password</Label>
+ <PasswordBox Grid.Row="4" Grid.Column="1" Name="sourcePasswordBox" />
+ <PasswordBox Grid.Row="4" Grid.Column="3" Name="destinationPasswordBox" Password=""/>
+
+ <Button Grid.Row="5" Grid.Column="1" Name="downloadPostsButton"
+ Click="downloadPostsButton_Click">Download</Button>
+ <Button Grid.Row="5" Grid.Column="3" Name="uploadPostsButton"
+ Click="uploadButton_Click">Upload</Button>
+
+ <Label Grid.Row="6">Posts</Label>
+ <ListView Grid.Row="6" Grid.Column="1" Name="sourcePostsListView">
+ <ListView.View>
+ <GridView>
+ <GridView.Columns>
+ <GridViewColumn Header="Date" DisplayMemberBinding="{Binding Published}"/>
+ <GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title.Text}" />
+ </GridView.Columns>
+ </GridView>
+ </ListView.View>
+ </ListView>
+ <ListView Grid.Row="6" Grid.Column="3" Name="destinationPostsListView">
+ <ListView.View>
+ <GridView>
+ <GridView.Columns>
+ <GridViewColumn Header="Date" DisplayMemberBinding="{Binding Published}"/>
+ <GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title.Text}" />
+ </GridView.Columns>
+ </GridView>
+ </ListView.View>
+ </ListView>
+
+ <Border Grid.Row="7" Grid.ColumnSpan="4" Grid.RowSpan="3" BorderBrush="Gray" BorderThickness="1"/>
+ <Label Grid.Row="7">Tools</Label>
+ <Button Grid.Row="7" Grid.Column="1" Name="generateRedirectsButton" Content="Generate redirect pages" Click="generateRedirectsButton_Click" />
+ <Button Grid.Row="7" Grid.Column="3" Name="fixInternalLinksButton" Content="Fix inter-post links to stay within blog" Click="fixInternalLinksButton_Click" />
+
+ <TextBlock Grid.Row="8" TextWrapping="Wrap">Base dir for redirects</TextBlock>
+ <TextBox Grid.Row="8" Grid.Column="1" Name="redirectPagesLocationBox" Text="c:\darcs\nerdbank.net\cs" />
+
+ <Label Grid.Row="10">Log</Label>
+ <TextBox Grid.Row="10" Grid.Column="1" Grid.ColumnSpan="3" IsReadOnly="True" Name="logBox"
+ MinHeight="50" VerticalScrollBarVisibility="Auto" />
+ </Grid>
+</Window>
179 Window1.xaml.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+using System.Windows.Input;
+using Google.GData.Client;
+using System.Net;
+using System.Xml.XPath;
+using System.Xml;
+using System.Collections.ObjectModel;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Globalization;
+
+namespace Atom2Blogger {
+ public partial class Window1 : Window {
+ public Window1() {
+ InitializeComponent();
+
+ sourcePostsListView.ItemsSource = oldBlogEntries;
+ destinationPostsListView.ItemsSource = newBlogEntries;
+ }
+
+ const string atomNamespace = "http://www.w3.org/2005/Atom";
+ Uri newFeedUri { get { return new Uri(destinationFeed.Text); } }
+ ObservableCollection<AtomEntry> oldBlogEntries = new ObservableCollection<AtomEntry>();
+ ObservableCollection<AtomEntry> newBlogEntries = new ObservableCollection<AtomEntry>();
+ Dictionary<AtomEntry, AtomEntry> oldToNewEntryMapping = new Dictionary<AtomEntry, AtomEntry>();
+ Dictionary<Uri, Uri> oldToNewEntryUriMapping = new Dictionary<Uri, Uri>();
+
+ void downloadPostsButton_Click(object sender, RoutedEventArgs e) {
+ Cursor = Cursors.Wait;
+ try {
+ downloadOldPosts();
+ downloadNewPosts();
+ createMapping();
+ } finally {
+ Cursor = Cursors.Arrow;
+ }
+ }
+ void uploadButton_Click(object sender, RoutedEventArgs e) {
+ Service service = getNewService();
+ foreach (AtomEntry oldEntry in oldBlogEntries.Where(entry => !oldToNewEntryMapping.ContainsKey(entry))) {
+ oldToNewEntryMapping[oldEntry] = service.Insert(newFeedUri, oldEntry);
+ }
+
+ // Get an updated list of posts on the new blog.
+ downloadNewPosts();
+ createMapping();
+ }
+
+ Service getNewService() {
+ Service service = new Service("blogger", "Atom2Blogger");
+ service.Credentials = new NetworkCredential(destinationUsernameBox.Text, destinationPasswordBox.Password);
+ return service;
+ }
+ AtomFeed getNewFeed() {
+ Service service = getNewService();
+ FeedQuery query = new FeedQuery(destinationFeed.Text);
+ query.NumberToRetrieve = 100;
+ AtomFeed feed = service.Query(query);
+ return feed;
+ }
+
+ void downloadOldPosts() {
+ oldBlogEntries.Clear();
+ var req = HttpWebRequest.Create(sourceFeed.Text);
+ req.Credentials = new NetworkCredential(sourceUsernameBox.Text, sourcePasswordBox.Password);
+ var resp = req.GetResponse();
+ var doc = new XPathDocument(resp.GetResponseStream());
+ var xpath = doc.CreateNavigator();
+ XmlNamespaceManager nsmgr = new XmlNamespaceManager(xpath.NameTable);
+ nsmgr.AddNamespace("atom", atomNamespace);
+ foreach (XPathNavigator entryXml in xpath.Select("/atom:feed/atom:entry", nsmgr)) {
+ AtomEntry entry = new AtomEntry();
+ entry.Title.Text = entryXml.SelectSingleNode("atom:title", nsmgr).Value;
+ entry.Published = entryXml.SelectSingleNode("atom:published", nsmgr).ValueAsDateTime;
+ entry.Updated = entryXml.SelectSingleNode("atom:updated", nsmgr).ValueAsDateTime;
+ entry.Content.Type = entryXml.SelectSingleNode("atom:content/@type", nsmgr).Value;
+ entry.Content.Content = entryXml.SelectSingleNode("atom:content", nsmgr).Value;
+ foreach (XPathNavigator categoryXml in entryXml.Select("atom:category/@term", nsmgr)) {
+ entry.Categories.Add(new AtomCategory(categoryXml.Value));
+ }
+ entry.SelfUri = new AtomUri(entryXml.SelectSingleNode("atom:link/@href", nsmgr).Value);
+
+ oldBlogEntries.Add(entry);
+ }
+ }
+ void downloadNewPosts() {
+ newBlogEntries.Clear();
+
+ AtomFeed feed = getNewFeed();
+ foreach (AtomEntry entry in feed.Entries) {
+ newBlogEntries.Add(entry);
+ }
+ }
+ void createMapping() {
+ oldToNewEntryMapping.Clear();
+ foreach (AtomEntry oldEntry in oldBlogEntries) {
+ AtomEntry newEntry = newBlogEntries.SingleOrDefault(e =>
+ (e.Published - oldEntry.Published) < TimeSpan.FromSeconds(2) &&
+ e.Title.Text.Equals(oldEntry.Title.Text, StringComparison.OrdinalIgnoreCase));
+ if (newEntry != null) {
+ oldToNewEntryMapping[oldEntry] = newEntry;
+ oldToNewEntryUriMapping[new Uri(oldEntry.SelfUri.Content)] =
+ new Uri(newEntry.Links[0].HRef.Content);
+ }
+ }
+ }
+
+ string filterTrackingImage(string content) {
+ return Regex.Replace(content, @"\<img src="".+/aggbug\.aspx\?PostID=\w+"" width=""1"" height=""1""\>", "");
+ }
+
+ void generateRedirectsButton_Click(object sender, RoutedEventArgs e) {
+ foreach (var entryMapping in oldToNewEntryUriMapping) {
+ Uri oldUri = entryMapping.Key;
+ Uri newUri = entryMapping.Value;
+ string redirectFilePath = Path.Combine(redirectPagesLocationBox.Text, oldUri.AbsolutePath.Substring(1));
+ string redirectedDirectoryPath = Path.GetDirectoryName(redirectFilePath);
+ if (!Directory.Exists(redirectedDirectoryPath)) {
+ Directory.CreateDirectory(redirectedDirectoryPath);
+ }
+ using (StreamWriter sw = File.CreateText(redirectFilePath)) {
+ if (Path.GetExtension(redirectFilePath).Equals(".aspx", StringComparison.OrdinalIgnoreCase)) {
+ sw.WriteLine("<%@ Page Language=\"C#\" %>");
+ sw.WriteLine("<% Response.Redirect(\"{0}\"); %>", newUri);
+ } else {
+ sw.WriteLine("<html>");
+ sw.WriteLine(" <head>");
+ sw.WriteLine(" <meta http-equiv=\"refresh\" content=\"0;url={0}\" />", newUri);
+ sw.WriteLine(" </head>");
+ sw.WriteLine("</html>");
+ }
+ }
+ }
+
+ // Create feed redirects
+
+ }
+ void fixInternalLinksButton_Click(object sender, RoutedEventArgs e) {
+ Cursor = Cursors.Wait;
+ try {
+ foreach (AtomEntry entry in getNewFeed().Entries) {
+ string changedContent = filterTrackingImage(entry.Content.Content);
+ foreach (var urlMapping in oldToNewEntryUriMapping) {
+ string oldUrl = urlMapping.Key.ToString();
+ string newUrl = urlMapping.Value.ToString();
+ changedContent = Regex.Replace(changedContent, Regex.Escape(oldUrl),
+ newUrl, RegexOptions.IgnoreCase);
+ }
+ // Only update content field if a change occurred, so we don't upload unchanged content.
+ if (changedContent != entry.Content.Content) {
+ entry.Content.Content = changedContent;
+ }
+ // Fix categories
+ if (entry.Categories.Count == 0) {
+ AtomEntry oldPost = oldBlogEntries.SingleOrDefault(
+ oldEntry => oldEntry.Title.Text == entry.Title.Text &&
+ (oldEntry.Published - entry.Published) < TimeSpan.FromHours(9));
+ if (oldPost != null) {
+ foreach (AtomCategory cat in oldPost.Categories) {
+ AtomCategory newCat = new AtomCategory(cat.Term);
+ newCat.Scheme = "http://www.blogger.com/atom/ns#";
+ entry.Categories.Add(newCat);
+ }
+ }
+ }
+
+ if (entry.IsDirty()) {
+ AtomEntry updatedEntry = entry.Update();
+ }
+ }
+ } finally {
+ Cursor = Cursors.Arrow;
+ }
+ }
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.