Thursday, 10 January 2019

How to install Android 6.0 Marshmallow on Samsung Galaxy Tab 2



Downloads: https://drive.google.com/drive/folders/1VjfyucY4X7le73y0eV1IHVHdTihZpC9H?usp=sharing

root tablet

Download files -> install drivers in Sumsong drivers for mobile folder -> connect to laptop via use cable -> in CF-Auto-Root folder -> click odin3-v3.10 -> on new window click AP button -> confirm com port detected -> select .md5 file in the directory -> click start -> observe reset status, wait till status change to pass

enable usb debugging

in about tablet -> click kernel version 7 times, enter developer mode -> in developer, enable usb debugging -> change usb connection mode to media ->
copy cm-13..zip and open_gapps..zip into root directory of tablet.

install recovery manager

in app store install ROM manager, or manually install com.koushikdutta..apk from download -> reboot in recovery mode -> install cm-13..zip -> install gapps..zip -> reboot

Wednesday, 9 January 2019

node upload to cloud, download from cloud

project sitehttps://chuanshuoge1-node-upload.herokuapp.com/

upload by button

upload by drag & drop

files uploaded to cloud, stored in binary

download from cloud

files are zipped and downloaded

extract zip folder

delete files on cloud

Concept

upload

files -> front end axios -> node server multer read -> req.files -> convert file to binary buffer -> save on cloud

download

cloud download -> node server jszip -> front end axios -> file-save -> computer

package.json

{
  "name": "upload",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "heroku-postbuild": "cd client && npm install && npm run build"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.18.3",
    "express": "^4.16.4",
    "jszip": "^3.1.5",
    "mongoose": "^5.4.2",
    "mongoose-timestamp": "^0.6.0",
    "multer": "^1.4.1"
  }
}

-------------------------------------

client/package.json

{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "antd": "^3.12.1",
    "axios": "^0.18.0",
    "file-saver": "^2.0.0",
    "react": "^16.7.0",
    "react-dom": "^16.7.0",
    "react-scripts": "2.1.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ],
  "proxy": "http://localhost:5000"
}

--------------------------------------------

server.js

const express = require('express')
const bodyParser = require('body-parser')
const multer = require('multer');
const path = require('path');
const mongoose = require('mongoose');
const fileSchema = require('./schema');
const jszip = require('jszip');

const app = express();

//Body parser Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

//public folder
//app.use(express.static('./public'));
app.use(express.static(path.join(__dirname, 'client/build')));

const cloudUpload = multer();

//upload to cloud
app.post('/uploadToCloud', cloudUpload.any(), (req, res, next) => {

    if (!req.files) { next('file not found') }

    req.files.map(async (item, index) => {

        const newFile = new fileSchema({
            buffer: item.buffer,
            fileName: item.originalname
        })

        //save file in mongodb
        try {
            await newFile.save();

            if (index === req.files.length - 1) { res.send('ok'); }
        } catch (err) {
            return next(err)
        }
    })
})

app.post('/downloadfile', async (req, res, next) => {
    try {
        const lists = req.body;
        //find files with ids match that in the lists which contains array of ids.
        const files = await fileSchema.find({ '_id': { $in: lists } });
        console.log(files)

        if (files.length === 0) {
            return next('files not found')
        }

        const zip = new jszip();

        files.map(async (item, index) => {

            await zip.file(item.fileName, item.buffer, { base64: true })

            if (index === files.length - 1) {
                zip.generateAsync({ type: "nodebuffer" })
                    .then(function (content) {

                        res.send(content);
                    })
                    .catch(err => {
                        next(err)
                    });
            }

        })


    } catch (err) {
        return next(err)
    }
})

app.get('/searchfile', async (req, res, next) => {
    try {
        //only download id and name, ignore actual file 
        const files = await fileSchema.find({}, 'fileName');

        res.send(files)
    } catch (err) {
        return next(err)
    }
})

//delete all
app.delete('/delete', async (req, res, next) => {

    try {
        const lists = req.body;
        await fileSchema.find({ '_id': { $in: lists } }).remove();

        res.send('deleted')
    } catch (err) {
        return next(err);
    }
});


