This rocks
[backups/.git] / main.cc
1 #include <iostream>
2 #include <fstream>
3 #include <iterator>
4 #include <algorithm>
5 #include <cassert>
6 #include <ctime>
7
8 #include "filedata.hpp"
9
10 using namespace std;
11
12 unsigned long long current_time() {
13   unsigned long long rc = 0;
14   time_t now_tt = time( 0 );
15   tm *now = localtime( &now_tt );
16   rc += ( now->tm_year + 1900ULL ) * 10000000000ULL;
17   rc += ( now->tm_mon  + 1ULL )    * 100000000ULL;
18   rc +=   now->tm_mday             * 1000000ULL;
19   rc +=   now->tm_hour             * 10000ULL;
20   rc +=   now->tm_min              * 100ULL;
21   rc +=   now->tm_sec;
22
23   return rc;
24 }
25
26 template<class I, class O>
27 bool copy_until_full( I begin, I end, O out, unsigned long long &space ) {
28   const unsigned long long block_size = 0x200ULL;
29   bool complete = true;
30
31   I i = begin;
32   while( 0 != space && i != end ) {
33     unsigned long long size = (*i)->getFileSize();
34     unsigned long long blocks = size & ( ~(block_size-1) );
35     if( blocks < size ) blocks += block_size;
36
37     if( blocks <= space ) {
38       space -= blocks;
39       out = *i;
40       ++out;
41     } else {
42       // We missed a file that should be included so the backup is not complete
43       complete = false;
44     }
45     ++i;
46   }
47   return complete;
48 }
49
50 template<class SET>
51 void populate_set( istream &in, SET &files ) {
52   do {
53     FileData *data = new FileData();
54     in >> data;
55     if( data->getFileName().size() ) {
56       files.insert( data );
57     } else {
58       delete data;
59     }
60   } while( ! in.eof() );
61 }
62
63 template<class SET>
64 void partition_sets( const SET &current, const SET &old,
65                      SET &added, SET &common, SET &deleted  ) {
66   FileDataNameCmp cmp;
67
68   set_difference( current.begin(), current.end(),
69                   old.begin(),     old.end(),
70                   inserter( added, added.begin() ),
71                   cmp );
72
73   set_difference( old.begin(),     old.end(),
74                   current.begin(), current.end(),
75                   inserter( deleted, deleted.begin() ),
76                   cmp );
77
78   set_union(      current.begin(), current.end(),
79                   old.begin(),     old.end(),
80                   inserter( common, common.begin() ),
81                   cmp );
82 }
83
84 int main() {
85   // Parse the list of current files on stdin
86   file_set current;
87   populate_set( cin, current );
88
89   file_set backed_up;
90   ifstream db( "test.db" );
91   if( db && db.good() ) {
92     populate_set( db, backed_up );
93   }
94
95   // Now divide the two sets into three sets (added, deleted and common )
96   file_set added, deleted, common;
97   partition_sets( current, backed_up, added, common, deleted );
98
99   { // This little block will copy the last_backup_date from the second set to the first
100     FileDataNameCmp cmp;
101
102     file_set common_with_dates;
103     set_union( backed_up.begin(), backed_up.end(),
104                current.begin(),   current.end(),
105                inserter( common_with_dates, common_with_dates.begin() ),
106                cmp );
107
108     file_set::iterator i = common.begin(), j = common_with_dates.begin();
109     for( ; i != common.end(); ++i, ++j ) {
110       (*i)->setLastBackupDate( (*j)->getLastBackupDate() );
111     }
112   }
113
114   // Now find the list of files to backup.
115   file_set backups;
116
117   // backup all added files
118   copy( added.begin(), added.end(), inserter( backups, backups.begin() ) );
119
120   // backup common files that have changed since the last backup date.
121   for( file_set::iterator i = common.begin(); i != common.end(); ++i ) {
122     if( (*i)->getLastBackupDate() < (*i)->getModifiedDate() ) {
123       backups.insert( *i );
124     }
125   }
126
127   // Now, sort the backups by filesize and build a list of up to SIZE
128   file_vector backups_s;
129   copy( backups.begin(), backups.end(), back_inserter( backups_s ) );
130
131   FileDataSizeCmp sizecmp;
132   sort( backups_s.begin(), backups_s.end(), sizecmp );
133
134   file_set final;
135   unsigned long long space = 0x107c00000ULL;  // 4220 MBytes
136
137   insert_iterator<file_set> final_i( final, final.begin() );
138
139   // Copy files over until full or out of files
140   bool complete = copy_until_full( backups_s.rbegin(),
141                                    backups_s.rend(),
142                                    final_i,
143                                    space );
144
145   // Now, sort the non-backed-up list by last_backup_date and back-fill
146   if( 0 != space ) {
147     file_vector leftovers;
148     FileDataNameCmp cmp;
149     set_difference( current.begin(), current.end(),
150                     final.begin(),   final.end(),
151                     back_inserter( leftovers ),
152                     cmp );
153
154     FileDataLastBackupCmp lastbackupcmp;
155     sort( leftovers.begin(), leftovers.end(), lastbackupcmp );
156
157     copy_until_full( leftovers.begin(), leftovers.end(), final_i, space );
158   }
159
160   unsigned long long now = current_time();
161   for( file_set::iterator k = final.begin(); k != final.end(); ++k ) {
162     (*k)->setLastBackupDate( now );
163   }
164
165   // Write the 'current' list to the dbfile
166   ofstream dbout( "test.db" );
167   copy( current.begin(), current.end(), ostream_iterator<FileData*>( dbout, "" ) );
168
169   // Write the 'final' list to stdout
170   copy( final.begin(), final.end(), ostream_iterator<FileData*>( cout, "" ) );
171
172   if( ! complete ) { cerr << "incomplete" << endl; }
173
174   // Clean-up
175   for( file_set::iterator i = backed_up.begin(); i != backed_up.end(); ++i ) { delete *i; }
176   for( file_set::iterator i = current.begin();   i != current.end();   ++i ) { delete *i; }
177 }