migrate.pl 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. #!/usr/bin/perl
  2. #Migrate tCMS1 data to tCMS2 flat file data model
  3. use strict;
  4. use warnings;
  5. use JSON::MaybeXS;
  6. use File::Slurper();
  7. use HTML::Parser;
  8. use UUID::Tiny ':std';
  9. use File::Copy;
  10. use DateTime;
  11. =head1 SYNOPSIS
  12. Migrate tCMS1 data to the tCMS2 flat file data model.
  13. Walks a tCMS1 docroot and writes each microblog entry and blog post out as a
  14. tCMS2 post under data/files, keyed by the original creation time so that the
  15. ordering survives.
  16. =head2 USAGE
  17. Edit $docroot and $dir below to point at the tCMS1 site, then:
  18. bin/migrate.pl
  19. Run from the tCMS root; the output paths are relative to it. Follow up with
  20. bin/build_index.pl, as this writes the flat files directly and leaves the post
  21. index untouched.
  22. =head2 CAVEATS
  23. Historical. This is the first of the migrate scripts and only applies to a
  24. tCMS1 site; anything newer wants migrate2.pl onwards.
  25. Microblog entries come in two flavours -- JSON, and hand written HTML from
  26. before that -- so each file is tried as JSON first and parsed as HTML if that
  27. fails. Post authorship is recovered from file ownership, with www-data and
  28. nologin owners becoming 'nobody'.
  29. There is an C<exit 0> partway through, before the video migration. That code
  30. is unreachable as written and has never been run against anything current.
  31. =cut
  32. #Edit this to be whatever you need it to be
  33. my $docroot = "/var/www/teodesian.net/doc";
  34. my $dir = "/var/www/teodesian.net/doc/microblog/";
  35. opendir( my $dh, $dir ) or die;
  36. my @days = grep { !/^\./ } readdir $dh;
  37. closedir $dh;
  38. my $ring = JSON::MaybeXS->new();
  39. foreach my $day (@days) {
  40. opendir( my $dht, "$dir/$day" ) or die;
  41. my @times = grep { !/^\./ } readdir $dht;
  42. closedir $dht;
  43. my ( $month, $date, $year ) = split( /\./, $day );
  44. foreach my $time (@times) {
  45. my ( $hour, $min, $sec ) = split( /:/, $time );
  46. my $data;
  47. my $file = "$dir/$day/$time";
  48. print "Migrate $file\n";
  49. eval {
  50. my $content = File::Slurper::read_text($file);
  51. $data = $ring->decode($content);
  52. $data = json_remap($data);
  53. };
  54. $data = html_post($file) unless $data;
  55. my $dt = DateTime->new(
  56. year => $year + 2000,
  57. month => $month,
  58. day => $date,
  59. hour => $hour,
  60. minute => $min,
  61. second => $sec,
  62. );
  63. $data->{created} = $dt->epoch();
  64. $data->{id} = $data->{created};
  65. $data->{tags} = [ 'public', 'news' ];
  66. $data->{version} = 0;
  67. open( my $fh, '>', "data/files/$data->{created}" ) or die;
  68. print $fh encode_json( [$data] );
  69. close $fh;
  70. }
  71. }
  72. # Migrate blog posts
  73. $dir = "$docroot/blog";
  74. opendir( my $bh, $dir ) or die;
  75. my @blogs = grep { -f "$dir/$_" } readdir $bh;
  76. closedir $bh;
  77. my $offset = 0;
  78. foreach my $post (
  79. sort {
  80. my $anum = $a =~ m/^(\d*)-/;
  81. my $bnum = $b =~ m/^(\d*)-/;
  82. $b <=> $a
  83. } @blogs
  84. ) {
  85. my $postname = $post;
  86. $postname =~ s/^\d*-//g;
  87. $postname =~ s/\.post$//g;
  88. my $content = File::Slurper::read_text("$dir/$post");
  89. my $data = {
  90. title => $postname,
  91. data => $content,
  92. tags => [ 'blog', 'public' ],
  93. };
  94. my ( undef, undef, undef, undef, $uid, undef, undef, undef, undef, $ctime ) = stat("$dir/$post");
  95. my $user = lc( getpwuid($uid) );
  96. $user = scalar( grep { $user eq $_ } qw{/sbin/nologin www-data} ) ? 'nobody' : $user;
  97. $data->{user} = $user;
  98. $ctime += $offset;
  99. $data->{created} = $ctime;
  100. $data->{id} = $ctime;
  101. $data->{href} = "/blog/$ctime";
  102. $data->{version} = 0;
  103. print "Migrate blog post '$post'\n";
  104. open( my $fh, '>', "data/files/$data->{created}" ) or die;
  105. print $fh encode_json( [$data] );
  106. close $fh;
  107. $offset--;
  108. }
  109. exit 0;
  110. my $vdir = "$docroot/fileshare/video";
  111. opendir( my $vh, $vdir ) or die;
  112. my @vidyas = grep { -f "$vdir/$_" && $_ =~ m/\.m4v$/ } readdir $vh;
  113. closedir $vh;
  114. foreach my $vid (@vidyas) {
  115. my $postname = $vid;
  116. $postname =~ s/_/ /g;
  117. $postname =~ s/\.mv4$//g;
  118. my $data = {
  119. title => $postname,
  120. data => "Description forthcoming",
  121. tags => [ 'video', 'public' ],
  122. preview => "/img/sys/testpattern.jpg",
  123. };
  124. my ( undef, undef, undef, undef, $uid, undef, undef, undef, undef, $ctime ) = stat("$vdir/$vid");
  125. my $user = lc( getpwuid($uid) );
  126. $user = scalar( grep { $user eq $_ } qw{/sbin/nologin www-data} ) ? 'nobody' : $user;
  127. $data->{user} = $user;
  128. $data->{created} = $ctime;
  129. $data->{id} = $ctime;
  130. $data->{href} = "/assets/$ctime-$vid";
  131. $data->{version} = 0;
  132. #Copy over the video
  133. File::Copy::copy( "$vdir/$vid", "www/assets/$ctime-$vid" );
  134. print "Migrate video '$vid'\n";
  135. open( my $fh, '>', "data/files/$data->{created}" ) or die;
  136. print $fh encode_json( [$data] );
  137. close $fh;
  138. }
  139. sub json_remap {
  140. my $json = shift;
  141. return {
  142. preview => $json->{"image"},
  143. data => $json->{"comment"},
  144. user => lc( $json->{"poster"} ),
  145. title => $json->{"title"},
  146. audio_href => $json->{"audio"},
  147. href => $json->{"url"},
  148. video_href => $json->{"video"},
  149. };
  150. }
  151. sub html_post {
  152. my $file = shift;
  153. my $is_first_link = 1;
  154. my $data = { data => '', href => '' };
  155. my $p = HTML::Parser->new(
  156. handlers => [
  157. start => [
  158. sub {
  159. my ( $self, $attr, $text, $tagname ) = @_;
  160. if ( $tagname eq 'a' && $is_first_link ) {
  161. $data->{href} = $attr->{href};
  162. return;
  163. }
  164. return if $is_first_link;
  165. return if $tagname eq 'hr';
  166. $data->{data} .= $text;
  167. },
  168. 'self, attr, text,tagname'
  169. ],
  170. text => [
  171. sub {
  172. my ( $self, $attr, $text, $tagname ) = @_;
  173. if ($is_first_link) {
  174. $data->{title} .= $text;
  175. return;
  176. }
  177. $data->{data} .= $text;
  178. },
  179. 'self, attr, text,tagname'
  180. ],
  181. end => [
  182. sub {
  183. my ( $self, $attr, $text, $tagname ) = @_;
  184. if ( $tagname eq 'a' && $is_first_link ) {
  185. $is_first_link = 0;
  186. return;
  187. }
  188. return if $is_first_link;
  189. $data->{data} .= $text;
  190. },
  191. 'self, attr, text,tagname'
  192. ],
  193. ],
  194. );
  195. $p->parse_file($file);
  196. #Get the user name from ownership
  197. my ( undef, undef, undef, undef, $uid, undef, undef, undef, undef, $ctime ) = stat($file);
  198. my $user = lc( getpwuid($uid) );
  199. $user = scalar( grep { $user eq $_ } qw{/sbin/nologin www-data} ) ? 'nobody' : $user;
  200. $data->{user} = $user;
  201. $data->{created} = $ctime;
  202. return $data;
  203. }