app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname + '/client/build/index.html'));
});

const port = process.env.PORT || 5000;
app.listen(port, () => {
    const url = "mongodb://xxxxxx@ds149984.mlab.com:49984/upload"
    mongoose.connect(url, {
        useNewUrlParser: true
    });

});

const db = mongoose.connection;

db.on('error', (err) => console.log(err));

db.once('open', () => {
    // require('./routes/userAccount')(app);
    console.log('server started');
});

--------------------------------------------

schema.js

const mongoose = require('mongoose');
const timestamp = require('mongoose-timestamp');

const fileSchema = new mongoose.Schema({
    buffer: {
        type: Buffer,
    },
    fileName: {
        type: String,
    }
})

fileSchema.plugin(timestamp);

const schema = mongoose.model('fileSchema', fileSchema);

module.exports = schema;

--------------------------------------------------

client/app.js

import React, { Component } from 'react';
import './App.css';
import ButtonUpload from './buttonUpload';
import Delete from './delete';
import Download from './download';
import { Row, Col, Checkbox, Icon } from 'antd';
import 'antd/dist/antd.css';
import axios from 'axios';
import DragUpload from './dragUpload';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      files: [],
      downloadCheck: [],
      deleteCheck: [],
    };
  }

  componentWillMount() {
    this.cloudSearch();
  }

  cloudSearch() {
    axios({
      method: 'get',
      url: '/searchfile'
    })
      .then(res => {
        this.setState({ files: res.data })
      })
      .catch(err => {
        console.log(err)
      })
  }

  downloadCheck(e) {
    this.setState({ downloadCheck: e })
  }

  deleteCheck(e) {
    this.setState({ deleteCheck: e })
  }

  render() {



    return (
      <div style={{ padding: '16px' }}>
        <h1 style={{ textAlign: 'center' }}><Icon type="laptop" />  <Icon type="swap" /> <Icon type="global" /> <Icon type="swap" /> <Icon type="database" /> <Icon type="swap" /> <Icon type="global" /> <Icon type="swap" /> <Icon type="cloud" /></h1>
        <Row>
          <Col xs={12} sm={8}>
            <ButtonUpload refresh={() => this.cloudSearch()}></ButtonUpload><br />

            <DragUpload refresh={() => this.cloudSearch()}></DragUpload>

          </Col>
          <Col xs={12} sm={8}>
            <h3>Download Files from Cloud</h3>

            <Checkbox.Group onChange={(e) => this.downloadCheck(e)} style={{ width: '100%' }} >
              <Row>
                {this.state.files.map((item, index) =>
                  <Col key={index}>
                    <Checkbox value={item._id} >{item.fileName}</Checkbox>
                  </Col>
                )}
              </Row>
            </Checkbox.Group><br /><br />

            <Download check={this.state.downloadCheck}></Download><br />

          </Col>
          <Col xs={12} sm={8}>
            <h3>Delete Files on Cloud</h3>

            <Checkbox.Group onChange={(e) => this.deleteCheck(e)} style={{ width: '100%' }} >
              <Row>
                {this.state.files.map((item, index) =>
                  <Col key={index}>
                    <Checkbox value={item._id} >{item.fileName}</Checkbox>
                  </Col>
                )}
              </Row>
            </Checkbox.Group><br /><br />

            <Delete refresh={() => this.cloudSearch()} check={this.state.deleteCheck}></Delete>

          </Col>
        </Row>

      </div>
    );
  }
}

export default App;

---------------------------------------------------

client/buttonUpload.js

import React, { Component } from 'react';
import './App.css';
import 'antd/dist/antd.css';
import { Button, Upload, Icon, message, Progress } from 'antd';
import axios from 'axios';

class ButtonUpload extends Component {
    constructor(props) {
        super(props);

        this.state = {
            fileList: [],
            uploading: false,
            progress: 0,
        };
    }

