1
+ using System ;
2
+ using System . Text . RegularExpressions ;
3
+ using System . Text ;
4
+ using System . IO ;
5
+
6
+ namespace bmpurge
7
+ {
8
+ enum Modes {
9
+ BEFORE ,
10
+ AFTER ,
11
+ BETWEEN ,
12
+ NONE
13
+ }
14
+
15
+ class Program
16
+ {
17
+ static long ToUnixTime ( DateTime date ) {
18
+ DateTime date_univ = date . ToUniversalTime ( ) ;
19
+ DateTime epoch = new DateTime ( 1970 , 1 , 1 , 0 , 0 , 0 , 0 , DateTimeKind . Utc ) ;
20
+ TimeSpan diff = date_univ - epoch ;
21
+ return ( long ) Math . Floor ( diff . TotalSeconds ) ;
22
+ }
23
+
24
+ public static void Main ( string [ ] args )
25
+ {
26
+ if ( args . Length < 3 ) {
27
+ Console . WriteLine ( "Invalid number of arguments: expecting 2 or more!" ) ;
28
+ return ;
29
+ }
30
+ String path = null ;
31
+
32
+ for ( int i = 0 ; i < args . Length ; i ++ ) {
33
+ if ( File . Exists ( args [ i ] ) ) {
34
+ path = args [ i ] ;
35
+ break ;
36
+ }
37
+ }
38
+ if ( String . IsNullOrEmpty ( path ) ) {
39
+ Console . WriteLine ( "Invalid argument: please specify an existing file!" ) ;
40
+ return ;
41
+ }
42
+
43
+ bool before = false ;
44
+ bool after = false ;
45
+ String before_str = null ;
46
+ String after_str = null ;
47
+ Modes mode = Modes . NONE ;
48
+ for ( int i = 0 ; i < args . Length ; i ++ ) {
49
+ args [ i ] = args [ i ] . Trim ( ) ;
50
+
51
+ if (
52
+ (
53
+ (
54
+ ( args [ i ] == "-before" && ! before ) || ( args [ i ] == "-after" && ! after ) // if arg is before/after and no before/after arg has been found yet
55
+ ) && (
56
+ args . Length <= ( i + 1 ) // but if there is no index after the arg
57
+ ||
58
+ ( args [ i + 1 ] == "-before" || args [ i + 1 ] == "-after" ) // or if the next arg is after/before instead of a timestamp
59
+ )
60
+ )
61
+ ) {
62
+ // then -> invalid arg: no timestamp
63
+ Console . WriteLine ( "Invalid argument: no time stamp specified!" ) ;
64
+ return ;
65
+ }
66
+ // if arg is before but before has already been found
67
+ if ( ( args [ i ] == "-before" && before ) || ( args [ i ] == "-after" && after ) ) {
68
+ // then skip
69
+ continue ;
70
+ }
71
+
72
+ if ( args [ i ] == "-before" ) {
73
+ before = true ;
74
+ before_str = args [ i + 1 ] ;
75
+ mode = Modes . BEFORE ;
76
+ } else if ( args [ i ] == "-after" ) {
77
+ after = true ;
78
+ after_str = args [ i + 1 ] ;
79
+ mode = Modes . AFTER ;
80
+ }
81
+ }
82
+ if ( before && after ) {
83
+ mode = Modes . BETWEEN ;
84
+ }
85
+ if ( ! before && ! after ) {
86
+ Console . WriteLine ( "Invalid argument: missing arguments!" ) ;
87
+ return ;
88
+ }
89
+
90
+ // first index: default before date: 1/1/1970
91
+ // second index: default after date: 1/1/2500
92
+ // if mistakenly not read from args[] then nothing will be deleted
93
+ // since no bookmarks should have been created before 01 Jan 1970 or after 01 Jan 2500
94
+ long [ ] dates = { 0 , 16725225600 } ;
95
+
96
+ if ( before ) {
97
+ DateTime beforeT ;
98
+ if ( String . IsNullOrEmpty ( before_str ) || ! DateTime . TryParse ( before_str , out beforeT ) ) {
99
+ Console . WriteLine ( "Invalid argument: could not read 'before' timestamp!" ) ;
100
+ return ;
101
+ }
102
+
103
+ dates [ 0 ] = ToUnixTime ( new DateTime ( beforeT . Ticks , DateTimeKind . Local ) ) ;
104
+ }
105
+
106
+ if ( after ) {
107
+ DateTime afterT ;
108
+ if ( String . IsNullOrEmpty ( after_str ) || ! DateTime . TryParse ( after_str , out afterT ) ) {
109
+ Console . WriteLine ( "Invalid argument: could not read 'after' timestamp!" ) ;
110
+ return ;
111
+ }
112
+
113
+ dates [ 1 ] = ToUnixTime ( new DateTime ( afterT . Ticks , DateTimeKind . Local ) ) ;
114
+ }
115
+
116
+ // if document isn't a netscape bookmarks file, quit
117
+ String file_content = File . ReadAllText ( path , Encoding . UTF8 ) ;
118
+ String result = file_content ;
119
+ if ( file_content . Length == 0 || ! file_content . Contains ( "<!DOCTYPE NETSCAPE-Bookmark-file-1>" ) ) {
120
+ Console . WriteLine ( "Invalid argument: file is empty or invalid." ) ;
121
+ return ;
122
+ }
123
+
124
+ // regex (unescaped): /<a[^>^<]*add_date="(?'date'\d*)"[^>^<]*>[^>^<]*<\/a>/gmis
125
+ Regex bookmark_regex = new Regex ( "\\ s*<dt><a[^>^<]*add_date=\" (?'date'\\ d*)\" [^>^<]*>(?'title'[^>^<]*)<\\ /a>\\ s" , RegexOptions . IgnoreCase | RegexOptions . Singleline ) ;
126
+ MatchCollection matches = bookmark_regex . Matches ( file_content ) ;
127
+ if ( matches . Count == 0 ) {
128
+ Console . WriteLine ( "No results found!" ) ;
129
+ return ;
130
+ }
131
+
132
+ foreach ( Match m in matches ) {
133
+ if ( ! m . Groups [ "date" ] . Success || ! m . Groups [ "title" ] . Success )
134
+ continue ;
135
+
136
+ long date = Int64 . Parse ( m . Groups [ "date" ] . Value ) ;
137
+
138
+ switch ( mode ) {
139
+ case Modes . AFTER :
140
+ if ( date > dates [ 1 ] ) {
141
+ result = result . Replace ( m . Value , "" ) ;
142
+ Console . WriteLine ( "REMOVED: {0}" , m . Groups [ "title" ] . Value ) ;
143
+ }
144
+ break ;
145
+ case Modes . BEFORE :
146
+ if ( date < dates [ 0 ] ) {
147
+ result = result . Replace ( m . Value , "" ) ;
148
+ Console . WriteLine ( "REMOVED: {0}" , m . Groups [ "title" ] . Value ) ;
149
+ }
150
+ break ;
151
+ case Modes . BETWEEN :
152
+ if ( date > dates [ 1 ] && date < dates [ 0 ] ) {
153
+ result = result . Replace ( m . Value , "" ) ;
154
+ Console . WriteLine ( "REMOVED: {0}" , m . Groups [ "title" ] . Value ) ;
155
+ }
156
+ break ;
157
+ case Modes . NONE :
158
+ Console . WriteLine ( "An unknown error occured." ) ;
159
+ break ;
160
+ }
161
+ }
162
+ File . WriteAllText ( path , result , Encoding . UTF8 ) ;
163
+ return ;
164
+ }
165
+ }
166
+ }
0 commit comments