Skip to content

Commit 51cf902

Browse files
committed
Merge branch '2.10-release'
2 parents b698d14 + 9c6e9bd commit 51cf902

File tree

6 files changed

+161
-25
lines changed

6 files changed

+161
-25
lines changed

app/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ dependencies {
2727
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.1.1'
2828
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.1.1'
2929
implementation 'com.facebook.fresco:fresco:1.10.0'
30-
30+
implementation 'com.drewnoakes:metadata-extractor:2.11.0'
31+
3132
// UI
3233
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
3334
implementation 'com.github.chrisbanes:PhotoView:2.0.0'

app/src/main/java/fr/free/nrw/commons/contributions/Contribution.java

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,33 @@
33
import android.content.Context;
44
import android.net.Uri;
55
import android.os.Parcel;
6-
import android.support.annotation.IntDef;
76
import android.support.annotation.NonNull;
87
import android.support.annotation.StringDef;
98

109
import java.lang.annotation.Retention;
1110
import java.text.SimpleDateFormat;
11+
import java.util.Calendar;
1212
import java.util.Date;
1313
import java.util.Locale;
14+
import java.util.TimeZone;
1415

15-
import fr.free.nrw.commons.BuildConfig;
1616
import fr.free.nrw.commons.CommonsApplication;
1717
import fr.free.nrw.commons.Media;
18+
import fr.free.nrw.commons.filepicker.UploadableFile;
1819
import fr.free.nrw.commons.settings.Prefs;
1920
import fr.free.nrw.commons.utils.ConfigUtils;
21+
import fr.free.nrw.commons.utils.StringUtils;
2022

2123
import static java.lang.annotation.RetentionPolicy.SOURCE;
2224

2325
public class Contribution extends Media {
2426

27+
//{{According to EXIF data|2009-01-09}}
28+
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "|date={{According to EXIF data|%s}}";
29+
30+
//{{date|2009|1|9}} → 9 January 2009
31+
private static final String TEMPLATE_DATA_OTHER_SOURCE = "{{date|%d|%d|%d}}";
32+
2533
public static Creator<Contribution> CREATOR = new Creator<Contribution>() {
2634
@Override
2735
public Contribution createFromParcel(Parcel parcel) {
@@ -57,6 +65,7 @@ public Contribution[] newArray(int i) {
5765
private boolean isMultiple;
5866
private String wikiDataEntityId;
5967
private Uri contentProviderUri;
68+
private String dateCreatedSource;
6069

6170
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date dateCreated,
6271
int state, long dataLength, Date dateUploaded, long transferred,
@@ -71,13 +80,15 @@ public Contribution(Uri contentUri, String filename, Uri localUri, String imageU
7180
this.width = width;
7281
this.height = height;
7382
this.license = license;
83+
this.dateCreatedSource = "";
7484
}
7585

7686
public Contribution(Uri localUri, String imageUrl, String filename, String description, long dataLength,
7787
Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords) {
7888
super(localUri, imageUrl, filename, description, dataLength, dateCreated, dateUploaded, creator);
7989
this.decimalCoords = decimalCoords;
8090
this.editSummary = editSummary;
91+
this.dateCreatedSource = "";
8192
}
8293

8394
public Contribution(Parcel in) {
@@ -99,7 +110,13 @@ public void writeToParcel(Parcel parcel, int flags) {
99110
parcel.writeInt(isMultiple ? 1 : 0);
100111
}
101112

113+
public String getDateCreatedSource() {
114+
return dateCreatedSource;
115+
}
102116

117+
public void setDateCreatedSource(String dateCreatedSource) {
118+
this.dateCreatedSource = dateCreatedSource;
119+
}
103120

104121
public boolean getMultiple() {
105122
return isMultiple;
@@ -143,20 +160,19 @@ public void setDateUploaded(Date date) {
143160

144161
public String getPageContents(Context applicationContext) {
145162
StringBuilder buffer = new StringBuilder();
146-
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
147-
148163
buffer
149164
.append("== {{int:filedesc}} ==\n")
150165
.append("{{Information\n")
151166
.append("|description=").append(getDescription()).append("\n")
152167
.append("|source=").append("{{own}}\n")
153168
.append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n");
154-
if (dateCreated != null) {
155-
buffer
156-
.append("|date={{According to EXIF data|").append(isoFormat.format(dateCreated)).append("}}\n");
169+
170+
String templatizedCreatedDate = getTemplatizedCreatedDate();
171+
if (!StringUtils.isNullOrWhiteSpace(templatizedCreatedDate)) {
172+
buffer.append("|date=").append(templatizedCreatedDate);
157173
}
158-
buffer
159-
.append("}}").append("\n");
174+
175+
buffer.append("}}").append("\n");
160176

161177
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
162178
if (decimalCoords != null) {
@@ -178,6 +194,28 @@ public String getPageContents(Context applicationContext) {
178194
return buffer.toString();
179195
}
180196

197+
/**
198+
* Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE
199+
* @return
200+
*/
201+
private String getTemplatizedCreatedDate() {
202+
if (dateCreated != null) {
203+
if (UploadableFile.DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource)) {
204+
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
205+
return String.format(Locale.ENGLISH, TEMPLATE_DATE_ACC_TO_EXIF, isoFormat.format(dateCreated)) + "\n";
206+
} else {
207+
Calendar calendar = Calendar.getInstance();
208+
calendar.setTime(dateCreated);
209+
calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
210+
return String.format(Locale.ENGLISH, TEMPLATE_DATA_OTHER_SOURCE,
211+
calendar.get(Calendar.YEAR),
212+
calendar.get(Calendar.MONTH),
213+
calendar.get(Calendar.DAY_OF_MONTH)) + "\n";
214+
}
215+
}
216+
return "";
217+
}
218+
181219
@Override
182220
public void setFilename(String filename) {
183221
this.filename = filename;

app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ public static void onUpdate(SQLiteDatabase db, int from, int to) {
300300
onUpdate(db, from, to);
301301
return;
302302
}
303-
if (from == 8) {
303+
if (from > 5) {
304304
// Added place field
305305
db.execSQL(ADD_WIKI_DATA_ENTITY_ID_FIELD);
306306
from++;

app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@
66
import android.os.Parcel;
77
import android.os.Parcelable;
88

9+
import com.drew.imaging.ImageMetadataReader;
10+
import com.drew.imaging.ImageProcessingException;
11+
import com.drew.metadata.Metadata;
12+
import com.drew.metadata.exif.ExifSubIFDDirectory;
13+
914
import java.io.File;
15+
import java.io.IOException;
16+
import java.util.Date;
17+
18+
import javax.annotation.Nullable;
1019

1120
import fr.free.nrw.commons.upload.FileUtils;
1221

@@ -62,16 +71,32 @@ public int describeContents() {
6271
return 0;
6372
}
6473

74+
75+
/**
76+
* First try to get the file creation date from EXIF else fall back to CP
77+
* @param context
78+
* @return
79+
*/
80+
@Nullable
81+
public DateTimeWithSource getFileCreatedDate(Context context) {
82+
DateTimeWithSource dateTimeFromExif = getDateTimeFromExif();
83+
if (dateTimeFromExif == null) {
84+
return getFileCreatedDateFromCP(context);
85+
} else {
86+
return dateTimeFromExif;
87+
}
88+
}
89+
6590
/**
6691
* Get filePath creation date from uri from all possible content providers
6792
*
6893
* @return
6994
*/
70-
public long getFileCreatedDate(Context context) {
95+
private DateTimeWithSource getFileCreatedDateFromCP(Context context) {
7196
try {
7297
Cursor cursor = context.getContentResolver().query(contentUri, null, null, null, null);
7398
if (cursor == null) {
74-
return -1;//Could not fetch last_modified
99+
return null;//Could not fetch last_modified
75100
}
76101
//Content provider contracts for opening gallery from the app and that by sharing from gallery from outside are different and we need to handle both the cases
77102
int lastModifiedColumnIndex = cursor.getColumnIndex("last_modified");//If gallery is opened from in app
@@ -80,18 +105,69 @@ public long getFileCreatedDate(Context context) {
80105
}
81106
//If both the content providers do not give the data, lets leave it to Jesus
82107
if (lastModifiedColumnIndex == -1) {
83-
return -1l;
108+
return null;
84109
}
85110
cursor.moveToFirst();
86-
return cursor.getLong(lastModifiedColumnIndex);
111+
return new DateTimeWithSource(cursor.getLong(lastModifiedColumnIndex), DateTimeWithSource.CP_SOURCE);
87112
} catch (Exception e) {
88-
return -1;////Could not fetch last_modified
113+
return null;////Could not fetch last_modified
89114
}
90115
}
91116

117+
/**
118+
* Get filePath creation date from uri from EXIF
119+
*
120+
* @return
121+
*/
122+
private DateTimeWithSource getDateTimeFromExif() {
123+
Metadata metadata;
124+
try {
125+
metadata = ImageMetadataReader.readMetadata(file);
126+
ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
127+
if (directory!=null && directory.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) {
128+
Date date = directory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
129+
return new DateTimeWithSource(date, DateTimeWithSource.EXIF_SOURCE);
130+
}
131+
} catch (ImageProcessingException e) {
132+
e.printStackTrace();
133+
} catch (IOException e) {
134+
e.printStackTrace();
135+
}
136+
return null;
137+
}
138+
92139
@Override
93140
public void writeToParcel(Parcel parcel, int i) {
94141
parcel.writeParcelable(contentUri, 0);
95142
parcel.writeSerializable(file);
96143
}
144+
145+
/**
146+
* This class contains the epochDate along with the source from which it was extracted
147+
*/
148+
public class DateTimeWithSource {
149+
public static final String CP_SOURCE = "contentProvider";
150+
public static final String EXIF_SOURCE = "exif";
151+
152+
private final long epochDate;
153+
private final String source;
154+
155+
public DateTimeWithSource(long epochDate, String source) {
156+
this.epochDate = epochDate;
157+
this.source = source;
158+
}
159+
160+
public DateTimeWithSource(Date date, String source) {
161+
this.epochDate = date.getTime();
162+
this.source = source;
163+
}
164+
165+
public long getEpochDate() {
166+
return epochDate;
167+
}
168+
169+
public String getSource() {
170+
return source;
171+
}
172+
}
97173
}

app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class UploadModel {
4343
"",
4444
GPSExtractor.DUMMY,
4545
null,
46-
-1L) {
46+
-1L, "") {
4747
};
4848
private final BasicKvStore basicKvStore;
4949
private final List<String> licenses;
@@ -99,10 +99,16 @@ private UploadItem getUploadItem(UploadableFile uploadableFile,
9999
String source,
100100
SimilarImageInterface similarImageInterface) {
101101
fileProcessor.initFileDetails(Objects.requireNonNull(uploadableFile.getFilePath()), context.getContentResolver());
102-
long fileCreatedDate = uploadableFile.getFileCreatedDate(context);
102+
UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile.getFileCreatedDate(context);
103+
long fileCreatedDate = -1;
104+
String createdTimestampSource = "";
105+
if (dateTimeWithSource != null) {
106+
fileCreatedDate = dateTimeWithSource.getEpochDate();
107+
createdTimestampSource = dateTimeWithSource.getSource();
108+
}
103109
Timber.d("File created date is %d", fileCreatedDate);
104110
GPSExtractor gpsExtractor = fileProcessor.processFileCoordinates(similarImageInterface);
105-
return new UploadItem(Uri.parse(uploadableFile.getFilePath()), uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate);
111+
return new UploadItem(Uri.parse(uploadableFile.getFilePath()), uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate, createdTimestampSource);
106112
}
107113

108114
void onItemsProcessed(Place place, List<UploadItem> uploadItems) {
@@ -284,11 +290,13 @@ Observable<Contribution> buildContributions(List<String> categoryStringList) {
284290
contribution.setTag("mimeType", item.mimeType);
285291
contribution.setSource(item.source);
286292
contribution.setContentProviderUri(item.mediaUri);
293+
287294
Timber.d("Created timestamp while building contribution is %s, %s",
288295
item.getCreatedTimestamp(),
289296
new Date(item.getCreatedTimestamp()));
290297
if (item.createdTimestamp != -1L) {
291298
contribution.setDateCreated(new Date(item.getCreatedTimestamp()));
299+
contribution.setDateCreatedSource(item.getCreatedTimestampSource());
292300
//Set the date only if you have it, else the upload service is gonna try it the other way
293301
}
294302
return contribution;
@@ -332,10 +340,15 @@ static class UploadItem {
332340
private boolean visited;
333341
private boolean error;
334342
private long createdTimestamp;
343+
private String createdTimestampSource;
335344
private BehaviorSubject<Integer> imageQuality;
336345

337346
@SuppressLint("CheckResult")
338-
UploadItem(Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords, @Nullable Place place, long createdTimestamp) {
347+
UploadItem(Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords,
348+
@Nullable Place place,
349+
long createdTimestamp,
350+
String createdTimestampSource) {
351+
this.createdTimestampSource = createdTimestampSource;
339352
title = new Title();
340353
descriptions = new ArrayList<>();
341354
descriptions.add(new Description());
@@ -348,6 +361,10 @@ static class UploadItem {
348361
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
349362
}
350363

364+
public String getCreatedTimestampSource() {
365+
return createdTimestampSource;
366+
}
367+
351368
public String getMimeType() {
352369
return mimeType;
353370
}

app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionDaoTest.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,19 @@ class ContributionDaoTest {
108108
@Test
109109
fun migrateTableVersionFrom_v6_to_v7() {
110110
Table.onUpdate(database, 6, 7)
111-
// Table didn't change in version 7
112-
verifyZeroInteractions(database)
111+
// Table has changed in version 7
112+
inOrder(database) {
113+
verify<SQLiteDatabase>(database).execSQL(Table.ADD_WIKI_DATA_ENTITY_ID_FIELD)
114+
}
113115
}
114116

115117
@Test
116118
fun migrateTableVersionFrom_v7_to_v8() {
117119
Table.onUpdate(database, 7, 8)
118-
// Table didn't change in version 8
119-
verifyZeroInteractions(database)
120+
// Table has changed in version 8
121+
inOrder(database) {
122+
verify<SQLiteDatabase>(database).execSQL(Table.ADD_WIKI_DATA_ENTITY_ID_FIELD)
123+
}
120124
}
121125

122126
@Test
@@ -355,4 +359,4 @@ class ContributionDaoTest {
355359
contribution.wikiDataEntityId = "Q1"
356360
return contribution
357361
}
358-
}
362+
}

0 commit comments

Comments
 (0)