    handleUpload = () => {
        const { fileList } = this.state;
        const formData = new FormData();
        fileList.forEach((file) => {
            formData.append('files[]', file);
        });

        this.setState({
            uploading: true,
        });

        axios({
            method: 'post',
            url: '/uploadToCloud',
            data: formData,
            headers: { 'Content-Type': 'multipart/form-data', "Access-Control-Allow-Origin": "*" },
            onUploadProgress: progressEvent => {
                const p = parseInt(progressEvent.loaded / progressEvent.total * 99);
                this.setState({ progress: p });
            },
        })
            .then(res => {
                console.log(res.data);
                if (res.data === 'ok') {

                    this.setState({
                        fileList: [],
                        uploading: false,
                        progress: 100,
                    });

                    message.success('upload successful');
                    this.props.refresh();
                }
            })
            .catch((err) => {
                message.error(err.toString());
            })
    }

    render() {

        const { uploading, fileList } = this.state;
        const configs = {
            multiple: true,

            onRemove: (file) => {
                this.setState((state) => {
                    const index = state.fileList.indexOf(file);
                    const newFileList = state.fileList.slice();
                    newFileList.splice(index, 1);
                    return {
                        fileList: newFileList,
                    };
                });
            },

            beforeUpload: (file) => {
                this.setState(state => ({
                    fileList: [...state.fileList, file],
                    progress: 0,
                }));
                return false;
            },

            fileList,

        };

        return (
            <div >
                <Upload {...configs}>
                    <Button>
                        <Icon type="upload" /> Select File
          </Button>
                </Upload>
                <Button
                    type="primary"
                    onClick={this.handleUpload}
                    disabled={fileList.length === 0}
                    loading={uploading}
                    style={{ marginTop: 16 }}
                >
                    {uploading ? 'Uploading' : 'Start Upload'}
                </Button>{' '}
                <Progress type="circle" percent={this.state.progress} width={38} />
            </div>
        );
    }
}

export default ButtonUpload;

----------------------------------------------------------

client/dragUpload.js

import React, { Component } from 'react';
import './App.css';
import 'antd/dist/antd.css';
import { Upload, Icon, message } from 'antd';

const Dragger = Upload.Dragger;

class DragUpload extends Component {
    constructor(props) {
        super(props);

        this.state = {

        };
    }

    onChange(info) {
        const status = info.file.status;
        if (status !== 'uploading') {
            console.log(info.file, info.fileList);
        }
        if (status === 'done') {
            message.success(`${info.file.name} file uploaded successfully.`);

            this.props.refresh();

        } else if (status === 'error') {
            message.error(`${info.file.name} file upload failed.`);
        }
    }

    render() {
        const baseUrl = 'https://chuanshuoge1-node-upload.herokuapp.com' || 'http://localhost:3000';
        const url = baseUrl + '/uploadToCloud'


        return (
            <div style={{ paddingRight: '16px' }}>
                <Dragger multiple={true} action={url} onChange={(e) => this.onChange(e)}>
                    <p className="ant-upload-drag-icon">
                        <Icon type="inbox" />
                    </p>
                    <p className="ant-upload-text">Click or drag file to this area to upload</p>
                    <p className="ant-upload-hint">Support for a single or bulk upload. Strictly prohibit from uploading company data or other band files</p>
                </Dragger>
            </div>
        );
    }
}

export default DragUpload;

---------------------------------------

client/download.js

import React, { Component } from 'react';
import './App.css';
import 'antd/dist/antd.css';
import { Button, message, Progress } from 'antd';
import axios from 'axios';
import saveAs from 'file-saver';

class Download extends Component {
    constructor(props) {
        super(props);

        this.state = {
            progress: 0,
        };
    }


    downloadAxios = () => {
        this.setState({ progress: 0 })
        axios({
            url: '/downloadfile',
            method: 'post',
            data: this.props.check, //array of ids selected by checkbox
            responseType: 'blob', // important
            onDownloadProgress: progressEvent => {
                const p = parseInt(progressEvent.loaded / progressEvent.total * 100);
                this.setState({ progress: p });
            },
        })
            .then(res => {
                const name = Date.now() + '.zip'
                saveAs(res.data, name);

            })
            .catch(err => {
                message.error(err)
            })
    }

    render() {

        return (
            <div  >
                <Button type='dashed' onClick={() => { this.downloadAxios() }} disabled={this.props.check.length === 0 ? true : false}>Download zip</Button>{' '}
                <Progress type="circle" percent={this.state.progress} width={38} />
            </div>
        );
    }
}

