• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 Shenzhen Kaihong Digital Industry Development Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { Log } from '../utils/Log';
17import { ViewType } from '../models/ViewType';
18import { userFileModel } from './UserFileModel';
19import { MediaConstants } from '../constants/MediaConstants';
20import { selectManager } from '../common/SelectManager';
21import photoAccessHelper from '@ohos.file.photoAccessHelper';
22import fs from '@ohos.file.fs';
23import { screenManager } from '../common/ScreenManager';
24import image from '@ohos.multimedia.image';
25
26const TAG = 'UserFileDataItem';
27const STATUS_UNDEFINED = -1;
28const STATUS_FALSE = 0;
29const STATUS_TRUE = 1;
30
31export interface DateAdded {
32  dateAdded: number;
33  viewType: ViewType;
34}
35
36export class UserFileDataItem implements DateAdded {
37  viewType: ViewType = ViewType.ITEM;
38  readonly hashIndex: number;
39  index: number;
40  dateAdded: number = 0;
41  dateModified: number;
42  dateTaken: number;
43  status: number = MediaConstants.UNDEFINED;
44  isSelect: boolean = false;
45  uri: string;
46  orientation: number;
47  duration: number;
48  size: number;
49  width: number; // width changed by orientation
50  height: number; // height changed by orientation
51  imgWidth: number; // may be smaller than width, as width is too large
52  imgHeight: number; // may be smaller than height, as height is too large
53  path: string = '';
54  title: string;
55  displayName: string;
56  mediaType: photoAccessHelper.PhotoType;
57  favouriteStatus: number = STATUS_UNDEFINED;
58  canRotate: number = STATUS_UNDEFINED;
59  selections: string = '';
60  selectionArgs: string[] = [];
61  deviceId: string = '';
62  longitude: string = '';
63  latitude: string = '';
64  shootingParams: string = '';
65  fileAsset: photoAccessHelper.PhotoAsset = undefined;
66  defaultThumbnail: PixelMap = undefined;
67  thumbnailArray: Map<string, PixelMap> = new Map<string, PixelMap>();
68
69  constructor(selections: string, selectionArgs: string[], deviceId: string, index: number) {
70    this.selections = selections;
71    this.selectionArgs = selectionArgs;
72    this.deviceId = deviceId;
73    this.hashIndex = index;
74    this.index = index;
75  }
76
77  getHashCode(): string {
78    // 时间线界面角度,收藏状态变更,都需要刷新界面;大图浏览界面角度变更,需要刷新界面
79    return this.status === MediaConstants.UNDEFINED ?
80      '' + this.hashIndex :
81      this.uri + this.favouriteStatus + ' ' + this.orientation + ' ' + this.isSelect;
82  }
83
84  async loadFileAsset(): Promise<photoAccessHelper.PhotoAsset> {
85    return await userFileModel.getMediaItemByUri(this.uri);
86  }
87
88  isLoad(): boolean {
89    if (this.status > MediaConstants.UNDEFINED) {
90      return true;
91    }
92    return false;
93  }
94
95  async load(isForce: boolean): Promise<void> {
96    Log.info(TAG, 'load ' + this.status);
97    if (this.status > (isForce ? MediaConstants.PART_LOADED : MediaConstants.UNDEFINED)) {
98      return;
99    }
100    let fileAsset = await this.loadFileAsset();
101    if (fileAsset != null) {
102      this.update(fileAsset);
103    }
104    return;
105  }
106
107  async update(fileAsset: photoAccessHelper.PhotoAsset): Promise<void> {
108    this.fileAsset = fileAsset;
109    this.uri = fileAsset.uri;
110    this.displayName = fileAsset.displayName;
111    this.mediaType = fileAsset.photoType;
112    this.width = screenManager.getWinWidth();
113    this.height = screenManager.getWinHeight();
114    this.orientation = MediaConstants.ROTATE_NONE;
115    try {
116      this.orientation = fileAsset.get(photoAccessHelper.PhotoKeys.ORIENTATION.toString()) as number;
117      Log.info(TAG, 'orientation ' + this.orientation);
118    } catch (err) {
119      Log.error(TAG, 'get orientation ' + JSON.stringify(err));
120    }
121    try {
122      this.duration = fileAsset.get(photoAccessHelper.PhotoKeys.DURATION.toString()) as number;
123      Log.info(TAG, 'duration ' + this.duration);
124    } catch (err) {
125      Log.error(TAG, 'get duration ' + JSON.stringify(err));
126    }
127    try {
128      if (this.orientation === MediaConstants.ROTATE_ONCE || this.orientation === MediaConstants.ROTATE_THIRD) {
129        this.width = fileAsset.get(photoAccessHelper.PhotoKeys.HEIGHT.toString()) as number;
130        this.height = fileAsset.get(photoAccessHelper.PhotoKeys.WIDTH.toString()) as number;
131      } else {
132        this.width = fileAsset.get(photoAccessHelper.PhotoKeys.WIDTH.toString()) as number;
133        this.height = fileAsset.get(photoAccessHelper.PhotoKeys.HEIGHT.toString()) as number;
134      }
135      Log.info(TAG, 'width ' + this.width);
136      Log.info(TAG, 'height ' + this.height);
137    } catch (err) {
138      Log.error(TAG, 'get width height ' + JSON.stringify(err));
139    }
140    try {
141      this.title = fileAsset.get(photoAccessHelper.PhotoKeys.TITLE.toString()) as string;
142      Log.info(TAG, 'title ' + this.title);
143    } catch (err) {
144      Log.error(TAG, 'get title ' + JSON.stringify(err));
145    }
146    try {
147      this.dateAdded = fileAsset.get(photoAccessHelper.PhotoKeys.DATE_ADDED.toString()) as number * 1000;
148      this.dateModified = fileAsset.get(photoAccessHelper.PhotoKeys.DATE_MODIFIED.toString()) as number * 1000;
149      this.dateTaken = fileAsset.get(photoAccessHelper.PhotoKeys.DATE_TAKEN.toString()) as number * 1000;
150      Log.info(TAG, 'dateAdded ' + this.dateAdded);
151    } catch (err) {
152      Log.error(TAG, 'get date ' + JSON.stringify(err));
153    }
154    try {
155      this.favouriteStatus = fileAsset.get(photoAccessHelper.PhotoKeys.FAVORITE.toString()) as boolean ? STATUS_TRUE : STATUS_FALSE
156      Log.info(TAG, 'favouriteStatus ' + this.favouriteStatus);
157    } catch (err) {
158      Log.error(TAG, 'get favouriteStatus ' + JSON.stringify(err));
159    }
160    try {
161      this.size = fileAsset.get(photoAccessHelper.PhotoKeys.SIZE.toString()) as number;
162      Log.info(TAG, 'size ' + this.size);
163    } catch (err) {
164      Log.error(TAG, 'get favouriteStatus ' + JSON.stringify(err));
165    }
166    let size = { width: MediaConstants.DEFAULT_SIZE, height: MediaConstants.DEFAULT_SIZE };
167    if (fileAsset != null && this.defaultThumbnail == undefined) {
168      try {
169        this.defaultThumbnail = await this.fileAsset.getThumbnail(size);
170      } catch (err) {
171        Log.error(TAG, 'getThumbnail error: ' + JSON.stringify(err));
172      }
173    }
174    this.isSelect = selectManager.isSelect(this.uri, this.isSelect);
175    this.imgWidth = this.width;
176    this.imgHeight = this.height;
177    if (this.width > 0 && this.height > 0) {
178      this.status = MediaConstants.LOADED;
179    } else {
180      this.status = MediaConstants.PART_LOADED;
181    }
182    this.getExif(fileAsset);
183  }
184
185  async getExif(fileAsset: photoAccessHelper.PhotoAsset): Promise<void> {
186    let file = await fs.open(fileAsset.uri);
187    let imageSourceApi: image.ImageSource = image.createImageSource(file.fd);
188    let light = '';
189    let fNumber = '';
190    let photographicSensitivity = '';
191
192    await Promise.all([
193      imageSourceApi.getImageProperty(image.PropertyKey.GPS_LATITUDE).then(data => this.latitude = data)
194        .catch((err) => Log.error(TAG, 'getImageProperty latitude error: ' + JSON.stringify(err))),
195      imageSourceApi.getImageProperty(image.PropertyKey.GPS_LONGITUDE).then(data => this.longitude = data)
196        .catch((err) => Log.error(TAG, 'getImageProperty longitude error: ' + JSON.stringify(err))),
197      imageSourceApi.getImageProperty(image.PropertyKey.EXPOSURE_TIME).then(data => light = data)
198        .catch((err) => Log.error(TAG, 'getImageProperty light error: ' + JSON.stringify(err))),
199      imageSourceApi.getImageProperty(image.PropertyKey.F_NUMBER).then(data => fNumber = data)
200        .catch((err) => Log.error(TAG, 'getImageProperty fNumber error: ' + JSON.stringify(err))),
201      imageSourceApi.getImageProperty(image.PropertyKey.PHOTOGRAPHIC_SENSITIVITY)
202        .then(data => photographicSensitivity = data)
203        .catch((err) => Log.error(TAG, 'getImageProperty photographicSensitivity error: ' + JSON.stringify(err))),
204    ]);
205
206    this.shootingParams = 'F_NUMBER ' + fNumber +
207      ' PHOTOGRAPHIC_SENSITIVITY ' + photographicSensitivity +
208      ' EXPOSURE_TIME ' + light;
209    fs.closeSync(file.fd);
210    Log.info(TAG, `getExif end, latitude: ${this.latitude}, longitude: ${this.longitude}, light: ${light},
211    fNumber: ${fNumber}, photographicSensitivity: ${photographicSensitivity}`);
212  }
213
214  async getThumbnail(width: number, height: number): Promise<PixelMap> {
215    Log.debug(TAG, 'getThumbnail ' + this.status);
216    if (this.status !== MediaConstants.LOADED && this.status !== MediaConstants.PART_LOADED) {
217      Log.warn(TAG, 'getThumbnail fail as status: ' + this.status);
218      return undefined;
219    }
220    if (width === MediaConstants.DEFAULT_SIZE && height === MediaConstants.DEFAULT_SIZE) {
221      return this.defaultThumbnail;
222    }
223    let newThumbnail: PixelMap = undefined;
224    let size = { width: width, height: height };
225    let cacheThumbnail = this.thumbnailArray.get(width.toString() + height.toString());
226    if (cacheThumbnail != null) {
227      return cacheThumbnail;
228    }
229    if (this.fileAsset != undefined) {
230      try {
231        newThumbnail = await this.fileAsset.getThumbnail(size);
232        this.thumbnailArray.set(width.toString() + height.toString(), newThumbnail);
233      } catch (err) {
234        Log.error(TAG, 'getThumbnail error: ' + JSON.stringify(err));
235      }
236    }
237    return newThumbnail;
238  }
239
240  getAlt(): Resource {
241    if (this.mediaType === photoAccessHelper.PhotoType.VIDEO) {
242      return $r('app.media.alt_video_placeholder');
243    } else {
244      return $r('app.media.alt_placeholder');
245    }
246  }
247
248  setSelect(isSelect: boolean): void {
249    this.isSelect = isSelect;
250    selectManager.setSelect(this.uri, this.isSelect);
251  }
252
253  async onDelete(): Promise<boolean> {
254    try {
255      await userFileModel.deleteOne(this.uri);
256      selectManager.deleteSelect(this.uri);
257      this.status = MediaConstants.TRASHED;
258      return true;
259    } catch (err) {
260      Log.error(TAG, 'onDelete ' + this.index + ' error: ' + JSON.stringify(err));
261      return false;
262    }
263  }
264
265  async addToAlbum(albumUri:string): Promise<boolean> {
266    try {
267      await userFileModel.addPhotoToAlbumByUserFileMgr(albumUri, this.uri);
268      selectManager.deleteSelect(this.uri);
269      this.status = MediaConstants.TRASHED;
270      return true;
271    } catch (err) {
272      Log.error(TAG, 'addToAlbum ' + this.index + ' error: ' + JSON.stringify(err));
273      return false;
274    }
275  }
276
277  isDeleted(): boolean {
278    return this.status === MediaConstants.TRASHED;
279  }
280
281  async isFavor(): Promise<boolean> {
282    if (this.favouriteStatus === STATUS_UNDEFINED) {
283      let fileAsset = await this.loadFileAsset();
284      try {
285        this.favouriteStatus = (fileAsset.get(photoAccessHelper.PhotoKeys.FAVORITE.toString()) as boolean) ? STATUS_TRUE : STATUS_FALSE;
286      } catch (err) {
287        Log.error(TAG, 'isFavor error: ' + JSON.stringify(err));
288      }
289    }
290    return this.favouriteStatus === STATUS_TRUE;
291  }
292
293  async isVideo(): Promise<boolean> {
294    let fileAsset = await this.loadFileAsset();
295    this.mediaType = fileAsset.photoType;
296    if (this.mediaType === photoAccessHelper.PhotoType.VIDEO) {
297      return true;
298    }
299    return false;
300  }
301
302  async setFavor(): Promise<boolean> {
303    let status = !(await this.isFavor());
304    try {
305      let fileAsset = await this.loadFileAsset();
306      await fileAsset.setFavorite(status);
307      this.favouriteStatus = status ? STATUS_TRUE : STATUS_FALSE;
308      return true;
309    } catch (err) {
310      return false;
311    }
312  }
313
314  async setName(name: string): Promise<void> {
315    let fileAsset = await this.loadFileAsset();
316    let displayName = fileAsset.displayName;
317    let index = displayName.lastIndexOf('.');
318    displayName = name + displayName.slice(index);
319    this.displayName = displayName;
320    this.title = name;
321    try {
322      fileAsset.set(photoAccessHelper.PhotoKeys.TITLE.toString(), name);
323      await fileAsset.commitModify();
324    } catch (err) {
325      Log.error(TAG, 'setName error: ' + JSON.stringify(err));
326    }
327  }
328}
329