BackupHelperDispatcher.java revision 43a4a8c777fbb8f71540ac7fbe82674489ef557b
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.app.backup;
18
19import android.os.ParcelFileDescriptor;
20import android.util.Log;
21
22import java.io.FileDescriptor;
23import java.io.IOException;
24import java.util.Map;
25import java.util.TreeMap;
26
27/** @hide */
28public class BackupHelperDispatcher {
29    private static final String TAG = "BackupHelperDispatcher";
30
31    private static class Header {
32        int chunkSize; // not including the header
33        String keyPrefix;
34    }
35
36    TreeMap<String,BackupHelper> mHelpers = new TreeMap<String,BackupHelper>();
37
38    public BackupHelperDispatcher() {
39    }
40
41    public void addHelper(String keyPrefix, BackupHelper helper) {
42        mHelpers.put(keyPrefix, helper);
43    }
44
45    public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
46             ParcelFileDescriptor newState) throws IOException {
47        // First, do the helpers that we've already done, since they're already in the state
48        // file.
49        int err;
50        Header header = new Header();
51        TreeMap<String,BackupHelper> helpers = (TreeMap<String,BackupHelper>)mHelpers.clone();
52        FileDescriptor oldStateFD = null;
53
54        if (oldState != null) {
55            oldStateFD = oldState.getFileDescriptor();
56            while ((err = readHeader_native(header, oldStateFD)) >= 0) {
57                if (err == 0) {
58                    BackupHelper helper = helpers.get(header.keyPrefix);
59                    Log.d(TAG, "handling existing helper '" + header.keyPrefix + "' " + helper);
60                    if (helper != null) {
61                        doOneBackup(oldState, data, newState, header, helper);
62                        helpers.remove(header.keyPrefix);
63                    } else {
64                        skipChunk_native(oldStateFD, header.chunkSize);
65                    }
66                }
67            }
68        }
69
70        // Then go through and do the rest that we haven't done.
71        for (Map.Entry<String,BackupHelper> entry: helpers.entrySet()) {
72            header.keyPrefix = entry.getKey();
73            Log.d(TAG, "handling new helper '" + header.keyPrefix + "'");
74            BackupHelper helper = entry.getValue();
75            doOneBackup(oldState, data, newState, header, helper);
76        }
77    }
78
79    private void doOneBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
80            ParcelFileDescriptor newState, Header header, BackupHelper helper)
81            throws IOException {
82        int err;
83        FileDescriptor newStateFD = newState.getFileDescriptor();
84
85        // allocate space for the header in the file
86        int pos = allocateHeader_native(header, newStateFD);
87        if (pos < 0) {
88            throw new IOException("allocateHeader_native failed (error " + pos + ")");
89        }
90
91        data.setKeyPrefix(header.keyPrefix);
92
93        // do the backup
94        helper.performBackup(oldState, data, newState);
95
96        // fill in the header (seeking back to pos).  The file pointer will be returned to
97        // where it was at the end of performBackup.  Header.chunkSize will not be filled in.
98        err = writeHeader_native(header, newStateFD, pos);
99        if (err != 0) {
100            throw new IOException("writeHeader_native failed (error " + err + ")");
101        }
102    }
103
104    public void performRestore(BackupDataInput input, int appVersionCode,
105            ParcelFileDescriptor newState)
106            throws IOException {
107        boolean alreadyComplained = false;
108
109        BackupDataInputStream stream = new BackupDataInputStream(input);
110        while (input.readNextHeader()) {
111
112            String rawKey = input.getKey();
113            int pos = rawKey.indexOf(':');
114            if (pos > 0) {
115                String prefix = rawKey.substring(0, pos);
116                BackupHelper helper = mHelpers.get(prefix);
117                if (helper != null) {
118                    stream.dataSize = input.getDataSize();
119                    stream.key = rawKey.substring(pos+1);
120                    helper.restoreEntity(stream);
121                } else {
122                    if (!alreadyComplained) {
123                        Log.w(TAG, "Couldn't find helper for: '" + rawKey + "'");
124                        alreadyComplained = true;
125                    }
126                }
127            } else {
128                if (!alreadyComplained) {
129                    Log.w(TAG, "Entity with no prefix: '" + rawKey + "'");
130                    alreadyComplained = true;
131                }
132            }
133            input.skipEntityData(); // In case they didn't consume the data.
134        }
135
136        // Write out the state files -- mHelpers is a TreeMap, so the order is well defined.
137        for (BackupHelper helper: mHelpers.values()) {
138            helper.writeNewStateDescription(newState);
139        }
140    }
141
142    private static native int readHeader_native(Header h, FileDescriptor fd);
143    private static native int skipChunk_native(FileDescriptor fd, int bytesToSkip);
144
145    private static native int allocateHeader_native(Header h, FileDescriptor fd);
146    private static native int writeHeader_native(Header h, FileDescriptor fd, int pos);
147}
148
149