export default Download;

--------------------------------------------

client/delete.js

import React, { Component } from 'react';
import './App.css';
import 'antd/dist/antd.css';
import { Button, message } from 'antd';
import axios from 'axios';


class Delete extends Component {
    constructor(props) {
        super(props);

        this.state = {

        };
    }

    delete = () => {
        axios({
            method: 'delete',
            url: '/delete',
            data: this.props.check,
        })
            .then(res => {
                message.success('deleted all')
                this.props.refresh();
            })
            .catch(err => {
                console.log(err)
            })
    }

    render() {



        return (
            <div  >
                <Button type='danger' onClick={() => { this.delete() }} disabled={this.props.check.length === 0 ? true : false}>Delete</Button>
            </div>
        );
    }
}

export default Delete;

--------------------------------------------------

multer interpreted sample

[ { fieldname: 'files[]',
    originalname: 'IMG_20170411_183204.jpg',
    encoding: '7bit',
    mimetype: 'image/jpeg',
    buffer:
     <Buffer ff d8 ff e1 49 b8 45 78 69 66 00 00 49 49 2a 00 08 00 00 00 11 00 0e 01 02 00 20 00 00 00 da 00 00 00 0f 01 02 00 20 00 00 00 fa 00 00 00 10 01 02 00 ... >,
    size: 4731016 },
  { fieldname: 'files[]',
    originalname: 'IMG_20170411_183241.png',
    encoding: '7bit',
    mimetype: 'image/png',
    buffer:
     <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 04 31 00 00 03 5e 08 02 00 00 00 84 6d 02 db 00 00 00 01 73 52 47 42 00 ae ce 1c e9 00 00 00 04 ... >,
    size: 1534256 },
  { fieldname: 'files[]',
    originalname: 'IMG_20170411_183247.jpg',
    encoding: '7bit',
    mimetype: 'image/jpeg',
    buffer:
     <Buffer ff d8 ff e1 49 77 45 78 69 66 00 00 49 49 2a 00 08 00 00 00 11 00 0e 01 02 00 20 00 00 00 da 00 00 00 0f 01 02 00 20 00 00 00 fa 00 00 00 10 01 02 00 ... >,
    size: 4464859 } ]

---------------------------------------

jsziped sample

<Buffer 50 4b 03 04 0a 00 00 00 00 00 8e 9c 29 4e 48 9a d4 db 30 69 17 00 30 69 17 00 17 00 00 00 49 4d 47 5f 32 30 31 37 30 34 31 31 5f 31 38 33 32 34 31 2e ... >

---------------------------------
reference:
http://chuanshuoge2.blogspot.com/2019/01/node-upload-image.html

Saturday, 5 January 2019

File Upload With Multer in Express, download with JSZip, file-saver

//index.html
<form action="/uploadmultiple" enctype="multipart/form-data" method="POST">
        Select images: <input type="file" name="myFiles" multiple>
        <input type="submit" value="Upload your files" />
    </form>

body-parser doesn't handle multipart bodies, which is what FormData is submitted as.
Instead, use a module like multer.

//server.js
// Set Storage Engine
const storage = multer.diskStorage({

    destination: './public/upload',

    filename: function (req, file, cb) {
        cb(null, 'file-' + Date.now() + path.extname(file.originalname))
    }
})

const upload = multer({
    storage: storage,
    limits: {
        fileSize: 1024 * 1024 * 10
    }, //10Mb
    //fileFilter: (req, file, cb) => {
    //Allowed ext
    //  const filetypes = /jpeg|jpg|png|gif/;
    //Check ext
    //  const extname = filetypes.text( path.extname(file.originalname).toLowerCase());
    //Check mime
    //  const mimetype = filetypes.text(file.mimetype);

    //  if(mimetype && extname){
    //   return cb(null,true);
    //  }else{
    //   cb('Error: Images only!');
    // }
    // }
})

//Uploading multiple files to server
app.post('/uploadmultiple', upload.array('myFiles', 12), (req, res, next) => {
    const files = req.files
    if (!files) {
        const error = new Error('Please choose files')
        error.httpStatusCode = 400
        return next(error)
    }

    res.send(files)
})

