Newer
Older
return (array && array.length)
? baseExtremum(array, baseIteratee(iteratee, 2), baseGt)
: undefined;
/***/ (function(module, exports, __webpack_require__) {
var isSymbol = __webpack_require__(1506);
/**
* The base implementation of methods like `_.max` and `_.min` which accepts a
* `comparator` to determine the extremum value.
*
* @private
* @param {Array} array The array to iterate over.
* @param {Function} iteratee The iteratee invoked per iteration.
* @param {Function} comparator The comparator used to compare values.
* @returns {*} Returns the extremum value.
*/
function baseExtremum(array, iteratee, comparator) {
var index = -1,
length = array.length;
while (++index < length) {
var value = array[index],
current = iteratee(value);
if (current != null && (computed === undefined
? (current === current && !isSymbol(current))
: comparator(current, computed)
)) {
var computed = current,
result = value;
}
}
return result;
}
module.exports = baseExtremum;
/***/ }),
/* 1622 */
/***/ (function(module, exports) {
/**
* The base implementation of `_.gt` which doesn't coerce arguments.
*
* @private
* @param {*} value The value to compare.
* @param {*} other The other value to compare.
* @returns {boolean} Returns `true` if `value` is greater than `other`,
* else `false`.
*/
function baseGt(value, other) {
return value > other;
}
/***/ }),
/* 1623 */
/***/ (function(module, exports, __webpack_require__) {
var parse = __webpack_require__(1624)
207073
207074
207075
207076
207077
207078
207079
207080
207081
207082
207083
207084
207085
207086
207087
207088
207089
207090
207091
207092
207093
207094
207095
207096
/**
* @category Day Helpers
* @summary Add the specified number of days to the given date.
*
* @description
* Add the specified number of days to the given date.
*
* @param {Date|String|Number} date - the date to be changed
* @param {Number} amount - the amount of days to be added
* @returns {Date} the new date with the days added
*
* @example
* // Add 10 days to 1 September 2014:
* var result = addDays(new Date(2014, 8, 1), 10)
* //=> Thu Sep 11 2014 00:00:00
*/
function addDays (dirtyDate, dirtyAmount) {
var date = parse(dirtyDate)
var amount = Number(dirtyAmount)
date.setDate(date.getDate() + amount)
return date
}
module.exports = addDays
/***/ (function(module, exports, __webpack_require__) {
var getTimezoneOffsetInMilliseconds = __webpack_require__(1625)
var isDate = __webpack_require__(1626)
var MILLISECONDS_IN_HOUR = 3600000
var MILLISECONDS_IN_MINUTE = 60000
var DEFAULT_ADDITIONAL_DIGITS = 2
var parseTokenDateTimeDelimeter = /[T ]/
var parseTokenPlainTime = /:/
// year tokens
var parseTokenYY = /^(\d{2})$/
var parseTokensYYY = [
/^([+-]\d{2})$/, // 0 additional digits
/^([+-]\d{3})$/, // 1 additional digit
/^([+-]\d{4})$/ // 2 additional digits
]
var parseTokenYYYY = /^(\d{4})/
var parseTokensYYYYY = [
/^([+-]\d{4})/, // 0 additional digits
/^([+-]\d{5})/, // 1 additional digit
/^([+-]\d{6})/ // 2 additional digits
]
// date tokens
var parseTokenMM = /^-(\d{2})$/
var parseTokenDDD = /^-?(\d{3})$/
var parseTokenMMDD = /^-?(\d{2})-?(\d{2})$/
var parseTokenWww = /^-?W(\d{2})$/
var parseTokenWwwD = /^-?W(\d{2})-?(\d{1})$/
// time tokens
var parseTokenHH = /^(\d{2}([.,]\d*)?)$/
var parseTokenHHMM = /^(\d{2}):?(\d{2}([.,]\d*)?)$/
var parseTokenHHMMSS = /^(\d{2}):?(\d{2}):?(\d{2}([.,]\d*)?)$/
// timezone tokens
var parseTokenTimezone = /([Z+-].*)$/
var parseTokenTimezoneZ = /^(Z)$/
var parseTokenTimezoneHH = /^([+-])(\d{2})$/
var parseTokenTimezoneHHMM = /^([+-])(\d{2}):?(\d{2})$/
207146
207147
207148
207149
207150
207151
207152
207153
207154
207155
207156
207157
207158
207159
207160
207161
207162
207163
207164
207165
207166
207167
207168
207169
207170
207171
207172
207173
207174
207175
207176
207177
207178
207179
207180
207181
207182
207183
207184
207185
/**
* @category Common Helpers
* @summary Convert the given argument to an instance of Date.
*
* @description
* Convert the given argument to an instance of Date.
*
* If the argument is an instance of Date, the function returns its clone.
*
* If the argument is a number, it is treated as a timestamp.
*
* If an argument is a string, the function tries to parse it.
* Function accepts complete ISO 8601 formats as well as partial implementations.
* ISO 8601: http://en.wikipedia.org/wiki/ISO_8601
*
* If all above fails, the function passes the given argument to Date constructor.
*
* @param {Date|String|Number} argument - the value to convert
* @param {Object} [options] - the object with options
* @param {0 | 1 | 2} [options.additionalDigits=2] - the additional number of digits in the extended year format
* @returns {Date} the parsed date in the local time zone
*
* @example
* // Convert string '2014-02-11T11:30:30' to date:
* var result = parse('2014-02-11T11:30:30')
* //=> Tue Feb 11 2014 11:30:30
*
* @example
* // Parse string '+02014101',
* // if the additional number of digits in the extended year format is 1:
* var result = parse('+02014101', {additionalDigits: 1})
* //=> Fri Apr 11 2014 00:00:00
*/
function parse (argument, dirtyOptions) {
if (isDate(argument)) {
// Prevent the date to lose the milliseconds when passed to new Date() in IE10
return new Date(argument.getTime())
} else if (typeof argument !== 'string') {
return new Date(argument)
}
var options = dirtyOptions || {}
var additionalDigits = options.additionalDigits
if (additionalDigits == null) {
additionalDigits = DEFAULT_ADDITIONAL_DIGITS
} else {
additionalDigits = Number(additionalDigits)
}
var dateStrings = splitDateString(argument)
var parseYearResult = parseYear(dateStrings.date, additionalDigits)
var year = parseYearResult.year
var restDateString = parseYearResult.restDateString
var date = parseDate(restDateString, year)
if (date) {
var timestamp = date.getTime()
var time = 0
var offset
if (dateStrings.time) {
time = parseTime(dateStrings.time)
}
if (dateStrings.timezone) {
offset = parseTimezone(dateStrings.timezone) * MILLISECONDS_IN_MINUTE
} else {
var fullTime = timestamp + time
var fullTimeDate = new Date(fullTime)
offset = getTimezoneOffsetInMilliseconds(fullTimeDate)
// Adjust time when it's coming from DST
var fullTimeDateNextDay = new Date(fullTime)
fullTimeDateNextDay.setDate(fullTimeDate.getDate() + 1)
var offsetDiff =
getTimezoneOffsetInMilliseconds(fullTimeDateNextDay) -
getTimezoneOffsetInMilliseconds(fullTimeDate)
if (offsetDiff > 0) {
offset += offsetDiff
}
}
return new Date(timestamp + time + offset)
} else {
return new Date(argument)
}
}
function splitDateString (dateString) {
var dateStrings = {}
var array = dateString.split(parseTokenDateTimeDelimeter)
var timeString
if (parseTokenPlainTime.test(array[0])) {
dateStrings.date = null
timeString = array[0]
} else {
dateStrings.date = array[0]
timeString = array[1]
}
if (timeString) {
var token = parseTokenTimezone.exec(timeString)
if (token) {
dateStrings.time = timeString.replace(token[1], '')
dateStrings.timezone = token[1]
} else {
dateStrings.time = timeString
}
}
return dateStrings
}
function parseYear (dateString, additionalDigits) {
var parseTokenYYY = parseTokensYYY[additionalDigits]
var parseTokenYYYYY = parseTokensYYYYY[additionalDigits]
// YYYY or ±YYYYY
token = parseTokenYYYY.exec(dateString) || parseTokenYYYYY.exec(dateString)
if (token) {
var yearString = token[1]
return {
year: parseInt(yearString, 10),
restDateString: dateString.slice(yearString.length)
}
}
// YY or ±YYY
token = parseTokenYY.exec(dateString) || parseTokenYYY.exec(dateString)
if (token) {
var centuryString = token[1]
return {
year: parseInt(centuryString, 10) * 100,
restDateString: dateString.slice(centuryString.length)
}
}
// Invalid ISO-formatted year
return {
year: null
}
}
function parseDate (dateString, year) {
// Invalid ISO-formatted year
if (year === null) {
return null
}
var token
var date
var month
var week
// YYYY
if (dateString.length === 0) {
date = new Date(0)
date.setUTCFullYear(year)
return date
// YYYY-MM
token = parseTokenMM.exec(dateString)
if (token) {
date = new Date(0)
month = parseInt(token[1], 10) - 1
date.setUTCFullYear(year, month)
return date
// YYYY-DDD or YYYYDDD
token = parseTokenDDD.exec(dateString)
if (token) {
date = new Date(0)
var dayOfYear = parseInt(token[1], 10)
date.setUTCFullYear(year, 0, dayOfYear)
return date
// YYYY-MM-DD or YYYYMMDD
token = parseTokenMMDD.exec(dateString)
if (token) {
date = new Date(0)
month = parseInt(token[1], 10) - 1
var day = parseInt(token[2], 10)
date.setUTCFullYear(year, month, day)
return date
// YYYY-Www or YYYYWww
token = parseTokenWww.exec(dateString)
if (token) {
week = parseInt(token[1], 10) - 1
return dayOfISOYear(year, week)
// YYYY-Www-D or YYYYWwwD
token = parseTokenWwwD.exec(dateString)
if (token) {
week = parseInt(token[1], 10) - 1
var dayOfWeek = parseInt(token[2], 10) - 1
return dayOfISOYear(year, week, dayOfWeek)
}
// Invalid ISO-formatted date
return null
}
function parseTime (timeString) {
var token
var hours
var minutes
// hh
token = parseTokenHH.exec(timeString)
if (token) {
hours = parseFloat(token[1].replace(',', '.'))
return (hours % 24) * MILLISECONDS_IN_HOUR
// hh:mm or hhmm
token = parseTokenHHMM.exec(timeString)
if (token) {
hours = parseInt(token[1], 10)
minutes = parseFloat(token[2].replace(',', '.'))
return (hours % 24) * MILLISECONDS_IN_HOUR +
minutes * MILLISECONDS_IN_MINUTE
// hh:mm:ss or hhmmss
token = parseTokenHHMMSS.exec(timeString)
if (token) {
hours = parseInt(token[1], 10)
minutes = parseInt(token[2], 10)
var seconds = parseFloat(token[3].replace(',', '.'))
return (hours % 24) * MILLISECONDS_IN_HOUR +
minutes * MILLISECONDS_IN_MINUTE +
seconds * 1000
// Invalid ISO-formatted time
return null
}
function parseTimezone (timezoneString) {
var token
var absoluteOffset
// Z
token = parseTokenTimezoneZ.exec(timezoneString)
if (token) {
return 0
// ±hh
token = parseTokenTimezoneHH.exec(timezoneString)
if (token) {
absoluteOffset = parseInt(token[2], 10) * 60
return (token[1] === '+') ? -absoluteOffset : absoluteOffset
// ±hh:mm or ±hhmm
token = parseTokenTimezoneHHMM.exec(timezoneString)
if (token) {
absoluteOffset = parseInt(token[2], 10) * 60 + parseInt(token[3], 10)
return (token[1] === '+') ? -absoluteOffset : absoluteOffset
}
function dayOfISOYear (isoYear, week, day) {
week = week || 0
day = day || 0
var date = new Date(0)
date.setUTCFullYear(isoYear, 0, 4)
var fourthOfJanuaryDay = date.getUTCDay() || 7
var diff = week * 7 + day + 1 - fourthOfJanuaryDay
date.setUTCDate(date.getUTCDate() + diff)
return date
}
module.exports = parse
/***/ }),
/* 1625 */
/***/ (function(module, exports) {
var MILLISECONDS_IN_MINUTE = 60000
* Google Chrome as of 67.0.3396.87 introduced timezones with offset that includes seconds.
* They usually appear for dates that denote time before the timezones were introduced
* (e.g. for 'Europe/Prague' timezone the offset is GMT+00:57:44 before 1 October 1891
* and GMT+01:00:00 after that date)
* Date#getTimezoneOffset returns the offset in minutes and would return 57 for the example above,
* which would lead to incorrect calculations.
*
* This function returns the timezone offset in milliseconds that takes seconds in account.
module.exports = function getTimezoneOffsetInMilliseconds (dirtyDate) {
var date = new Date(dirtyDate.getTime())
var baseTimezoneOffset = date.getTimezoneOffset()
date.setSeconds(0, 0)
var millisecondsPartOfTimezoneOffset = date.getTime() % MILLISECONDS_IN_MINUTE
return baseTimezoneOffset * MILLISECONDS_IN_MINUTE + millisecondsPartOfTimezoneOffset
}
/***/ }),
/* 1626 */
/***/ (function(module, exports) {
* @category Common Helpers
* @summary Is the given argument an instance of Date?
* @description
* Is the given argument an instance of Date?
* @param {*} argument - the argument to check
* @returns {Boolean} the given argument is an instance of Date
207479
207480
207481
207482
207483
207484
207485
207486
207487
207488
207489
207490
207491
207492
207493
207494
207495
207496
207497
207498
* // Is 'mayonnaise' a Date?
* var result = isDate('mayonnaise')
* //=> false
*/
function isDate (argument) {
return argument instanceof Date
}
module.exports = isDate
/***/ }),
/* 1627 */
/***/ (function(module, exports, __webpack_require__) {
var parse = __webpack_require__(1624)
/**
* @category Common Helpers
* @summary Is the first date after the second one?
* @description
* Is the first date after the second one?
* @param {Date|String|Number} date - the date that should be after the other one to return true
* @param {Date|String|Number} dateToCompare - the date to compare with
* @returns {Boolean} the first date is after the second date
* @example
* // Is 10 July 1989 after 11 February 1987?
* var result = isAfter(new Date(1989, 6, 10), new Date(1987, 1, 11))
* //=> true
function isAfter (dirtyDate, dirtyDateToCompare) {
var date = parse(dirtyDate)
var dateToCompare = parse(dirtyDateToCompare)
return date.getTime() > dateToCompare.getTime()
}
207518
207519
207520
207521
207522
207523
207524
207525
207526
207527
207528
207529
207530
207531
207532
207533
207534
207535
207536
207537
207538
207539
module.exports = isAfter
/***/ }),
/* 1628 */
/***/ (function(module, exports, __webpack_require__) {
const groupBy = __webpack_require__(1579)
const sortBy = __webpack_require__(1583)
const { eitherIncludes } = __webpack_require__(1614)
const getDateTransaction = op => op.date.substr(0, 10)
/**
* Groups `iterables` via `grouper` and returns an iterator
* that yields [groupKey, groups]
*/
const zipGroup = function*(iterables, grouper) {
const grouped = iterables.map(items => groupBy(items, grouper))
for (const key of Object.keys(grouped[0]).sort()) {
const groups = grouped.map(keyedGroups => keyedGroups[key] || [])
yield [key, groups]
207541
207542
207543
207544
207545
207546
207547
207548
207549
207550
207551
207552
207553
207554
207555
207556
207557
207558
207559
207560
207561
207562
207563
207564
207565
207566
207567
207568
207569
207570
207571
207572
207573
207574
207575
207576
207577
207578
207579
207580
207581
207582
207583
207584
207585
207586
207587
207588
}
const squash = (str, char) => {
const rx = new RegExp(String.raw`${char}{2,}`, 'gi')
return str && str.replace(rx, char)
}
const redactedNumber = /\b[0-9X]+\b/gi
const dateRx = /\b\d{2}\/\d{2}\/\d{4}\b/g
const cleanLabel = label => label && label.replace(redactedNumber, '')
const withoutDate = str => str && str.replace(dateRx, '')
const compacted = str => str && str.replace(/\s/g, '').replace(/-/g, '')
const scoreLabel = (newTr, existingTr) => {
if (
squash(existingTr.originalBankLabel, ' ') ===
squash(newTr.originalBankLabel, ' ')
) {
return [200, 'originalBankLabel']
} else if (
compacted(existingTr.originalBankLabel) ===
compacted(newTr.originalBankLabel)
) {
return [120, 'originalBankLabelCompacted']
} else if (
withoutDate(existingTr.originalBankLabel) ===
withoutDate(newTr.originalBankLabel)
) {
// For some transfers, the date in the originalBankLabel is different between
// BudgetInsight and Linxo
return [150, 'originalBankLabelWithoutDate']
} else if (existingTr.label === newTr.label) {
return [100, 'label']
} else if (
eitherIncludes(existingTr.label.toLowerCase(), newTr.label.toLowerCase())
) {
return [70, 'eitherIncludes']
} else if (
eitherIncludes(
cleanLabel(existingTr.label.toLowerCase()),
cleanLabel(newTr.label.toLowerCase())
)
) {
return [50, 'fuzzy-eitherIncludes']
} else {
// Nothing matches, we penalize so that the score is below 0
return [-1000, 'label-penalty']
const DAY = 1000 * 60 * 60 * 24
const getDeltaDate = (newTr, existingTr) => {
const nDate1 = new Date(newTr.date.substr(0, 10))
const eDate1 = new Date(existingTr.date.substr(0, 10))
const delta = Math.abs(eDate1 - nDate1)
if (newTr.realisationDate) {
const nDate2 = new Date(newTr.realisationDate.substr(0, 10))
const delta2 = Math.abs(eDate1 - nDate2)
return Math.min(delta, delta2)
} else {
return delta
}
}
const scoreMatching = (newTr, existingTr, options = {}) => {
const methods = []
const res = {
op: existingTr,
methods
}
if (options.maxDateDelta) {
const delta = getDeltaDate(newTr, existingTr)
if (delta > options.maxDateDelta) {
// Early exit, transactions are two far off time-wise
res.points = -1000
return res
methods.push('approx-date')
const [labelPoints, labelMethod] = scoreLabel(newTr, existingTr)
methods.push(labelMethod)
const amountDiff = Math.abs(existingTr.amount - newTr.amount)
const amountPoints = amountDiff === 0 ? methods.push('amount') && 100 : -1000
const points = amountPoints + labelPoints
res.points = points
return res
const matchTransaction = (newTr, existingTrs, options = {}) => {
const exactVendorId = existingTrs.find(
existingTr =>
existingTr.vendorId &&
newTr.vendorId &&
existingTr.vendorId === newTr.vendorId
)
if (exactVendorId) {
return { match: exactVendorId, method: 'vendorId' }
207645
207646
207647
207648
207649
207650
207651
207652
207653
207654
207655
207656
207657
207658
207659
207660
207661
207662
207663
207664
// Now we try to do it based on originalBankLabel, label and amount.
// We score candidates according to their degree of matching
// with the current transaction.
// Candidates with score below 0 will be discarded.
const withPoints = existingTrs.map(existingTr =>
scoreMatching(newTr, existingTr, options)
)
const candidates = sortBy(withPoints, x => -x.points).filter(
x => x.points > 0
)
return candidates.length > 0
? {
match: candidates[0].op,
method: candidates[0].methods.join('-')
}
: {
candidates
}
/**
* Logic to match a transaction and removing it from the transactions to
* match. `matchingFn` is the function used for matching.
*/
const matchTransactionToGroup = function*(newTrs, existingTrs, options = {}) {
const toMatch = Array.isArray(existingTrs) ? [...existingTrs] : []
for (let newTr of newTrs) {
const res = {
transaction: newTr
}
const result =
toMatch.length > 0 ? matchTransaction(newTr, toMatch, options) : null
if (result) {
Object.assign(res, result)
const matchIdx = toMatch.indexOf(result.match)
if (matchIdx > -1) {
toMatch.splice(matchIdx, 1)
}
}
yield res
207691
207692
207693
207694
207695
207696
207697
207698
207699
207700
207701
207702
207703
207704
207705
207706
207707
207708
207709
207710
207711
207712
207713
/**
* Several logics to match transactions.
*
* First group transactions per day and match transactions in
* intra-day mode.
* Then relax the date constraint 1 day per 1 day to reach
* a maximum of 5 days of differences
*/
const matchTransactions = function*(newTrs, existingTrs) {
const unmatchedNew = new Set(newTrs)
const unmatchedExisting = new Set(existingTrs)
// eslint-disable-next-line no-unused-vars
for (let [date, [newGroup, existingGroup]] of zipGroup(
[newTrs, existingTrs],
getDateTransaction
)) {
for (let result of matchTransactionToGroup(newGroup, existingGroup)) {
if (result.match) {
unmatchedExisting.delete(result.match)
unmatchedNew.delete(result.transaction)
yield result
}
}
207716
207717
207718
207719
207720
207721
207722
207723
207724
207725
207726
207727
207728
207729
207730
207731
207732
207733
207734
const deltas = [3, 4, 5]
for (let delta of deltas) {
for (let result of matchTransactionToGroup(
Array.from(unmatchedNew),
Array.from(unmatchedExisting),
{
maxDateDelta: delta * DAY
}
)) {
if (result.method) {
result.method += `-delta${delta}`
}
if (result.match) {
unmatchedExisting.delete(result.match)
unmatchedNew.delete(result.transaction)
}
if (result.match || delta === deltas[deltas.length - 1]) {
yield result
}
module.exports = {
matchTransactions,
scoreMatching
/***/ (function(module, exports, __webpack_require__) {
const Document = __webpack_require__(1393)
const sumBy = __webpack_require__(1630)
class BankAccountStats extends Document {
static checkCurrencies(accountsStats) {
const currency = accountsStats[0].currency
for (const accountStats of accountsStats) {
if (accountStats.currency !== currency) {
return false
}
}
static sum(accountsStats) {
if (accountsStats.length === 0) {
throw new Error('You must give at least one stats object')
}
207770
207771
207772
207773
207774
207775
207776
207777
207778
207779
207780
207781
207782
207783
207784
207785
207786
if (!this.checkCurrencies(accountsStats)) {
throw new Error('Currency of all stats object must be the same.')
}
const properties = [
'income',
'additionalIncome',
'mortgage',
'loans',
'fixedCharges'
]
const summedStats = properties.reduce((sums, property) => {
sums[property] = sumBy(
accountsStats,
accountStats => accountStats[property] || 0
)
return sums
}, {})
summedStats.currency = accountsStats[0].currency
return summedStats
}
}
BankAccountStats.doctype = 'io.cozy.bank.accounts.stats'
BankAccountStats.idAttributes = ['_id']
BankAccountStats.version = 1
BankAccountStats.checkedAttributes = null
module.exports = BankAccountStats
/***/ }),
/* 1630 */
/***/ (function(module, exports, __webpack_require__) {
var baseIteratee = __webpack_require__(1545),
baseSum = __webpack_require__(1631);
* This method is like `_.sum` except that it accepts `iteratee` which is
* invoked for each element in `array` to generate the value to be summed.
* The iteratee is invoked with one argument: (value).
207817
207818
207819
207820
207821
207822
207823
207824
207825
207826
207827
207828
207829
207830
207831
207832
207833
* @static
* @memberOf _
* @since 4.0.0
* @category Math
* @param {Array} array The array to iterate over.
* @param {Function} [iteratee=_.identity] The iteratee invoked per element.
* @returns {number} Returns the sum.
* @example
*
* var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
*
* _.sumBy(objects, function(o) { return o.n; });
* // => 20
*
* // The `_.property` iteratee shorthand.
* _.sumBy(objects, 'n');
* // => 20
function sumBy(array, iteratee) {
return (array && array.length)
? baseSum(array, baseIteratee(iteratee, 2))
: 0;
}
/***/ }),
/* 1631 */
/***/ (function(module, exports) {
/**
* The base implementation of `_.sum` and `_.sumBy` without support for
* iteratee shorthands.
*
* @private
* @param {Array} array The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {number} Returns the sum.
*/
function baseSum(array, iteratee) {
var result,
index = -1,
length = array.length;
while (++index < length) {
var current = iteratee(array[index]);
if (current !== undefined) {
result = result === undefined ? current : (result + current);
}
module.exports = baseSum;
/***/ }),
/* 1632 */
/***/ (function(module, exports, __webpack_require__) {
const trimEnd = __webpack_require__(1633)
const Document = __webpack_require__(1393)
const FILENAME_WITH_EXTENSION_REGEX = /(.+)(\..*)$/
207883
207884
207885
207886
207887
207888
207889
207890
207891
207892
207893
207894
207895
207896
207897
/**
* Class representing the file model.
* @extends Document
*/
class CozyFile extends Document {
/**
* async getFullpath - Gets a file's path
*
* @param {string} dirID The id of the parent directory
* @param {string} name The file's name
* @return {string} The full path of the file in the cozy
**/
static async getFullpath(dirId, name) {
if (!dirId) {
throw new Error('You must provide a dirId')
const parentDir = await this.get(dirId)
const parentDirectoryPath = trimEnd(parentDir.path, '/')
return `${parentDirectoryPath}/${name}`
207905
207906
207907
207908
207909
207910
207911
207912
207913
207914
207915
207916
207917
207918
207919
207920
207921
207922
/**
* Move file to destination.
*
* @param {string} fileId - The file's id (required)
* @param {object} destination
* @param {string} destination.folderId - The destination folder's id (required)
* @param {string} destination.path - The file's path after the move (optional, used to optimize performance in case of conflict)
* @param {string} force - Whether we should overwrite the destination in case of conflict (defaults to false)
* @returns {Promise} - A promise that returns the move action response and the deleted file id (if any) if resolved or an Error if rejected
*
*/
static async move(fileId, destination, force = false) {
const { folderId, path } = destination
const filesCollection = this.cozyClient.collection('io.cozy.files')
try {
const resp = await filesCollection.updateFileMetadata(fileId, {
dir_id: folderId
})
207924
207925
207926
207927
207928
207929
207930
207931
207932
207933
207934
207935
207936
207937
207938
207939
207940
207941
207942
return {
moved: resp.data,
deleted: null
}
} catch (e) {
if (e.status === 409 && force) {
let destinationPath
if (path) {
destinationPath = path
} else {
const movedFile = await this.get(fileId)
const filename = movedFile.name
destinationPath = await this.getFullpath(folderId, filename)
}
const conflictResp = await filesCollection.statByPath(destinationPath)
await filesCollection.destroy(conflictResp.data)
const resp = await filesCollection.updateFileMetadata(fileId, {
dir_id: folderId
})
return {
moved: resp.data,
deleted: conflictResp.data.id
}
} else {
throw e
}
}
/**
* Method to split both the filename and the extension
*
* @param {Object} file An io.cozy.files
* @return {Object} return an object with {filename: , extension: }
*/
static splitFilename(file) {
if (!file.name) throw new Error('file should have a name property ')
if (file.type === 'file') {
const match = file.name.match(FILENAME_WITH_EXTENSION_REGEX)
if (match) {
return { filename: match[1], extension: match[2] }
}
}
return { filename: file.name, extension: '' }
/**
*
* Method to upload a file even if a file with the same name already exists.
*
* @param {String} path Fullpath for the file ex: path/to/
* @param {Object} file HTML Object file
* @param {Object} metadata An object containing the wanted metadata to attach
*/
static async overrideFileForPath(pathArg, file, metadata) {
let path = pathArg
if (!path.endsWith('/')) path = path + '/'
const filesCollection = this.cozyClient.collection('io.cozy.files')
const existingFile = await filesCollection.statByPath(path + file.name)
207986
207987
207988
207989
207990
207991
207992
207993
207994
207995
207996
207997
207998
207999
208000
const { id: fileId, dir_id: dirId } = existingFile.data
const resp = await filesCollection.updateFile(file, {
dirId,
fileId,
metadata
})
return resp
} catch (error) {
if (/Not Found/.test(error)) {
const dirId = await filesCollection.ensureDirectoryExists(path)
const createdFile = await filesCollection.createFile(file, {
dirId,
metadata
})
return createdFile