PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/app/src/main/java/org/fdroid/fdroid/data/RepoProvider.java

https://gitlab.com/khairulazlan-abdhalim/fdroidclient
Java | 382 lines | 294 code | 64 blank | 24 comment | 36 complexity | 71cdbd9b76417a5718e31774001161aa MD5 | raw file
  1. package org.fdroid.fdroid.data;
  2. import android.content.ContentResolver;
  3. import android.content.ContentUris;
  4. import android.content.ContentValues;
  5. import android.content.Context;
  6. import android.content.UriMatcher;
  7. import android.database.Cursor;
  8. import android.net.Uri;
  9. import android.provider.BaseColumns;
  10. import android.text.TextUtils;
  11. import android.util.Log;
  12. import org.fdroid.fdroid.Utils;
  13. import java.util.ArrayList;
  14. import java.util.List;
  15. public class RepoProvider extends FDroidProvider {
  16. private static final String TAG = "RepoProvider";
  17. public static final class Helper {
  18. private static final String TAG = "RepoProvider.Helper";
  19. private Helper() { }
  20. public static Repo findByUri(Context context, Uri uri) {
  21. ContentResolver resolver = context.getContentResolver();
  22. Cursor cursor = resolver.query(uri, DataColumns.ALL, null, null, null);
  23. return cursorToRepo(cursor);
  24. }
  25. public static Repo findById(Context context, long repoId) {
  26. return findById(context, repoId, DataColumns.ALL);
  27. }
  28. public static Repo findById(Context context, long repoId,
  29. String[] projection) {
  30. ContentResolver resolver = context.getContentResolver();
  31. Uri uri = RepoProvider.getContentUri(repoId);
  32. Cursor cursor = resolver.query(uri, projection, null, null, null);
  33. return cursorToRepo(cursor);
  34. }
  35. public static Repo findByAddress(Context context, String address) {
  36. return findByAddress(context, address, DataColumns.ALL);
  37. }
  38. public static Repo findByAddress(Context context,
  39. String address, String[] projection) {
  40. List<Repo> repos = findBy(
  41. context, DataColumns.ADDRESS, address, projection);
  42. return repos.size() > 0 ? repos.get(0) : null;
  43. }
  44. public static List<Repo> all(Context context) {
  45. return all(context, DataColumns.ALL);
  46. }
  47. public static List<Repo> all(Context context, String[] projection) {
  48. ContentResolver resolver = context.getContentResolver();
  49. Uri uri = RepoProvider.getContentUri();
  50. Cursor cursor = resolver.query(uri, projection, null, null, null);
  51. return cursorToList(cursor);
  52. }
  53. private static List<Repo> findBy(Context context,
  54. String fieldName,
  55. String fieldValue,
  56. String[] projection) {
  57. ContentResolver resolver = context.getContentResolver();
  58. Uri uri = RepoProvider.getContentUri();
  59. final String[] args = {fieldValue};
  60. Cursor cursor = resolver.query(
  61. uri, projection, fieldName + " = ?", args, null);
  62. return cursorToList(cursor);
  63. }
  64. private static List<Repo> cursorToList(Cursor cursor) {
  65. int knownRepoCount = cursor != null ? cursor.getCount() : 0;
  66. List<Repo> repos = new ArrayList<>(knownRepoCount);
  67. if (cursor != null) {
  68. if (knownRepoCount > 0) {
  69. cursor.moveToFirst();
  70. while (!cursor.isAfterLast()) {
  71. repos.add(new Repo(cursor));
  72. cursor.moveToNext();
  73. }
  74. }
  75. cursor.close();
  76. }
  77. return repos;
  78. }
  79. private static Repo cursorToRepo(Cursor cursor) {
  80. Repo repo = null;
  81. if (cursor != null) {
  82. if (cursor.getCount() > 0) {
  83. cursor.moveToFirst();
  84. repo = new Repo(cursor);
  85. }
  86. cursor.close();
  87. }
  88. return repo;
  89. }
  90. public static void update(Context context, Repo repo,
  91. ContentValues values) {
  92. ContentResolver resolver = context.getContentResolver();
  93. // Change the name to the new address. Next time we update the repo
  94. // index file, it will populate the name field with the proper
  95. // name, but the best we can do is guess right now.
  96. if (values.containsKey(DataColumns.ADDRESS) &&
  97. !values.containsKey(DataColumns.NAME)) {
  98. String name = Repo.addressToName(values.getAsString(DataColumns.ADDRESS));
  99. values.put(DataColumns.NAME, name);
  100. }
  101. /*
  102. * If the repo is signed and has a public key, then guarantee that
  103. * the fingerprint is also set. The stored fingerprint is checked
  104. * when a repo URI is received by FDroid to prevent bad actors from
  105. * overriding repo configs with other keys. So if the fingerprint is
  106. * not stored yet, calculate it and store it. If the fingerprint is
  107. * stored, then check it against the calculated fingerprint just to
  108. * make sure it is correct. If the fingerprint is empty, then store
  109. * the calculated one.
  110. */
  111. if (values.containsKey(DataColumns.SIGNING_CERT)) {
  112. String publicKey = values.getAsString(DataColumns.SIGNING_CERT);
  113. String calcedFingerprint = Utils.calcFingerprint(publicKey);
  114. if (values.containsKey(DataColumns.FINGERPRINT)) {
  115. String fingerprint = values.getAsString(DataColumns.FINGERPRINT);
  116. if (!TextUtils.isEmpty(publicKey)) {
  117. if (TextUtils.isEmpty(fingerprint)) {
  118. values.put(DataColumns.FINGERPRINT, calcedFingerprint);
  119. } else if (!fingerprint.equals(calcedFingerprint)) {
  120. // TODO the UI should represent this error!
  121. Log.e(TAG, "The stored and calculated fingerprints do not match!");
  122. Log.e(TAG, "stored: " + fingerprint);
  123. Log.e(TAG, "calced: " + calcedFingerprint);
  124. }
  125. }
  126. } else if (!TextUtils.isEmpty(publicKey)) {
  127. // no fingerprint in 'values', so put one there
  128. values.put(DataColumns.FINGERPRINT, calcedFingerprint);
  129. }
  130. }
  131. if (values.containsKey(DataColumns.IN_USE)) {
  132. Integer inUse = values.getAsInteger(DataColumns.IN_USE);
  133. if (inUse != null && inUse == 0) {
  134. values.put(DataColumns.LAST_ETAG, (String) null);
  135. }
  136. }
  137. final Uri uri = getContentUri(repo.getId());
  138. final String[] args = {Long.toString(repo.getId())};
  139. resolver.update(uri, values, DataColumns._ID + " = ?", args);
  140. repo.setValues(values);
  141. }
  142. /**
  143. * This doesn't do anything other than call "insert" on the content
  144. * resolver, but I thought I'd put it here in the interests of having
  145. * each of the CRUD methods available in the helper class.
  146. */
  147. public static Uri insert(Context context,
  148. ContentValues values) {
  149. ContentResolver resolver = context.getContentResolver();
  150. Uri uri = RepoProvider.getContentUri();
  151. return resolver.insert(uri, values);
  152. }
  153. public static void remove(Context context, long repoId) {
  154. ContentResolver resolver = context.getContentResolver();
  155. Uri uri = RepoProvider.getContentUri(repoId);
  156. resolver.delete(uri, null, null);
  157. }
  158. public static void purgeApps(Context context, Repo repo) {
  159. Uri apkUri = ApkProvider.getRepoUri(repo.getId());
  160. ContentResolver resolver = context.getContentResolver();
  161. int apkCount = resolver.delete(apkUri, null, null);
  162. Utils.debugLog(TAG, "Removed " + apkCount + " apks from repo " + repo.name);
  163. Uri appUri = AppProvider.getNoApksUri();
  164. int appCount = resolver.delete(appUri, null, null);
  165. Utils.debugLog(TAG, "Removed " + appCount + " apps with no apks.");
  166. }
  167. public static int countAppsForRepo(Context context, long repoId) {
  168. ContentResolver resolver = context.getContentResolver();
  169. final String[] projection = {ApkProvider.DataColumns._COUNT_DISTINCT_ID};
  170. Uri apkUri = ApkProvider.getRepoUri(repoId);
  171. Cursor cursor = resolver.query(apkUri, projection, null, null, null);
  172. int count = 0;
  173. if (cursor != null) {
  174. if (cursor.getCount() > 0) {
  175. cursor.moveToFirst();
  176. count = cursor.getInt(0);
  177. }
  178. cursor.close();
  179. }
  180. return count;
  181. }
  182. }
  183. public interface DataColumns extends BaseColumns {
  184. String ADDRESS = "address";
  185. String NAME = "name";
  186. String DESCRIPTION = "description";
  187. String IN_USE = "inuse";
  188. String PRIORITY = "priority";
  189. String SIGNING_CERT = "pubkey";
  190. String FINGERPRINT = "fingerprint";
  191. String MAX_AGE = "maxage";
  192. String LAST_ETAG = "lastetag";
  193. String LAST_UPDATED = "lastUpdated";
  194. String VERSION = "version";
  195. String IS_SWAP = "isSwap";
  196. String USERNAME = "username";
  197. String PASSWORD = "password";
  198. String TIMESTAMP = "timestamp";
  199. String[] ALL = {
  200. _ID, ADDRESS, NAME, DESCRIPTION, IN_USE, PRIORITY, SIGNING_CERT,
  201. FINGERPRINT, MAX_AGE, LAST_UPDATED, LAST_ETAG, VERSION, IS_SWAP,
  202. USERNAME, PASSWORD, TIMESTAMP,
  203. };
  204. }
  205. private static final String PROVIDER_NAME = "RepoProvider";
  206. private static final String PATH_ALL_EXCEPT_SWAP = "allExceptSwap";
  207. private static final int CODE_ALL_EXCEPT_SWAP = CODE_SINGLE + 1;
  208. private static final UriMatcher MATCHER = new UriMatcher(-1);
  209. static {
  210. MATCHER.addURI(AUTHORITY + "." + PROVIDER_NAME, null, CODE_LIST);
  211. MATCHER.addURI(AUTHORITY + "." + PROVIDER_NAME, PATH_ALL_EXCEPT_SWAP, CODE_ALL_EXCEPT_SWAP);
  212. MATCHER.addURI(AUTHORITY + "." + PROVIDER_NAME, "#", CODE_SINGLE);
  213. }
  214. public static String getAuthority() {
  215. return AUTHORITY + "." + PROVIDER_NAME;
  216. }
  217. public static Uri getContentUri() {
  218. return Uri.parse("content://" + AUTHORITY + "." + PROVIDER_NAME);
  219. }
  220. public static Uri getContentUri(long repoId) {
  221. return ContentUris.withAppendedId(getContentUri(), repoId);
  222. }
  223. public static Uri allExceptSwapUri() {
  224. return getContentUri().buildUpon()
  225. .appendPath(PATH_ALL_EXCEPT_SWAP)
  226. .build();
  227. }
  228. @Override
  229. protected String getTableName() {
  230. return DBHelper.TABLE_REPO;
  231. }
  232. @Override
  233. protected String getProviderName() {
  234. return "RepoProvider";
  235. }
  236. @Override
  237. protected UriMatcher getMatcher() {
  238. return MATCHER;
  239. }
  240. @Override
  241. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
  242. if (TextUtils.isEmpty(sortOrder)) {
  243. sortOrder = "_ID ASC";
  244. }
  245. switch (MATCHER.match(uri)) {
  246. case CODE_LIST:
  247. // Do nothing (don't restrict query)
  248. break;
  249. case CODE_SINGLE:
  250. selection = (selection == null ? "" : selection + " AND ") +
  251. DataColumns._ID + " = " + uri.getLastPathSegment();
  252. break;
  253. case CODE_ALL_EXCEPT_SWAP:
  254. selection = DataColumns.IS_SWAP + " = 0 OR " + DataColumns.IS_SWAP + " IS NULL ";
  255. break;
  256. default:
  257. Log.e(TAG, "Invalid URI for repo content provider: " + uri);
  258. throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri);
  259. }
  260. Cursor cursor = db().query(getTableName(), projection, selection, selectionArgs, null, null, sortOrder);
  261. cursor.setNotificationUri(getContext().getContentResolver(), uri);
  262. return cursor;
  263. }
  264. @Override
  265. public Uri insert(Uri uri, ContentValues values) {
  266. if (!values.containsKey(DataColumns.ADDRESS)) {
  267. throw new UnsupportedOperationException("Cannot add repo without an address.");
  268. }
  269. // The following fields have NOT NULL constraints in the DB, so need
  270. // to be present.
  271. if (!values.containsKey(DataColumns.IN_USE)) {
  272. values.put(DataColumns.IN_USE, 1);
  273. }
  274. if (!values.containsKey(DataColumns.PRIORITY)) {
  275. values.put(DataColumns.PRIORITY, 10);
  276. }
  277. if (!values.containsKey(DataColumns.MAX_AGE)) {
  278. values.put(DataColumns.MAX_AGE, 0);
  279. }
  280. if (!values.containsKey(DataColumns.VERSION)) {
  281. values.put(DataColumns.VERSION, 0);
  282. }
  283. if (!values.containsKey(DataColumns.NAME)) {
  284. final String address = values.getAsString(DataColumns.ADDRESS);
  285. values.put(DataColumns.NAME, Repo.addressToName(address));
  286. }
  287. long id = db().insertOrThrow(getTableName(), null, values);
  288. Utils.debugLog(TAG, "Inserted repo. Notifying provider change: '" + uri + "'.");
  289. getContext().getContentResolver().notifyChange(uri, null);
  290. return getContentUri(id);
  291. }
  292. @Override
  293. public int delete(Uri uri, String where, String[] whereArgs) {
  294. switch (MATCHER.match(uri)) {
  295. case CODE_LIST:
  296. // Don't support deleting of multiple repos.
  297. return 0;
  298. case CODE_SINGLE:
  299. where = (where == null ? "" : where + " AND ") +
  300. "_ID = " + uri.getLastPathSegment();
  301. break;
  302. default:
  303. Log.e(TAG, "Invalid URI for repo content provider: " + uri);
  304. throw new UnsupportedOperationException("Invalid URI for repo content provider: " + uri);
  305. }
  306. int rowsAffected = db().delete(getTableName(), where, whereArgs);
  307. Utils.debugLog(TAG, "Deleted repos. Notifying provider change: '" + uri + "'.");
  308. getContext().getContentResolver().notifyChange(uri, null);
  309. return rowsAffected;
  310. }
  311. @Override
  312. public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
  313. int numRows = db().update(getTableName(), values, where, whereArgs);
  314. Utils.debugLog(TAG, "Updated repo. Notifying provider change: '" + uri + "'.");
  315. getContext().getContentResolver().notifyChange(uri, null);
  316. return numRows;
  317. }
  318. }