//download files from cloud to server
app.get('/downloadfile', async (req, res, next) => {
    try {
        const files = await fileSchema.find({});
        if (files.length === 0) {
            return next('database empty')
        }

        files.map(async (item, index) => {
            const buf = Buffer.from(item.buffer, 'base64');

            await fs.writeFile(path.join(__dirname, '/public/download', item.fileName), buf, function (error) {
                if (error) {
                    return next(error);
                } else {
                    console.log(item.fileName, ' downloaded');
                    return true;
                }
            });

            if (index === (files.length - 1)) { res.send({ 'message': 'ok' }) }
        })

    } catch (err) {
        return next(err)
    }
})

reference:
https://code.tutsplus.com/tutorials/file-upload-with-multer-in-node--cms-32088
https://www.youtube.com/watch?v=9Qzmri1WaaE
https://stackoverflow.com/questions/4796914/store-images-in-a-mongodb-database
https://chuanshuoge2.blogspot.com/2018/12/bank-express-ejs-mongodb.html
https://gist.github.com/sid24rane/bdf557cf9f835181a994439da0b5b82a
https://ant.design/components/upload/
https://stackoverflow.com/questions/43013858/ajax-post-a-file-from-a-form-with-axios
https://www.npmjs.com/package/multer

https://stackoverflow.com/questions/25209073/sending-multiple-responses-with-the-same-response-object-in-express-js
https://medium.freecodecamp.org/how-to-create-file-upload-with-react-and-node-2aa3f9aab3f0
https://www.npmjs.com/package/axios
https://github.com/axios/axios/issues/639

https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743
https://stuk.github.io/jszip/
https://www.npmjs.com/package/file-saver
https://github.com/Stuk/jszip/issues/310

https://stackoverflow.com/questions/24348437/mongoose-select-a-specific-field-with-find
https://stackoverflow.com/questions/8303900/mongodb-mongoose-findmany-find-all-documents-with-ids-listed-in-array

Thursday, 3 January 2019

Securing React Redux Apps With JWT Tokens

Server doesn’t need to ask DB to know who the user is because the user info is embedded inside the token itself! #performance!


XSS attack

reference:
https://medium.com/@rajaraodv/securing-react-redux-apps-with-jwt-tokens-fcfe81356ea0
https://jwt.io/

Python Crash Course



//multiline text
"""
line1
line2
"""

//format
f"{1+2} {len('name')} {'name'}"

//string
.upper() .lower() .title() ,.strip() .rstrip() .lstrip()  name[start : end] .find('name') .replace('a' , 'b')
a in b a not in b

//number
x = 0b10
print(bin(x))

x = 0x12c
print(hex(x))

x = 1 + 2j
print(x)

print(10/3) 3.3333333333333335
print(10//3) 3
print(10 % 3) 1
print(10**3) 1000

import math
math.floor(1.2)

//buildin function
https://docs.python.org/3/library/functions.html

//math module
https://docs.python.org/3/library/math.html

x = input("x: ")

print(int(x))
print(float(x))
print(bool(x))

#Falsy
# "", 0, [], None (null)

age = 22
if age >= 18:
    print('adult')
elif age >= 13:
    print('teenager')
else:
    print('child')

age = 22
if 18 <= age < 65:
    print('eligible')

age = 22
message = 'eligible' if age >= 18 else 'Not eligible'

for x in "Python":
    print(x)

for x in ['a', 'b', 'c']:
    print(x)

for x in range(0, 10, 2):
    print(x)

print(range(5))
print([1, 2, 3, 4, 5])
print(type(range(5)))

P
y
t
h
o
n
a
b
c
0
2
4
6
8
range(0, 5)
[1, 2, 3, 4, 5]
<class 'range'>

names = ['John', 'Mary']
found = False
for name in names:
    if name.startswith('J'):
        print('found')
        found = True
        break
if not found:
    print('not found')

names = ['John', 'Mary']
for name in names:
    if name.startswith('J'):
        print('found')
        break
else:
    print('not found')

guess = 0
answer = 5
while answer != guess:
    guess = int(input('guess: '))
else:
    pass

guess: 1
guess: 2
guess: 3
guess: 4
guess: 5
PS C:\Users\bob\python1\.vscode>

def increment(number: int, by) -> tuple:
    return (number, number + by)


print(increment(2, by=3))
(2, 5)

def multiply(*list):
    print(list)
    total = 1
    for number in list:
        total *= number
    return total


print(multiply([2, 3, 4, 5]))

([2, 3, 4, 5],)
[2, 3, 4, 5]

def save_user(**user):
    print(user)
    print(user['name'])


save_user(id=1, name='admin')

{'id': 1, 'name': 'admin'}
admin

message = 'a'


def greet():
    global message
    message = 'b'


greet()
print(message)

in debug panel click gear to create launch.json
F5 start debug, F10 debug next line, F11 step into debugging current line

home key move cursor to beginning of line, end key to the end of the line
ctrl + home move cursor to beginning of file, ctrl + end end of the file
select line, hold Alt + arrow to move line up and down
ctrl + / comment out a line

def fizz_buzz(input):
    if (input % 3 == 0) and (input % 5 == 0):
        return 'fizzbuzz'
    if input % 3 == 0:
        return 'fizz'
    if input % 5 == 0:
        return 'buzz'
    return input


print(fizz_buzz(15))


Tuesday, 1 January 2019

加拿大报税Tips:出租房报税,有哪些费用可以抵税

利息

贷款利息通常是房东最大的抵扣项目,没有之一。对于擅长利用杠杆的投资者很少有全款付清的情形发生。

除了常见的贷款利息,其他形式的借款比如Line of credit 只要借款的直接用途(direct use rule)是用于出租房的,利息同样可以抵扣。

保险
房东购买的房屋保险 例如房东保险 (Landlord Insurance)。如果保险是买一次可以保两年的,那只能抵减当年这部分的保费,剩余的第二年再抵。

广告费
房东在宣传出租房时在各大媒体网站报刊杂志电线杆上登出的招租广告产生的费用可抵。

物业管理费
请物业管理公司或地产经纪打理出租房代招租客的托管费可以抵。家大业大的房东请全职工人管理自己的房子们,付给工人的工资、CPP、EI、保险类的费用都可以抵。房东亲历亲为自己动手管的,不能抵。

办公用品

签文件(Lease)时用到的 Office Supply 包括钢笔啊、铅笔啊、打印纸啊、订书钉啊,以及邮票都可以用来抵税。

地税

注意地税是property tax, 不是land transfer tax (土地转让税). 地税是每年都要交的属于current cost (当期费用)可抵减房租。而土地转让税是一次性的,只有买房交接时在律师那里付一次。土地转让税是购房成本的一部分,不能报税时抵减房租。

房屋维修
雇人修房子换炉头,马桶等这类的费用(人工+材料)可以抵。但是,如房东自己动手,只可抵材料费,房东的人工不算。

专业人士费用
这个可以包括请律师撰写租约或者追讨房租的律师费,以及请会计师记账报出租房税务的费用。

注意:为买卖投资房而产生的律师费是不能抵减房租的,要等到卖房计算增值的时候才用得上。

水电气费

如果租房合同上注明水电气cable这些费用由房东支付的,可以抵。

车辆费/路费
通常,房东的“Travel”是不能抵扣的。CRA 看到房东产生Travel Expense 会引发心理上的各种不适。所以遵循“合理”和“保守”两大会计准则,很少有房东去申报这个费用。但是只要满足下面两种情况中的一种,车辆费也是可以抵扣的哦。详情如下:

一套出租房:出租房和房东自家必须在同一地区,分别在两个城市的不行,并且房东要亲自做出租房的维护,路费是为了运载工具和材料的。

两套以上出租房:除了可以抵扣左面提到的这些,还可以加上房东为收租产生的车费以及房东为了去监工而产生的车费(出租房和房东自家可以不在同一城市)。

注意:
① 如果只有一套出租房,为收租而产生的车费,房东本人的时间和人工不能抵。
② 金额要合理、保守。
③ 必须有automobilelog, 记录下每次出行的日期,目的和里数。